【发布时间】:2019-06-09 18:06:12
【问题描述】:
我们生成预签名 URL,以便用户将文件直接上传到 S3 存储桶。运行集成测试,我们发现了一个失败的测试,其中对预签名 URL 的 HTTP PUT 请求产生了SignatureDoesNotMatch 错误响应。令人惊讶的是,相同的代码使用另一个存储桶运行良好。我们一直在尝试导致测试失败的原始存储桶,当它突然开始工作而没有任何实际代码更改时,我们感到很惊讶。
我们注意到,在我们创建存储桶后大约两个小时,测试成功运行。由于我们位于 UTC+0200,我们怀疑该问题与该时差和/或某些时钟同步问题有关。我们开始确认我们的怀疑,即相同的预签名 URL 在足够的时间过去后会突然起作用。剧透:确实如此!
以下代码创建一个全新的存储桶,生成一个适合文件上传的预签名 URL (ClientMethod='put_object'),并尝试使用 requests 库 HTTP PUT 一些数据。我们每 60 秒重试一次 PUT 数据,直到在创建存储桶后 5419 秒(或 90 分钟)最终成功。
注意:即使之后删除了存储桶,运行相同的脚本(使用相同的存储桶名称)现在也会立即成功。如果您想重新确认此行为,请确保第二次使用不同的存储桶名称。
import logging
import time
import boto3
import requests
from botocore.client import Config
logger = logging.getLogger(__name__)
# region = "eu-central-1"
# region = "eu-west-1"
# region = "us-west-1"
region = "us-east-1"
s3_client = boto3.client('s3', region_name=region, config=Config(signature_version='s3v4'))
if __name__ == "__main__":
bucket_name = "some-globally-unique-bucket-name"
key_for_file = "test-file.txt"
# create bucket
if region == "us-east-1":
# https://github.com/boto/boto3/issues/125
s3_client.create_bucket(Bucket=bucket_name, ACL='private')
else:
s3_client.create_bucket(Bucket=bucket_name, ACL='private',
CreateBucketConfiguration={'LocationConstraint': region})
creation_time = time.time()
# generate presigned URL
file_data = b"Hello Test World"
expires_in = 4 * 3600
url = s3_client.generate_presigned_url(ClientMethod='put_object', ExpiresIn=expires_in,
Params={'Bucket': bucket_name, 'Key': key_for_file})
time_since_bucket_creation = time.time() - creation_time
time_interval = 60
max_time_passed = expires_in
success = False
try:
while time_since_bucket_creation < max_time_passed:
response = requests.put(url, data=file_data)
if response.status_code == 200:
success = True
break
if b"<Code>SignatureDoesNotMatch</Code>" in response.content:
reason = "SignatureDoesNotMatch"
else:
reason = str(response.content)
time_since_bucket_creation = time.time() - creation_time
print("="*50)
print(f"{time_since_bucket_creation:.2f} s after bucket creation")
print(f"unable to PUT data to url: {url}")
print(f"reason: {reason}")
print(response.content)
time.sleep(time_interval)
except KeyboardInterrupt:
print("Gracefully shutting down...")
if success:
print("YAY! File Upload was successful!")
time_since_bucket_creation = time.time() - creation_time
print(f"{time_since_bucket_creation:.2f} seconds after bucket creation")
s3_client.delete_object(Bucket=bucket_name, Key=key_for_file)
# delete bucket
s3_client.delete_bucket(Bucket=bucket_name)
我们使用 AWS EKS 集群运行集成测试,我们在其中创建一个集群以及一些数据库、S3 存储桶等,并在测试完成后拆除所有内容。必须等待 90 分钟才能使 URL 预签名生效。
我的问题
我做错了什么吗?
这是预期的行为吗?
是否有可接受的解决方法?
有人可以使用上面的代码确认这种行为吗?
编辑
根据 cmets 中的“Michael - sqlbot”的建议,我更新了代码以在“us-east-1”区域中创建一个存储桶。奇怪的if 声明是必要的,正如here 所记录的那样。我能够证实 Michael 的怀疑,即“us-east-1”无法重现该行为。
如果感兴趣,错误情况下返回的XML:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<AWSAccessKeyId>REDACTED</AWSAccessKeyId>
<StringToSign>AWS4-HMAC-SHA256
20190609T170351Z
20190609/eu-central-1/s3/aws4_request
c143cb44fa45c56e52b04e61b777ae2206e0aaeed40dafc78e036878fa91dfd6</StringToSign>
<SignatureProvided>REDACTED</SignatureProvided>
<StringToSignBytes>REDACTED</StringToSignBytes>
<CanonicalRequest>PUT
/test-file.txt
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=REDACTED%2F20190609%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20190609T170351Z&X-Amz-Expires=14400&X-Amz-SignedHeaders=host
host:some-globally-unique-bucket-name.s3.eu-central-1.amazonaws.com
host
UNSIGNED-PAYLOAD</CanonicalRequest>
<CanonicalRequestBytes>REDACTED</CanonicalRequestBytes>
<RequestId>E6CBBC7D2E4D322E</RequestId>
<HostId>j1dM1MNaXaDhzMUXKhqdHd6+/Rl1C3GzdL9YDq0CuP8brQZQV6vbyE9Z63HBHiBWSo+hb6zHKVs=</HostId>
</Error>
【问题讨论】:
-
首先——这绝对是出乎意料的......但是:“即使之后删除了存储桶,运行相同的脚本(使用相同的存储桶名称)现在也会立即成功。” i> 什么意思,立马成功?您的意思是,如果您重新创建具有相同名称的存储桶,它会立即工作吗?请使用两个新的存储桶名称测试您的代码,一个在 us-east-1 中,一个在 us-east-2 中。有什么区别吗?我有一个理论——关于不应该发生的事情——但如果正确,us-east-2 将显示相同的错误行为,而 us-east-1 不会。
-
(请注意,如果是时钟问题,欧盟/美国的时差不会改变任何东西。所有 S3 地区都使用 UTC,而不是该地区的本地时间。但我不认为这是时间问题.
SignatureDoesNotMatch不应该基于时钟错误而被抛出。还有其他例外。) -
@Michael-sqlbot 是的,我的意思是当我再次删除并重新创建存储桶时,它会立即工作。请记住,这仍然是在初始存储桶创建 90 分钟之后。我也能够证实你的理论:我无法在 us-east-1 中重现这种行为。
-
正如@JohnRotenstein 所暗示的那样,使用
generate_presigned_post在创建存储桶后立即工作。但是,这似乎不适用于分段上传,因为必须使用 PUT 请求来上传每个单独的部分。