'AWS S3 X-Amz-Expires with authorization headers is ignored
I use the following code in Python to generate authorization headers for getting an object in an S3 bucket (where AWS_SECRET_ACCESS_KEY
, AWS_ACCESS_KEY_ID
, AWS_DEFAULT_REGION
, and DEFAULT_BUCKET
are all set via environment variables in another piece of code)
def get_s3_headers(object_name):
'''Get authorization headers required to access a certain object in S3.'''
headers = _sig_v4_headers(pre_auth_headers={},
service='s3',
host=f'{DEFAULT_BUCKET}.s3.amazonaws.com',
method='GET',
path='/' + object_name,
query={ 'X-Amz-Expires' : '120' },
payload=b'')
return headers
def _sig_v4_headers(pre_auth_headers, service, host, method, path, query, payload):
# Define use of signature v4
algorithm = 'AWS4-HMAC-SHA256'
now = datetime.datetime.utcnow()
amzdate = now.strftime('%Y%m%dT%H%M%SZ')
datestamp = now.strftime('%Y%m%d')
payload_hash = hashlib.sha256(payload).hexdigest()
credential_scope = f'{datestamp}/{AWS_DEFAULT_REGION}/{service}/aws4_request'
pre_auth_headers_lower = {
header_key.lower(): ' '.join(header_value.split())
for header_key, header_value in pre_auth_headers.items()
}
required_headers = {
'host': host,
'x-amz-content-sha256': payload_hash,
'x-amz-date': amzdate,
}
headers = {**pre_auth_headers_lower, **required_headers}
header_keys = sorted(headers.keys())
signed_headers = ';'.join(header_keys)
def signature():
def canonical_request():
canonical_uri = urllib.parse.quote(path, safe='/~')
quoted_query = sorted(
(urllib.parse.quote(key, safe='~'), urllib.parse.quote(value, safe='~'))
for key, value in query.items()
)
canonical_querystring = '&'.join(f'{key}={value}' for key, value in quoted_query)
canonical_headers = ''.join(f'{key}:{headers[key]}\n' for key in header_keys)
print(canonical_querystring)
return f'{method}\n{canonical_uri}\n{canonical_querystring}\n' + \
f'{canonical_headers}\n{signed_headers}\n{payload_hash}'
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
string_to_sign = f'{algorithm}\n{amzdate}\n{credential_scope}\n' + \
hashlib.sha256(canonical_request().encode('utf-8')).hexdigest()
date_key = sign(('AWS4' + AWS_SECRET_ACCESS_KEY).encode('utf-8'), datestamp)
region_key = sign(date_key, AWS_DEFAULT_REGION)
service_key = sign(region_key, service)
request_key = sign(service_key, 'aws4_request')
return sign(request_key, string_to_sign).hex()
return {
'uri' : f'https://{host}{path}',
'headers' : {
**pre_auth_headers,
'x-amz-date': amzdate,
'x-amz-content-sha256': payload_hash,
'Authorization': f'{algorithm} Credential={AWS_ACCESS_KEY_ID}/{credential_scope}, '
f'SignedHeaders={signed_headers}, Signature=' + signature(),
}
}
Calling get_s3_headers with a valid object key returns something similar to the following:
"headers": {
"Authorization": "AWS4-HMAC-SHA256 Credential=AKIAYS3VM3EBIFL7FKE5/20220324/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=<string of characters>",
"x-amz-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"x-amz-date": "20220324T193132Z"
},
"uri": "https://hotspot-storage.s3.amazonaws.com/posts/61e0dd8056716196cf357434"
Calling GET
on https://hotspot-storage.s3.amazonaws.com/posts/61e0dd8056716196cf357434?X-Amz-Expires=120
returns the correct image with no expiration errors. Changing X-Amz-Expires
to any other value returns a "Signature not valid" exception as expected. However, I can still use the link and the headers after 120 seconds, as if X-Amz-Expires
doesn't actually do anything. I have zero clue why this is not working, so any help would be much appreciated.
EDIT
Changing X-Amz-Expires
to 0 in both the query string and Python code still allows access to the link. The expiration time always defaults to 15 minutes.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|