I'm trying to create a signed URL to be used for uploading files directly to Google Cloud Storage (GCS). I had this working using POST using this Github example, which makes use of a policy. Per best practice, I'm refactoring to use PUT and getting a SignatureDoesNotMatch error:
<?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>
Per the docs on creating a signed URL with a program and the GCP example Python code, I am doing this process:
- building my Signature string
- signing it
- base64 encoding it
- url encoding the result (the python example doesn't do this though...
Since this is running on a Google App Engine (GAE) App, I shouldn't need to get a JSON key file for my service account user, but rather use App Identity Services to sign it. Here's my code within a Flask project:
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))
The log.debug statement prints a signature file which perfectly matches the signature in the GCS XML error response above. If they match, then why can't I upload?
Using gsutil, I can create a signed URL using the same GAE service account, and it works fine in Postman. I see gsutil URL-encodes the signature, but when creating my own signed URL, it doesn't seem to matter either way: GCS gets my PUT request and complains that the signature doesn't match, even though the signature it shows me matches my logged debug message. I've also tried with and without a trailing \n in the original signature string.
EDIT: The POST example I followed Base64 encodes the Policy before it sings, and again after it signs it. I tried this approach with the PUT signature creation and it made no difference
<StringToSign>element? - Brandon YarbroughSignToString- hamx0r