Skip to content

Commit 2845307

Browse files
committed
Limit the size of manifests/signatures during sync
Adds limit to the size of manifests and signatures as a safeguard to avoid DDoS attack during sync operations. To also prevent this during image upload, this commit configures a `client_max_body_size` for manifests and signatures Nginx endpoints. closes: #532
1 parent 5908f47 commit 2845307

File tree

5 files changed

+68
-1
lines changed

5 files changed

+68
-1
lines changed

CHANGES/532.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added a limit to the size of manifests and signatures during sync tasks and
2+
updated the Nginx snippet to also limit the size of the body for these endpoints.

pulp_container/app/tasks/sync_stages.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
MEDIA_TYPE,
1616
SIGNATURE_API_EXTENSION_VERSION,
1717
SIGNATURE_HEADER,
18+
SIGNATURE_PAYLOAD_MAX_SIZE,
1819
SIGNATURE_SOURCE,
1920
SIGNATURE_TYPE,
2021
V2_ACCEPT_HEADERS,
@@ -35,8 +36,11 @@
3536
calculate_digest,
3637
filter_resources,
3738
get_content_data,
39+
is_signature_size_valid,
3840
)
3941

42+
from pulp_container.app.exceptions import ManifestSignatureInvalid
43+
4044
log = logging.getLogger(__name__)
4145

4246

@@ -545,6 +549,14 @@ async def create_signatures(self, man_dc, signature_source):
545549
"Error: {} {}".format(signature_url, exc.status, exc.message)
546550
)
547551

552+
if not is_signature_size_valid(signature_download_result.path):
553+
log.info(
554+
"Signature size is not valid, the max allowed size is {}.".format(
555+
SIGNATURE_PAYLOAD_MAX_SIZE
556+
)
557+
)
558+
raise ManifestSignatureInvalid(digest=man_digest_reformatted)
559+
548560
with open(signature_download_result.path, "rb") as f:
549561
signature_raw = f.read()
550562

@@ -566,7 +578,12 @@ async def create_signatures(self, man_dc, signature_source):
566578
# signature extensions endpoint does not like any unnecessary headers to be sent
567579
await signatures_downloader.run(extra_data={"headers": {}})
568580
with open(signatures_downloader.path) as signatures_fd:
569-
api_extension_signatures = json.loads(signatures_fd.read())
581+
try:
582+
api_extension_signatures = json.loads(
583+
signatures_fd.read(SIGNATURE_PAYLOAD_MAX_SIZE)
584+
)
585+
except json.decoder.JSONDecodeError:
586+
raise ManifestSignatureInvalid(digest=man_dc.content.digest)
570587
for signature in api_extension_signatures.get("signatures", []):
571588
if (
572589
signature.get("schemaVersion") == SIGNATURE_API_EXTENSION_VERSION

pulp_container/app/utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
from django.core.files.storage import default_storage as storage
1414
from django.db import IntegrityError
1515
from functools import partial
16+
from pathlib import Path
1617
from rest_framework.exceptions import Throttled
1718

1819
from pulpcore.plugin.models import Artifact, Task
1920

2021
from pulp_container.constants import (
2122
MANIFEST_MEDIA_TYPES,
23+
MANIFEST_PAYLOAD_MAX_SIZE,
2224
MEDIA_TYPE,
25+
SIGNATURE_PAYLOAD_MAX_SIZE,
2326
)
2427
from pulp_container.app.exceptions import ManifestInvalid
2528
from pulp_container.app.json_schemas import (
@@ -221,6 +224,22 @@ def validate_manifest(content_data, media_type, digest):
221224
reason=f'{".".join(map(str, error.path))}: {error.message}', digest=digest
222225
)
223226

227+
manifests = content_data.get("manifests", None)
228+
if manifests and not _is_manifest_size_valid(manifests):
229+
raise ManifestInvalid(
230+
reason="Manifest size is not valid, the max allowed size is {}.".format(
231+
MANIFEST_PAYLOAD_MAX_SIZE
232+
),
233+
digest=digest,
234+
)
235+
236+
237+
def _is_manifest_size_valid(manifests):
238+
for manifest in manifests:
239+
if manifest.get("size") > MANIFEST_PAYLOAD_MAX_SIZE:
240+
return False
241+
return True
242+
224243

225244
def calculate_digest(manifest):
226245
"""
@@ -342,3 +361,9 @@ def filter_resources(element_list, include_patterns, exclude_patterns):
342361
if exclude_patterns:
343362
element_list = filter(partial(exclude, patterns=exclude_patterns), element_list)
344363
return list(element_list)
364+
365+
366+
def is_signature_size_valid(file_path):
367+
if Path(file_path).stat().st_size > SIGNATURE_PAYLOAD_MAX_SIZE:
368+
return False
369+
return True

pulp_container/app/webserver_snippets/nginx.conf

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,25 @@ location /token/ {
3838
proxy_redirect off;
3939
proxy_pass http://pulp-api;
4040
}
41+
42+
location ~* /v2/.*/manifests/.*$ {
43+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44+
proxy_set_header X-Forwarded-Proto $scheme;
45+
proxy_set_header Host $http_host;
46+
# we don't want nginx trying to do something clever with
47+
# redirects, we set the Host: header above already.
48+
proxy_redirect off;
49+
proxy_pass http://pulp-api;
50+
client_max_body_size 4m;
51+
}
52+
53+
location ~* /extensions/v2/.*/signatures/.*$ {
54+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
55+
proxy_set_header X-Forwarded-Proto $scheme;
56+
proxy_set_header Host $http_host;
57+
# we don't want nginx trying to do something clever with
58+
# redirects, we set the Host: header above already.
59+
proxy_redirect off;
60+
proxy_pass http://pulp-api;
61+
client_max_body_size 4m;
62+
}

pulp_container/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@
6969

7070
MEGABYTE = 1_000_000
7171
SIGNATURE_PAYLOAD_MAX_SIZE = 4 * MEGABYTE
72+
MANIFEST_PAYLOAD_MAX_SIZE = 4 * MEGABYTE
7273

7374
SIGNATURE_API_EXTENSION_VERSION = 2

0 commit comments

Comments
 (0)