Skip to content

Commit

Permalink
Add ManifestSignature model and related serializer/filter/viewset.
Browse files Browse the repository at this point in the history
  • Loading branch information
goosemania committed Nov 29, 2021
1 parent aa0f678 commit aac67ec
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES/9507.misc
@@ -0,0 +1 @@
Added model, serializer, filter and viewset for image manifest signature.
34 changes: 34 additions & 0 deletions pulp_container/app/migrations/0021_manifestsignature.py
@@ -0,0 +1,34 @@
# Generated by Django 3.2.9 on 2021-11-29 13:44

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('core', '0077_move_remote_url_credentials'),
('container', '0020_update_push_repo_perms'),
]

operations = [
migrations.CreateModel(
name='ManifestSignature',
fields=[
('content_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='container_manifestsignature', serialize=False, to='core.content')),
('name', models.CharField(db_index=True, max_length=255)),
('digest', models.CharField(max_length=255)),
('type', models.CharField(choices=[('atomic', 'atomic')], max_length=255)),
('key_id', models.CharField(db_index=True, max_length=255)),
('timestamp', models.PositiveIntegerField()),
('creator', models.CharField(blank=True, max_length=255)),
('data', models.BinaryField()),
('signed_manifest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='signed_manifests', to='container.manifest')),
],
options={
'default_related_name': '%(app_label)s_%(model_name)s',
'unique_together': {('digest',)},
},
bases=('core.content',),
),
]
48 changes: 46 additions & 2 deletions pulp_container/app/models.py
Expand Up @@ -35,7 +35,7 @@


from . import downloaders
from pulp_container.constants import MEDIA_TYPE
from pulp_container.constants import MEDIA_TYPE, SIGNATURE_TYPE


logger = getLogger(__name__)
Expand Down Expand Up @@ -200,6 +200,50 @@ class Meta:
unique_together = (("name", "tagged_manifest"),)


class ManifestSignature(Content):
"""
A signature for a manifest.
Fields:
name (models.CharField): A signature name in the 'manifest_digest@random_name' format
digest (models.CharField): A signature sha256 digest.
type (models.CharField): A signature type as specified in signature metadata. Currently
it's only "atomic container signature".
key_id (models.CharField): A key id identified by gpg (last 8 bytes of the fingerprint).
timestamp (models.PositiveIntegerField): A signature timestamp identified by gpg.
creator (models.CharField): A signature creator.
data (models.BinaryField): A signature, base64 encoded.
Relations:
signed_manifest (models.ForeignKey): A manifest this signature is relevant to.
"""

PROTECTED_FROM_RECLAIM = False

TYPE = "signature"

SIGNATURE_CHOICES = ((SIGNATURE_TYPE.ATOMIC_SHORT, SIGNATURE_TYPE.ATOMIC_SHORT),)

name = models.CharField(max_length=255, db_index=True)
digest = models.CharField(max_length=255)
type = models.CharField(max_length=255, choices=SIGNATURE_CHOICES)
key_id = models.CharField(max_length=255, db_index=True)
timestamp = models.PositiveIntegerField()
creator = models.CharField(max_length=255, blank=True)
data = models.BinaryField()

signed_manifest = models.ForeignKey(
Manifest, null=False, related_name="signed_manifests", on_delete=models.CASCADE
)
# TODO: Maybe there should be an optional field with a FK to a signing_service for the cases
# when Pulp creates a signature.

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
unique_together = (("digest",),)


class ContainerNamespace(BaseModel, AutoAddObjPermsMixin):
"""
Namespace for the container registry.
Expand Down Expand Up @@ -370,7 +414,7 @@ class ContainerRepository(
"""

TYPE = "container"
CONTENT_TYPES = [Blob, Manifest, Tag]
CONTENT_TYPES = [Blob, Manifest, Tag, ManifestSignature]
REMOTE_TYPES = [ContainerRemote]
PUSH_ENABLED = False
ACCESS_POLICY_VIEWSET_NAME = "repositories/container/container"
Expand Down
33 changes: 33 additions & 0 deletions pulp_container/app/serializers.py
Expand Up @@ -125,6 +125,39 @@ class Meta:
model = models.Blob


class ManifestSignatureSerializer(NoArtifactContentSerializer):
"""
Serializer for image manifest signatures.
"""

name = serializers.CharField(
help_text="Signature name in the format of `digest_algo:manifest_digest@random_32_chars`"
)
digest = serializers.CharField(help_text="sha256 digest of the signature blob")
type = serializers.CharField(help_text="Container signature type, e.g. 'atomic'")
key_id = serializers.CharField(help_text="Signing key ID")
timestamp = serializers.IntegerField(help_text="Timestamp of a signature")
creator = serializers.CharField(help_text="Signature creator")
signed_manifest = DetailRelatedField(
many=False,
help_text="Manifest that is signed",
view_name="container-manifests-detail",
queryset=models.Manifest.objects.all(),
)

class Meta:
fields = NoArtifactContentSerializer.Meta.fields + (
"name",
"digest",
"type",
"key_id",
"timestamp",
"creator",
"signed_manifest",
)
model = models.ManifestSignature


class RegistryPathField(serializers.CharField):
"""
Serializer Field for the registry_path field of the ContainerDistribution.
Expand Down
42 changes: 42 additions & 0 deletions pulp_container/app/viewsets.py
Expand Up @@ -100,6 +100,22 @@ class Meta:
}


class ManifestSignatureFilter(ContentFilter):
"""
FilterSet for image signatures.
"""

manifest = CharInFilter(field_name="signed_manifest__digest", lookup_expr="in")

class Meta:
model = models.ManifestSignature
fields = {
"name": NAME_FILTER_OPTIONS,
"digest": ["exact", "in"],
"key_id": ["exact", "in"],
}


class ContainerDistributionFilter(DistributionFilter):
"""
FilterSet for ContainerDistributions
Expand Down Expand Up @@ -294,6 +310,32 @@ class BlobViewSet(ContainerContentQuerySetMixin, ReadOnlyContentViewSet):
}


class ManifestSignatureViewSet(ContainerContentQuerySetMixin, ReadOnlyContentViewSet):
"""
ViewSet for image signatures.
"""

endpoint_name = "signatures"
queryset = models.ManifestSignature.objects.all()
serializer_class = serializers.ManifestSignatureSerializer
filterset_class = ManifestSignatureFilter

DEFAULT_ACCESS_POLICY = {
"statements": [
{
"action": ["list"],
"principal": "authenticated",
"effect": "allow",
},
{
"action": ["retrieve"],
"principal": "authenticated",
"effect": "allow",
},
],
}


class ContainerRemoteViewSet(RemoteViewSet):
"""
Container remotes represent an external repository that implements the Container
Expand Down
4 changes: 4 additions & 0 deletions pulp_container/constants.py
Expand Up @@ -17,3 +17,7 @@
)
EMPTY_BLOB = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
BLOB_CONTENT_TYPE = "application/octet-stream"
SIGNATURE_TYPE = SimpleNamespace(
ATOMIC_FULL="atomic container signature", # full version is present in the signed document
ATOMIC_SHORT="atomic", # short version is used in the JSON produced by API extension
)

0 comments on commit aac67ec

Please sign in to comment.