diff --git a/CHANGES/504.feature b/CHANGES/504.feature new file mode 100644 index 000000000..49efdca8e --- /dev/null +++ b/CHANGES/504.feature @@ -0,0 +1 @@ +Added an extensions API endpoint for downloading image signatures. diff --git a/pulp_container/app/registry_api.py b/pulp_container/app/registry_api.py index dca2a781a..ac0da80f2 100644 --- a/pulp_container/app/registry_api.py +++ b/pulp_container/app/registry_api.py @@ -292,7 +292,12 @@ def default_response_headers(self): Provide common headers to all responses. """ headers = super().default_response_headers - headers.update({"Docker-Distribution-Api-Version": "registry/2.0"}) + headers.update( + { + "Docker-Distribution-Api-Version": "registry/2.0", + "X-Registry-Supports-Signatures": "1", + } + ) return headers def get_exception_handler_context(self): @@ -970,3 +975,42 @@ def receive_artifact(self, chunk): artifact = Artifact.objects.get(sha256=artifact.sha256) artifact.touch() return artifact + + +class Signatures(ContainerRegistryApiMixin, ViewSet): + """A ViewSet for image signatures.""" + + lookup_value_regex = "sha256:[0-9a-f]{64}" + + def head(self, request, path, pk=None): + """Respond to HEAD requests querying signatures by sha256.""" + return self.get(request, path, pk=pk) + + def get(self, request, path, pk): + """Return a signature identified by its sha256 checksum.""" + _, _, repository_version = self.get_drv_pull(path) + + try: + manifest = models.Manifest.objects.get(digest=pk) + except models.Manifest.DoesNotExit: + raise ManifestNotFound(reference=pk) + + signatures = models.ManifestSignature.objects.filter( + signed_manifest=manifest, pk__in=repository_version.content + ) + + return Response(self.get_response_data(signatures)) + + @staticmethod + def get_response_data(signatures): + """Extract version, type, name, and content from the passed signature data.""" + data = [] + for signature in signatures: + signature = { + "version": 2, + "type": signature.type, + "name": signature.name, + "content": signature.data, + } + data.append(signature) + return {"signatures": data} diff --git a/pulp_container/app/tasks/sync_stages.py b/pulp_container/app/tasks/sync_stages.py index ee1d8c358..c99c629f5 100644 --- a/pulp_container/app/tasks/sync_stages.py +++ b/pulp_container/app/tasks/sync_stages.py @@ -473,7 +473,7 @@ async def create_signatures(self, man_dc, signature_source): key_id=signature_json["signing_key_id"], timestamp=signature_json["signature_timestamp"], creator=signature_json["optional"].get("creator"), - data=base64.b64encode(signature_raw), + data=base64.b64encode(signature_raw).decode(), ) sig_dc = DeclarativeContent( content=signature, diff --git a/pulp_container/app/urls.py b/pulp_container/app/urls.py index b600b3026..0b7786561 100644 --- a/pulp_container/app/urls.py +++ b/pulp_container/app/urls.py @@ -6,6 +6,7 @@ BlobUploads, CatalogView, Manifests, + Signatures, TagsListView, VersionView, ) @@ -25,6 +26,7 @@ router.register(r"^v2/(?P.+)/blobs/uploads\/?", BlobUploads, basename="docker-upload") router.register(r"^v2/(?P.+)/blobs", Blobs, basename="blobs") router.register(r"^v2/(?P.+)/manifests", Manifests, basename="manifests") +router.register(r"^extensions/v2/(?P.+)/signatures", Signatures, basename="signatures") urlpatterns = [ path("token/", BearerTokenView.as_view()), diff --git a/pulp_container/app/webserver_snippets/apache.conf b/pulp_container/app/webserver_snippets/apache.conf index f52664a00..e7e2f6989 100644 --- a/pulp_container/app/webserver_snippets/apache.conf +++ b/pulp_container/app/webserver_snippets/apache.conf @@ -1,6 +1,9 @@ ProxyPass /v2 ${pulp-api}/v2 ProxyPassReverse /v2 ${pulp-api}/v2 +ProxyPass /extensions/v2 ${pulp-api}/extensions/v2 +ProxyPassReverse /extensions/v2 ${pulp-api}/extensions/v2 + ProxyPass /pulp/container ${pulp-content}/pulp/container ProxyPassReverse /pulp/container ${pulp-content}/pulp/container diff --git a/pulp_container/app/webserver_snippets/nginx.conf b/pulp_container/app/webserver_snippets/nginx.conf index 765cc0837..1034fe3d8 100644 --- a/pulp_container/app/webserver_snippets/nginx.conf +++ b/pulp_container/app/webserver_snippets/nginx.conf @@ -9,6 +9,16 @@ location /v2/ { client_max_body_size 0; } +location extensions/v2/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://pulp-api; +} + location /pulp/container/ { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;