【问题标题】:Signed URLs on GAE with Python for GCS PUT request在 GAE 上使用 Python 为 GCS PUT 请求签名的 URL
【发布时间】:2018-04-23 00:31:26
【问题描述】:

我正在尝试创建一个签名 URL,用于将文件直接上传到 Google Cloud Storage (GCS)。我使用POST 使用this Github example 进行这项工作,它利用了策略。根据最佳实践,我正在重构以使用 PUT 并得到 SignatureDoesNotMatch 错误:

<?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 Google secret key and signing method.</Message><StringToSign>PUT


123456789
/mybucket/mycat.jpg</StringToSign></Error>

根据creating a signed URL with a programGCP example Python code 上的文档,我正在执行此过程:

  1. 构建我的签名字符串
  2. 签名
  3. base64 编码
  4. url 编码结果(虽然 python 示例没有这样做......

由于这是在 Google App Engine (GAE) 应用程序上运行的,因此我不需要为我的服务帐户用户获取 JSON 密钥文件,而是使用 App Identity Services 对其进行签名。这是我在 Flask 项目中的代码:

google_access_id = app_identity.get_service_account_name()
expires = arrow.utcnow().replace(minutes=+10).replace(microseconds=0).timestamp
resource = '/mybucket/mycat.jpg'

args = self.get_parser.parse_args()
signature_string = 'PUT\n'
# take MD5 of file being uploaded and its content type, if provided
content_md5 = args.get('md5') or ''
content_type = args.get('contenttype') or ''
signature_string = ('PUT\n'
                    '{md5}\n'
                    '{content_type}\n'
                    '{expires}\n'
                    '{resource}\n').format(
                    md5=content_md5,
                    content_type=content_type,
                    expires=expires,
                    resource=resource)
log.debug('signature string:\n{}'.format(signature_string))
_, signature_bytes = app_identity.sign_blob(signature_string)
signature = base64.b64encode(signature_bytes)
# URL encode signature
signature = urllib.quote(signature)
media_url = 'https://storage.googleapis.com{}'.format(resource)
return dict(GoogleAccessId=google_access_id,
            Expires=expires,
            Signature=signature,
            bucket='mybucket',
            media_url='{}?GoogleAccessId={}&Expires={}&Signature={}'.format(media_url, google_access_id, expires, signature))

log.debug 语句打印一个签名文件,该文件与上面的 GCS XML 错误响应中的签名完全匹配。如果它们匹配,那为什么我不能上传?

使用gsutil,我可以使用相同的 GAE 服务帐户创建一个签名 URL,它在 Postman 中运行良好。我看到gsutil 对签名进行 URL 编码,但是在创建我自己的签名 URL 时,这两种方法似乎都无关紧要:GCS 收到我的 PUT 请求并抱怨签名不匹配,即使它显示的签名我匹配我记录的调试消息。我也尝试过在原始签名字符串中使用和不使用尾随 \n 。

编辑:POST 示例我遵循Base64 encodes the Policy before it sings在它签名后再次。我在创建 PUT 签名时尝试了这种方法,但没有任何区别

【问题讨论】:

  • 您指定的是“md5”还是“contenttype”参数?看起来您的实际请求并未在请求本身中指定它们。调试语句中生成的值是否与 &lt;StringToSign&gt; 元素中的值完全匹配?
  • 我的请求中没有包含 md5 或内容类型详细信息,也没有在签名字符串中指定。据我所知,内容完全相同:我签名的字符串与返回的 SignToString 的 XML 值完全匹配

标签: python python-2.7 google-app-engine google-cloud-storage


【解决方案1】:

答案与 SO 和其他地方的其他答案非常接近,注意到需要使用 Content-Type 标头。这部分是正确的,但被我的主要问题所掩盖:我依赖具有“编辑器”权限的默认 GAE 服务帐户,我认为该帐户可以读取和写入 GCS。我从该帐户创建了一个密钥文件并将其与gsutil 一起使用,然后给了我这个线索:

myDefaultGAEserviceaccount@appspot.gserviceaccount.com does not have permissions on gs://mybucket/cat.jpg, using this link will likely result in a 403 error until at least READ permissions are granted

我在尝试将文件放在那里时从 GCS 收到错误是对的,但这不是 404 错误,而是问题中显示的SignatureDoesNotMatch 错误。

解决方案是两部分:

  1. 从“存储”权限集中为默认 GAE 服务帐户授予更多权限:
    1. 存储对象创建器
    2. 存储对象查看器
  2. 确保在将文件放入 GCS 时使用 Content-Type 标头,因为即使我没有在要签名的签名字符串中指定它,它也会默认为 text/plain 并要求在标头中使用它。

最后一个观察结果:即使在我为我的帐户添加了正确的权限之后,我仍然会收到 gsutil 的警告,直到我从 IAM 控制台创建了一个新的 JSON 密钥文件。此外,即使在 IAM 控制台上设置了权限,使用 gsutil 和旧密钥文件创建的签名 URL 也无法工作(SignatureDoesNotMatch 错误)。在 GAE 上运行的 Python 代码运行良好,无需任何更新 - 它只需要在 IAM 中设置的权限,然后匹配 Content-Headers。

【讨论】:

    猜你喜欢
    • 2023-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 2021-04-28
    • 2022-07-16
    • 2020-12-16
    相关资源
    最近更新 更多