diff --git a/CHANGES/528.feature b/CHANGES/528.feature new file mode 100644 index 000000000..ca3629878 --- /dev/null +++ b/CHANGES/528.feature @@ -0,0 +1 @@ +Added support for syncing signatures using docker API extension. diff --git a/pulp_container/app/registry_api.py b/pulp_container/app/registry_api.py index 7af44b9db..1b38bbe1d 100644 --- a/pulp_container/app/registry_api.py +++ b/pulp_container/app/registry_api.py @@ -59,7 +59,7 @@ RegistryPermission, TokenPermission, ) -from pulp_container.constants import EMPTY_BLOB, SIGNATURE_HEADER +from pulp_container.constants import EMPTY_BLOB, SIGNATURE_API_EXTENSION_VERSION, SIGNATURE_HEADER FakeView = namedtuple("FakeView", ["action", "get_object"]) @@ -1002,7 +1002,7 @@ def get_response_data(signatures): data = [] for signature in signatures: signature = { - "schemaVersion": 2, + "schemaVersion": SIGNATURE_API_EXTENSION_VERSION, "type": signature.type, "name": signature.name, "content": signature.data, diff --git a/pulp_container/app/tasks/sync_stages.py b/pulp_container/app/tasks/sync_stages.py index a737cca8a..3c08bb552 100644 --- a/pulp_container/app/tasks/sync_stages.py +++ b/pulp_container/app/tasks/sync_stages.py @@ -15,7 +15,13 @@ from pulpcore.plugin.stages import DeclarativeArtifact, DeclarativeContent, Stage from pulpcore.plugin.constants import TASK_STATES -from pulp_container.constants import MEDIA_TYPE, SIGNATURE_HEADER, SIGNATURE_SOURCE, SIGNATURE_TYPE +from pulp_container.constants import ( + MEDIA_TYPE, + SIGNATURE_API_EXTENSION_VERSION, + SIGNATURE_HEADER, + SIGNATURE_SOURCE, + SIGNATURE_TYPE, +) from pulp_container.app.models import ( Blob, BlobManifest, @@ -347,6 +353,29 @@ def _create_manifest_declarative_artifact(self, relative_url, saved_artifact, di ) return da + def _create_signature_declarative_content( + self, signature_raw, man_dc, name=None, signature_b64=None + ): + signature_json = extract_data_from_signature(signature_raw, man_dc.content.digest) + if signature_json is None: + return + + sig_digest = hashlib.sha256(signature_raw).hexdigest() + signature = ManifestSignature( + name=name or f"{man_dc.content.digest}@{sig_digest[:32]}", + digest=f"sha256:{sig_digest}", + type=SIGNATURE_TYPE.ATOMIC_SHORT, + key_id=signature_json["signing_key_id"], + timestamp=signature_json["signature_timestamp"], + creator=signature_json["optional"].get("creator"), + data=signature_b64 or base64.b64encode(signature_raw).decode(), + ) + sig_dc = DeclarativeContent( + content=signature, + extra_data={"signed_manifest_dc": man_dc}, + ) + return sig_dc + def create_manifest(self, list_dc, manifest_data): """ Create an Image Manifest from manifest data in a ManifestList. @@ -459,31 +488,38 @@ async def create_signatures(self, man_dc, signature_source): signature_raw = f.read() signature_counter += 1 - signature_json = extract_data_from_signature(signature_raw, man_dc.content.digest) - if signature_json is None: - continue - - sig_digest = hashlib.sha256(signature_raw).hexdigest() - signature = ManifestSignature( - name=f"{man_dc.content.digest}@{sig_digest[:32]}", - digest=f"sha256:{sig_digest}", - type=SIGNATURE_TYPE.ATOMIC_SHORT, - key_id=signature_json["signing_key_id"], - timestamp=signature_json["signature_timestamp"], - creator=signature_json["optional"].get("creator"), - data=base64.b64encode(signature_raw).decode(), - ) - sig_dc = DeclarativeContent( - content=signature, - extra_data={"signed_manifest_dc": man_dc}, - ) - signature_dcs.append(sig_dc) + sig_dc = self._create_signature_declarative_content(signature_raw, man_dc) + if sig_dc: + signature_dcs.append(sig_dc) return signature_dcs elif signature_source == SIGNATURE_SOURCE.API_EXTENSION: - # TODO in a PR for the extension support - pass + signatures_url = urlpath_sanitize( + self.remote.url, + "extensions/v2", + self.remote.upstream_name, + "signatures", + man_dc.content.digest, + ) + signatures_downloader = self.remote.get_downloader(url=signatures_url) + await signatures_downloader.run() + with open(signatures_downloader.path) as signatures_fd: + api_extension_signatures = json.loads(signatures_fd.read()) + for signature in api_extension_signatures.get("signatures", []): + if ( + signature["schemaVersion"] == SIGNATURE_API_EXTENSION_VERSION + and signature["type"] == SIGNATURE_TYPE.ATOMIC_SHORT + ): + signature_base64 = signature["content"] + signature_raw = base64.b64decode(signature_base64) + sig_dc = self._create_signature_declarative_content( + signature_raw, man_dc, signature["name"], signature_base64 + ) + if sig_dc: + signature_dcs.append(sig_dc) + + return signature_dcs return [] diff --git a/pulp_container/constants.py b/pulp_container/constants.py index 498db001c..87918e0de 100644 --- a/pulp_container/constants.py +++ b/pulp_container/constants.py @@ -33,5 +33,5 @@ ATOMIC_SHORT="atomic", # short version is used in the JSON produced by API extension ) SIGNATURE_SOURCE = SimpleNamespace(SIGSTORE="sigstore", API_EXTENSION="API extension") - SIGNATURE_HEADER = "X-Registry-Supports-Signatures" +SIGNATURE_API_EXTENSION_VERSION = 2