fix: verify WebUI OCI source revision

This commit is contained in:
Hermes Agent 2026-08-23 02:59:08 +00:00
parent 735a2b240d
commit 99a9de936b
2 changed files with 60 additions and 3 deletions

View File

@ -17,6 +17,7 @@ from hermes_webui_flux_release import (
DEFAULT_IMAGE as DEFAULT_IMAGE, DEFAULT_IMAGE as DEFAULT_IMAGE,
DESTINATION_PATTERN, DESTINATION_PATTERN,
DIGEST_PATTERN, DIGEST_PATTERN,
REVISION_PATTERN,
render_workload as render_workload, render_workload as render_workload,
validate_destination, validate_destination,
validate_release_artifacts as validate_flux_release_artifacts, validate_release_artifacts as validate_flux_release_artifacts,
@ -232,13 +233,17 @@ def assert_tag_absent(
def verify_registry_digest( def verify_registry_digest(
destination: str, destination: str,
digest: str, digest: str,
source_revision: str,
*, *,
username: str, username: str,
password: str, password: str,
opener: Callable[[urllib.request.Request, int], Any] = _registry_request, opener: Callable[[urllib.request.Request, int], Any] = _registry_request,
) -> None: ) -> None:
"""Verify Harbor independently resolves the pushed tag to Kaniko's digest.""" """Verify Harbor resolves the tag, digest, and persisted source revision."""
digest = _validated(digest, DIGEST_PATTERN, "image digest") digest = _validated(digest, DIGEST_PATTERN, "image digest")
source_revision = _validated(
source_revision, REVISION_PATTERN, "source revision"
)
status, body = _artifact_response( status, body = _artifact_response(
destination, username=username, password=password, opener=opener destination, username=username, password=password, opener=opener
) )
@ -263,6 +268,11 @@ def verify_registry_digest(
raise RuntimeError("Harbor artifact does not contain the expected tag") raise RuntimeError("Harbor artifact does not contain the expected tag")
if matching_tags[0].get("immutable") is not True: if matching_tags[0].get("immutable") is not True:
raise RuntimeError("Harbor did not enforce the expected tag as immutable") raise RuntimeError("Harbor did not enforce the expected tag as immutable")
labels = ((artifact.get("extra_attrs") or {}).get("config") or {}).get("Labels")
if not isinstance(labels, dict):
raise RuntimeError("Harbor artifact omitted OCI image labels")
if labels.get("org.opencontainers.image.revision") != source_revision:
raise RuntimeError("Harbor OCI source-revision label does not match")
def validate_release_artifacts( def validate_release_artifacts(
@ -356,7 +366,11 @@ def main() -> int:
destination=args.destination, destination=args.destination,
) )
verify_registry_digest( verify_registry_digest(
args.destination, digest, username=username, password=password args.destination,
digest,
args.source_revision,
username=username,
password=password,
) )
write_release_artifacts( write_release_artifacts(
digest=digest, digest=digest,

View File

@ -232,12 +232,24 @@ def test_release_verifies_exact_webui_harbor_artifact_and_policy() -> None:
{ {
"digest": digest, "digest": digest,
"tags": [{"name": f"git-{revision}-build-9", "immutable": True}], "tags": [{"name": f"git-{revision}-build-9", "immutable": True}],
"extra_attrs": {
"config": {
"Labels": {
"org.opencontainers.image.revision": revision,
}
}
},
} }
).encode() ).encode()
) )
module.verify_registry_digest( module.verify_registry_digest(
destination, digest, username="robot", password="private", opener=artifact_open destination,
digest,
revision,
username="robot",
password="private",
opener=artifact_open,
) )
request = seen[0][0] request = seen[0][0]
assert "/repositories/hermes-webui/artifacts/" in request.full_url assert "/repositories/hermes-webui/artifacts/" in request.full_url
@ -283,6 +295,37 @@ def test_release_verifies_exact_webui_harbor_artifact_and_policy() -> None:
) )
@pytest.mark.parametrize("labels", [None, {}, {"org.opencontainers.image.revision": "c" * 40}])
def test_release_rejects_missing_or_wrong_harbor_source_revision(labels) -> None:
"""A tag derived from Git cannot substitute for the persisted OCI label."""
module = _load(RELEASE, f"webui_registry_revision_{labels!r}")
revision = "a" * 40
destination = f"{module.DEFAULT_IMAGE}:git-{revision}-build-9"
digest = "sha256:" + "b" * 64
def artifact_open(_request, _timeout):
config = {} if labels is None else {"Labels": labels}
return _Response(
json.dumps(
{
"digest": digest,
"tags": [{"name": f"git-{revision}-build-9", "immutable": True}],
"extra_attrs": {"config": config},
}
).encode()
)
with pytest.raises(RuntimeError, match="OCI (image labels|source-revision label)"):
module.verify_registry_digest(
destination,
digest,
revision,
username="robot",
password="private",
opener=artifact_open,
)
def test_flux_tracks_webui_policy_before_jenkins() -> None: def test_flux_tracks_webui_policy_before_jenkins() -> None:
"""The immutable Harbor rule is reviewed desired state, not a pipeline wish.""" """The immutable Harbor rule is reviewed desired state, not a pipeline wish."""
harbor = yaml.safe_load( harbor = yaml.safe_load(