pulpcore 3.83.2__py3-none-any.whl → 3.85.0__py3-none-any.whl
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.
Potentially problematic release.
This version of pulpcore might be problematic. Click here for more details.
- pulp_certguard/app/__init__.py +1 -1
- pulp_certguard/app/models.py +7 -26
- pulp_certguard/app/serializers.py +0 -2
- pulp_certguard/rhsm/__init__.py +4 -0
- pulp_certguard/rhsm/rhsm_check_path.py +198 -0
- pulp_certguard/tests/unit/certdata.py +249 -0
- pulp_certguard/tests/unit/test_rhsm_check_path.py +213 -0
- pulp_file/app/__init__.py +1 -1
- pulp_file/app/migrations/0001_initial_squashed_0016_add_domain.py +0 -20
- pulp_file/app/migrations/0017_alter_filealternatecontentsource_alternatecontentsource_ptr_and_more.py +1 -1
- pulpcore/app/apps.py +2 -12
- pulpcore/app/entrypoint.py +22 -22
- pulpcore/app/migrations/0001_squashed_0090_char_to_text_field.py +0 -95
- pulpcore/app/migrations/0091_systemid.py +1 -1
- pulpcore/app/migrations/0134_task_insert_trigger.py +81 -0
- pulpcore/app/migrations/0135_task_pulp_task_resources_index.py +25 -0
- pulp_file/app/migrations/0006_delete_filefilesystemexporter.py → pulpcore/app/migrations/0136_delete_basedistribution.py +3 -3
- pulpcore/app/migrations/0137_appstatus.py +33 -0
- pulpcore/app/migrations/0138_vulnerabilityreport.py +33 -0
- pulpcore/app/models/__init__.py +4 -1
- pulpcore/app/models/publication.py +0 -41
- pulpcore/app/models/status.py +145 -0
- pulpcore/app/models/task.py +8 -0
- pulpcore/app/models/vulnerability_report.py +34 -0
- pulpcore/app/serializers/__init__.py +1 -0
- pulpcore/app/serializers/content.py +13 -1
- pulpcore/app/serializers/repository.py +8 -1
- pulpcore/app/serializers/vulnerability_report.py +27 -0
- pulpcore/app/settings.py +13 -38
- pulpcore/app/tasks/__init__.py +2 -0
- pulpcore/app/tasks/purge.py +8 -5
- pulpcore/app/tasks/vulnerability_report.py +159 -0
- pulpcore/app/viewsets/__init__.py +1 -0
- pulpcore/app/viewsets/vulnerability_report.py +20 -0
- pulpcore/constants.py +8 -0
- pulpcore/content/__init__.py +23 -22
- pulpcore/content/handler.py +5 -2
- pulpcore/migrations.py +38 -11
- pulpcore/openapi/__init__.py +8 -0
- pulpcore/plugin/models/__init__.py +2 -0
- pulpcore/plugin/serializers/__init__.py +2 -0
- pulpcore/plugin/tasking.py +2 -0
- pulpcore/plugin/viewsets/__init__.py +2 -0
- pulpcore/pytest_plugin.py +21 -21
- pulpcore/tasking/entrypoint.py +12 -2
- pulpcore/tasking/tasks.py +5 -30
- pulpcore/tasking/worker.py +115 -74
- pulpcore/tests/functional/api/test_auth.py +18 -3
- pulpcore/tests/functional/api/test_login.py +62 -32
- pulpcore/tests/functional/api/test_openapi_schema.py +32 -15
- pulpcore/tests/functional/api/using_plugin/test_checkpoint.py +23 -1
- pulpcore/tests/functional/api/using_plugin/test_proxy.py +1 -1
- pulpcore/tests/unit/content/test_heartbeat.py +11 -8
- pulpcore/tests/unit/test_vulnerability_report.py +74 -0
- {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/METADATA +13 -18
- {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/RECORD +60 -156
- pulp_certguard/app/utils.py +0 -28
- pulp_certguard/tests/unit/test_models.py +0 -9
- pulp_file/app/migrations/0001_initial.py +0 -59
- pulp_file/app/migrations/0002_file_related_names.py +0 -55
- pulp_file/app/migrations/0003_auto_20191014_1721.py +0 -18
- pulp_file/app/migrations/0004_filefilesystemexporter.py +0 -21
- pulp_file/app/migrations/0005_filerepository.py +0 -24
- pulp_file/app/migrations/0007_filefilesystemexporter.py +0 -25
- pulp_file/app/migrations/0008_add_manifest_field.py +0 -19
- pulp_file/app/migrations/0009_move_data_to_new_master_distribution_model.py +0 -77
- pulp_file/app/migrations/0010_auto_publish.py +0 -23
- pulp_file/app/migrations/0011_fix_auto_publish.py +0 -36
- pulp_file/app/migrations/0012_delete_filefilesystemexporter.py +0 -28
- pulp_file/app/migrations/0013_file_acs.py +0 -24
- pulp_file/app/migrations/0014_new_rbac_permissions.py +0 -33
- pulp_file/app/migrations/0015_allow_null_manifest.py +0 -23
- pulp_file/app/migrations/0016_add_domain.py +0 -25
- pulpcore/app/migrations/0001_initial.py +0 -451
- pulpcore/app/migrations/0002_increase_artifact_size_field.py +0 -18
- pulpcore/app/migrations/0003_remove_upload_completed.py +0 -17
- pulpcore/app/migrations/0004_add_duplicated_reserved_resources.py +0 -45
- pulpcore/app/migrations/0005_progressreport_code.py +0 -19
- pulpcore/app/migrations/0006_repository_plugin_managed.py +0 -18
- pulpcore/app/migrations/0007_delete_progress_proxies.py +0 -19
- pulpcore/app/migrations/0008_published_metadata_as_content.py +0 -44
- pulpcore/app/migrations/0009_remove_task_non_fatal_errors.py +0 -17
- pulpcore/app/migrations/0010_pulp_fields.py +0 -570
- pulpcore/app/migrations/0011_relative_path.py +0 -28
- pulpcore/app/migrations/0012_auto_20191104_2000.py +0 -31
- pulpcore/app/migrations/0013_repository_pulp_type.py +0 -18
- pulpcore/app/migrations/0014_remove_repository_plugin_managed.py +0 -17
- pulpcore/app/migrations/0015_auto_20191112_1426.py +0 -33
- pulpcore/app/migrations/0016_charfield_to_textfield.py +0 -68
- pulpcore/app/migrations/0017_remove_task_parent.py +0 -17
- pulpcore/app/migrations/0018_auto_20191127_2350.py +0 -20
- pulpcore/app/migrations/0019_add_signing_service_model.py +0 -27
- pulpcore/app/migrations/0020_change_publishedartifact_constraints.py +0 -17
- pulpcore/app/migrations/0021_add_signing_service_foreign_key.py +0 -24
- pulpcore/app/migrations/0022_rename_last_version.py +0 -27
- pulpcore/app/migrations/0023_change_exporter_models.py +0 -82
- pulpcore/app/migrations/0024_use_local_storage_for_uploads.py +0 -19
- pulpcore/app/migrations/0025_task_parent_task.py +0 -19
- pulpcore/app/migrations/0026_task_group.py +0 -32
- pulpcore/app/migrations/0027_export_backend.py +0 -31
- pulpcore/app/migrations/0028_import_importer_pulpimporter_pulpimporterrepository.py +0 -85
- pulpcore/app/migrations/0029_export_delete.py +0 -19
- pulpcore/app/migrations/0030_taskgroup_all_tasks_dispatched.py +0 -24
- pulpcore/app/migrations/0031_import_export_validate_params.py +0 -19
- pulpcore/app/migrations/0032_export_to_chunks.py +0 -27
- pulpcore/app/migrations/0033_increase_remote_artifact_size_field.py +0 -18
- pulpcore/app/migrations/0034_groupprogressreport.py +0 -32
- pulpcore/app/migrations/0035_content_upstream_id.py +0 -18
- pulpcore/app/migrations/0036_unprotect_last_export.py +0 -19
- pulpcore/app/migrations/0037_pulptemporaryfile.py +0 -28
- pulpcore/app/migrations/0038_repository_remote.py +0 -19
- pulpcore/app/migrations/0039_change_download_concurrency.py +0 -25
- pulpcore/app/migrations/0040_set_admin_is_staff.py +0 -28
- pulpcore/app/migrations/0041_accesspolicy.py +0 -29
- pulpcore/app/migrations/0042_rbac_for_tasks.py +0 -56
- pulpcore/app/migrations/0043_toc_attribute.py +0 -19
- pulpcore/app/migrations/0044_temp_file_artifact_field.py +0 -20
- pulpcore/app/migrations/0045_accesspolicy_permissions_allow_null.py +0 -19
- pulpcore/app/migrations/0046_task__resource_job_id.py +0 -35
- pulpcore/app/migrations/0047_improve_orphan_cleanup.py +0 -59
- pulpcore/app/migrations/0048_fips_checksums.py +0 -38
- pulpcore/app/migrations/0049_add_file_field_to_uploadchunk.py +0 -24
- pulpcore/app/migrations/0050_namespace_access_policies.py +0 -28
- pulpcore/app/migrations/0051_timeoutfields.py +0 -34
- pulpcore/app/migrations/0052_tasking_logging_cid.py +0 -18
- pulpcore/app/migrations/0053_remote_headers.py +0 -19
- pulpcore/app/migrations/0054_add_public_key.py +0 -104
- pulpcore/app/migrations/0055_label.py +0 -31
- pulpcore/app/migrations/0056_remote_rate_limit.py +0 -18
- pulpcore/app/migrations/0057_add_label_indexes.py +0 -23
- pulpcore/app/migrations/0058_accesspolicy_customized.py +0 -18
- pulpcore/app/migrations/0059_proxy_creds.py +0 -23
- pulpcore/app/migrations/0060_data_migration_proxy_creds.py +0 -45
- pulpcore/app/migrations/0061_call_handle_artifact_checksums_command.py +0 -87
- pulpcore/app/migrations/0062_add_new_distribution_mastermodel.py +0 -36
- pulpcore/app/migrations/0063_repository_retained_versions.py +0 -18
- pulpcore/app/migrations/0064_add_new_style_task_columns.py +0 -109
- pulpcore/app/migrations/0064_repository_user_hidden.py +0 -18
- pulpcore/app/migrations/0065_merge_20210615_1211.py +0 -14
- pulpcore/app/migrations/0066_download_concurrency_and_retry_changes.py +0 -24
- pulpcore/app/migrations/0067_add_protect_to_task_reservation.py +0 -19
- pulpcore/app/migrations/0068_add_timestamp_of_interest.py +0 -23
- pulpcore/app/migrations/0069_update_json_fields.py +0 -63
- pulpcore/app/migrations/0070_rename_retained_versions.py +0 -18
- pulpcore/app/migrations/0071_filesystemexport_filesystemexporter.py +0 -35
- pulpcore/app/migrations/0072_add_method_to_filesystem_exporter.py +0 -18
- pulpcore/app/migrations/0073_encrypt_remote_fields.py +0 -139
- pulpcore/app/migrations/0074_acs.py +0 -47
- pulpcore/app/migrations/0075_rbaccontentguard.py +0 -25
- pulpcore/app/migrations/0076_remove_reserved_resource.py +0 -39
- pulpcore/app/migrations/0077_move_remote_url_credentials.py +0 -41
- pulpcore/app/migrations/0078_grouprole_role_userrole.py +0 -70
- pulpcore/app/migrations/0079_rename_permissions_assignment_accesspolicy_creation_hooks.py +0 -18
- pulpcore/app/migrations/0080_proxy_group_model.py +0 -37
- pulpcore/app/migrations/0081_reapplabel_group_permissions.py +0 -59
- pulpcore/app/migrations/0082_add_manage_roles_permissions.py +0 -17
- pulpcore/app/migrations/0083_alter_group_options.py +0 -17
- pulpcore/app/migrations/0084_alter_rbaccontentguard_options.py +0 -17
- pulpcore/app/migrations/0085_contentredirectcontentguard.py +0 -26
- pulpcore/app/migrations/0086_task_json_fields.py +0 -77
- pulpcore/app/migrations/0087_taskschedule.py +0 -34
- pulpcore/app/migrations/0088_accesspolicy_queryset_scoping.py +0 -18
- pulpcore/app/migrations/0089_alter_contentredirectcontentguard_options.py +0 -17
- pulpcore/app/migrations/0090_char_to_text_field.py +0 -79
- pulpcore/tests/unit/migration/test_0077_move_remote_url_credentials.py +0 -35
- {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/WHEEL +0 -0
- {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/entry_points.txt +0 -0
- {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/licenses/LICENSE +0 -0
- {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import asyncio
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
from asgiref.sync import sync_to_async
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
|
|
8
|
+
from pulpcore.app.util import get_domain
|
|
9
|
+
from pulpcore.app.models.progress import ProgressReport
|
|
10
|
+
from pulpcore.app.models.vulnerability_report import VulnerabilityReport
|
|
11
|
+
from pulpcore.constants import OSV_QUERY_URL, TASK_STATES
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VulnerabilityReportScanner:
|
|
15
|
+
"""
|
|
16
|
+
A scanner class to collect vulnerabilities from Pulp Contents using the OSV.dev API.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
semaphore (asyncio.Semaphore): Controls concurrent API requests to osv.dev service.
|
|
20
|
+
generator (AsyncGenerator): The async generator with the content data to be scanned for
|
|
21
|
+
vulnerabilities.
|
|
22
|
+
session (aiohttp.ClientSession): HTTP session for making requests to the OSV.dev API.
|
|
23
|
+
Defaults to a new session if not provided.
|
|
24
|
+
created (ProgressReport): Tracks the number of new VulnerabilityReport records created
|
|
25
|
+
during the scanning process.
|
|
26
|
+
updated (ProgressReport): Tracks the number of existing VulnerabilityReport records
|
|
27
|
+
updated during the scanning process.
|
|
28
|
+
total_scanned (ProgressReport): Tracks the total number of content items that have
|
|
29
|
+
been processed for vulnerability scanning.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, semaphore, generator, session=None):
|
|
33
|
+
self.semaphore = semaphore
|
|
34
|
+
self.generator = generator
|
|
35
|
+
self.session = session or aiohttp.ClientSession()
|
|
36
|
+
self.created = ProgressReport(
|
|
37
|
+
message="Vulnerability Reports created", code="created", state=TASK_STATES.RUNNING
|
|
38
|
+
)
|
|
39
|
+
self.updated = ProgressReport(
|
|
40
|
+
message="Vulnerability Reports updated", code="updated", state=TASK_STATES.RUNNING
|
|
41
|
+
)
|
|
42
|
+
self.total_scanned = ProgressReport(
|
|
43
|
+
message="Content scanned", code="total_scanned", state=TASK_STATES.RUNNING
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
async def scan_packages(self):
|
|
47
|
+
"""
|
|
48
|
+
Main entry point for scanning packages for vulnerabilities. Progress is tracked through
|
|
49
|
+
three ProgressReport instances (created, updated, total_scanned).
|
|
50
|
+
"""
|
|
51
|
+
tasks = []
|
|
52
|
+
async for content in self.generator:
|
|
53
|
+
tasks.append(asyncio.create_task(self.scan_package(content)))
|
|
54
|
+
await asyncio.gather(*tasks)
|
|
55
|
+
await self.session.close()
|
|
56
|
+
|
|
57
|
+
if self.created.done > 0:
|
|
58
|
+
self.created.state = TASK_STATES.COMPLETED
|
|
59
|
+
await self.created.asave()
|
|
60
|
+
if self.total_scanned.done > 0:
|
|
61
|
+
self.total_scanned.state = TASK_STATES.COMPLETED
|
|
62
|
+
await self.total_scanned.asave()
|
|
63
|
+
if self.updated.done > 0:
|
|
64
|
+
self.updated.state = TASK_STATES.COMPLETED
|
|
65
|
+
await self.updated.asave()
|
|
66
|
+
|
|
67
|
+
async def scan_package(self, repo_content_osv_data):
|
|
68
|
+
"""
|
|
69
|
+
Makes a request to the osv.dev API and store the results in VulnerabilityReport model.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
repo_content_osv_data (Dict[str, Any]): A dictionary with osv.dev expected request
|
|
73
|
+
data format plus associated Pulp Content and RepoVersion.
|
|
74
|
+
"""
|
|
75
|
+
vulns, content, repo_version = await self._process_reports(repo_content_osv_data)
|
|
76
|
+
await self._save_vulnerability_report(vulns, content, repo_version)
|
|
77
|
+
|
|
78
|
+
async def _process_reports(self, repo_content_osv_data):
|
|
79
|
+
"""
|
|
80
|
+
Process vulnerability scanning data by querying the OSV API and extracting results.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
repo_content_osv_data (Dict[str, Any]): A dictionary containing OSV.dev expected
|
|
84
|
+
request data format plus associated Pulp Content and RepositoryVersion.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
tuple:
|
|
88
|
+
- vulns (List[Dict]): List of vulnerability (vulns) returned from OSV.dev API
|
|
89
|
+
- pulp_content (Content): The Pulp Content instance that was scanned
|
|
90
|
+
- repo_version (RepositoryVersion or None): The RepositoryVersion where the
|
|
91
|
+
Content is present
|
|
92
|
+
"""
|
|
93
|
+
vulns = []
|
|
94
|
+
async with self.semaphore:
|
|
95
|
+
repo_version = repo_content_osv_data.pop("repo_version", None)
|
|
96
|
+
pulp_content = repo_content_osv_data.pop("content")
|
|
97
|
+
vulnerability_data = await self._query_osv_api(repo_content_osv_data)
|
|
98
|
+
vulns = vulnerability_data.get("vulns", [])
|
|
99
|
+
while next_page_token := vulnerability_data.get("next_page_token"):
|
|
100
|
+
repo_content_osv_data["page_token"] = next_page_token
|
|
101
|
+
vulnerability_data = await self._query_osv_api(repo_content_osv_data)
|
|
102
|
+
vulns.extend(vulnerability_data)
|
|
103
|
+
await self.total_scanned.aincrement()
|
|
104
|
+
return vulns, pulp_content, repo_version
|
|
105
|
+
|
|
106
|
+
async def _query_osv_api(self, osv_data):
|
|
107
|
+
"""
|
|
108
|
+
Make a single HTTP POST request to the OSV.dev vulnerability database API.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
osv_data (Dict[str, Any]): OSV query data dictionary containing package information
|
|
112
|
+
in the format expected by the OSV.dev API.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Dict[str, Any]: JSON response from the OSV API containing vulnerability data.
|
|
116
|
+
"""
|
|
117
|
+
async with self.session.post(url=OSV_QUERY_URL, json=osv_data) as response:
|
|
118
|
+
try:
|
|
119
|
+
return await response.json()
|
|
120
|
+
except aiohttp.ContentTypeError:
|
|
121
|
+
raise RuntimeError("Vuln report task failed to query osv.dev data.")
|
|
122
|
+
|
|
123
|
+
async def _save_vulnerability_report(self, vulns, content, repo_version):
|
|
124
|
+
"""
|
|
125
|
+
Save vulnerability report to the database.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
vulns (List): osv.dev vulns output
|
|
129
|
+
content (Content): A Pulp Content instance which vulnerabilities were verified
|
|
130
|
+
repo_version (RepositoryVersion): The RepositoryVersion where Content is present
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
vuln_report, created = await sync_to_async(VulnerabilityReport.objects.update_or_create)(
|
|
134
|
+
vulns=vulns, pulp_domain=get_domain(), content=content
|
|
135
|
+
)
|
|
136
|
+
await vuln_report.repo_versions.aadd(repo_version)
|
|
137
|
+
|
|
138
|
+
if created:
|
|
139
|
+
await self.created.aincrement()
|
|
140
|
+
else:
|
|
141
|
+
await self.updated.aincrement()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def check_content(func, args):
|
|
145
|
+
"""
|
|
146
|
+
Spawn tasks for each async generator value to make the API requests (scan) to osv.dev.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
func (callable | str): A Pulp plugin function that should return an async generator with
|
|
150
|
+
the osv.dev expected request data format plus associated Pulp Content and RepoVersion.
|
|
151
|
+
args (tuple): The positional arguments to pass on to the func.
|
|
152
|
+
"""
|
|
153
|
+
module_name, function_name = func.rsplit(".", 1)
|
|
154
|
+
module = importlib.import_module(module_name)
|
|
155
|
+
func = getattr(module, function_name)
|
|
156
|
+
|
|
157
|
+
semaphore = asyncio.Semaphore(settings.VULN_REPORT_TASK_LIMITER)
|
|
158
|
+
vuln_report_scanner = VulnerabilityReportScanner(semaphore, func(*args))
|
|
159
|
+
await vuln_report_scanner.scan_packages()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin
|
|
2
|
+
|
|
3
|
+
from pulpcore.app.models.vulnerability_report import VulnerabilityReport
|
|
4
|
+
from pulpcore.app.serializers.vulnerability_report import VulnerabilityReportSerializer
|
|
5
|
+
from pulpcore.app.viewsets.base import NamedModelViewSet
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class VulnerabilityReportViewSet(
|
|
9
|
+
NamedModelViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin
|
|
10
|
+
):
|
|
11
|
+
|
|
12
|
+
endpoint_name = "vuln_report"
|
|
13
|
+
queryset = VulnerabilityReport.objects.prefetch_related("repo_versions").select_related(
|
|
14
|
+
"content"
|
|
15
|
+
)
|
|
16
|
+
serializer_class = VulnerabilityReportSerializer
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def routable(cls):
|
|
20
|
+
return True
|
pulpcore/constants.py
CHANGED
|
@@ -12,6 +12,10 @@ TASK_UNBLOCKING_LOCK = 84
|
|
|
12
12
|
TASK_METRICS_HEARTBEAT_LOCK = 74
|
|
13
13
|
STORAGE_METRICS_LOCK = 72
|
|
14
14
|
|
|
15
|
+
# Reasons to send along a task worker wakeup call.
|
|
16
|
+
TASK_WAKEUP_UNBLOCK = "unblock"
|
|
17
|
+
TASK_WAKEUP_HANDLE = "handle"
|
|
18
|
+
|
|
15
19
|
#: All valid task states.
|
|
16
20
|
TASK_STATES = SimpleNamespace(
|
|
17
21
|
WAITING="waiting",
|
|
@@ -127,3 +131,7 @@ CHECKPOINT_TS_FORMAT = "%Y%m%dT%H%M%SZ"
|
|
|
127
131
|
# The upper boundary represents an unsigned 32-bit integer and prevents overflow
|
|
128
132
|
ORPHAN_PROTECTION_TIME_LOWER_BOUND = 0
|
|
129
133
|
ORPHAN_PROTECTION_TIME_UPPER_BOUND = 4294967295 # (2^32)-1
|
|
134
|
+
|
|
135
|
+
# VULNERABILITY REPORT CONSTANTS
|
|
136
|
+
# OSV API URL
|
|
137
|
+
OSV_QUERY_URL = "https://api.osv.dev/v1/query"
|
pulpcore/content/__init__.py
CHANGED
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
|
|
8
8
|
from asgiref.sync import sync_to_async
|
|
9
9
|
from aiohttp import web
|
|
10
|
-
|
|
10
|
+
from gunicorn.arbiter import Arbiter
|
|
11
11
|
import django
|
|
12
12
|
|
|
13
13
|
|
|
@@ -16,12 +16,13 @@ django.setup()
|
|
|
16
16
|
|
|
17
17
|
from django.conf import settings # noqa: E402: module level not at top of file
|
|
18
18
|
from django.db.utils import ( # noqa: E402: module level not at top of file
|
|
19
|
+
IntegrityError,
|
|
19
20
|
InterfaceError,
|
|
20
21
|
DatabaseError,
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
from pulpcore.app.apps import pulp_plugin_configs # noqa: E402: module level not at top of file
|
|
24
|
-
from pulpcore.app.models import
|
|
25
|
+
from pulpcore.app.models import AppStatus # noqa: E402: module level not at top of file
|
|
25
26
|
from pulpcore.app.util import get_worker_name # noqa: E402: module level not at top of file
|
|
26
27
|
|
|
27
28
|
from .handler import Handler # noqa: E402: module level not at top of file
|
|
@@ -48,40 +49,40 @@ CONTENT_MODULE_NAME = "content"
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
async def _heartbeat():
|
|
51
|
-
content_app_status = None
|
|
52
52
|
name = get_worker_name()
|
|
53
53
|
heartbeat_interval = settings.CONTENT_APP_TTL // 4
|
|
54
54
|
msg = "Content App '{name}' heartbeat written, sleeping for '{interarrival}' seconds".format(
|
|
55
55
|
name=name, interarrival=heartbeat_interval
|
|
56
56
|
)
|
|
57
|
-
fail_msg = (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
).format(name=name, interarrival=heartbeat_interval)
|
|
57
|
+
fail_msg = ("Content App '{name}' failed to write a heartbeat to the database.").format(
|
|
58
|
+
name=name
|
|
59
|
+
)
|
|
61
60
|
versions = {app.label: app.version for app in pulp_plugin_configs()}
|
|
62
61
|
|
|
62
|
+
try:
|
|
63
|
+
app_status = await AppStatus.objects.acreate(
|
|
64
|
+
name=name, app_type="content", versions=versions
|
|
65
|
+
)
|
|
66
|
+
except IntegrityError:
|
|
67
|
+
log.error(f"A content app with name {name} already exists in the database.")
|
|
68
|
+
exit(Arbiter.WORKER_BOOT_ERROR)
|
|
63
69
|
try:
|
|
64
70
|
while True:
|
|
71
|
+
await asyncio.sleep(heartbeat_interval)
|
|
65
72
|
try:
|
|
66
|
-
|
|
67
|
-
name=name, defaults={"versions": versions}
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
if not created:
|
|
71
|
-
await sync_to_async(content_app_status.save_heartbeat)()
|
|
72
|
-
|
|
73
|
-
if content_app_status.versions != versions:
|
|
74
|
-
content_app_status.versions = versions
|
|
75
|
-
await content_app_status.asave(update_fields=["versions"])
|
|
76
|
-
|
|
73
|
+
await app_status.asave_heartbeat()
|
|
77
74
|
log.debug(msg)
|
|
78
75
|
except (InterfaceError, DatabaseError):
|
|
79
76
|
await sync_to_async(Handler._reset_db_connection)()
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
try:
|
|
78
|
+
await app_status.asave_heartbeat()
|
|
79
|
+
log.debug(msg)
|
|
80
|
+
except (InterfaceError, DatabaseError):
|
|
81
|
+
log.error(fail_msg)
|
|
82
|
+
exit(Arbiter.WORKER_BOOT_ERROR)
|
|
82
83
|
finally:
|
|
83
|
-
if
|
|
84
|
-
await
|
|
84
|
+
if app_status:
|
|
85
|
+
await app_status.adelete()
|
|
85
86
|
|
|
86
87
|
|
|
87
88
|
async def _heartbeat_ctx(app):
|
pulpcore/content/handler.py
CHANGED
|
@@ -705,8 +705,11 @@ class Handler:
|
|
|
705
705
|
rel_path = rel_path[len(distro.base_path) :]
|
|
706
706
|
rel_path = rel_path.lstrip("/")
|
|
707
707
|
|
|
708
|
-
if
|
|
709
|
-
|
|
708
|
+
# Check if we need to redirect to add a trailing slash for distro root
|
|
709
|
+
if not path.endswith("/") and (
|
|
710
|
+
rel_path == "" # Distro root
|
|
711
|
+
or (distro.checkpoint and "/" not in rel_path) # Timestamped checkpoint root
|
|
712
|
+
):
|
|
710
713
|
raise HTTPMovedPermanently(f"{request.path}/")
|
|
711
714
|
|
|
712
715
|
original_rel_path = rel_path
|
pulpcore/migrations.py
CHANGED
|
@@ -3,6 +3,8 @@ from packaging.version import parse as parse_version
|
|
|
3
3
|
from django.conf import settings
|
|
4
4
|
from django.utils import timezone
|
|
5
5
|
from django.db.migrations.operations.base import Operation
|
|
6
|
+
from django.db.models import F
|
|
7
|
+
from django.db.models.functions import Now
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class RequireVersion(Operation):
|
|
@@ -22,21 +24,41 @@ class RequireVersion(Operation):
|
|
|
22
24
|
|
|
23
25
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
|
24
26
|
from_state.clear_delayed_apps_cache()
|
|
25
|
-
ApiAppStatus = from_state.apps.get_model("core", "ApiAppStatus")
|
|
26
|
-
ContentAppStatus = from_state.apps.get_model("core", "ContentAppStatus")
|
|
27
|
-
Worker = from_state.apps.get_model("core", "Worker")
|
|
28
27
|
|
|
29
28
|
needed_version = parse_version(self.version)
|
|
30
29
|
errors = []
|
|
30
|
+
found_either_table = False
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
try:
|
|
33
|
+
ApiAppStatus = from_state.apps.get_model("core", "ApiAppStatus")
|
|
34
|
+
ContentAppStatus = from_state.apps.get_model("core", "ContentAppStatus")
|
|
35
|
+
Worker = from_state.apps.get_model("core", "Worker")
|
|
36
|
+
except LookupError:
|
|
37
|
+
pass
|
|
38
|
+
else:
|
|
39
|
+
for worker_class, ttl, class_name in [
|
|
40
|
+
(ApiAppStatus, settings.API_APP_TTL, "api server"),
|
|
41
|
+
(ContentAppStatus, settings.CONTENT_APP_TTL, "content server"),
|
|
42
|
+
(Worker, settings.WORKER_TTL, "pulp worker"),
|
|
43
|
+
]:
|
|
44
|
+
for worker in worker_class.objects.filter(
|
|
45
|
+
last_heartbeat__gte=timezone.now() - timezone.timedelta(seconds=ttl)
|
|
46
|
+
):
|
|
47
|
+
present_version = worker.versions.get(self.plugin)
|
|
48
|
+
if (
|
|
49
|
+
present_version is not None
|
|
50
|
+
and parse_version(present_version) < needed_version
|
|
51
|
+
):
|
|
52
|
+
errors.append(
|
|
53
|
+
f" - '{self.plugin}'='{present_version}' "
|
|
54
|
+
f"with {class_name} '{worker.name}'"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
found_either_table = True
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
AppStatus = from_state.apps.get_model("core", "AppStatus")
|
|
61
|
+
for worker in AppStatus.objects.filter(F("last_heartbeat") + F("ttl") >= Now()):
|
|
40
62
|
present_version = worker.versions.get(self.plugin)
|
|
41
63
|
if present_version is not None and parse_version(present_version) < needed_version:
|
|
42
64
|
errors.append(
|
|
@@ -44,6 +66,11 @@ class RequireVersion(Operation):
|
|
|
44
66
|
f"with {class_name} '{worker.name}'"
|
|
45
67
|
)
|
|
46
68
|
|
|
69
|
+
found_either_table = True
|
|
70
|
+
except LookupError:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
assert found_either_table
|
|
47
74
|
if errors:
|
|
48
75
|
raise RuntimeError(
|
|
49
76
|
"\n".join(
|
pulpcore/openapi/__init__.py
CHANGED
|
@@ -468,6 +468,14 @@ class BasicAuthenticationScheme(OpenApiAuthenticationExtension):
|
|
|
468
468
|
}
|
|
469
469
|
|
|
470
470
|
|
|
471
|
+
class PulpRemoteUserAuthenticationScheme(OpenApiAuthenticationExtension):
|
|
472
|
+
target_class = "pulpcore.app.authentication.PulpRemoteUserAuthentication"
|
|
473
|
+
name = "remoteUserAuthentication"
|
|
474
|
+
|
|
475
|
+
def get_security_definition(self, auto_schema):
|
|
476
|
+
return settings.REMOTE_USER_OPENAPI_SECURITY_SCHEME
|
|
477
|
+
|
|
478
|
+
|
|
471
479
|
class JSONHeaderRemoteAuthenticationScheme(OpenApiAuthenticationExtension):
|
|
472
480
|
target_class = "pulpcore.app.authentication.JSONHeaderRemoteAuthentication"
|
|
473
481
|
name = "json_header_remote_authentication"
|
|
@@ -38,6 +38,7 @@ from pulpcore.app.serializers import (
|
|
|
38
38
|
ValidateFieldsMixin,
|
|
39
39
|
validate_unknown_fields,
|
|
40
40
|
TaskSerializer,
|
|
41
|
+
VulnerabilityReportSerializer,
|
|
41
42
|
)
|
|
42
43
|
|
|
43
44
|
from .content import (
|
|
@@ -87,4 +88,5 @@ __all__ = [
|
|
|
87
88
|
"TaskSerializer",
|
|
88
89
|
"NoArtifactContentUploadSerializer",
|
|
89
90
|
"SingleArtifactContentUploadSerializer",
|
|
91
|
+
"VulnerabilityReportSerializer",
|
|
90
92
|
]
|
pulpcore/plugin/tasking.py
CHANGED
|
@@ -12,11 +12,13 @@ from pulpcore.app.tasks import (
|
|
|
12
12
|
orphan_cleanup,
|
|
13
13
|
reclaim_space,
|
|
14
14
|
)
|
|
15
|
+
from pulpcore.app.tasks.vulnerability_report import check_content
|
|
15
16
|
from pulpcore.app.tasks.repository import add_and_remove
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
__all__ = [
|
|
19
20
|
"ageneral_update",
|
|
21
|
+
"check_content",
|
|
20
22
|
"dispatch",
|
|
21
23
|
"fs_publication_export",
|
|
22
24
|
"fs_repo_version_export",
|
|
@@ -33,6 +33,7 @@ from pulpcore.app.viewsets import (
|
|
|
33
33
|
RolesMixin,
|
|
34
34
|
TaskGroupViewSet,
|
|
35
35
|
TaskViewSet,
|
|
36
|
+
VulnerabilityReportViewSet,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
from pulpcore.app.viewsets.custom_filters import (
|
|
@@ -89,4 +90,5 @@ __all__ = [
|
|
|
89
90
|
"NoArtifactContentViewSet",
|
|
90
91
|
"NoArtifactContentUploadViewSet",
|
|
91
92
|
"SingleArtifactContentUploadViewSet",
|
|
93
|
+
"VulnerabilityReportViewSet",
|
|
92
94
|
]
|
pulpcore/pytest_plugin.py
CHANGED
|
@@ -608,28 +608,28 @@ def backend_settings_factory(pulp_settings):
|
|
|
608
608
|
"bucket_name",
|
|
609
609
|
]
|
|
610
610
|
keys["storages.backends.azure_storage.AzureStorage"] = [
|
|
611
|
-
"
|
|
612
|
-
"
|
|
613
|
-
"
|
|
614
|
-
"
|
|
615
|
-
"
|
|
616
|
-
"
|
|
617
|
-
"
|
|
611
|
+
"account_name",
|
|
612
|
+
"azure_container",
|
|
613
|
+
"account_key",
|
|
614
|
+
"expiration_secs",
|
|
615
|
+
"overwrite_files",
|
|
616
|
+
"location",
|
|
617
|
+
"connection_string",
|
|
618
618
|
]
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
return
|
|
619
|
+
|
|
620
|
+
def get_installation_storage_option(key, backend):
|
|
621
|
+
value = pulp_settings.STORAGES["default"]["OPTIONS"].get(key)
|
|
622
|
+
# Some FileSystem backend options may be defined in the top settings module
|
|
623
|
+
if backend == "pulpcore.app.models.storage.FileSystem" and not value:
|
|
624
|
+
value = getattr(pulp_settings, key, None)
|
|
625
|
+
return value
|
|
626
|
+
|
|
627
|
+
storage_settings = storage_settings or dict()
|
|
628
|
+
storage_backend = storage_class or pulp_settings.STORAGES["default"]["BACKEND"]
|
|
629
|
+
unset_storage_settings = (k for k in keys[storage_backend] if k not in storage_settings)
|
|
630
|
+
for key in unset_storage_settings:
|
|
631
|
+
storage_settings[key] = get_installation_storage_option(key, storage_backend)
|
|
632
|
+
return storage_backend, storage_settings
|
|
633
633
|
|
|
634
634
|
return _settings_factory
|
|
635
635
|
|
pulpcore/tasking/entrypoint.py
CHANGED
|
@@ -21,8 +21,18 @@ _logger = logging.getLogger(__name__)
|
|
|
21
21
|
@click.option(
|
|
22
22
|
"--reload/--no-reload", help="Reload worker on code changes. [requires hupper to be installed.]"
|
|
23
23
|
)
|
|
24
|
+
@click.option(
|
|
25
|
+
"--auxiliary/--no-auxiliary",
|
|
26
|
+
default=False,
|
|
27
|
+
help="Auxiliary workers do not perform housekeeping duties.",
|
|
28
|
+
)
|
|
24
29
|
@click.command()
|
|
25
|
-
def worker(
|
|
30
|
+
def worker(
|
|
31
|
+
pid,
|
|
32
|
+
burst,
|
|
33
|
+
reload,
|
|
34
|
+
auxiliary,
|
|
35
|
+
):
|
|
26
36
|
"""A Pulp worker."""
|
|
27
37
|
|
|
28
38
|
if reload:
|
|
@@ -40,4 +50,4 @@ def worker(pid, burst, reload):
|
|
|
40
50
|
|
|
41
51
|
_logger.info("Starting distributed type worker")
|
|
42
52
|
|
|
43
|
-
PulpcoreWorker().run(burst=burst)
|
|
53
|
+
PulpcoreWorker(auxiliary=auxiliary).run(burst=burst)
|
pulpcore/tasking/tasks.py
CHANGED
|
@@ -9,12 +9,11 @@ import traceback
|
|
|
9
9
|
import tempfile
|
|
10
10
|
import threading
|
|
11
11
|
from asgiref.sync import sync_to_async
|
|
12
|
-
from datetime import timedelta
|
|
13
12
|
from gettext import gettext as _
|
|
14
13
|
|
|
15
14
|
from django.conf import settings
|
|
16
15
|
from django.db import connection, transaction
|
|
17
|
-
from django.db.models import Model
|
|
16
|
+
from django.db.models import Model
|
|
18
17
|
from django_guid import get_guid
|
|
19
18
|
from pulpcore.app.apps import MODULE_PLUGIN_VERSIONS
|
|
20
19
|
from pulpcore.app.models import Task, TaskGroup
|
|
@@ -23,8 +22,8 @@ from pulpcore.constants import (
|
|
|
23
22
|
TASK_FINAL_STATES,
|
|
24
23
|
TASK_INCOMPLETE_STATES,
|
|
25
24
|
TASK_STATES,
|
|
26
|
-
TASK_DISPATCH_LOCK,
|
|
27
25
|
IMMEDIATE_TIMEOUT,
|
|
26
|
+
TASK_WAKEUP_UNBLOCK,
|
|
28
27
|
)
|
|
29
28
|
from pulpcore.middleware import x_task_diagnostics_var
|
|
30
29
|
from pulpcore.tasking.kafka import send_task_notification
|
|
@@ -47,10 +46,10 @@ def _validate_and_get_resources(resources):
|
|
|
47
46
|
return list(resource_set)
|
|
48
47
|
|
|
49
48
|
|
|
50
|
-
def wakeup_worker():
|
|
49
|
+
def wakeup_worker(reason="unknown"):
|
|
51
50
|
# Notify workers
|
|
52
51
|
with connection.connection.cursor() as cursor:
|
|
53
|
-
cursor.execute("
|
|
52
|
+
cursor.execute("SELECT pg_notify('pulp_worker_wakeup', %s)", (reason,))
|
|
54
53
|
|
|
55
54
|
|
|
56
55
|
def execute_task(task):
|
|
@@ -228,13 +227,6 @@ def dispatch(
|
|
|
228
227
|
notify_workers = False
|
|
229
228
|
with contextlib.ExitStack() as stack:
|
|
230
229
|
with transaction.atomic():
|
|
231
|
-
# Task creation need to be serialized so that pulp_created will provide a stable order
|
|
232
|
-
# at every time. We specifically need to ensure that each task, when committed to the
|
|
233
|
-
# task table will be the newest with respect to `pulp_created`.
|
|
234
|
-
with connection.cursor() as cursor:
|
|
235
|
-
# Wait for exclusive access and release automatically after transaction.
|
|
236
|
-
cursor.execute("SELECT pg_advisory_xact_lock(%s, %s)", [0, TASK_DISPATCH_LOCK])
|
|
237
|
-
newest_created = Task.objects.aggregate(Max("pulp_created"))["pulp_created__max"]
|
|
238
230
|
task = Task.objects.create(
|
|
239
231
|
state=TASK_STATES.WAITING,
|
|
240
232
|
logging_cid=(get_guid()),
|
|
@@ -250,23 +242,6 @@ def dispatch(
|
|
|
250
242
|
profile_options=x_task_diagnostics_var.get(None),
|
|
251
243
|
)
|
|
252
244
|
task.refresh_from_db() # The database may have assigned a timestamp for us.
|
|
253
|
-
if newest_created and task.pulp_created <= newest_created:
|
|
254
|
-
# Let this workaround not row forever into the future.
|
|
255
|
-
if newest_created - task.pulp_created > timedelta(seconds=1):
|
|
256
|
-
# Do not commit the transaction if this condition is not met.
|
|
257
|
-
# If we ever hit this, think about delegating the timestamping to PostgresQL.
|
|
258
|
-
raise RuntimeError("Clockscrew detected. Task dispatching would be dangerous.")
|
|
259
|
-
# Try to work around the smaller glitch
|
|
260
|
-
task.pulp_created = newest_created + timedelta(milliseconds=1)
|
|
261
|
-
task.save()
|
|
262
|
-
if task_group:
|
|
263
|
-
task_group.refresh_from_db()
|
|
264
|
-
if task_group.all_tasks_dispatched:
|
|
265
|
-
task.set_canceling()
|
|
266
|
-
task.set_canceled(
|
|
267
|
-
TASK_STATES.CANCELED, "All tasks in group have been dispatched/canceled."
|
|
268
|
-
)
|
|
269
|
-
return task
|
|
270
245
|
if immediate:
|
|
271
246
|
# Grab the advisory lock before the task hits the db.
|
|
272
247
|
stack.enter_context(task)
|
|
@@ -308,7 +283,7 @@ def dispatch(
|
|
|
308
283
|
task.set_canceling()
|
|
309
284
|
task.set_canceled(TASK_STATES.CANCELED, "Resources temporarily unavailable.")
|
|
310
285
|
if notify_workers:
|
|
311
|
-
wakeup_worker()
|
|
286
|
+
wakeup_worker(TASK_WAKEUP_UNBLOCK)
|
|
312
287
|
return task
|
|
313
288
|
|
|
314
289
|
|