Skip to content

Commit a87e82d

Browse files
committed
Sign packages when modifying repo content
fixes #1300
1 parent 9f3d131 commit a87e82d

6 files changed

Lines changed: 152 additions & 20 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Generated by Django 4.2.27 on 2025-12-29 19:23
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
import django_lifecycle.mixins
6+
import pulpcore.app.models.base
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('core', '0145_domainize_import_export'),
13+
('deb', '0034_package_signing'),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name='DebPackageSigningResult',
19+
fields=[
20+
('pulp_id', models.UUIDField(default=pulpcore.app.models.base.pulp_uuid, editable=False, primary_key=True, serialize=False)),
21+
('pulp_created', models.DateTimeField(auto_now_add=True)),
22+
('pulp_last_updated', models.DateTimeField(auto_now=True, null=True)),
23+
('sha256', models.TextField(max_length=64)),
24+
('package_signing_fingerprint', models.TextField(max_length=40)),
25+
('result', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.content')),
26+
],
27+
options={
28+
'unique_together': {('sha256', 'package_signing_fingerprint')},
29+
},
30+
bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model),
31+
),
32+
]

pulp_deb/app/models/signing_service.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from typing import Optional
88

99
import gnupg
10-
from pulpcore.plugin.models import SigningService
10+
from django.db import models
11+
from pulpcore.plugin.models import BaseModel, Content, SigningService
1112

1213

1314
def prepare_gpg(temp_directory_name, public_key, pubkey_fingerprint):
@@ -255,3 +256,16 @@ def _validate_deb_package(
255256
raise Exception(
256257
f"'{deb_package_path}' appears to have been signed using the wrong key!"
257258
)
259+
260+
261+
class DebPackageSigningResult(BaseModel):
262+
"""
263+
A model used for storing the result of signing an RPM package.
264+
"""
265+
266+
sha256 = models.TextField(max_length=64)
267+
package_signing_fingerprint = models.TextField(max_length=40)
268+
result = models.ForeignKey(Content, on_delete=models.CASCADE)
269+
270+
class Meta:
271+
unique_together = ("sha256", "package_signing_fingerprint")

pulp_deb/app/tasks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from .publishing import publish, publish_verbatim
33
from .synchronizing import synchronize
44
from .copy import copy_content
5+
from .signing import sign_and_create, signed_add_and_remove

pulp_deb/app/tasks/signing.py

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
from pathlib import Path
22
from tempfile import NamedTemporaryFile
33

4-
from pulpcore.plugin.models import Upload, UploadChunk, Artifact, CreatedResource, PulpTemporaryFile
5-
from pulpcore.plugin.tasking import general_create
4+
from pulpcore.plugin.models import (
5+
Upload,
6+
UploadChunk,
7+
Artifact,
8+
ContentArtifact,
9+
CreatedResource,
10+
PulpTemporaryFile,
11+
)
12+
from pulpcore.plugin.tasking import add_and_remove, general_create
613
from pulpcore.plugin.util import get_url
714

8-
from pulp_deb.app.models.signing_service import AptPackageSigningService
15+
from pulp_deb.app.models.signing_service import AptPackageSigningService, DebPackageSigningResult
16+
from pulp_deb.app.models import AptRepository, Package
917

1018

1119
def _save_file(fileobj, final_package):
@@ -22,6 +30,20 @@ def _save_upload(uploadobj, final_package):
2230
final_package.flush()
2331

2432

33+
def _sign_file(package_file, signing_service, signing_fingerprint):
34+
result = signing_service.sign(
35+
package_file.name, pubkey_fingerprint=signing_fingerprint
36+
)
37+
signed_package_path = Path(result["deb_package"])
38+
if not signed_package_path.exists():
39+
raise Exception(f"Signing script did not create the signed package: {result}")
40+
artifact = Artifact.init_and_validate(str(signed_package_path))
41+
artifact.save()
42+
resource = CreatedResource(content_object=artifact)
43+
resource.save()
44+
return artifact
45+
46+
2547
def sign_and_create(
2648
app_label,
2749
serializer_name,
@@ -43,16 +65,7 @@ def sign_and_create(
4365
uploaded_package = Upload.objects.get(pk=temporary_file_pk)
4466
_save_upload(uploaded_package, final_package)
4567

46-
result = package_signing_service.sign(
47-
final_package.name, pubkey_fingerprint=signing_fingerprint
48-
)
49-
signed_package_path = Path(result["deb_package"])
50-
if not signed_package_path.exists():
51-
raise Exception(f"Signing script did not create the signed package: {result}")
52-
artifact = Artifact.init_and_validate(str(signed_package_path))
53-
artifact.save()
54-
resource = CreatedResource(content_object=artifact)
55-
resource.save()
68+
artifact = _sign_file(final_package, package_signing_service, signing_fingerprint)
5669
uploaded_package.delete()
5770
# Create Package content
5871
data["artifact"] = get_url(artifact)
@@ -64,3 +77,60 @@ def sign_and_create(
6477
if "upload" in data:
6578
del data["upload"]
6679
general_create(app_label, serializer_name, data=data, context=context, *args, **kwargs)
80+
81+
82+
def signed_add_and_remove(
83+
repository_pk, add_content_units, remove_content_units, base_version_pk=None
84+
):
85+
repo = AptRepository.objects.get(pk=repository_pk)
86+
87+
if repo.package_signing_service:
88+
# sign each package and replace it in the add_content_units list
89+
signed_packages = []
90+
91+
for package in Package.objects.filter(pk__in=add_content_units):
92+
content_artifact = package.contentartifact_set.first()
93+
artifact_obj = content_artifact.artifact
94+
95+
with NamedTemporaryFile(mode="wb", dir=".", delete=False) as final_package:
96+
artifact_file = artifact_obj.file
97+
_save_file(artifact_file, final_package)
98+
99+
# TODO: check if the package is already signed with our fingerprint
100+
101+
# check if the package has been signed in the past with our fingerprint
102+
if existing_result := DebPackageSigningResult.objects.filter(
103+
sha256=content_artifact.artifact.sha256,
104+
package_signing_fingerprint=repo.package_signing_fingerprint,
105+
).first():
106+
signed_packages.append(existing_result.result_id)
107+
continue
108+
109+
# create a new signed version of the package
110+
artifact = _sign_file(
111+
final_package, repo.package_signing_service, repo.package_signing_fingerprint
112+
)
113+
signed_package = package
114+
signed_package.pk = None
115+
signed_package.pulp_id = None
116+
signed_package.pkgId = artifact.sha256
117+
signed_package.checksum_type = "sha256"
118+
signed_package.save()
119+
ContentArtifact.objects.create(
120+
artifact=artifact,
121+
content=signed_package,
122+
relative_path=content_artifact.relative_path,
123+
)
124+
DebPackageSigningResult.objects.create(
125+
sha256=artifact_obj.sha256,
126+
package_signing_fingerprint=repo.package_signing_fingerprint,
127+
result=signed_package,
128+
)
129+
130+
resource = CreatedResource(content_object=signed_package)
131+
resource.save()
132+
signed_packages.append(signed_package.pk)
133+
134+
add_content_units = signed_packages
135+
136+
return add_and_remove(repository_pk, add_content_units, remove_content_units, base_version_pk)

pulp_deb/app/viewsets/content.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from drf_spectacular.utils import extend_schema
2222

2323
from pulp_deb.app import models, serializers
24-
from pulp_deb.app.tasks import signing as deb_sign
24+
from pulp_deb.app.tasks import sign_and_create
2525

2626

2727
class GenericContentFilter(ContentFilter):
@@ -312,7 +312,7 @@ def create(self, request):
312312
serializer.validated_data.get("repository"),
313313
]
314314
task = dispatch(
315-
deb_sign.sign_and_create,
315+
sign_and_create,
316316
exclusive_resources=task_exclusive,
317317
args=tuple(task_args.values()),
318318
kwargs={

pulp_deb/app/viewsets/repository.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
from pulp_deb.app.models.content.content import Package
99
from pulp_deb.app.models.content.structure_content import PackageReleaseComponent
1010
from pulp_deb.app.serializers import AptRepositorySyncURLSerializer
11+
from pulpcore.app.tasks import signed_add_and_remove
1112

1213
from pulpcore.plugin.util import extract_pk, get_url
1314
from pulpcore.plugin.actions import ModifyRepositoryActionMixin
1415
from pulpcore.plugin.serializers import (
1516
AsyncOperationResponseSerializer,
1617
RepositoryAddRemoveContentSerializer,
1718
)
18-
from pulpcore.plugin.models import RepositoryVersion
19+
from pulpcore.plugin.models import Repository, ContentArtifact, RepositoryVersion
1920
from pulpcore.plugin.tasking import dispatch
2021
from pulpcore.plugin.viewsets import (
2122
OperationPostponedResponse,
@@ -29,6 +30,8 @@
2930

3031

3132
class AptModifyRepositoryActionMixin(ModifyRepositoryActionMixin):
33+
modify_task = signed_add_and_remove
34+
3235
@extend_schema(
3336
description="Trigger an asynchronous task to create a new repository version.",
3437
summary="Modify Repository Content",
@@ -37,13 +40,25 @@ class AptModifyRepositoryActionMixin(ModifyRepositoryActionMixin):
3740
@action(detail=True, methods=["post"], serializer_class=RepositoryAddRemoveContentSerializer)
3841
def modify(self, request, pk):
3942
remove_content_units = request.data.get("remove_content_units", [])
40-
package_hrefs = [href for href in remove_content_units if "/packages/" in href]
43+
remove_package_hrefs = [href for href in remove_content_units if "/packages/" in href]
4144

42-
if package_hrefs:
43-
prc_hrefs = self._get_matching_prc_hrefs(package_hrefs)
45+
if remove_package_hrefs:
46+
prc_hrefs = self._get_matching_prc_hrefs(remove_package_hrefs)
4447
remove_content_units.extend(prc_hrefs)
4548
request.data["remove_content_units"] = remove_content_units
4649

50+
add_content_units = request.data.get("add_content_units", [])
51+
add_package_hrefs = [href for href in add_content_units if "/packages/" in href]
52+
repository = Repository.objects.get(pk=request.data["repository"])
53+
if add_content_units and repository.package_signing_service:
54+
ondemand_ca = ContentArtifact.objects.filter(
55+
content_id__in=add_package_hrefs, artifact__isnull=True
56+
)
57+
if ondemand_ca.count() > 0:
58+
raise DRFValidationError(
59+
_("Cannot add on-demand content to repo with set package signing service.")
60+
)
61+
4762
return super().modify(request, pk)
4863

4964
def _get_matching_prc_hrefs(self, package_hrefs):

0 commit comments

Comments
 (0)