Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/scripts/before_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pulp_scheme: "https"
image:
name: "pulp"
tag: "ci_build"
ci_base: "ghcr.io/pulp/pulp-ci-centos9:latest"
ci_base: "ghcr.io/pulp/pulp-ci-centos10:latest"
source: "${COMPONENT_SOURCE}"
ci_requirements: $(test -f ci_requirements.txt && echo -n true || echo -n false)
upperbounds: $(test "${TEST}" = "pulp" && echo -n true || echo -n false)
Expand Down
1 change: 1 addition & 0 deletions CHANGES/+fix-add-signing-service.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `add-signing-service` management command failing with "There are N keys matching the key id" for PGP keys that have subkeys.
1 change: 1 addition & 0 deletions CHANGES/+gpg_verify.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed an issue where gpg_verify() was rejecting some valid PGP signatures after changes made in pulpcore 3.108.
1 change: 1 addition & 0 deletions CHANGES/+header-too-large.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Increased the content app's maximum HTTP header field size from 8190 to 16384 bytes to support PQC (post-quantum) X.509 certificates forwarded via the `X-CLIENT-CERT` header.
1 change: 1 addition & 0 deletions CHANGES/7479.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `--backend` option to the `add-signing-service` management command, enabling Sequoia (`sq`) as an alternative to GPG for key management. Use `--backend sq` to register signing services using Sequoia's key store.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ include test_requirements.txt
exclude releasing.md
exclude AGENTS.md
exclude CLAUDE.md
exclude Makefile
recursive-exclude pulpcore/tasking/task_trigger_demonstration *
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# WARNING: DO NOT EDIT!
#
# This file was generated by plugin_template, and is managed by it. Please use
# './plugin-template --ci pulpcore' to update this file.
#
# For more info visit https://github.com/pulp/plugin_template

.PHONY: format
format:
ruff format
ruff check --select I --fix

.PHONY: lint
lint:
yamllint -s -d '{extends: relaxed, rules: {line-length: disable}}' .github/workflows
bump-my-version bump --dry-run --allow-dirty release
ruff format --check --diff
ruff check
check-manifest
python .ci/scripts/check_requirements.py
1 change: 1 addition & 0 deletions functest_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pytest-xdist
python-gnupg
proxy.py~=2.4.10
trustme~=1.2.1
cryptography>=49.0

# pulp_file tests
beautifulsoup4
Expand Down
78 changes: 78 additions & 0 deletions pulp_certguard/tests/functional/api/test_x509_certguard.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid
from collections import namedtuple
from urllib.parse import quote, urljoin

import pytest
Expand All @@ -12,6 +13,11 @@
X509_UNTRUSTED_CLIENT_CERT_FILE_PATH,
)

PQCGuardedDistribution = namedtuple(
"PQCGuardedDistribution",
["distribution", "algorithm", "client_cert_pem", "untrusted_client_cert_pem"],
)


@pytest.fixture(scope="class")
def x509_certguard_factory(x509_content_guards_api_client, gen_object_with_cleanup):
Expand Down Expand Up @@ -42,6 +48,31 @@ def x509_guarded_distribution(
return distribution


@pytest.fixture(scope="class")
def pqc_guarded_distribution(
pqc_certificate_authority,
x509_content_guards_api_client,
gen_object_with_cleanup,
file_distribution_factory,
repository_test_file,
):
pca = pqc_certificate_authority
content_guard = gen_object_with_cleanup(
x509_content_guards_api_client,
{
"name": str(uuid.uuid4()),
"ca_certificate": pca.ca_cert_pem,
},
)
distribution = file_distribution_factory(
repository=repository_test_file.pulp_href,
content_guard=content_guard.pulp_href,
)
return PQCGuardedDistribution(
distribution, pca.algorithm, pca.client_cert_pem, pca.untrusted_client_cert_pem
)


@pytest.fixture(
scope="module",
params=[
Expand Down Expand Up @@ -92,3 +123,50 @@ def test_download(
headers=cert_data and {"X-CLIENT-CERT": cert_data},
)
assert response.status_code == status_code


class TestPQCX509CertGuard:
"""Test X.509 content guard with PQC (ML-DSA) certificates.

Parameterized over ML-DSA-65 (~7.5KB, under the default 8190-byte header
limit) and ML-DSA-87 (~10KB, over the limit).
"""

def test_download_with_valid_cert(
self,
pqc_guarded_distribution,
distribution_base_url,
):
distribution = pqc_guarded_distribution.distribution
url = distribution_base_url(distribution.base_url)
cert_pem = quote(pqc_guarded_distribution.client_cert_pem)
response = requests.get(
urljoin(url, "test_file"),
headers={"X-CLIENT-CERT": cert_pem},
allow_redirects=False,
)
assert response.status_code in (200, 302)

def test_download_with_untrusted_cert(
self,
pqc_guarded_distribution,
distribution_base_url,
):
distribution = pqc_guarded_distribution.distribution
url = distribution_base_url(distribution.base_url)
cert_pem = quote(pqc_guarded_distribution.untrusted_client_cert_pem)
response = requests.get(
urljoin(url, "test_file"),
headers={"X-CLIENT-CERT": cert_pem},
)
assert response.status_code == 403

def test_download_with_no_cert(
self,
pqc_guarded_distribution,
distribution_base_url,
):
distribution = pqc_guarded_distribution.distribution
url = distribution_base_url(distribution.base_url)
response = requests.get(urljoin(url, "test_file"))
assert response.status_code == 403
1 change: 0 additions & 1 deletion pulp_certguard/tests/functional/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
X509_CERTS_BASE_PATH, "un_urlencoded_cert.txt"
)


RHSM_CA_CERT_FILE_PATH = os.path.join(_CURRENT_DIR, "artifacts", "rhsm", "katello-default-ca.crt")

RHSM_CLIENT_CERT_FROM_UNTRUSTED_CA = os.path.join(
Expand Down
58 changes: 58 additions & 0 deletions pulp_file/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,64 @@ def _file_remote_client_cert_req_factory(*, manifest_path, policy, **kwargs):
return _file_remote_client_cert_req_factory


@pytest.fixture(scope="class")
def file_fixture_server_pqc_ssl(pqc_ssl_ctx, file_fixtures_root, gen_fixture_server):
return gen_fixture_server(file_fixtures_root, pqc_ssl_ctx)


@pytest.fixture(scope="class")
def file_fixture_server_pqc_ssl_client_cert_req(
pqc_ssl_ctx_req_client_auth, file_fixtures_root, gen_fixture_server
):
return gen_fixture_server(file_fixtures_root, pqc_ssl_ctx_req_client_auth)


@pytest.fixture(scope="class")
def file_remote_pqc_ssl_factory(
file_fixture_server_pqc_ssl,
file_bindings,
pqc_certificate_authority,
gen_object_with_cleanup,
):
def _file_remote_pqc_ssl_factory(*, manifest_path, policy, **kwargs):
url = file_fixture_server_pqc_ssl.make_url(manifest_path)
kwargs.update(
{
"url": str(url),
"policy": policy,
"name": str(uuid.uuid4()),
"ca_cert": pqc_certificate_authority.ca_cert_pem,
}
)
return gen_object_with_cleanup(file_bindings.RemotesFileApi, kwargs)

return _file_remote_pqc_ssl_factory


@pytest.fixture(scope="class")
def file_remote_pqc_client_cert_req_factory(
file_fixture_server_pqc_ssl_client_cert_req,
file_bindings,
pqc_certificate_authority,
gen_object_with_cleanup,
):
def _file_remote_pqc_client_cert_req_factory(*, manifest_path, policy, **kwargs):
url = file_fixture_server_pqc_ssl_client_cert_req.make_url(manifest_path)
kwargs.update(
{
"url": str(url),
"policy": policy,
"name": str(uuid.uuid4()),
"ca_cert": pqc_certificate_authority.ca_cert_pem,
"client_cert": pqc_certificate_authority.client_cert_pem,
"client_key": pqc_certificate_authority.client_key_pem,
}
)
return gen_object_with_cleanup(file_bindings.RemotesFileApi, kwargs)

return _file_remote_pqc_client_cert_req_factory


@pytest.fixture(scope="class")
def file_repository_factory(file_bindings, gen_object_with_cleanup):
"""A factory to generate a File Repository with auto-deletion after the test run."""
Expand Down
46 changes: 46 additions & 0 deletions pulp_file/tests/functional/api/test_remote_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,52 @@ def test_http_sync_ssl_with_client_cert_req(
)


@pytest.mark.parallel
def test_http_sync_pqc_ssl_tls_validation_on(
file_bindings,
file_remote_pqc_ssl_factory,
file_repo,
basic_manifest_path,
monitor_task,
):
"""
Test file on_demand sync with https:// using PQC (ML-DSA-65) server certificate.
"""
remote_on_demand = file_remote_pqc_ssl_factory(
manifest_path=basic_manifest_path, policy="on_demand", tls_validation=True
)

_run_basic_sync_and_assert(
file_bindings,
remote_on_demand,
file_repo,
monitor_task,
)


@pytest.mark.parallel
def test_http_sync_pqc_ssl_with_client_cert_req(
file_bindings,
file_remote_pqc_client_cert_req_factory,
file_repo,
basic_manifest_path,
monitor_task,
):
"""
Test file on_demand sync with https:// using PQC (ML-DSA-65) mutual TLS authentication.
"""
remote_on_demand = file_remote_pqc_client_cert_req_factory(
manifest_path=basic_manifest_path, policy="on_demand"
)

_run_basic_sync_and_assert(
file_bindings,
remote_on_demand,
file_repo,
monitor_task,
)


@pytest.mark.parallel
def test_ondemand_to_immediate_sync(
file_bindings,
Expand Down
Loading
Loading