Skip to content

Commit a16ced7

Browse files
committed
Ensure signing only happens when using *_sign policies
This commit adds two new sync policies, `additive_sign` and `mirror_content_only_sign`, which are identical to the non `_sign` versions, but will sign packages during the sync process. It also adds a test to ensure that signing *doesn't* happen when not using a `*_sign` policy. And, because sync is smart enough to skip packages that have identical nevras, we need to ensure that all packages are garbage collected before running the tests, so we're adding the delete_orphans_pre fixture to both tests. Signed-off-by: Jonathan Dieter <jdieter@ciq.com>
1 parent 9767c43 commit a16ced7

6 files changed

Lines changed: 114 additions & 15 deletions

File tree

pulp_rpm/app/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,18 @@
5353

5454
SYNC_POLICIES = SimpleNamespace(
5555
ADDITIVE="additive",
56+
ADDITIVE_SIGN="additive_sign",
5657
MIRROR_COMPLETE="mirror_complete",
5758
MIRROR_CONTENT_ONLY="mirror_content_only",
59+
MIRROR_CONTENT_ONLY_SIGN="mirror_content_only_sign",
5860
)
5961

6062
SYNC_POLICY_CHOICES = (
6163
(SYNC_POLICIES.ADDITIVE, SYNC_POLICIES.ADDITIVE),
64+
(SYNC_POLICIES.ADDITIVE_SIGN, SYNC_POLICIES.ADDITIVE_SIGN),
6265
(SYNC_POLICIES.MIRROR_COMPLETE, SYNC_POLICIES.MIRROR_COMPLETE),
6366
(SYNC_POLICIES.MIRROR_CONTENT_ONLY, SYNC_POLICIES.MIRROR_CONTENT_ONLY),
67+
(SYNC_POLICIES.MIRROR_CONTENT_ONLY_SIGN, SYNC_POLICIES.MIRROR_CONTENT_ONLY_SIGN),
6468
)
6569

6670
CR_PACKAGE_ATTRS = SimpleNamespace(

pulp_rpm/app/serializers/repository.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,14 +472,16 @@ class RpmRepositorySyncURLSerializer(RepositorySyncURLSerializer):
472472
)
473473
sync_policy = serializers.ChoiceField(
474474
help_text=_(
475-
"Options: 'additive', 'mirror_complete', 'mirror_content_only'. Default: 'additive'. "
475+
"Options: 'additive', 'additive_sign', 'mirror_complete', 'mirror_content_only', "
476+
"'mirror_content_only_sign'. Default: 'additive'. "
476477
"Modifies how the sync is performed. 'mirror_complete' will clone the original "
477478
"metadata and create an automatic publication from it, but comes with some "
478479
"limitations and does not work for certain repositories. 'mirror_content_only' will "
479480
"change the repository contents to match the remote but the metadata will be "
480481
"regenerated and will not be bit-for-bit identical. 'additive' will retain the "
481482
"existing contents of the repository and add the contents of the repository being "
482-
"synced."
483+
"synced. The '_sign' variants will also sign RPM packages during sync if the "
484+
"repository has a signing service configured."
483485
),
484486
choices=SYNC_POLICY_CHOICES,
485487
required=False,

pulp_rpm/app/tasks/synchronizing.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,9 @@ def is_subrepo(directory):
566566
namespace=directory,
567567
)
568568

569-
dv = RpmDeclarativeVersion(first_stage=stage, repository=repo, mirror=mirror)
569+
dv = RpmDeclarativeVersion(
570+
first_stage=stage, repository=repo, mirror=mirror, sync_policy=sync_policy
571+
)
570572
repo_version = dv.create() or repo.latest_version()
571573

572574
repo_config["sync_details"]["most_recent_version"] = repo_version.number
@@ -615,6 +617,7 @@ def __init__(self, *args, **kwargs):
615617
616618
Adding it here, because we call RpmDeclarativeVersion multiple times in sync.
617619
"""
620+
self.sync_policy = kwargs.pop("sync_policy", None)
618621
kwargs["acs"] = True
619622
super().__init__(*args, **kwargs)
620623

@@ -642,7 +645,7 @@ def pipeline_stages(self, new_version):
642645
[
643646
ArtifactDownloader(),
644647
ArtifactSaver(),
645-
RpmArtifactSigningStage(self.repository),
648+
RpmArtifactSigningStage(self.repository, self.sync_policy),
646649
QueryExistingContents(),
647650
RpmContentSaver(),
648651
RpmInterrelateContent(),
@@ -1582,15 +1585,17 @@ class RpmArtifactSigningStage(Stage):
15821585
This stage runs after ArtifactSaver, allowing us to sign the synchronized rpms
15831586
"""
15841587

1585-
def __init__(self, repository):
1588+
def __init__(self, repository, sync_policy=None):
15861589
"""
15871590
Initialize the signing stage.
15881591
15891592
Args:
15901593
repository: RpmRepository instance that may have signing service configured
1594+
sync_policy: Sync policy being used for this sync operation
15911595
"""
15921596
super().__init__()
15931597
self.repository = repository
1598+
self.sync_policy = sync_policy
15941599
# Populate the repository package signing service and fingerprint as they can't be
15951600
# populated asynchronously
15961601
_ = self.repository.package_signing_service
@@ -1614,9 +1619,24 @@ async def run(self):
16141619
f"No package signing service configured for repository {self.repository.name}"
16151620
)
16161621

1622+
# Check if this is a re-sign sync policy
1623+
should_sign = (
1624+
self.sync_policy
1625+
in (SYNC_POLICIES.MIRROR_CONTENT_ONLY_SIGN, SYNC_POLICIES.ADDITIVE_SIGN)
1626+
and signing_service
1627+
and fingerprint
1628+
)
1629+
1630+
if should_sign:
1631+
log.info(f"Re-signing RPM packages during sync with policy: {self.sync_policy}")
1632+
elif signing_service and fingerprint:
1633+
log.info(
1634+
f"Signing service configured but not re-signing with policy: {self.sync_policy}"
1635+
)
1636+
16171637
async for batch in self.batches():
16181638
for d_content in batch:
1619-
if signing_service and fingerprint:
1639+
if should_sign:
16201640
for d_artifact in d_content.d_artifacts:
16211641
await self._sign_rpm_artifact(d_artifact, signing_service, fingerprint)
16221642
await self.put(d_content)

pulp_rpm/app/viewsets/repository.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,14 @@ def sync(self, request, pk):
218218
sync_policy = SYNC_POLICIES.ADDITIVE if not mirror else SYNC_POLICIES.MIRROR_COMPLETE
219219

220220
# validate some invariants that involve repository-wide settings.
221-
if sync_policy in (SYNC_POLICIES.MIRROR_COMPLETE, SYNC_POLICIES.MIRROR_CONTENT_ONLY):
221+
if sync_policy in (
222+
SYNC_POLICIES.MIRROR_COMPLETE,
223+
SYNC_POLICIES.MIRROR_CONTENT_ONLY,
224+
SYNC_POLICIES.MIRROR_CONTENT_ONLY_SIGN,
225+
):
222226
err_msg = (
223-
"Cannot use '{}' in combination with a 'mirror_complete' or "
224-
"'mirror_content_only' sync policy."
227+
"Cannot use '{}' in combination with a 'mirror_complete', "
228+
"'mirror_content_only' or 'mirror_content_only_sign' sync policy."
225229
)
226230
if repository.retain_package_versions > 0:
227231
raise DRFValidationError(err_msg.format("retain_package_versions"))

pulp_rpm/tests/functional/api/test_package_signing.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,60 @@ def test_sign_chunked_package_on_upload(
237237
assert rpm_tool.verify_signature(downloaded_package)
238238

239239

240-
@pytest.mark.parallel
240+
def test_no_sign_packages_on_sync_if_additive(
241+
init_and_sync,
242+
tmp_path,
243+
gen_object_with_cleanup,
244+
download_content_unit,
245+
signing_gpg_extra,
246+
rpm_package_signing_service,
247+
rpm_package_api,
248+
rpm_repository_api,
249+
rpm_repository_factory,
250+
rpm_publication_factory,
251+
rpm_distribution_factory,
252+
delete_orphans_pre,
253+
):
254+
"""
255+
Ensure that sync doesn't sign packages if they're synced with a non `*_sign` policy
256+
"""
257+
from pulp_rpm.app.shared_utils import RpmTool
258+
259+
# Setup GPG and RPM tool
260+
gpg_a, _ = signing_gpg_extra
261+
fingerprint = gpg_a.fingerprint
262+
263+
rpm_tool = RpmTool(tmp_path)
264+
rpm_tool.import_pubkey_string(gpg_a.pubkey)
265+
266+
# Create repository with package signing service configured
267+
repository = rpm_repository_factory(
268+
package_signing_service=rpm_package_signing_service.pulp_href,
269+
package_signing_fingerprint=fingerprint,
270+
)
271+
init_and_sync(repository=repository)
272+
273+
# Get synced packages - refresh repository to get latest version
274+
updated_repository = rpm_repository_api.read(repository.pulp_href)
275+
packages = rpm_package_api.list(repository_version=updated_repository.latest_version_href)
276+
assert packages.count > 0, "No packages were synced"
277+
278+
# Test the first package to verify it was signed during sync
279+
test_package = packages.results[0]
280+
281+
# Verify that the final served package is not signed
282+
publication = rpm_publication_factory(repository=repository.pulp_href)
283+
distribution = rpm_distribution_factory(publication=publication.pulp_href)
284+
downloaded_package = tmp_path / "package.rpm"
285+
downloaded_package.write_bytes(
286+
download_content_unit(
287+
distribution.base_path, get_package_repo_path(test_package.location_href)
288+
)
289+
)
290+
with pytest.raises(InvalidSignatureError, match="The package is not signed: .*"):
291+
rpm_tool.verify_signature(downloaded_package)
292+
293+
241294
def test_sign_packages_on_sync(
242295
init_and_sync,
243296
tmp_path,
@@ -250,9 +303,10 @@ def test_sign_packages_on_sync(
250303
rpm_repository_factory,
251304
rpm_publication_factory,
252305
rpm_distribution_factory,
306+
delete_orphans_pre,
253307
):
254308
"""
255-
Sign an Rpm Package on repo sync endpoint
309+
Ensure that sync does sign packages if they're synced with a `*_sign` policy
256310
"""
257311
from pulp_rpm.app.shared_utils import RpmTool
258312

@@ -271,7 +325,7 @@ def test_sign_packages_on_sync(
271325
package_signing_service=rpm_package_signing_service.pulp_href,
272326
package_signing_fingerprint=fingerprint,
273327
)
274-
init_and_sync(repository=repository)
328+
init_and_sync(repository=repository, sync_policy="additive_sign")
275329

276330
# Get synced packages - refresh repository to get latest version
277331
updated_repository = rpm_repository_api.read(repository.pulp_href)

pulp_rpm/tests/functional/api/test_sync.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -716,9 +716,17 @@ def test_sync_modular_static_context(init_and_sync, get_content_summary, get_con
716716

717717

718718
@pytest.mark.parallel
719-
@pytest.mark.parametrize("sync_policy", ["mirror_content_only", "additive"])
719+
@pytest.mark.parametrize(
720+
"sync_policy",
721+
[
722+
"mirror_content_only",
723+
"mirror_content_only_sign",
724+
"additive",
725+
"additive_sign",
726+
],
727+
)
720728
def test_sync_skip_srpm(init_and_sync, sync_policy, get_content):
721-
"""In mirror_content_only mode, skip_types is allowed."""
729+
"""In mirror_content_only* mode, skip_types is allowed."""
722730
repository, _ = init_and_sync(
723731
url=SRPM_UNSIGNED_FIXTURE_URL, skip_types=["srpm"], sync_policy=sync_policy
724732
)
@@ -1113,7 +1121,14 @@ def test_additive_mode(init_and_sync, get_content):
11131121

11141122

11151123
@pytest.mark.parallel
1116-
@pytest.mark.parametrize("sync_policy", ["mirror_complete", "mirror_content_only"])
1124+
@pytest.mark.parametrize(
1125+
"sync_policy",
1126+
[
1127+
"mirror_complete",
1128+
"mirror_content_only",
1129+
"mirror_content_only_sign",
1130+
],
1131+
)
11171132
def test_mirror_mode(sync_policy, init_and_sync, rpm_publication_api, get_content_summary):
11181133
"""Test of mirror mode."""
11191134
repository, remote = init_and_sync(url=SRPM_UNSIGNED_FIXTURE_URL, policy="on_demand")

0 commit comments

Comments
 (0)