Skip to content

Commit

Permalink
Specify and remember the Signing Service that should be used at Repos…
Browse files Browse the repository at this point in the history
…itory or Release creation time.

closes #641
  • Loading branch information
sdherr committed Jan 31, 2023
1 parent b5d6048 commit 18cb978
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGES/641.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Specify and remember the Signing Services we want to use for each Repo / Release.
7 changes: 6 additions & 1 deletion docs/feature_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,12 @@ The short version:

``/pulp/api/v3/signing-services/``

6. And use it when you Publish content. ``pulp_deb`` will call out to your script to sign the ``Release`` file and publish the signatures as part of the Publish action.
6. And use it when you Publish content. ``pulp_deb`` will call out to your script to sign the ``Release`` file and publish the signatures as part of the Publish action. The three ways you can specify the Signing Service are:

1. If you specify ``signing_service`` when creating the Publication, that service will sign all the Releases in the Publication.
2. You can specify that particular Release in a Repository use particular SigningServices by setting the ``signing_service_release_overrides`` field on the Repository. For example:
``signing_service_release_overrides = {"bionic": "/pulp/api/v3/signing-services/433a1f70-c589-4413-a803-c50b842ea9b5/"}``
3. Finally, if you have set a ``signing_service`` on a Repository then the other Releases in that Repo will use that Service.

.. _verbatim_publishing:

Expand Down
36 changes: 36 additions & 0 deletions pulp_deb/app/migrations/0020_add_default_signing_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 3.2.16 on 2022-11-28 16:56

from django.db import migrations, models
import django.db.models.deletion
import django_lifecycle.mixins
import uuid


class Migration(migrations.Migration):

dependencies = [
('deb', '0019_immutable_metadata_constraints'),
]

operations = [
migrations.AddField(
model_name='aptrepository',
name='signing_service',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='deb_aptrepository', to='deb.aptreleasesigningservice'),
),
migrations.CreateModel(
name='AptRepositoryReleaseServiceOverride',
fields=[
('pulp_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('pulp_created', models.DateTimeField(auto_now_add=True)),
('pulp_last_updated', models.DateTimeField(auto_now=True, null=True)),
('release_distribution', models.TextField()),
('repository', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='signing_service_release_overrides', to='deb.aptrepository')),
('signing_service', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='deb.aptreleasesigningservice')),
],
options={
'unique_together': {('repository', 'release_distribution')},
},
bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model),
),
]
6 changes: 3 additions & 3 deletions pulp_deb/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
Package,
)

from .signing_service import AptReleaseSigningService

from .structure_content import (
Release,
ReleaseArchitecture,
Expand All @@ -20,6 +22,4 @@

from .remote import AptRemote

from .repository import AptRepository

from .signing_service import AptReleaseSigningService
from .repository import AptRepository, AptRepositoryReleaseServiceOverride
38 changes: 36 additions & 2 deletions pulp_deb/app/models/repository.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from pulpcore.plugin.models import Repository

from django.db import models
from pulpcore.plugin.models import BaseModel, Repository
from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_version_paths

from pulp_deb.app.models import (
AptReleaseSigningService,
AptRemote,
GenericContent,
InstallerFileIndex,
Expand Down Expand Up @@ -39,9 +40,27 @@ class AptRepository(Repository):
AptRemote,
]

signing_service = models.ForeignKey(
AptReleaseSigningService, on_delete=models.PROTECT, null=True
)
# Implicit signing_service_release_overrides

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

def release_signing_service(self, release):
"""
Return the Signing Service specified in the overrides if there is one for this release,
else return self.signing_service.
"""
if isinstance(release, Release):
release = release.distribution
try:
override = self.signing_service_release_overrides.get(release_distribution=release)
return override.signing_service
except AptRepositoryReleaseServiceOverride.DoesNotExist:
return self.signing_service

def initialize_new_version(self, new_version):
"""
Remove old metadata from the repo before performing anything else for the new version. This
Expand Down Expand Up @@ -75,3 +94,18 @@ def finalize_new_version(self, new_version):
if distribution in distributions:
raise DuplicateDistributionException(distribution)
distributions.append(distribution)


class AptRepositoryReleaseServiceOverride(BaseModel):
"""
Override the SigningService that a single Release will use in this AptRepository.
"""

repository = models.ForeignKey(
AptRepository, on_delete=models.CASCADE, related_name="signing_service_release_overrides"
)
signing_service = models.ForeignKey(AptReleaseSigningService, on_delete=models.PROTECT)
release_distribution = models.TextField()

class Meta:
unique_together = (("repository", "release_distribution"),)
96 changes: 94 additions & 2 deletions pulp_deb/app/serializers/repository_serializers.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,118 @@
from gettext import gettext as _
from django.db import transaction
from pulpcore.plugin.models import SigningService
from pulpcore.plugin.serializers import (
RelatedField,
RepositorySerializer,
RepositorySyncURLSerializer,
validate_unknown_fields,
)
from pulpcore.plugin.util import get_url

from pulp_deb.app.models import AptRepository
from pulp_deb.app.models import (
AptRepositoryReleaseServiceOverride,
AptReleaseSigningService,
AptRepository,
)

from jsonschema import Draft7Validator
from rest_framework import serializers
from rest_framework.exceptions import ValidationError as DRFValidationError
from pulp_deb.app.schema import COPY_CONFIG_SCHEMA


class ServiceOverrideField(serializers.DictField):
child = RelatedField(
view_name="signing-services-detail",
queryset=AptReleaseSigningService.objects.all(),
many=False,
required=False,
allow_null=True,
)

def to_representation(self, overrides):
return {
# Cast to parent class so get_url can look up resource url.
x.release_distribution: get_url(SigningService(x.signing_service.pk))
for x in overrides.all()
}


class AptRepositorySerializer(RepositorySerializer):
"""
A Serializer for AptRepository.
"""

signing_service = RelatedField(
help_text="A reference to an associated signing service. Used if "
"AptPublication.signing_service is not set",
view_name="signing-services-detail",
queryset=AptReleaseSigningService.objects.all(),
many=False,
required=False,
allow_null=True,
)
signing_service_release_overrides = ServiceOverrideField(
default=dict,
required=False,
help_text=_(
"A dictionary of Release distributions and the Signing Service URLs they should use."
"Example: "
'{"bionic": "/pulp/api/v3/signing-services/433a1f70-c589-4413-a803-c50b842ea9b5/"}'
),
)

class Meta:
fields = RepositorySerializer.Meta.fields
fields = RepositorySerializer.Meta.fields + (
"signing_service",
"signing_service_release_overrides",
)
model = AptRepository

@transaction.atomic
def create(self, validated_data):
"""Create an AptRepository, special handling for signing_service_release_overrides."""
overrides = validated_data.pop("signing_service_release_overrides", -1)
repo = super().create(validated_data)

try:
self._update_overrides(repo, overrides)
except DRFValidationError as exc:
repo.delete()
raise exc
return repo

def update(self, instance, validated_data):
"""Update an AptRepository, special handling for signing_service_release_overrides."""
overrides = validated_data.pop("signing_service_release_overrides", -1)
with transaction.atomic():
self._update_overrides(instance, overrides)
instance = super().update(instance, validated_data)
return instance

def _update_overrides(self, repo, overrides):
"""Update signing_service_release_overrides."""
if overrides == -1:
# Sentinel value, no updates
return

current = {x.release_distribution: x for x in repo.signing_service_release_overrides.all()}
# Intentionally only updates items the user specified.
for distro, service in overrides.items():
if not service and distro in current: # the user wants to delete this override
current[distro].delete()
elif service:
signing_service = AptReleaseSigningService.objects.get(pk=service)
if distro in current: # update
current[distro] = signing_service
current[distro].save()
else: # create
AptRepositoryReleaseServiceOverride(
repository=repo,
signing_service=signing_service,
release_distribution=distro,
).save()


class AptRepositorySyncURLSerializer(RepositorySyncURLSerializer):
"""
Expand Down
8 changes: 6 additions & 2 deletions pulp_deb/app/tasks/publishing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from pulp_deb.app.models import (
AptPublication,
AptRepository,
Package,
PackageReleaseComponent,
Release,
Expand Down Expand Up @@ -100,7 +101,7 @@ def publish(repository_version_pk, simple=False, structured=False, signing_servi
publication.simple = simple
publication.structured = structured
publication.signing_service = signing_service
repository = repo_version.repository
repository = AptRepository.objects.get(pk=repo_version.repository.pk)

if simple:
codename = "default"
Expand All @@ -125,6 +126,7 @@ def publish(repository_version_pk, simple=False, structured=False, signing_servi
description=repository.description,
label=repository.name,
version=str(repo_version.number),
signing_service=repository.signing_service,
)

for package in Package.objects.filter(
Expand Down Expand Up @@ -165,6 +167,7 @@ def publish(repository_version_pk, simple=False, structured=False, signing_servi
label=repository.name,
version=str(repo_version.number),
suite=release.suite,
signing_service=repository.release_signing_service(release),
)

for prc in PackageReleaseComponent.objects.filter(
Expand Down Expand Up @@ -243,6 +246,7 @@ def __init__(
version,
description=None,
suite=None,
signing_service=None,
):
self.publication = publication
self.distribution = distribution
Expand Down Expand Up @@ -276,7 +280,7 @@ def __init__(

self.architectures = architectures
self.components = {component: _ComponentHelper(self, component) for component in components}
self.signing_service = publication.signing_service
self.signing_service = publication.signing_service or signing_service

def add_metadata(self, metadata):
artifact = metadata._artifacts.get()
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# All things django and asyncio are deliberately left to pulpcore
# Example transitive requirements: asgiref, asyncio, aiohttp
pulpcore>=3.21.0.dev,<3.25
pulpcore>=3.23.0.dev,<3.25
python-debian>=0.1.44,<0.2.0
python-gnupg>=0.4.9,<0.6
jsonschema>=4.6,<5.0

0 comments on commit 18cb978

Please sign in to comment.