我正在尝试将现有的 Discourse 服务器迁移到一个新的基于 AWS 的环境中,将上传存储在 S3 存储桶中,并使用客户管理的密钥 (SSE-C) 进行服务器端加密。在恢复过程中,上传没有进入 S3 —— 每次上传都失败。通过谨慎使用 tap|p 调试,我发现上传已完成,但返回的 etag 验证失败,因为每次从 S3 上传后返回的 etag 都不同。例如,这是在两次不同的恢复尝试中 put_object 调用 的返回值:
# 运行 1
# <struct Aws::S3::Types::PutObjectOutput expiration=nil, etag="\"d49ec2006cfd6fe957af2f711edd9a4b\"", checksum_crc32=nil, checksum_crc32c=nil, checksum_sha1=nil, checksum_sha256=nil, server_side_encryption="aws:kms", version_id="xAF23wQ.zwpoxVmmGiTjxfX0svMZbHAe", sse_customer_algorithm=nil, sse_customer_key_md5=nil, ssekms_key_id="**redacted**", ssekms_encryption_context=nil, bucket_key_enabled=true, request_charged=nil>
# 运行 2:
# <struct Aws::S3::Types::PutObjectOutput expiration=nil, etag="\"05edffee421c6aef950b3d4418ada293\"", checksum_crc32=nil, checksum_crc32c=nil, checksum_sha1=nil, checksum_sha256=nil, server_side_encryption="aws:kms", version_id="H2_8SVh.Yx2LKB4GIjhyPbVoj_.Vc1E2", sse_customer_algorithm=nil, sse_customer_key_md5=nil, ssekms_key_id="**redacted**", ssekms_encryption_context=nil, bucket_key_enabled=true, request_charged=nil>
(我知道这些是相同的文件,因为我还打印了客户端 MD5 校验和和 put_object 请求选项,其中包含文件名)
事实证明,ETag 响应头 在使用 SSE-C 时表现……不同:
使用客户提供的密钥 (SSE-C) 或 AWS Key Management Service (AWS KMS) 密钥 (SSE-KMS) 进行服务器端加密的对象,其 ETag 不是其对象数据的 MD5 摘要。
我能找到的唯一一种方法是使用 SSE-C 进行上传完整性验证,即发送 Content-MD5 请求头,让 S3 进行损坏检测。另外请注意,已上传检查 在使用 SSE-C 时也会中断,但至少可以使用 SKIP_ETAG_VERIFY 禁用它。
我没有立即提交 PR,因为有两种方法可以解决这个问题:
- 只需将
SKIP_ETAG_VERIFY扩展到包含上传后验证,这是一种廉价且粗糙的方法,需要用户知道他们使用 SSE-C 意味着他们必须打开它;或者 - 切换到使用 Content-MD5 头(最好是始终使用)进行上传完整性保护,这有一个好处是适用于所有人,但代价是一个更大的 PR。
(顺便说一句,我相当震惊没有人之前遇到过这个问题——难道没有人使用 Discourse 和 SSE-C 进行上传吗?!?)