I couldn't find a simple example on how to implement Google Cloud Storage Signed Urls on Google App Engine with Python. Please write a step by step guide. :)
3 Answers
I created this repo: https://github.com/voscausa/appengine-gcs-signed-url
Using GAE Pyrthon app_identity.get_service_account_name()
and app_identity.sign_blob()
makes creating signed url's very easy, without using a PEM key. The app shows how to download a GCS file.
But if you use the SDK to test the app, you have to use:
- --appidentity_email_address
- --appidentity_private_key_path
because creating a signed url is not part of the GCS client.

- 11,253
- 2
- 39
- 67
The other solutions work but there is a simpler way using generate_signed_url. This method does the same thing as @voscausa's answer but is less tedious and has custom exceptions and support for other environments.
def sign_url(obj, expires_after_seconds=60):
client = storage.Client()
default_bucket = '%s.appspot.com' % app_identity.get_application_id()
bucket = client.get_bucket(default_bucket)
blob = storage.Blob(obj, bucket)
expiration_time = int(time.time() + expires_after_seconds)
url = blob.generate_signed_url(expiration_time)
return url
What vascausa said regarding local development server testing
But if you use the SDK to test the app, you have to use:
--appidentity_email_address
--appidentity_private_key_path
because creating a signed url is not part of the GCS client.
still holds.
-
3I tried this approach, but I'm getting an error: `AttributeError: you need a private key to sign credentials.the credentials you are currently using
just contains a token. see https://google-cloud-python.readthedocs.io/en/latest/core/auth.html?highlight=authentication#setting-up-a-service-account for more details.` I was under the impression that this would work by default since the service runs using the default service acount. What am I missing? – ajn May 29 '19 at 08:54 -
2Testing locally works fine when setting the GOOGLE_APPLICATION_CREDENTIALS environment variable to the json keyfile matching the service account – ajn May 29 '19 at 09:23
-
@ajn I'm getting the same issue, did you find a work around? – Ari Jul 18 '19 at 22:44
-
@AriVictor The work around in our case was that we implemented it in java instead. Don't want the hassle of providing explicit serve account credentials, but also didn't want to use python 2.7. – ajn Jul 22 '19 at 07:57
Here's how we made it work:
Step 1: Get p12 file/certificate
Download p12 file from https://console.developers.google.com/ “APIs & auth / Credentials” tab.
Step 2: Convert p12 file to DER format
Find a Linux computer open and connect using Terminal
Command:
openssl pkcs12 -in -nodes -nocerts >
# The current Google password for the p12 file is notasecret
Command: openssl rsa -in -inform PEM -out -outform DER
Step 3: Convert DER file to base64 encoded string
Python console:
private_key = open(‘<filename.der>’, 'rb').read()
print private_key.encode('base64')
Copy and paste into App engine script.
Step 4: Enable PyCrypto in AppEngine
app.yaml must have a line to enable PyCrypto:
- name: pycrypto
version: latest
Step 5: Python code to create Signed URL
import Crypto.Hash.SHA256 as SHA256
import Crypto.PublicKey.RSA as RSA
import Crypto.Signature.PKCS1_v1_5 as PKCS1_v1_5
der_key = “””<copy-paste-the-base64-converted-key>”””.decode('base64')
bucket = <your cloud storage bucket name (default is same as app id)>
filename = <path + filename>
valid_seconds = 5
expiration = int(time.time() + valid_seconds)
signature_string = 'GET\n\n\n%s\n' % expiration
signature_string += bucket + filename
# Sign the string with the RSA key.
signature = ''
try:
start_key_time = datetime.datetime.utcnow()
rsa_key = RSA.importKey(der_key, passphrase='notasecret')
#objects['rsa_key'] = rsa_key.exportKey('PEM').encode('base64')
signer = PKCS1_v1_5.new(rsa_key)
signature_hash = SHA256.new(signature_string)
signature_bytes = signer.sign(signature_hash)
signature = signature_bytes.encode('base64')
objects['sig'] = signature
except:
objects['PEM_error'] = traceback.format_exc()
try:
# Storage
STORAGE_CLIENT_EMAIL = <Client Email from Credentials console: Service Account Email Address>
STORAGE_API_ENDPOINT = 'https://storage.googleapis.com'
# Set the query parameters.
query_params = {'GoogleAccessId': STORAGE_CLIENT_EMAIL,
'Expires': str(expiration),
'Signature': signature}
# This is the signed URL:
download_href = STORAGE_API_ENDPOINT + bucket + filename + '?' + urllib.urlencode(query_params)
except:
pass
Sources

- 6,961
- 3
- 40
- 41

- 247
- 1
- 4
- 10