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)
46 changes: 46 additions & 0 deletions docs/user/guides/sign_packages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Sign Debian Packages

Sign a Debian package using a registered package signing service.

Currently, only signing on upload and when modifying a repo's content are 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 fingerprint of the key you want to use, in prefixed format (e.g. `v4:<hex-fingerprint>`
or `keyid:<16-hex-char>`). The key must be accessible by the signing service you are using.
The raw fingerprint is forwarded to the signing script via the `PULP_SIGNING_KEY_FINGERPRINT`
environment variable, and the prefix is forwarded via `PULP_SIGNING_FINGERPRINT_TYPE`.

### 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
# The fingerprint must use the prefixed format, e.g. "v4:7FC42CD5F3D8EEC37FC42CD5F3D8EEC3DEADBEEF"
http POST $API_ROOT/repositories/deb/apt \
name="MyDebRepo" \
package_signing_service=$SIGNING_SERVICE_HREF \
package_signing_fingerprint="v4:$SIGNING_FINGERPRINT"

# Upload a package
pulp deb content upload \
--repository ${REPOSITORY} \
--file ${DEB_FILE}
```
81 changes: 78 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,76 @@ 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.

!!! note
Currently only `_gpgorigin` signatures (as produced by `debsigs --sign=origin`) are supported.

### 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 raw fingerprint (without prefix) is passed to the script via the
`PULP_SIGNING_KEY_FINGERPRINT` environment variable, and the fingerprint type prefix (e.g.
`v4`, `keyid`) is passed via `PULP_SIGNING_FINGERPRINT_TYPE`.

### 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
`PULP_SIGNING_FINGERPRINT_TYPE` environment variable indicates the fingerprint type
(e.g. `v4`, `keyid`).
- 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}"
# PULP_SIGNING_FINGERPRINT_TYPE contains the fingerprint type prefix (e.g. "v4", "keyid")
FINGERPRINT_TYPE="${PULP_SIGNING_FINGERPRINT_TYPE:-v4}"
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"
```

79 changes: 79 additions & 0 deletions pulp_deb/app/migrations/0035_package_signing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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", "0034_aptpublication_layout"),
]

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),
),
]
44 changes: 44 additions & 0 deletions pulp_deb/app/migrations/0036_add_deb_package_signing_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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", "0035_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),
),
]
30 changes: 30 additions & 0 deletions pulp_deb/app/migrations/0037_DATA_fix_signing_fingerprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.db import migrations


def replace_empty_fingerprint_with_null(apps, schema_editor):
"""Replace empty and bare-prefix package_signing_fingerprint values with NULL."""
AptRepository = apps.get_model("deb", "AptRepository")
AptRepository.objects.filter(package_signing_fingerprint="").update(
package_signing_fingerprint=None
)


def replace_null_fingerprint_with_empty(apps, schema_editor):
"""Replace NULL package_signing_fingerprint values with empty string."""
AptRepository = apps.get_model("deb", "AptRepository")
AptRepository.objects.filter(package_signing_fingerprint=None).update(
package_signing_fingerprint=""
)


class Migration(migrations.Migration):
dependencies = [
("deb", "0036_add_deb_package_signing_result"),
]

operations = [
migrations.RunPython(
replace_empty_fingerprint_with_null,
replace_null_fingerprint_with_empty,
),
]
Loading
Loading