GAE上使用Python签署的GCS PUT请求URL

时间:2017-11-09 21:10:46

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

我正在尝试创建一个签名网址,用于将文件直接上传到Google云端存储(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示例不会这样做...
  5. 由于这是在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服务帐户创建签名网址,并且在Postman中可以正常使用。我看到gsutil对签名进行URL编码,但在创建我自己的签名URL时,它似乎无关紧要:GCS获取我的PUT请求并抱怨签名不匹配,即使签名它显示我匹配我记录的调试消息。我也试过在原始签名字符串中使用和不使用尾随\ n。

    编辑 POST示例我跟踪Base64 encodes the Policy before it sings后再次签名后。我尝试使用PUT签名创建这种方法,并没有区别

1 个答案:

答案 0 :(得分: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错误。

解决方案是2部分:

  1. 从“存储”权限集中为默认GAE服务帐户授予更多权限:
    1. 存储对象创建器
    2. 存储对象查看器
  2. 确保在将文件输入GCS时使用Content-Type标题,因为即使我没有在要签名的签名字符串中指定它,它也会默认为text/plain并要求在标题中。
  3. 最后一次观察:即使我向帐户添加了正确的权限,我仍然会收到gsutil的警告,直到我从IAM控制台创建了一个新的JSON密钥文件。此外,即使在IAM控制台上设置了权限,使用gsutil和旧密钥文件创建的签名URL也无法正常工作(SignatureDoesNotMatch错误)。在GAE上运行的Python代码工作正常,没有任何更新 - 它只需要在IAM中设置的权限,然后匹配Content-Headers。