diff --git a/pulp_container/app/registry_api.py b/pulp_container/app/registry_api.py index dca2a781a..f0de3e9d5 100644 --- a/pulp_container/app/registry_api.py +++ b/pulp_container/app/registry_api.py @@ -35,7 +35,7 @@ Throttled, ValidationError, ) -from rest_framework.generics import ListAPIView +from rest_framework.generics import ListAPIView, RetrieveAPIView from rest_framework.pagination import BasePagination from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import BaseRenderer, JSONRenderer @@ -67,7 +67,7 @@ class RepositoryNotFound(NotFound): - """Exception to render a 404 with the code 'NAME_UNKNOWN'""" + """An exception to render an HTTP 404 response with the code 'NAME_UNKNOWN'.""" def __init__(self, name): """Initialize the exception with the repository name.""" @@ -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]+" + + 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.""" + distribution, _, 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.tobytes().decode(), + } + data.append(signature) + return {"signatures": data} 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;