Skip to content
Open
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
4 changes: 0 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,6 @@ RUN patch -p1 -d /usr/local/lib/pulp/lib/python${PYTHON_VERSION}/site-packages <
COPY images/assets/patches/0044-Move-content-app-heartbeat-to-a-thread.patch /tmp/
RUN patch -p1 -d /usr/local/lib/pulp/lib/python${PYTHON_VERSION}/site-packages < /tmp/0044-Move-content-app-heartbeat-to-a-thread.patch

COPY images/assets/patches/0045-Include-DRF-default-auth-classes-when-token-auth-is-disabled.patch /tmp/
RUN patch -p1 -d /usr/local/lib/pulp/lib/python${PYTHON_VERSION}/site-packages < /tmp/0045-Include-DRF-default-auth-classes-when-token-auth-is-disabled.patch


COPY images/assets/keys/SIGSTORE-redhat-release3.pem /etc/pki/sigstore/SIGSTORE-redhat-release3
Expand All @@ -188,8 +186,6 @@ RUN patch -p1 -d /usr/local/lib/pulp/lib/python${PYTHON_VERSION}/site-packages <
COPY images/assets/patches/0053-python-agent-scan-task.patch /tmp/
RUN patch -p1 -d /usr/local/lib/pulp/lib/python${PYTHON_VERSION}/site-packages < /tmp/0053-python-agent-scan-task.patch

COPY images/assets/patches/0054-defer-contentid-cleanup-old-versions.patch /tmp/
RUN patch -p1 -d /usr/local/lib/pulp/lib/python${PYTHON_VERSION}/site-packages < /tmp/0054-defer-contentid-cleanup-old-versions.patch

COPY images/assets/patches/0055-decouple-livez-from-db.patch /tmp/
RUN patch -p1 -d /usr/local/lib/pulp/lib/python${PYTHON_VERSION}/site-packages < /tmp/0055-decouple-livez-from-db.patch
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
From ad565cd57a1d1f643d1fc2757965f22bbb164624 Mon Sep 17 00:00:00 2001
From: Dennis Kliban <dkliban@redhat.com>
Date: Tue, 4 Mar 2025 11:19:30 -0500
Subject: [PATCH] Re-root the registry API at /api/pulp/v2/

---
pulp_container/app/content.py | 2 +-
pulp_container/app/redirects.py | 4 ++--
pulp_container/app/registry_api.py | 4 +++-
pulp_container/app/token_verification.py | 16 +---------------
pulp_container/app/urls.py | 14 +++++++-------
5 files changed, 15 insertions(+), 27 deletions(-)

diff --git a/pulp_container/app/content.py b/pulp_container/app/content.py
index dbe6418d..006e8afb 100644
index 145533fc..000c97fc 100644
--- a/pulp_container/app/content.py
+++ b/pulp_container/app/content.py
@@ -6,7 +6,7 @@ from pulp_container.app.registry import Registry
@@ -7,7 +7,7 @@ from pulp_container.app.registry import Registry

registry = Registry()

Expand All @@ -25,10 +12,10 @@ index dbe6418d..006e8afb 100644
app.add_routes(
[
diff --git a/pulp_container/app/redirects.py b/pulp_container/app/redirects.py
index 0c7a5898..fa4808cb 100644
index 2ac80050..c7694cd7 100644
--- a/pulp_container/app/redirects.py
+++ b/pulp_container/app/redirects.py
@@ -21,7 +21,7 @@ class CommonRedirects:
@@ -22,7 +22,7 @@ class CommonRedirects:
self.path = path
self.request = request
self.path_prefix = (
Expand All @@ -38,10 +25,10 @@ index 0c7a5898..fa4808cb 100644
else "pulp/container"
)
diff --git a/pulp_container/app/registry_api.py b/pulp_container/app/registry_api.py
index e3b0766c..4e9114be 100644
index 24b007ae..91a5e102 100644
--- a/pulp_container/app/registry_api.py
+++ b/pulp_container/app/registry_api.py
@@ -98,6 +98,8 @@ from pulp_container.constants import (
@@ -97,6 +97,8 @@ from pulp_container.constants import (
V2_ACCEPT_HEADERS,
)

Expand All @@ -50,28 +37,35 @@ index e3b0766c..4e9114be 100644
log = logging.getLogger(__name__)

IGNORED_PULL_THROUGH_REMOTE_ATTRIBUTES = [
@@ -232,7 +234,7 @@ class ContainerRegistryApiMixin:
@@ -229,9 +231,14 @@ class ContainerRegistryApiMixin:
def authentication_classes(self):
"""
List of authentication classes to check for this view.
+
+ When token auth is disabled, includes both RegistryAuthentication (Basic auth)
+ and any authentication classes configured in DEFAULT_AUTHENTICATION_CLASSES.
+ This allows deployments to use custom authentication backends (e.g. remote
+ header-based auth) alongside standard Basic auth for container registry operations.
"""
if settings.get("TOKEN_AUTH_DISABLED", False):
- return [RegistryAuthentication]
+ return [RHServiceAccountCertAuthentication]
+ return [RegistryAuthentication, *api_settings.DEFAULT_AUTHENTICATION_CLASSES]
return [TokenAuthentication]

@property
diff --git a/pulp_container/app/token_verification.py b/pulp_container/app/token_verification.py
index d78738a4..faf05cd6 100644
index 019310ac..9c685b68 100644
--- a/pulp_container/app/token_verification.py
+++ b/pulp_container/app/token_verification.py
@@ -15,6 +15,7 @@ from rest_framework.authentication import (
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated
from rest_framework.permissions import BasePermission, SAFE_METHODS
@@ -14,6 +14,7 @@ from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated
from rest_framework.permissions import SAFE_METHODS, BasePermission

from pulp_container.app.utils import get_full_path
+from pulp_service.app.authorization import DomainBasedPermission

Scope = namedtuple("Scope", "resource_type, name, action")
User = get_user_model()
@@ -178,23 +179,11 @@ def get_scopes(request):
@@ -177,23 +178,11 @@ def get_scopes(request):
return scopes


Expand All @@ -97,10 +91,10 @@ index d78738a4..faf05cd6 100644

class TokenPermission(BasePermission):
diff --git a/pulp_container/app/urls.py b/pulp_container/app/urls.py
index 47c4342a..6c93b927 100644
index f1a54872..0b40d9a8 100644
--- a/pulp_container/app/urls.py
+++ b/pulp_container/app/urls.py
@@ -32,16 +32,16 @@ head_route = Route(
@@ -33,16 +33,16 @@ head_route = Route(
)

router.routes.append(head_route)
Expand All @@ -124,6 +118,3 @@ index 47c4342a..6c93b927 100644
path("", include(router.urls)),
]
# print(router.urls)
--
2.49.0

91 changes: 35 additions & 56 deletions images/assets/patches/0025-clamAV.patch
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
From eeab8cc111d03218b3f3b58200206b18aaa1f0b5 Mon Sep 17 00:00:00 2001
From: Dennis Kliban <dkliban@redhat.com>
Date: Tue, 24 Mar 2026 12:12:45 -0400
Subject: [PATCH] Extend dwloader to push data stream through clamAV

---
pulpcore/app/settings.py | 4 +++
pulpcore/content/handler.py | 23 ++++++++++++++++-
pulpcore/download/base.py | 41 +++++++++++++++++++++++++++++++
pulpcore/exceptions/__init__.py | 1 +
pulpcore/exceptions/validation.py | 19 ++++++++++++++
pulpcore/plugin/exceptions.py | 2 ++
6 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/pulpcore/app/settings.py b/pulpcore/app/settings.py
index 7d30421f2..ffbc2ab30 100644
index a4411f50e..29646bd29 100644
--- a/pulpcore/app/settings.py
+++ b/pulpcore/app/settings.py
@@ -425,6 +425,10 @@ VULN_REPORT_TASK_LIMITER = 10
# Replaces asyncio event loop with uvloop
UVLOOP_ENABLED = False
@@ -439,6 +439,10 @@ UVLOOP_ENABLED = False
# Replaces every non PulpException error msg with "An internal error occured"
REDACT_UNSAFE_EXCEPTIONS = False

+# ClamAV default config
+CLAMAV_HOST = None
Expand All @@ -28,7 +14,7 @@ index 7d30421f2..ffbc2ab30 100644
# Read more at https://www.dynaconf.com/django/

diff --git a/pulpcore/content/handler.py b/pulpcore/content/handler.py
index d00a53cba..51fffdc4d 100644
index 56e9addf0..7787124a0 100644
--- a/pulpcore/content/handler.py
+++ b/pulpcore/content/handler.py
@@ -17,6 +17,7 @@ from aiohttp.web_exceptions import (
Expand All @@ -38,19 +24,16 @@ index d00a53cba..51fffdc4d 100644
+ HTTPPreconditionFailed,
HTTPRequestRangeNotSatisfiable,
)
from yarl import URL
@@ -60,8 +61,9 @@ from pulpcore.app.util import ( # noqa: E402: module level not at top of file
)

from asgiref.sync import sync_to_async
@@ -62,6 +63,7 @@ from pulpcore.cache import AsyncContentCache # noqa: E402
from pulpcore.exceptions import ( # noqa: E402
- UnsupportedDigestValidationError,
DigestValidationError,
+ UnsupportedDigestValidationError,
UnsupportedDigestValidationError,
+ MalwareError,
)
from pulpcore.metrics import artifacts_size_counter # noqa: E402

@@ -916,6 +918,11 @@ class Handler:
@@ -924,6 +926,11 @@ class Handler:
url=url, r=ce.message
)
raise Error(reason=reason)
Expand All @@ -62,7 +45,7 @@ index d00a53cba..51fffdc4d 100644

if not any([repository, repo_version, publication, distro.remote]):
reason = _(
@@ -1298,6 +1305,10 @@ class Handler:
@@ -1306,6 +1313,10 @@ class Handler:
downloader.handle_data = handle_data
original_finalize = downloader.finalize
downloader.finalize = finalize
Expand All @@ -73,7 +56,7 @@ index d00a53cba..51fffdc4d 100644
failed_download = True
try:
download_result = await downloader.run(
@@ -1321,6 +1332,16 @@ class Handler:
@@ -1329,6 +1340,16 @@ class Handler:
"Learn more on <https://pulpproject.org/pulpcore/docs/user/learn/"
"on-demand-downloading/#on-demand-and-streamed-limitations>"
)
Expand All @@ -91,34 +74,33 @@ index d00a53cba..51fffdc4d 100644
if failed_download:
# remove the temporary file
diff --git a/pulpcore/download/base.py b/pulpcore/download/base.py
index b093ad9e4..ab8acff4b 100644
index 8900ff81c..2c34efbe6 100644
--- a/pulpcore/download/base.py
+++ b/pulpcore/download/base.py
@@ -1,6 +1,7 @@
from gettext import gettext as _

@@ -1,4 +1,6 @@
import asyncio
+from clamav_client import clamd
from collections import namedtuple
+from collections import namedtuple
import concurrent.futures
from concurrent.futures import ThreadPoolExecutor, ALL_COMPLETED
@@ -8,6 +9,7 @@ import logging
import logging
import os
import tempfile
@@ -7,6 +9,7 @@ from collections import namedtuple
from concurrent.futures import ALL_COMPLETED, ThreadPoolExecutor
from gettext import gettext as _
from pathlib import Path
+import struct
from urllib.parse import urlsplit

from django.conf import settings
@@ -18,6 +20,7 @@ from pulpcore.exceptions import (
@@ -18,6 +21,7 @@ from pulpcore.exceptions import (
SizeValidationError,
TimeoutException,
UnsupportedDigestValidationError,
+ MalwareError,
)

log = logging.getLogger(__name__)
@@ -114,6 +117,9 @@ class BaseDownloader:
@@ -114,6 +118,9 @@ class BaseDownloader:
).format(self.url, Artifact.DIGEST_FIELDS, set(self.expected_digests))
)

Expand All @@ -128,23 +110,23 @@ index b093ad9e4..ab8acff4b 100644
def _ensure_writer_has_open_file(self):
"""
Create a temporary file on demand.
@@ -158,6 +164,7 @@ class BaseDownloader:
@@ -158,6 +165,7 @@ class BaseDownloader:
self._ensure_writer_has_open_file()
self._writer.write(data)
self._record_size_and_digests_for_data(data)
+ self._send_chunk_to_clamav(data)

async def finalize(self):
"""
@@ -181,6 +188,7 @@ class BaseDownloader:
@@ -181,6 +189,7 @@ class BaseDownloader:
self._writer = None
self.validate_digests()
self.validate_size()
+ self.run_malware_scan()
log.debug(f"Downloaded file from {self.url}")

def fetch(self, extra_data=None):
@@ -210,6 +218,12 @@ class BaseDownloader:
@@ -210,6 +219,12 @@ class BaseDownloader:
concurrent.futures.wait(futures, timeout=None, return_when=ALL_COMPLETED)
self._size += len(data)

Expand All @@ -157,7 +139,7 @@ index b093ad9e4..ab8acff4b 100644
@property
def artifact_attributes(self):
"""
@@ -251,6 +265,25 @@ class BaseDownloader:
@@ -251,6 +266,25 @@ class BaseDownloader:
if actual_size != expected_size:
raise SizeValidationError(actual_size, expected_size, url=self.url)

Expand All @@ -183,7 +165,7 @@ index b093ad9e4..ab8acff4b 100644
async def run(self, extra_data=None):
"""
Run the downloader with concurrency restriction.
@@ -306,3 +339,11 @@ class BaseDownloader:
@@ -306,3 +340,11 @@ class BaseDownloader:
:meth:`~pulpcore.plugin.download.BaseDownloader.finalize`.
"""
raise NotImplementedError("Subclasses must define a _run() method that returns a coroutine")
Expand All @@ -196,10 +178,10 @@ index b093ad9e4..ab8acff4b 100644
+ self.clamd_client._init_socket()
+ self.clamd_client._send_command("INSTREAM")
diff --git a/pulpcore/exceptions/__init__.py b/pulpcore/exceptions/__init__.py
index 4c458e439..17e30978b 100644
index e6b935de6..1887e9c61 100644
--- a/pulpcore/exceptions/__init__.py
+++ b/pulpcore/exceptions/__init__.py
@@ -21,6 +21,7 @@ from .validation import (
@@ -23,6 +23,7 @@ from .validation import (
DigestValidationError,
InvalidSignatureError,
SizeValidationError,
Expand All @@ -208,10 +190,10 @@ index 4c458e439..17e30978b 100644
MissingDigestValidationError,
UnsupportedDigestValidationError,
diff --git a/pulpcore/exceptions/validation.py b/pulpcore/exceptions/validation.py
index 2131e5fce..94cd27ab1 100644
index 985aad65d..65813d1bd 100644
--- a/pulpcore/exceptions/validation.py
+++ b/pulpcore/exceptions/validation.py
@@ -74,6 +74,25 @@ class SizeValidationError(ValidationError):
@@ -75,6 +75,25 @@ class SizeValidationError(ValidationError):
return f"[{self.error_code}] " + msg.format(expected=self.expected, actual=self.actual)


Expand All @@ -238,25 +220,22 @@ index 2131e5fce..94cd27ab1 100644
"""
Raised when attempting to save() an Artifact with an incomplete set of checksums.
diff --git a/pulpcore/plugin/exceptions.py b/pulpcore/plugin/exceptions.py
index 0406964ec..d1ffedd6a 100644
index 3e7e33852..ff36ef24d 100644
--- a/pulpcore/plugin/exceptions.py
+++ b/pulpcore/plugin/exceptions.py
@@ -1,6 +1,7 @@
@@ -1,6 +1,6 @@
from pulpcore.exceptions import (
ValidationError,
DigestValidationError,
- ExternalServiceError,
+ MalwareError,
InvalidSignatureError,
PulpException,
SizeValidationError,
@@ -17,6 +18,7 @@ from pulpcore.exceptions import (
MissingDigestValidationError,
PublishError,
@@ -17,6 +17,7 @@ from pulpcore.exceptions import (
__all__ = [
"ValidationError",
"DigestValidationError",
+ "MalwareError",
"InvalidSignatureError",
"PulpException",
"SizeValidationError",
--
2.53.0

Loading
Loading