Skip to content

Commit

Permalink
Add support to sync signatures using docker API extension
Browse files Browse the repository at this point in the history
closes pulp#528
  • Loading branch information
goosemania authored and ipanova committed Feb 15, 2022
1 parent d86bfc8 commit 7676bf5
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGES/528.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support for syncing signatures using docker API extension.
10 changes: 10 additions & 0 deletions docs/workflows/sync.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ Remote GET Response::
instead of the whole repository. Note that it is also possible to filter a bunch of tags that
matches defined criteria by leveraging wildcards.

Some registries contain signed images. Such registries provide signatures in different ways.
If a registry provides signatures via a dedicated SigStore, a URL to it should be specified in
the ``sigstore`` field when creating a Remote.

.. note::
Some registries provide docker API extensions for ``atomic container signature`` type only, or
have ``cosign`` type signatures that are stored as a separate OCI artifact in a registry.
Pulp will automatically sync signatures provided via the docker API extension. At the moment,
`cosign` signatures are not supported.


Reference: `Container Remote Usage <../restapi.html#tag/Remotes:-Container>`_

Expand Down
3 changes: 2 additions & 1 deletion pulp_container/app/registry_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
from pulp_container.app.utils import extract_data_from_signature
from pulp_container.constants import (
EMPTY_BLOB,
SIGNATURE_API_EXTENSION_VERSION,
SIGNATURE_HEADER,
SIGNATURE_PAYLOAD_MAX_SIZE,
SIGNATURE_TYPE,
Expand Down Expand Up @@ -945,7 +946,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,
Expand Down
84 changes: 59 additions & 25 deletions pulp_container/app/tasks/sync_stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -345,6 +351,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.
Expand Down Expand Up @@ -457,33 +486,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)

return signature_dcs
sig_dc = self._create_signature_declarative_content(signature_raw, man_dc)
if sig_dc:
signature_dcs.append(sig_dc)

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.get("schemaVersion") == SIGNATURE_API_EXTENSION_VERSION
and signature.get("type") == SIGNATURE_TYPE.ATOMIC_SHORT
):
signature_base64 = signature.get("content")
if signature_base64 is None:
continue
signature_raw = base64.b64decode(signature_base64)
sig_dc = self._create_signature_declarative_content(
signature_raw, man_dc, signature.get("name"), signature_base64
)
if sig_dc:
signature_dcs.append(sig_dc)

return []
return signature_dcs

def _include_layer(self, layer):
"""
Expand Down
3 changes: 2 additions & 1 deletion pulp_container/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@
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"

MEGABYTE = 1_000_000
SIGNATURE_PAYLOAD_MAX_SIZE = 4 * MEGABYTE

SIGNATURE_API_EXTENSION_VERSION = 2

0 comments on commit 7676bf5

Please sign in to comment.