8000 fix S3 pre-signed logic for IGNORED_SIGV4_HEADERS (#9609) · codeperl/localstack@46f056d · GitHub
[go: up one dir, main page]

Skip to content

Commit 46f056d

Browse files
authored
fix S3 pre-signed logic for IGNORED_SIGV4_HEADERS (localstack#9609)
1 parent 805920f commit 46f056d

File tree

2 files changed

+84
-17
lines changed

2 files changed

+84
-17
lines changed

localstack/services/s3/presigned_url.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,7 @@
6464
# headers to blacklist from request_dict.signed_headers
6565
BLACKLISTED_HEADERS = ["X-Amz-Security-Token"]
6666

67-
# query params overrides for multipart upload and node sdk
68-
# TODO: this will depends on query/post v2/v4. Manage independently
69-
ALLOWED_QUERY_PARAMS = [
70-
"x-id",
71-
"x-amz-user-agent",
72-
"x-amz-content-sha256",
73-
"versionid",
74-
"uploadid",
75-
"partnumber",
76-
]
77-
7867
IGNORED_SIGV4_HEADERS = [
79-
"x-id",
80-
"x-amz-user-agent",
8168
"x-amz-content-sha256",
8269
]
8370

@@ -578,11 +565,10 @@ def _get_signed_headers_and_filtered_query_string(self) -> Tuple[Dict[str, str],
578565
not_signed_headers = []
579566
for header, value in headers.items():
580567
header_low = header.lower()
581-
if header_low.startswith("x-amz-"):
568+
if header_low.startswith("x-amz-") and header_low not in signed_headers.lower():
582569
if header_low in IGNORED_SIGV4_HEADERS:
583570
continue
584-
if header_low not in signed_headers.lower():
585-
not_signed_headers.append(header_low)
571+
not_signed_headers.append(header_low)
586572
if header_low in signed_headers:
587573
signature_headers[header_low] = value
588574

tests/aws/services/s3/test_s3.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import shutil
1111
import tempfile
1212
import time
13+
from importlib.util import find_spec
1314
from io import BytesIO
1415
from operator import itemgetter
1516
from typing import TYPE_CHECKING
@@ -6546,7 +6547,7 @@ def test_pre_signed_url_forward_slash_bucket(
65466547
"signature_version",
65476548
["s3", "s3v4"],
65486549
)
6549-
@markers.aws.unknown
6550+
@markers.aws.validated
65506551
def test_s3_presign_url_encoding(
65516552
self, aws_client, s3_bucket, signature_version, patch_s3_skip_signature_validation_false
65526553
):
@@ -6568,6 +6569,86 @@ def test_s3_presign_url_encoding(
65686569
assert req.ok
65696570
assert req.content == b"123"
65706571

6572+
@markers.aws.validated
6573+
def test_s3_ignored_special_headers(
6574+
self,
6575+
s3_bucket,
6576+
patch_s3_skip_signature_validation_false,
6577+
monkeypatch,
6578+
):
6579+
# if the crt.auth is not available, not need to patch as it will use it by default
6580+
if find_spec("botocore.crt.auth"):
6581+
# the CRT client does not allow us to pass a protected header, it will trigger an exception, so we need
6582+
# to patch the Signer selection to the Python implementation which does not have this check
6583+
from botocore.auth import AUTH_TYPE_MAPS, S3SigV4QueryAuth
6584+
6585+
monkeypatch.setitem(AUTH_TYPE_MAPS, "s3v4-query", S3SigV4QueryAuth)
6586+
6587+
key = "my-key"
6588+
presigned_client = _s3_client_custom_config(
6589+
Config(signature_version="s3v4", s3={"payload_signing_enabled": True}),
6590+
endpoint_url=_endpoint_url(),
6591+
)
6592+
6593+
def add_content_sha_header(request, **kwargs):
6594+
request.headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"
6595+
6596+
presigned_client.meta.events.register(
6597+
"before-sign.s3.PutObject",
6598+
handler=add_content_sha_header,
6599+
)
6600+
try:
6601+
url = presigned_client.generate_presigned_url(
6602+
"put_object", Params={"Bucket": s3_bucket, "Key": key}
6603+
)
6604+
assert "x-amz-content-sha256" in url
6605+
# somehow, it's possible to add "x-amz-content-sha256" to signed headers, the AWS Go SDK does it
6606+
resp = requests.put(
6607+
url,
6608+
data="something",
6609+
verify=False,
6610+
headers={"x-amz-content-sha256": "UNSIGNED-PAYLOAD"},
6611+
)
6612+
assert resp.ok
6613+
6614+
# if signed but not provided, AWS will raise an exception
6615+
resp = requests.put(url, data="something", verify=False)
6616+
assert resp.status_code == 403
6617+
6618+
finally:
6619+
presigned_client.meta.events.unregister(
6620+
"before-sign.s3.PutObject",
6621+
add_content_sha_header,
6622+
)
6623+
6624+
# recreate the request, without the signed header
6625+
url = presigned_client.generate_presigned_url(
6626+
"put_object", Params={"Bucket": s3_bucket, "Key": key}
6627+
)
6628+
assert "x-amz-content-sha256" not in url
6629+
6630+
# assert that if provided and not signed, AWS will ignore it even if it starts with `x-amz`
6631+
resp = requests.put(
6632+
url,
6633+
data="something",
6634+
verify=False,
6635+
headers={"x-amz-content-sha256": "UNSIGNED-PAYLOAD"},
6636+
)
6637+
assert resp.ok
6638+
6639+
# assert that x-amz-user-agent is not ignored, it must be set in SignedHeaders
6640+
resp = requests.put(
6641+
url, data="something", verify=False, headers={"x-amz-user-agent": "test"}
6642+
)
6643+
assert resp.status_code == 403
6644+
6645+
# X-Amz-Signature needs to be the last query string parameter: insert x-id before like the Go SDK
6646+
index = url.find("&X-Amz-Signature")
6647+
rewritten_url = url[:index] + "&x-id=PutObject" + url[index:]
6648+
# however, the x-id query string parameter is not ignored
6649+
resp = requests.put(rewritten_url, data="something", verify=False)
6650+
assert resp.status_code == 403
6651+
65716652

65726653
class TestS3DeepArchive:
65736654
"""

0 commit comments

Comments
 (0)
0