Skip to content

Commit e365ef0

Browse files
author
Jan Stiborek
committed
Added option to disable payload signing. This is necessary e.g. for AWS Lattice which does not support payload signing.
1 parent e001f07 commit e365ef0

File tree

3 files changed

+58
-11
lines changed

3 files changed

+58
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- `httpx_auth.AWS4Auth` - added option to disable payload signing to allow use this Auth class with AWS Lattice service.
810

911
## [0.23.1] - 2025-01-07
1012
### Fixed

httpx_auth/_aws.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import hmac
99
from collections import defaultdict
1010
from posixpath import normpath
11-
from typing import Generator
11+
from typing import Generator, Sequence, Optional
1212
from urllib.parse import quote
1313

1414
import httpx
@@ -22,7 +22,15 @@ class AWS4Auth(httpx.Auth):
2222
requires_request_body = True
2323

2424
def __init__(
25-
self, access_id: str, secret_key: str, region: str, service: str, **kwargs
25+
self,
26+
access_id: str,
27+
secret_key: str,
28+
region: str,
29+
service: str,
30+
security_token: Optional[str] = None,
31+
include_headers: Sequence[str] = tuple(),
32+
enable_payload_signing: bool = True,
33+
**kwargs,
2634
):
2735
"""
2836
@@ -34,6 +42,9 @@ def __init__(
3442
:param service: The name of the service you're connecting to, as per endpoints at:
3543
http://docs.aws.amazon.com/general/latest/gr/rande.html
3644
e.g. elasticbeanstalk.
45+
:param enable_payload_signing: Whether to include payload hash in signature
46+
AWS Lattice service does not support payload signing - https://docs.aws.amazon.com/vpc-lattice/latest/ug/sigv4-authenticated-requests.html
47+
Setting this parameter to False will set x-amz-content-sha256 header value to "UNSIGNED-PAYLOAD"
3748
:param security_token: Used for the x-amz-security-token header, for use with STS temporary credentials.
3849
:param include_headers: Set of headers to include in the canonical and signed headers, in addition to:
3950
* host
@@ -48,12 +59,9 @@ def __init__(
4859
self.access_id = access_id
4960
self.region = region
5061
self.service = service
51-
52-
self.security_token = kwargs.get("security_token")
53-
54-
self.include_headers = {
55-
header.lower() for header in kwargs.get("include_headers", [])
56-
}
62+
self.enable_payload_signing = enable_payload_signing
63+
self.security_token = security_token
64+
self.include_headers = {header.lower() for header in include_headers}
5765

5866
def auth_flow(
5967
self, request: httpx.Request
@@ -69,9 +77,13 @@ def auth_flow(
6977
# The x-amz-content-sha256 header is required for all AWS Signature Version 4 requests.
7078
# It provides a hash of the request payload.
7179
# If there is no payload, you must provide the hash of an empty string.
72-
request.headers["x-amz-content-sha256"] = hashlib.sha256(
73-
request.read()
74-
).hexdigest()
80+
# This does not apply to AWS Lattice which does not support payload signing.
81+
# In this case the value of this header must be set to "UNSIGNED-PAYLOAD".
82+
if self.enable_payload_signing:
83+
content_hash_digest = hashlib.sha256(request.read()).hexdigest()
84+
else:
85+
content_hash_digest = "UNSIGNED-PAYLOAD"
86+
request.headers["x-amz-content-sha256"] = content_hash_digest
7587

7688
# https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
7789
# if you are using temporary security credentials, you need to include x-amz-security-token in your request.

tests/aws_signature_v4/test_aws4auth_async.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,3 +843,36 @@ async def test_aws_auth_without_path(httpx_mock: HTTPXMock):
843843

844844
async with httpx.AsyncClient() as client:
845845
await client.get("https://authorized_only", auth=auth)
846+
847+
848+
@time_machine.travel("2018-10-11T15:05:05.663979+00:00", tick=False)
849+
@pytest.mark.asyncio
850+
async def test_aws_auth_with_payload_signing_disabled(httpx_mock: HTTPXMock):
851+
852+
auth = httpx_auth.AWS4Auth(
853+
access_id="access_id",
854+
secret_key="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
855+
region="us-east-1",
856+
service="vpc-lattice-svcs",
857+
enable_payload_signing=False,
858+
include_headers={"x-xyz-my-super-cool-header"},
859+
)
860+
861+
httpx_mock.add_response(
862+
url="https://authorized_only",
863+
method="POST",
864+
match_headers={
865+
"x-amz-content-sha256": "UNSIGNED-PAYLOAD",
866+
"Authorization": "AWS4-HMAC-SHA256 Credential=access_id/20181011/us-east-1/vpc-lattice-svcs/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-xyz-my-super-cool-header, Signature=1a9ad454247bd09ddaf8c096d4b1f767625c68fcb1f73aa2f8e1c9a80b9d2231",
867+
"x-amz-date": "20181011T150505Z",
868+
"x-xyz-my-super-cool-header": "1",
869+
},
870+
)
871+
872+
async with httpx.AsyncClient() as client:
873+
await client.post(
874+
"https://authorized_only",
875+
auth=auth,
876+
content="some-data-here",
877+
headers={"x-xyz-my-super-cool-header": "1"},
878+
)

0 commit comments

Comments
 (0)