From cb8b3cdab262af1d223c0536220400b13c1d0a9a Mon Sep 17 00:00:00 2001 From: Thibault Person Date: Tue, 15 Mar 2022 15:12:57 +0100 Subject: [PATCH] Comply with AWS signature calculation (s3v4) The current implementation of s3 signature calculation rely on WSGI Url encoding which is discouraged by AWS: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html. This leads to reject requests with valid signature. This update encode only characters specified by AWS except 'A'-'Z', 'a'-'z', '0'-'9', '-', '.', '_', and '~' to comply AWS signature calculation. Fixes LP Bug #1961841 Change-Id: Ifa8f94544224c3379e7f2805f6f86d0b0a47279a --- swift/common/middleware/s3api/s3request.py | 3 ++- .../common/middleware/s3api/test_s3request.py | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/swift/common/middleware/s3api/s3request.py b/swift/common/middleware/s3api/s3request.py index 054cc0a851..e0bd434fe2 100644 --- a/swift/common/middleware/s3api/s3request.py +++ b/swift/common/middleware/s3api/s3request.py @@ -401,7 +401,8 @@ class SigV4Mixin(object): """ It won't require bucket name in canonical_uri for v4. """ - return swob.wsgi_to_bytes(self.environ.get('RAW_PATH_INFO', self.path)) + return swob.wsgi_to_bytes(swob.wsgi_quote( + self.environ.get('PATH_INFO', self.path), safe='-_.~/')) def _canonical_request(self): # prepare 'canonical_request' diff --git a/test/unit/common/middleware/s3api/test_s3request.py b/test/unit/common/middleware/s3api/test_s3request.py index 0d4ee27f5c..26a4bee3cb 100644 --- a/test/unit/common/middleware/s3api/test_s3request.py +++ b/test/unit/common/middleware/s3api/test_s3request.py @@ -898,6 +898,33 @@ class TestRequest(S3ApiTestCase): sigv4_req._canonical_request().endswith(b'UNSIGNED-PAYLOAD')) self.assertTrue(sigv4_req.check_signature('secret')) + @patch.object(S3Request, '_validate_dates', lambda *a: None) + def test_check_signature_sigv4_url_encode(self): + environ = { + 'HTTP_HOST': 'bucket.s3.test.com', + 'REQUEST_METHOD': 'PUT', + 'RAW_PATH_INFO': '/test/~/file,1_1:1-1'} + headers = { + 'Authorization': + 'AWS4-HMAC-SHA256 ' + 'Credential=test/20210104/us-east-1/s3/aws4_request, ' + 'SignedHeaders=host;x-amz-content-sha256;x-amz-date,' + 'Signature=06559fbf839b7ceac19d69f510a2d3b7dcb569c8df310965cc1' + '6a1dc55b3394a', + 'X-Amz-Content-SHA256': 'UNSIGNED-PAYLOAD', + 'Date': 'Mon, 04 Jan 2021 10:26:23 -0000', + 'X-Amz-Date': '20210104T102623Z'} + + # Virtual hosted-style + self.s3api.conf.storage_domain = 's3.test.com' + req = Request.blank( + environ['RAW_PATH_INFO'], environ=environ, headers=headers) + sigv4_req = SigV4Request(req.environ) + canonical_req = sigv4_req._canonical_request() + self.assertIn(b'PUT\n/test/~/file%2C1_1%3A1-1\n', canonical_req) + self.assertTrue(canonical_req.endswith(b'UNSIGNED-PAYLOAD')) + self.assertTrue(sigv4_req.check_signature('secret')) + @patch.object(S3Request, '_validate_dates', lambda *a: None) def test_check_sigv4_req_zero_content_length_sha256(self): # Virtual hosted-style