Skip to content

Commit

Permalink
WIP: Add gpgkey to ansible repository
Browse files Browse the repository at this point in the history
fixes pulp#1086

Required PR: pulp/pulpcore#2931
  • Loading branch information
mdellweg committed Jul 7, 2022
1 parent 92851f2 commit 61887ab
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 61 deletions.
12 changes: 0 additions & 12 deletions .github/workflows/scripts/post_before_script.sh

This file was deleted.

1 change: 1 addition & 0 deletions CHANGES/1086.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a `gpgkey` field to the ansible repository to ease verification of collection signatures.
1 change: 1 addition & 0 deletions CHANGES/1086.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed ``keyring`` attribute from repositories in favor of ``gpgkey``.
9 changes: 4 additions & 5 deletions docs/workflows/signature.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ how to add signature support.

Setup
-----
In order to verify signature validity on uploads you will need to store your trusted keyring on
your Pulp system and set the ``pulp_ansible`` setting ``ANSIBLE_CERTS_DIR`` to the folder of that
keyring. By default it is set to ``/etc/pulp/certs/`` if you want an easy place to store your
keyring.
In order to verify signature validity on uploads you will need to store your trusted key on the
repositories ``gpgkey`` attribute. Additionally you can provide a global verification key in the
``ANSIBLE_GLOBAL_GPGKEY`` setting. Invalid signatures will then be rejected by the system.

.. note::
You can upload signatures without supplying Pulp your keyring, but ``pulp_ansible`` will not
You can upload signatures without supplying Pulp any key, but ``pulp_ansible`` will not
perform validity checks on the uploaded signature.

In order to have ``pulp_ansible`` sign collections stored in your repositories you will need to set
Expand Down
22 changes: 22 additions & 0 deletions pulp_ansible/app/migrations/0042_ansiblerepository_gpgkey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.13 on 2022-07-01 10:19

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('ansible', '0041_alter_collectionversion_collection'),
]

operations = [
migrations.AddField(
model_name='ansiblerepository',
name='gpgkey',
field=models.TextField(null=True),
),
migrations.RemoveField(
model_name='ansiblerepository',
name='keyring',
),
]
2 changes: 1 addition & 1 deletion pulp_ansible/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class AnsibleRepository(Repository):
REMOTE_TYPES = [RoleRemote, CollectionRemote]

last_synced_metadata_time = models.DateTimeField(null=True)
keyring = models.FilePathField(path="/etc/pulp/certs/", recursive=True, blank=True)
gpgkey = models.TextField(null=True)

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
Expand Down
21 changes: 7 additions & 14 deletions pulp_ansible/app/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from gettext import gettext as _

from django.conf import settings
from drf_spectacular.utils import extend_schema_field
from jsonschema import Draft7Validator
from rest_framework import serializers

Expand Down Expand Up @@ -116,13 +115,6 @@ class Meta:
)


@extend_schema_field(str)
class FilePathField(serializers.FilePathField):
"""
Remove enum model in client generation, see https://github.com/pulp/pulp_ansible/issues/973.
"""


class AnsibleRepositorySerializer(RepositorySerializer):
"""
Serializer for Ansible Repositories.
Expand All @@ -131,16 +123,17 @@ class AnsibleRepositorySerializer(RepositorySerializer):
last_synced_metadata_time = serializers.DateTimeField(
help_text=_("Last synced metadata time."), allow_null=True, required=False
)
keyring = FilePathField(
path=settings.ANSIBLE_CERTS_DIR,
help_text=_("Location of keyring used to verify signatures uploaded to this repository"),
allow_blank=True,
default="",
gpgkey = serializers.CharField(
help_text="Gpg public key to verify collection signatures against",
required=False,
allow_null=True,
)

class Meta:
fields = RepositorySerializer.Meta.fields + ("last_synced_metadata_time", "keyring")
fields = RepositorySerializer.Meta.fields + (
"last_synced_metadata_time",
"gpgkey",
)
model = AnsibleRepository


Expand Down
2 changes: 1 addition & 1 deletion pulp_ansible/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
ANSIBLE_API_HOSTNAME = "https://" + socket.getfqdn()
ANSIBLE_CONTENT_HOSTNAME = settings.CONTENT_ORIGIN + "/pulp/content"
ANSIBLE_SIGNING_TASK_LIMITER = 10
ANSIBLE_CERTS_DIR = "/etc/pulp/certs/"
ANSIBLE_GLOBAL_GPGKEY = None
ANSIBLE_DEFAULT_DISTRIBUTION_PATH = None
ANSIBLE_URL_NAMESPACE = ""
45 changes: 26 additions & 19 deletions pulp_ansible/app/tasks/signature.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import aiofiles
import asyncio
import gnupg
import hashlib
import tempfile
import tarfile
from gettext import gettext as _

Expand All @@ -22,6 +22,8 @@
from django.core.files.storage import default_storage as storage
from pulpcore.plugin.models import SigningService, ProgressReport
from pulpcore.plugin.sync import sync_to_async_iterable, sync_to_async
from pulpcore.plugin.util import gpg_verify
from pulpcore.plugin.exceptions import InvalidSignatureError
from pulp_ansible.app.tasks.utils import get_file_obj_from_tarball
from rest_framework import serializers

Expand All @@ -32,24 +34,29 @@ def verify_signature_upload(data):
sig_data = file.read()
file.seek(0)
collection = data["signed_collection"]
keyring = None
if (repository := data.get("repository")) and repository.keyring:
keyring = repository.keyring
gpg = gnupg.GPG(keyring=keyring)
artifact = collection.contentartifact_set.select_related("artifact").first().artifact.file.name
artifact_file = storage.open(artifact)
with tarfile.open(fileobj=artifact_file, mode="r") as tar:
manifest = get_file_obj_from_tarball(tar, "MANIFEST.json", artifact_file)
with open("MANIFEST.json", mode="wb") as m:
m.write(manifest.read())
verified = gpg.verify_file(file, m.name)

if verified.trust_level is None or verified.trust_level < verified.TRUST_FULLY:
# Skip verification if repository isn't specified, or it doesn't have a keyring attached
if verified.fingerprint is None or keyring is not None:
raise serializers.ValidationError(
_("Signature verification failed: {}").format(verified.status)
)
repository = data.get("repository")
gpgkey = repository and repository.gpgkey or settings.ANSIBLE_GLOBAL_GPGKEY

if gpgkey:
artifact = (
collection.contentartifact_set.select_related("artifact").first().artifact.file.name
)
artifact_file = storage.open(artifact)
with tarfile.open(fileobj=artifact_file, mode="r") as tar:
manifest = get_file_obj_from_tarball(tar, "MANIFEST.json", artifact_file)
with tempfile.NamedTemporaryFile(dir=".") as manifest_file:
manifest_file.write(manifest.read())
manifest_file.flush()
try:
verified = gpg_verify(repository.gpgkey, file, manifest_file.name)
except InvalidSignatureError as e:
raise serializers.ValidationError(
_("Signature verification failed: {}").format(e.verified.status)
)
else:
raise serializers.ValidationError(
_("Signature could not be verified, because no public key was provided.")
)

data["data"] = sig_data
data["digest"] = file.hashers["sha256"].hexdigest()
Expand Down
28 changes: 19 additions & 9 deletions pulp_ansible/tests/functional/api/collection/test_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ def test_sign_locally_then_upload_signature(
tmp_path,
ansible_collections_api_client,
ansible_collection_signatures_client,
ansible_repo_factory,
pulp_trusted_public_key_fingerprint,
pulp_trusted_public_key,
):
"""Test uploading a locally produced Collection Signature."""
# NOTE: This test relies on the server global gpg keyring containing the Pulp QE key.
# This is carried out by the post_before_script.sh. Please remove that script (or parts of it)
# once this functionality has been rewritten to use keys read from the database.

repository = ansible_repo_factory(gpgkey=pulp_trusted_public_key)
collection, collection_url = build_and_upload_collection()

# Extract MANIFEST.json
Expand All @@ -89,22 +89,32 @@ def test_sign_locally_then_upload_signature(

# Locally sign the collection
signing_script_response = sign_with_ascii_armored_detached_signing_service(filename)
signature = signing_script_response["signature"]
signature_file = signing_script_response["signature"]

# Signature upload
task = ansible_collection_signatures_client.create(
signed_collection=collection_url, file=signature
signed_collection=collection_url, file=signature_file, repository=repository.pulp_href
)
signature_href = next(
(
item
for item in monitor_task(task.task).created_resources
if "content/ansible/collection_signatures/" in item
)
)
sig = monitor_task(task.task).created_resources[0]
assert "content/ansible/collection_signatures/" in sig
assert signature_href is not None
signature = ansible_collection_signatures_client.read(signature_href)
assert signature.pubkey_fingerprint == pulp_trusted_public_key_fingerprint

# Upload another collection that won't be signed
another_collection, another_collection_url = build_and_upload_collection()

# Check that invalid signatures can't be uploaded
with pytest.raises(Exception):
task = ansible_collection_signatures_client.create(
signed_collection=another_collection_url, file=signature
signed_collection=another_collection_url,
file=signature,
repository=repository.pulp_href,
)
monitor_task(task.task)

Expand Down

0 comments on commit 61887ab

Please sign in to comment.