Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/1300.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added (tech preview) support for signing Debian packages when uploading to a Repository.
1 change: 1 addition & 0 deletions docs/user/guides/_SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
* [Package Uploads](upload.md)
* [Publish Repositories](publish.md)
* [Signing Service Creation](signing_service.md)
* [Sign Packages](sign_packages.md)
* [Advanced Copy](advanced_copy.md)
* [Configuring Checksums](checksums.md)
52 changes: 52 additions & 0 deletions docs/user/guides/sign_packages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Sign Debian Packages

Sign a Debian package using a registered APT package signing service.

Currently, only on-upload signing is supported.

## On Upload

!!! tip "New in 3.9.0 (Tech Preview)"

Sign a Debian package when uploading it to a repository.

### Prerequisites

- Have an `AptPackageSigningService` registered
(see the [signing service guide](site:pulp_deb/docs/user/guides/signing_service/)).
- Have the V4 fingerprint of the key you want to use. The key must be accessible by the signing
service you are using (the fingerprint is forwarded via `PULP_SIGNING_KEY_FINGERPRINT`).

### Instructions

1. Configure a repository to enable signing.
- Both `package_signing_service` and `package_signing_fingerprint` must be set on the
repository (or provided via the REST API fields with the same names).
- With those fields set, every package upload to the repository will be signed by the service.
- Optionally, set `package_signing_fingerprint_release_overrides` if you need different keys per
dist.
2. Upload a package to this repository.

### Example

```bash
# Create or update a repository with signing enabled
http POST $API_ROOT/repositories/deb/apt \
name="MyDebRepo" \
package_signing_service=$SIGNING_SERVICE_HREF \
package_signing_fingerprint=$SIGNING_FINGERPRINT

# Upload a package
pulp deb content upload \
--repository ${REPOSITORY} \
--file ${DEB_FILE}
```

### Known Limitations

**Traffic overhead**: The signing of a package should happen inside of a Pulp worker.
[By design](site:pulpcore/docs/dev/learn/plugin-concepts/#tasks),
Pulp needs to temporarily commit the file to the default backend storage in order to make the Uploaded File available to the tasking system.
This implies in some extra traffic, compared to a scenario where a task could process the file directly.

**No sign tracking**: We do not track signing information of a package.
73 changes: 70 additions & 3 deletions docs/user/guides/signing_service.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Signing Service Creation

## Metadata

To sign your APT release files on your `pulp_deb` publications, you will first need to create a signing service of type `AptReleaseSigningService`.

## Prerequisites
### Prerequisites

Creating a singing service requires the following:

Expand All @@ -26,7 +28,7 @@ Creating a singing service requires the following:
}
```

## Example Signing Script
### Example Signing Script

The following example signing service script is used as part of the `pulp_deb` test suite:

Expand Down Expand Up @@ -66,7 +68,7 @@ echo { \

It assumes that both public and secret key for `GPG_KEY_ID="Pulp QE"` is present in the GPG home of the Pulp user and that the secret key is not protecteded by a password.

## Creation Steps
### Creation Steps

1. Add the public key to your pulp users GPG home, for example, if pulp workers are running as the `pulp` user:
```bash
Expand All @@ -84,3 +86,68 @@ It assumes that both public and secret key for `GPG_KEY_ID="Pulp QE"` is present
pulp signing-service show --name=PulpQE | jq -r .pulp_href
```
5. Start [using the signing service to sign metadata](https://staging-docs.pulpproject.org/pulp_deb/docs/user/guides/publish/#metadata-signing).


## Packages

!!! tip "New in 3.9.0 (Tech Preview)"

Package signing is available as a tech preview beginning with pulp_deb 3.9.0. Unlike metadata
signing, package signing modifies the `.deb` file directly, so it uses the
`deb:AptPackageSigningService` class.

### Prerequisites

- Install `debsigs` and ensure it can access the private key you want to use.
- Familiarize yourself with the general signing instructions in
[pulpcore](site:pulpcore/docs/admin/guides/sign-metadata/).
- Make sure the public key fingerprint you provide matches the key available to `debsigs`. During
package uploads the fingerprint is passed to the script via the
`PULP_SIGNING_KEY_FINGERPRINT` environment variable.

### Instructions

1. Create a signing script capable of signing a Debian package with `debsigs`.
- The script receives the package path as its first argument.
- The script must use `PULP_SIGNING_KEY_FINGERPRINT` to select the signing key.
- The script should return JSON describing the signed file:
```json
{"deb_package": "/absolute/path/to/signed.deb"}
```
2. Register the script with `pulpcore-manager add-signing-service`.
- Use `--class "deb:AptPackageSigningService"`.
- The public key fingerprint passed here is only used to validate the script registration.
3. Retrieve the signing service `pulp_href` for later use (for example via
`pulp signing-service show --name <NAME>`).

### Example

The following script illustrates how to sign packages using `debsigs`. It copies the uploaded file
into a working directory (defaulting to `PULP_TEMP_WORKING_DIR` when present), signs it in place,
and emits the JSON payload expected by pulp_deb.

```bash title="package-signing-script.sh"
#!/usr/bin/env bash
set -euo pipefail

PACKAGE_PATH=$1
FINGERPRINT="${PULP_SIGNING_KEY_FINGERPRINT:?PULP_SIGNING_KEY_FINGERPRINT is required}"
WORKDIR="${PULP_TEMP_WORKING_DIR:-$(mktemp -d)}"
SIGNED_PATH="${WORKDIR}/$(basename "${PACKAGE_PATH}")"

cp "${PACKAGE_PATH}" "${SIGNED_PATH}"
debsigs --sign=origin --default-key "${FINGERPRINT}" "${SIGNED_PATH}"

echo {"deb_package": "${SIGNED_PATH}"}
```

```bash
pulpcore-manager add-signing-service \
"SimpleDebSigningService" \
${SCRIPT_ABS_FILENAME} \
${KEYID} \
--class "deb:AptPackageSigningService"

pulp signing-service show --name "SimpleDebSigningService"
```

51 changes: 51 additions & 0 deletions pulp_deb/app/migrations/0034_package_signing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Generated by Django 4.2.25 on 2025-10-23 21:43

from django.db import migrations, models
import django.db.models.deletion
import django_lifecycle.mixins
import pulpcore.app.models.base


class Migration(migrations.Migration):

dependencies = [
('deb', '0033_aptalternatecontentsource'),
]

operations = [
migrations.CreateModel(
name='AptPackageSigningService',
fields=[
('signingservice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.signingservice')),
],
options={
'abstract': False,
},
bases=('core.signingservice',),
),
migrations.AddField(
model_name='aptrepository',
name='package_signing_fingerprint',
field=models.TextField(max_length=40, null=True),
),
migrations.AddField(
model_name='aptrepository',
name='package_signing_service',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='deb.aptpackagesigningservice'),
),
migrations.CreateModel(
name='AptRepositoryReleasePackageSigningFingerprintOverride',
fields=[
('pulp_id', models.UUIDField(default=pulpcore.app.models.base.pulp_uuid, 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)),
('package_signing_fingerprint', models.TextField(max_length=40)),
('release_distribution', models.TextField()),
('repository', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='package_signing_fingerprint_release_overrides', to='deb.aptrepository')),
],
options={
'unique_together': {('repository', 'release_distribution')},
},
bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model),
),
]
32 changes: 32 additions & 0 deletions pulp_deb/app/migrations/0035_add_deb_package_signing_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 4.2.27 on 2025-12-29 19:23

from django.db import migrations, models
import django.db.models.deletion
import django_lifecycle.mixins
import pulpcore.app.models.base


class Migration(migrations.Migration):

dependencies = [
('core', '0145_domainize_import_export'),
('deb', '0034_package_signing'),
]

operations = [
migrations.CreateModel(
name='DebPackageSigningResult',
fields=[
('pulp_id', models.UUIDField(default=pulpcore.app.models.base.pulp_uuid, 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)),
('sha256', models.TextField(max_length=64)),
('package_signing_fingerprint', models.TextField(max_length=40)),
('result', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.content')),
],
options={
'unique_together': {('sha256', 'package_signing_fingerprint')},
},
bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model),
),
]
8 changes: 6 additions & 2 deletions pulp_deb/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
SourcePackage,
)

from .signing_service import AptReleaseSigningService
from .signing_service import AptReleaseSigningService, AptPackageSigningService

from .content.metadata import (
Release,
Expand All @@ -28,6 +28,10 @@

from .remote import AptRemote

from .repository import AptRepository, AptRepositoryReleaseServiceOverride
from .repository import (
AptRepository,
AptRepositoryReleaseServiceOverride,
AptRepositoryReleasePackageSigningFingerprintOverride,
)

from .acs import AptAlternateContentSource
46 changes: 45 additions & 1 deletion pulp_deb/app/models/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
SourceIndex,
SourcePackage,
SourcePackageReleaseComponent,
AptPackageSigningService,
)

import logging
Expand Down Expand Up @@ -66,7 +67,15 @@ class AptRepository(Repository, AutoAddObjPermsMixin):
signing_service = models.ForeignKey(
AptReleaseSigningService, on_delete=models.PROTECT, null=True
)

package_signing_service = models.ForeignKey(
AptPackageSigningService, on_delete=models.SET_NULL, null=True
)

package_signing_fingerprint = models.TextField(null=True, max_length=40)

# Implicit signing_service_release_overrides
# Implicit package_signing_fingerprint_release_overrides

autopublish = models.BooleanField(default=False)

Expand Down Expand Up @@ -115,6 +124,21 @@ def release_signing_service(self, release):
except AptRepositoryReleaseServiceOverride.DoesNotExist:
return self.signing_service

def release_package_signing_fingerprint(self, release):
"""
Return the Package Signing Fingerprint specified in the overrides if there is one for this
release, else return self.package_signing_fingerprint.
"""
if isinstance(release, Release):
release = release.distribution
try:
override = self.package_signing_fingerprint_release_overrides.get(
release_distribution=release
)
return override.package_signing_fingerprint
except AptRepositoryReleasePackageSigningFingerprintOverride.DoesNotExist:
return self.package_signing_fingerprint

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 @@ -149,7 +173,9 @@ class AptRepositoryReleaseServiceOverride(BaseModel):
"""

repository = models.ForeignKey(
AptRepository, on_delete=models.CASCADE, related_name="signing_service_release_overrides"
AptRepository,
on_delete=models.CASCADE,
related_name="signing_service_release_overrides",
)
signing_service = models.ForeignKey(AptReleaseSigningService, on_delete=models.PROTECT)
release_distribution = models.TextField()
Expand All @@ -158,6 +184,24 @@ class Meta:
unique_together = (("repository", "release_distribution"),)


class AptRepositoryReleasePackageSigningFingerprintOverride(BaseModel):
"""
Override the signing fingerprint that a single Release will use in this AptRepository for
signing packages.
"""

repository = models.ForeignKey(
AptRepository,
on_delete=models.CASCADE,
related_name="package_signing_fingerprint_release_overrides",
)
package_signing_fingerprint = models.TextField(max_length=40)
release_distribution = models.TextField()

class Meta:
unique_together = (("repository", "release_distribution"),)


def find_dist_components(package_ids, content_set):
"""
Given a list of package_ids and a content_set, this function will find all distribution-
Expand Down
Loading
Loading