pulp-container 2.22.6__tar.gz → 2.22.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. {pulp_container-2.22.6 → pulp_container-2.22.7}/CHANGES.md +9 -0
  2. {pulp_container-2.22.6 → pulp_container-2.22.7}/PKG-INFO +2 -1
  3. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/__init__.py +1 -1
  4. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/models.py +0 -1
  5. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/registry_api.py +4 -2
  6. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/serializers.py +8 -17
  7. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/sign.py +4 -1
  8. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/sync_stages.py +4 -2
  9. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/synchronize.py +0 -1
  10. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/urls.py +0 -1
  11. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/utils.py +55 -26
  12. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/viewsets.py +0 -1
  13. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/constants.py +0 -1
  14. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_build_images.py +4 -8
  15. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_sync_signatures.py +67 -0
  16. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container.egg-info/PKG-INFO +2 -1
  17. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container.egg-info/requires.txt +1 -0
  18. {pulp_container-2.22.6 → pulp_container-2.22.7}/pyproject.toml +1 -1
  19. {pulp_container-2.22.6 → pulp_container-2.22.7}/requirements.txt +2 -1
  20. {pulp_container-2.22.6 → pulp_container-2.22.7}/setup.py +1 -1
  21. {pulp_container-2.22.6 → pulp_container-2.22.7}/COMMITMENT +0 -0
  22. {pulp_container-2.22.6 → pulp_container-2.22.7}/COPYRIGHT +0 -0
  23. {pulp_container-2.22.6 → pulp_container-2.22.7}/LICENSE +0 -0
  24. {pulp_container-2.22.6 → pulp_container-2.22.7}/MANIFEST.in +0 -0
  25. {pulp_container-2.22.6 → pulp_container-2.22.7}/README.rst +0 -0
  26. {pulp_container-2.22.6 → pulp_container-2.22.7}/functest_requirements.txt +0 -0
  27. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/__init__.py +0 -0
  28. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/access_policy.py +0 -0
  29. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/authorization.py +0 -0
  30. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/cache.py +0 -0
  31. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/checks.py +0 -0
  32. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/content.py +0 -0
  33. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/downloaders.py +0 -0
  34. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/exceptions.py +0 -0
  35. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/fields.py +0 -0
  36. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/global_access_conditions.py +0 -0
  37. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/json_schemas.py +0 -0
  38. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/management/__init__.py +0 -0
  39. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/management/commands/__init__.py +0 -0
  40. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/management/commands/container-handle-image-data.py +0 -0
  41. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/management/commands/container-repair-media-type.py +0 -0
  42. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0001_initial.py +0 -0
  43. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0002_containerrepository.py +0 -0
  44. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0003_oci_mediatype.py +0 -0
  45. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0004_upload.py +0 -0
  46. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0005_contentredirectcontentguard.py +0 -0
  47. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0006_containerpushrepository.py +0 -0
  48. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0007_clear_tags_artifacts_refs.py +0 -0
  49. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0008_include_exclude_tags.py +0 -0
  50. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0009_container_namespace.py +0 -0
  51. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0010_remove_uploadchunk.py +0 -0
  52. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0011_add_container_repository_permissions.py +0 -0
  53. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0012_add_container_namespace_permissions.py +0 -0
  54. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0013_add_pull_push_permissions.py +0 -0
  55. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0014_containerdistribution_private.py +0 -0
  56. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0015_manage_tags_push_repo.py +0 -0
  57. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0016_add_delete_versions_permission.py +0 -0
  58. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0017_add_granular_perms.py +0 -0
  59. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0018_containerdistribution_description.py +0 -0
  60. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0019_DATA_distribution_model_swap.py +0 -0
  61. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0020_update_push_repo_perms.py +0 -0
  62. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0021_data_move_redirect_content_guard_to_core.py +0 -0
  63. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0022_delete_contentredirectcontentguard.py +0 -0
  64. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0023_manifestsignature.py +0 -0
  65. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0024_containerremote_sigstore.py +0 -0
  66. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0025_signature_stored_in_textfield.py +0 -0
  67. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0026_manifest_signing_service.py +0 -0
  68. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0027_data_translate_perms_to_roles.py +0 -0
  69. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0028_add_role_manage_permissions.py +0 -0
  70. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0029_remove_blob_media_type.py +0 -0
  71. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0030_enforce_tagged_manifest_reference.py +0 -0
  72. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0031_replace_charf_with_textf.py +0 -0
  73. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0032_upload_artifact.py +0 -0
  74. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0033_raise_warning_for_repair.py +0 -0
  75. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0034_translate_signed_schema.py +0 -0
  76. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0035_alter_blob_content_ptr_and_more.py +0 -0
  77. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0036_containerpushrepository_pending_blobs_manifests.py +0 -0
  78. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0037_create_pull_through_cache_models.py +0 -0
  79. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0038_add_manifest_metadata_fields.py +0 -0
  80. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0039_manifest_data.py +0 -0
  81. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0040_add_remote_repo_filter.py +0 -0
  82. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0041_add_pull_through_pull_permissions.py +0 -0
  83. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0042_add_manifest_nature_field.py +0 -0
  84. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/0043_add_os_arch_image_size_manifest_fields.py +0 -0
  85. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/migrations/__init__.py +0 -0
  86. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/modelresource.py +0 -0
  87. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/redirects.py +0 -0
  88. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/registry.py +0 -0
  89. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/replica.py +0 -0
  90. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/settings.py +0 -0
  91. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/__init__.py +0 -0
  92. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/builder.py +0 -0
  93. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/download_image_data.py +0 -0
  94. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/recursive_add.py +0 -0
  95. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/recursive_remove.py +0 -0
  96. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/tag.py +0 -0
  97. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/tasks/untag.py +0 -0
  98. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/token_verification.py +0 -0
  99. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/webserver_snippets/__init__.py +0 -0
  100. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/webserver_snippets/apache.conf +0 -0
  101. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/app/webserver_snippets/nginx.conf +0 -0
  102. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/__init__.py +0 -0
  103. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/__init__.py +0 -0
  104. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/__init__.py +0 -0
  105. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/rbac_base.py +0 -0
  106. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_content_cache.py +0 -0
  107. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_crud_distributions.py +0 -0
  108. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_crud_remotes.py +0 -0
  109. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_flatpak.py +0 -0
  110. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_pull_content.py +0 -0
  111. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_pull_through_cache.py +0 -0
  112. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_pulpimportexport.py +0 -0
  113. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_push_content.py +0 -0
  114. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_push_signatures.py +0 -0
  115. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_rbac_push_repositories.py +0 -0
  116. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_rbac_remotes.py +0 -0
  117. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_rbac_repo_content.py +0 -0
  118. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_rbac_repo_versions.py +0 -0
  119. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_rbac_sync_repositories.py +0 -0
  120. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_recursive_add.py +0 -0
  121. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_recursive_remove.py +0 -0
  122. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_remote_filter_pull_through.py +0 -0
  123. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_repositories_list.py +0 -0
  124. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_sign_manifests.py +0 -0
  125. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_sync.py +0 -0
  126. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_tagging_images.py +0 -0
  127. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/api/test_token_authentication.py +0 -0
  128. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/conftest.py +0 -0
  129. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/constants.py +0 -0
  130. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/functional/utils.py +0 -0
  131. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/unit/__init__.py +0 -0
  132. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/unit/test_json_schemas.py +0 -0
  133. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/unit/test_models.py +0 -0
  134. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container/tests/unit/test_serializers.py +0 -0
  135. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container.egg-info/SOURCES.txt +0 -0
  136. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container.egg-info/dependency_links.txt +0 -0
  137. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container.egg-info/entry_points.txt +0 -0
  138. {pulp_container-2.22.6 → pulp_container-2.22.7}/pulp_container.egg-info/top_level.txt +0 -0
  139. {pulp_container-2.22.6 → pulp_container-2.22.7}/setup.cfg +0 -0
  140. {pulp_container-2.22.6 → pulp_container-2.22.7}/test_requirements.txt +0 -0
  141. {pulp_container-2.22.6 → pulp_container-2.22.7}/unittest_requirements.txt +0 -0
@@ -8,6 +8,15 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 2.22.7 (2026-03-30) {: #2.22.7 }
12
+
13
+ #### Bugfixes {: #2.22.7-bugfix }
14
+
15
+ - Don't blow up on encountering PQC signatures.
16
+ [#2237](https://github.com/pulp/pulp_container/issues/2237)
17
+
18
+ ---
19
+
11
20
  ## 2.22.6 (2026-03-18) {: #2.22.6 }
12
21
 
13
22
  #### Bugfixes {: #2.22.6-bugfix }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-container
3
- Version: 2.22.6
3
+ Version: 2.22.7
4
4
  Summary: Container plugin for the Pulp Project
5
5
  Home-page: https://pulpproject.org/
6
6
  Author: Pulp Team
@@ -20,6 +20,7 @@ Requires-Dist: jsonschema<4.24,>=4.4
20
20
  Requires-Dist: pulpcore<3.70,>=3.49.0
21
21
  Requires-Dist: pyjwt[crypto]<2.10,>=2.4
22
22
  Requires-Dist: pycares<4.9
23
+ Requires-Dist: pysequoia==0.1.32
23
24
  Dynamic: author
24
25
  Dynamic: author-email
25
26
  Dynamic: classifier
@@ -23,7 +23,7 @@ class PulpContainerPluginAppConfig(PulpPluginAppConfig):
23
23
 
24
24
  name = "pulp_container.app"
25
25
  label = "container"
26
- version = "2.22.6"
26
+ version = "2.22.7"
27
27
  python_package_name = "pulp-container"
28
28
 
29
29
  @staticmethod
@@ -41,7 +41,6 @@ from pulp_container.constants import (
41
41
  SIGNATURE_TYPE,
42
42
  )
43
43
 
44
-
45
44
  logger = getLogger(__name__)
46
45
 
47
46
 
@@ -1493,8 +1493,10 @@ class Signatures(ContainerRegistryApiMixin, ViewSet):
1493
1493
  except binascii.Error:
1494
1494
  raise ManifestSignatureInvalid(digest=pk)
1495
1495
 
1496
- signature_json = extract_data_from_signature(signature_raw, manifest.digest)
1497
- if signature_json is None:
1496
+ try:
1497
+ signature_json = extract_data_from_signature(signature_raw, manifest.digest)
1498
+ except ValueError as exc:
1499
+ log.warning("Error processing signature on upload: {}".format(exc))
1498
1500
  raise ManifestSignatureInvalid(digest=pk)
1499
1501
 
1500
1502
  sig_digest = hashlib.sha256(signature_raw).hexdigest()
@@ -32,7 +32,6 @@ from pulp_file.app.models import FileContent
32
32
  from pulp_container.app import models, fields
33
33
  from pulp_container.constants import SIGNATURE_TYPE
34
34
 
35
-
36
35
  VALID_SIGNATURE_NAME_REGEX = r"^sha256:[0-9a-f]{64}@[0-9a-f]{32}$"
37
36
  VALID_TAG_REGEX = r"^[A-Za-z0-9][A-Za-z0-9._-]*$"
38
37
  VALID_BASE_PATH_REGEX_COMPILED = re.compile(r"^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9])*$")
@@ -285,25 +284,21 @@ class ContainerRemoteSerializer(RemoteSerializer):
285
284
  child=serializers.CharField(max_length=255),
286
285
  allow_null=True,
287
286
  required=False,
288
- help_text=_(
289
- """
287
+ help_text=_("""
290
288
  A list of tags to include during sync.
291
289
  Wildcards *, ? are recognized.
292
290
  'include_tags' is evaluated before 'exclude_tags'.
293
- """
294
- ),
291
+ """),
295
292
  )
296
293
  exclude_tags = serializers.ListField(
297
294
  child=serializers.CharField(max_length=255),
298
295
  allow_null=True,
299
296
  required=False,
300
- help_text=_(
301
- """
297
+ help_text=_("""
302
298
  A list of tags to exclude during sync.
303
299
  Wildcards *, ? are recognized.
304
300
  'exclude_tags' is evaluated after 'include_tags'.
305
- """
306
- ),
301
+ """),
307
302
  )
308
303
 
309
304
  policy = serializers.ChoiceField(
@@ -343,25 +338,21 @@ class ContainerPullThroughRemoteSerializer(RemoteSerializer):
343
338
  child=serializers.CharField(max_length=255),
344
339
  allow_null=True,
345
340
  required=False,
346
- help_text=_(
347
- """
341
+ help_text=_("""
348
342
  A list of remotes to include during pull-through caching.
349
343
  Wildcards *, ? are recognized.
350
344
  'includes' is evaluated before 'excludes'.
351
- """
352
- ),
345
+ """),
353
346
  )
354
347
  excludes = serializers.ListField(
355
348
  child=serializers.CharField(max_length=255),
356
349
  allow_null=True,
357
350
  required=False,
358
- help_text=_(
359
- """
351
+ help_text=_("""
360
352
  A list of remotes to exclude during pull-through caching.
361
353
  Wildcards *, ? are recognized.
362
354
  'excludes' is evaluated after 'includes'.
363
- """
364
- ),
355
+ """),
365
356
  )
366
357
 
367
358
  class Meta:
@@ -133,7 +133,10 @@ async def create_signature(manifest, reference, signing_service):
133
133
  data = sig_fp.read()
134
134
  encoded_sig = base64.b64encode(data).decode()
135
135
  sig_digest = hashlib.sha256(data).hexdigest()
136
- sig_json = extract_data_from_signature(data, manifest.digest)
136
+ try:
137
+ sig_json = extract_data_from_signature(data, manifest.digest)
138
+ except ValueError:
139
+ raise
137
140
  manifest_digest = sig_json["critical"]["image"]["docker-manifest-digest"]
138
141
 
139
142
  signature = ManifestSignature(
@@ -414,8 +414,10 @@ class ContainerFirstStage(Stage):
414
414
  def _create_signature_declarative_content(
415
415
  self, signature_raw, man_dc, name=None, signature_b64=None
416
416
  ):
417
- signature_json = extract_data_from_signature(signature_raw, man_dc.content.digest)
418
- if signature_json is None:
417
+ try:
418
+ signature_json = extract_data_from_signature(signature_raw, man_dc.content.digest)
419
+ except ValueError as exc:
420
+ log.warning("Error processing signature on sync: {}".format(str(exc)))
419
421
  return
420
422
 
421
423
  sig_digest = hashlib.sha256(signature_raw).hexdigest()
@@ -13,7 +13,6 @@ from pulpcore.plugin.stages import (
13
13
  from .sync_stages import ContainerFirstStage, ContainerContentSaver
14
14
  from pulp_container.app.models import ContainerRemote, ContainerRepository
15
15
 
16
-
17
16
  log = logging.getLogger(__name__)
18
17
 
19
18
 
@@ -14,7 +14,6 @@ from pulp_container.app.registry_api import (
14
14
  VersionView,
15
15
  )
16
16
 
17
-
18
17
  router = SimpleRouter(trailing_slash=False)
19
18
 
20
19
  head_route = Route(
@@ -1,9 +1,6 @@
1
1
  import base64
2
2
  import hashlib
3
3
  import fnmatch
4
- import re
5
- import subprocess
6
- import gnupg
7
4
  import json
8
5
  import logging
9
6
  import time
@@ -15,6 +12,8 @@ from django.db import IntegrityError
15
12
  from functools import partial
16
13
  from rest_framework.exceptions import Throttled
17
14
 
15
+ from pysequoia.packet import PacketPile, Tag
16
+
18
17
  from pulpcore.plugin.models import Artifact, Task
19
18
 
20
19
  from pulp_container.constants import (
@@ -31,9 +30,6 @@ from pulp_container.app.json_schemas import (
31
30
  SIGNATURE_SCHEMA,
32
31
  )
33
32
 
34
- KEY_ID_REGEX_COMPILED = re.compile(r"keyid ([0-9A-F]+)")
35
- TIMESTAMP_REGEX_COMPILED = re.compile(r"created ([0-9]+)")
36
-
37
33
  signature_validator = Draft7Validator(SIGNATURE_SCHEMA)
38
34
 
39
35
  log = logging.getLogger(__name__)
@@ -78,6 +74,20 @@ def urlpath_sanitize(*args):
78
74
  return "/".join(segments)
79
75
 
80
76
 
77
+ def keyid_from_fingerprint(fingerprint):
78
+ """Derive a key ID from an OpenPGP fingerprint.
79
+
80
+ For v4 fingerprints (40 hex chars / 20 bytes), the key ID is the last 8 bytes.
81
+ For v6 fingerprints (64 hex chars / 32 bytes), the key ID is the first 8 bytes.
82
+ """
83
+ if len(fingerprint) == 40:
84
+ return fingerprint[-16:]
85
+ elif len(fingerprint) == 64:
86
+ return fingerprint[:16]
87
+ else:
88
+ raise ValueError(f"Unexpected fingerprint length: {len(fingerprint)}")
89
+
90
+
81
91
  def extract_data_from_signature(signature_raw, man_digest):
82
92
  """
83
93
  Extract data from an "integrated" signature, aka a signed non-encrypted document.
@@ -90,37 +100,56 @@ def extract_data_from_signature(signature_raw, man_digest):
90
100
  dict: JSON representation of the document and available data about signature
91
101
 
92
102
  """
93
- gpg = gnupg.GPG()
94
- crypt_obj = gpg.decrypt(signature_raw, extra_args=["--skip-verify"])
95
- if not crypt_obj.data:
96
- log.info(
97
- "It is not possible to read the signed document, GPG error: {}".format(crypt_obj.stderr)
103
+ try:
104
+ pile = PacketPile.from_bytes(signature_raw)
105
+ except Exception as exc:
106
+ raise ValueError(
107
+ "Signed document for manifest {} is un-parseable: {}".format(man_digest, str(exc))
98
108
  )
99
- return
109
+
110
+ literal_data = None
111
+ signing_key_id = None
112
+ signing_key_fingerprint = None
113
+ signature_timestamp = None
114
+
115
+ for packet in pile:
116
+ if packet.tag == Tag.Literal:
117
+ literal_data = bytes(packet.literal_data)
118
+ elif packet.tag == Tag.Signature:
119
+ if packet.issuer_key_id is not None:
120
+ signing_key_id = packet.issuer_key_id.upper()
121
+ elif packet.issuer_fingerprint is not None:
122
+ signing_key_fingerprint = packet.issuer_fingerprint.upper()
123
+ signing_key_id = keyid_from_fingerprint(signing_key_fingerprint)
124
+ else:
125
+ raise ValueError(
126
+ "Signature for manifest {} has no fingerprint or key_id".format(man_digest)
127
+ )
128
+ if packet.signature_created is not None:
129
+ signature_timestamp = int(packet.signature_created.timestamp())
130
+
131
+ if not literal_data:
132
+ raise ValueError("Signature for manifest {} has no literal data".format(man_digest))
100
133
 
101
134
  try:
102
- sig_json = json.loads(crypt_obj.data)
135
+ sig_json = json.loads(literal_data)
103
136
  except Exception as exc:
104
- log.info(
105
- "Signed document cannot be parsed to create a signature for {}."
106
- " Error: {}".format(man_digest, str(exc))
137
+ raise ValueError(
138
+ "Signed document cannot be parsed to create a signature for {}. Error: {}".format(
139
+ man_digest, str(exc)
140
+ )
107
141
  )
108
- return
109
142
 
110
143
  errors = []
111
144
  for error in signature_validator.iter_errors(sig_json):
112
145
  errors.append(f'{".".join(error.path)}: {error.message}')
113
146
 
114
147
  if errors:
115
- log.info("The signature for {} is not synced due to: {}".format(man_digest, errors))
116
- return
117
-
118
- # decrypted and unverified signatures do not have prepopulated the key_id and timestamp
119
- # fields; thus, it is necessary to use the debugging utilities of gpg to extract these
120
- # fields since they are not encrypted and still readable without decrypting the signature first
121
- packets = subprocess.check_output(["gpg", "--list-packets"], input=signature_raw).decode()
122
- sig_json["signing_key_id"] = KEY_ID_REGEX_COMPILED.search(packets).group(1)
123
- sig_json["signature_timestamp"] = TIMESTAMP_REGEX_COMPILED.search(packets).group(1)
148
+ raise ValueError("The signature for {} is not synced due to: {}".format(man_digest, errors))
149
+
150
+ sig_json["signing_key_id"] = signing_key_id
151
+ sig_json["signing_key_fingerprint"] = signing_key_fingerprint
152
+ sig_json["signature_timestamp"] = signature_timestamp
124
153
 
125
154
  return sig_json
126
155
 
@@ -44,7 +44,6 @@ from pulpcore.plugin.viewsets import (
44
44
 
45
45
  from pulp_container.app import models, serializers, tasks
46
46
 
47
-
48
47
  log = logging.getLogger(__name__)
49
48
 
50
49
 
@@ -1,6 +1,5 @@
1
1
  from types import SimpleNamespace
2
2
 
3
-
4
3
  MEDIA_TYPE = SimpleNamespace(
5
4
  MANIFEST_V1="application/vnd.docker.distribution.manifest.v1+json",
6
5
  MANIFEST_V1_SIGNED="application/vnd.docker.distribution.manifest.v1+prettyjws",
@@ -14,13 +14,11 @@ from pulp_container.constants import MANIFEST_TYPE
14
14
  def containerfile_name():
15
15
  """A fixture for a basic container file used for building images."""
16
16
  with NamedTemporaryFile() as containerfile:
17
- containerfile.write(
18
- b"""FROM quay.io/quay/busybox:latest
17
+ containerfile.write(b"""FROM quay.io/quay/busybox:latest
19
18
  # Copy a file using COPY statement. Use the relative path specified in the 'artifacts' parameter.
20
19
  COPY foo/bar/example.txt /tmp/inside-image.txt
21
20
  # Print the content of the file when the container starts
22
- CMD ["cat", "/tmp/inside-image.txt"]"""
23
- )
21
+ CMD ["cat", "/tmp/inside-image.txt"]""")
24
22
  containerfile.flush()
25
23
  yield containerfile.name
26
24
 
@@ -221,11 +219,9 @@ def test_without_build_context(
221
219
 
222
220
  def containerfile_without_context_files():
223
221
  with NamedTemporaryFile() as containerfile:
224
- containerfile.write(
225
- b"""FROM quay.io/quay/busybox:latest
222
+ containerfile.write(b"""FROM quay.io/quay/busybox:latest
226
223
  # Print the content of the file when the container starts
227
- CMD ["ls", "/"]"""
228
- )
224
+ CMD ["ls", "/"]""")
229
225
  containerfile.flush()
230
226
  yield containerfile.name
231
227
 
@@ -15,6 +15,9 @@ IMAGE_MANIFEST_TAG = "7.9-511-source"
15
15
  MANIFEST_LIST_TAG = "7.9"
16
16
  SIGSTORE_URL = "https://access.redhat.com/webassets/docker/content/sigstore"
17
17
 
18
+ UBI10_MICRO_REPOSITORY_NAME = "ubi10-micro"
19
+ UBI10_MICRO_TAG = "latest"
20
+
18
21
 
19
22
  @pytest.fixture
20
23
  def synced_repository(
@@ -127,3 +130,67 @@ def test_sync_images_without_sigstore_requiring_signatures(
127
130
 
128
131
  tags = container_tag_api.list(repository_version=synced_repository.latest_version_href).results
129
132
  assert len(tags) == 0
133
+
134
+
135
+ def test_sync_image_with_pqc_signatures(
136
+ delete_orphans_pre,
137
+ container_repository_api,
138
+ container_remote_api,
139
+ container_signature_api,
140
+ container_tag_api,
141
+ container_manifest_api,
142
+ gen_object_with_cleanup,
143
+ ):
144
+ """Sync ubi10-micro:latest from registry.access.redhat.com with all signatures."""
145
+ data = gen_container_remote(
146
+ url=REDHAT_REGISTRY_V2,
147
+ upstream_name=UBI10_MICRO_REPOSITORY_NAME,
148
+ policy="on_demand",
149
+ include_tags=[UBI10_MICRO_TAG],
150
+ sigstore=SIGSTORE_URL,
151
+ )
152
+ remote = gen_object_with_cleanup(container_remote_api, data)
153
+
154
+ repo = gen_object_with_cleanup(
155
+ container_repository_api, ContainerContainerRepository(**gen_repo())
156
+ )
157
+
158
+ sync_data = ContainerRepositorySyncURL(remote=remote.pulp_href, signed_only=False)
159
+ response = container_repository_api.sync(repo.pulp_href, sync_data)
160
+ monitor_task(response.task)
161
+
162
+ repo = container_repository_api.read(repo.pulp_href)
163
+
164
+ tags = container_tag_api.list(repository_version=repo.latest_version_href).results
165
+ assert len(tags) == 1
166
+ assert tags[0].name == UBI10_MICRO_TAG
167
+
168
+ signatures = container_signature_api.list(repository_version=repo.latest_version_href).results
169
+ assert len(signatures) > 0
170
+
171
+ # Assert that a signature using one of the "old" Red Hat signing release keys exist
172
+ expected_key_ids = ["199E2F91FD431D51", "E60D446E63405576"]
173
+ assert any(s.key_id in expected_key_ids for s in signatures), (
174
+ f"No signature found with key_ids {expected_key_ids}; "
175
+ f"found key_ids: {sorted({s.key_id for s in signatures})}"
176
+ )
177
+
178
+ # Assert that a signature using the Red Hat PQC (ML-DSA-87) signing key exists
179
+ # Fingerprint: FCD355B305707A62DA143AB6E422397E50FE8467A2A95343D246D6276AFEDF8F
180
+ # Key ID => first 8 bytes (16 hex chars)
181
+ expected_key_id = "FCD355B305707A62"
182
+ assert any(s.key_id == expected_key_id for s in signatures), (
183
+ f"No signature found with key_id {expected_key_id!r}; "
184
+ f"found key_ids: {sorted({s.key_id for s in signatures})}"
185
+ )
186
+
187
+ # ubi10-micro:latest is a manifest list; collect all listed manifests and verify
188
+ # that each has at least one signature
189
+ manifest_list = container_manifest_api.read(tags[0].tagged_manifest)
190
+ listed_manifests = [
191
+ container_manifest_api.read(lm_href) for lm_href in manifest_list.listed_manifests
192
+ ]
193
+ for lm in listed_manifests:
194
+ lm_signatures = [s for s in signatures if s.signed_manifest == lm.pulp_href]
195
+ assert len(lm_signatures) > 0, f"No signatures found for manifest {lm.digest}"
196
+ assert all(s.name.startswith(lm.digest) for s in lm_signatures)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-container
3
- Version: 2.22.6
3
+ Version: 2.22.7
4
4
  Summary: Container plugin for the Pulp Project
5
5
  Home-page: https://pulpproject.org/
6
6
  Author: Pulp Team
@@ -20,6 +20,7 @@ Requires-Dist: jsonschema<4.24,>=4.4
20
20
  Requires-Dist: pulpcore<3.70,>=3.49.0
21
21
  Requires-Dist: pyjwt[crypto]<2.10,>=2.4
22
22
  Requires-Dist: pycares<4.9
23
+ Requires-Dist: pysequoia==0.1.32
23
24
  Dynamic: author
24
25
  Dynamic: author-email
25
26
  Dynamic: classifier
@@ -2,3 +2,4 @@ jsonschema<4.24,>=4.4
2
2
  pulpcore<3.70,>=3.49.0
3
3
  pyjwt[crypto]<2.10,>=2.4
4
4
  pycares<4.9
5
+ pysequoia==0.1.32
@@ -57,7 +57,7 @@ ignore = [
57
57
  [tool.bumpversion]
58
58
  # This section is managed by the plugin template. Do not edit manually.
59
59
 
60
- current_version = "2.22.6"
60
+ current_version = "2.22.7"
61
61
  commit = false
62
62
  tag = false
63
63
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
@@ -1,4 +1,5 @@
1
1
  jsonschema>=4.4,<4.24
2
2
  pulpcore>=3.49.0,<3.70
3
3
  pyjwt[crypto]>=2.4,<2.10
4
- pycares<4.9
4
+ pycares<4.9
5
+ pysequoia==0.1.32
@@ -10,7 +10,7 @@ with open("README.rst") as f:
10
10
 
11
11
  setup(
12
12
  name="pulp-container",
13
- version="2.22.6",
13
+ version="2.22.7",
14
14
  description="Container plugin for the Pulp Project",
15
15
  long_description=long_description,
16
16
  license="GPLv2+",
File without changes