使用预签名 url 的分段上传 - Scaleway S3 兼容 object 存储

Multipart upload with presigned urls - Scaleway S3-compatible object storage

我正在尝试在 Scaleway Object 存储(S3 兼容)上使用预先签名的 urls 进行分段上传,我在浏览器生成的预检请求中遇到错误 (403)但我的 CORS 设置似乎设置正确。 (基本上通配符允许 headers 和起源)。

错误带有 403 状态代码,如下所示:

<?xml version='1.0' encoding='UTF-8'?>
<Error><Code>AccessDenied</Code><Message>Access Denied.</Message><RequestId>...</RequestId></Error>

我在这个问题上停留了一段时间,我尝试从我的浏览器复制 pre-flight 请求以在其他地方重现它并尝试稍微调整一下。 从 pre-flight 请求的 url 中删除查询参数使请求成功(returns 200,Access-Control-Allow-* 响应 headers 正确设置)但这显然是不是浏览器行为...

这不起作用(秘密、密钥和名称已更改)

curl 'https://bucket-name.s3.fr-par.scw.cloud/tmp-screenshot-2021-01-20-at-16-21-33.png?AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXX&Expires=1638217988&Signature=NnP1XLlcvPzZnsUgDAzm1Uhxri0%3D&partNumber=1&uploadId=OWI1NWY5ZGrtYzE3MS00MjcyLWI2NDAtNjFkYTM1MTRiZTcx' -X OPTIONS -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Referer: http://domain.tech/' -H 'Access-Control-Request-Method: PUT' -H 'Origin: http://domain.tech' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Sec-Fetch-Dest: empty' -H 'Sec-Fetch-Mode: no-cors' -H 'Sec-Fetch-Site: cross-site' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache'

This Works(秘密、密钥和名称已更改)

curl 'https://bucket-name.s3.fr-par.scw.cloud/tmp-screenshot-2021-01-20-at-16-21-33.png' -X OPTIONS -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Referer: http://domain.tech/' -H 'Access-Control-Request-Method: PUT' -H 'Origin: http://domain.tech' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Sec-Fetch-Dest: empty' -H 'Sec-Fetch-Mode: no-cors' -H 'Sec-Fetch-Site: cross-site' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache'

url 来自 aws-sdk 并以这种方式生成:

const S3Client = new S3({
  credentials: {
    accessKeyId: env.SCW_ACCESS_KEY,
    secretAccessKey: env.SCW_SECRET_KEY,
  },
  endpoint: `https://s3.${env.SCW_REGION}.scw.cloud`,
})

S3Client.getSignedUrlPromise('uploadPart', {
    Bucket: bucket,
    Key: key,
    UploadId: multipartUpload.UploadId,
    PartNumber: idx + 1,
})

并在前端使用这种方式:

// url being the url generated in backend as demonstrated above
const response = await fetch(url, {
  method: 'PUT',
  body: filePart,
  signal: abortController.signal,
})

如果有人能帮我解决这个或那个,那就太好了!

事实证明,在这种情况下,Scaleway Object 存储不完全兼容 S3。
这是一个解决方法:

  • 安装 aws4 库以轻松签署请求(或按照 this scaleway doc 手动签署您的请求)
  • 完全按照 this other scaleway doc 中的规定形成您的请求(这是 aws-sdk 行为不同的地方,它生成 url 和 AWSAccessKeyIdExpiresSignature 导致 scaleway API 失败的查询参数。Scaleway API 只需要 partNumberuploadId).
  • Return 生成的 url 和 headers 到前端
// Backend code
const signedRequest = aws4.sign(
  {
    method: 'PUT',
    path: `/${key}?partNumber=${idx + 1}&uploadId=${
      multipartUpload.UploadId
    }`,
    service: 's3',
    region: env.SCW_REGION,
    host: `${bucket}.s3.${env.SCW_REGION}.scw.cloud`,
  },
  {
    accessKeyId: env.SCW_ACCESS_KEY,
    secretAccessKey: env.SCW_SECRET_KEY,
  },
)

return {
  url: `https://${signedRequest.host}${signedRequest.path}`,
  headers: Object.keys(signedRequest.headers).map((key) => ({
    key,
    value: signedRequest.headers[key] as string,
  })),
}

然后在前端:

// Frontend code
const headers = signedRequest.headers.reduce<Record<string, string>>(
  (acc, h) => ({ ...acc, [h.key]: h.value }),
  {},
)

const response = await fetch(signedRequest.url, {
  method: 'PUT',
  body: filePart,
  headers,
  signal: abortController.signal,
})

Scaleway 知道这个问题,因为我直接与他们的支持团队讨论过,他们正在努力尽可能与 S3 兼容。当您阅读本文时,此问题可能已得到解决。 感谢他们真正快速的响应时间和认真对待这件事。