So…how do you get an AppEngine
app to use its service account to issue a sign some data or generate JWT
?
AppEngine
, Compute Engine
, Cloud Run
, Cloud Functions
, all provide you an easy way to give you an id_token to access services on GCP you deploy that accept those type of tokens.
But how would an appengine instance or any of the other platforms to sign a JWT or sign any arbitrary data with its own service account? For example you need to access a Cloud Endpoints
application which has Service Account JWT authentication?
Well, you can issue a private key for the appengine service account and then import that key as a GCP Secret…but thats a not a good idea (you should avoid downloading service account keys!)
The better idea is to somehow sign some data without a key.
how? use the iamcredentials.signBlob(), iamcredentials.signJwt() API and service account impersonation.
Basically, you’re allowing the appengine app to access its private key via API to do stuff like sign stuff.
For details, see
What this tutorial shows is how to setup an AppEngine Application, configure “self-impersonation” which will allow it to signJWT
or signBlob
using its own service account.
You can use this same technique to sign JWTs or signBlobs for the service account assigned to Compute Engine, GKE, Cloud Run, Cloud Functions too.
Some other references
First configure a google project which as appengine enabled (note you can apply much of this to Cloud Run, GCF or GCE instances)
export PROJECT_ID=`gcloud config get-value core/project`
export PROJECT_NUMBER=`gcloud projects describe $PROJECT_ID --format='value(projectNumber)'`
# allow the default service account to 'self ipersonate'
gcloud iam service-accounts add-iam-policy-binding \
$PROJECT_ID@appspot.gserviceaccount.com \
--member serviceAccount:$PROJECT_ID@appspot.gserviceaccount.com \
--role roles/iam.serviceAccountTokenCreator
# download the various files shown below into its own directory
# within that directory, install the dependencies into the lib/ folder
pip install -r requirements.txt -t lib
# now deploy the app (this will create a service called py-frontend)
gcloud app deploy --version=1 --no-promote --version 1
# tail the appengine logs
gcloud app logs tail -s py-frontend
In a new window, access the service
curl -s https://1-dot-py-frontend-dot-$PROJECT_ID.appspot.com
What you should see is several things some of which are just to baseline and to demo
First we just show details about the access_token
the appengine service runs as:
2022-04-24 12:28:50 py-frontend[1] {'id': '107145139691231222712', 'email': 'mineral-minutia-820@appspot.gserviceaccount.com', 'verified_email': True, 'picture': 'https://lh3.googleusercontent.com/a/default-user=s96-c'}
Then we show an id_token
for the appengine service
2022-04-24 12:28:50 py-frontend[1] {'args': {}, 'headers': {'Accept': 'application/json', 'Accept-Encoding': 'gzip, deflate', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImQzMzJhYjU0NWNjMTg5ZGYxMzNlZmRkYjNhNmM0MDJlYmY0ODlhYzIiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3lvdXJfYXVkaWVuY2UiLCJhenAiOiIxMDcxNDUxMzk2OTEyMzEyMjI3MTIiLCJlbWFpbCI6Im1pbmVyYWwtbWludXRpYS04MjBAYXBwc3BvdC5nc2VydmljZWFjY291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTY1MDgwNjkzMCwiaWF0IjoxNjUwODAzMzMwLCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMDcxNDUxMzk2OTEyMzEyMjI3MTIifQ.HkGtmciFatrBNM5WyhLxQsM25pI2y_GCO4upjv05oZo5m5K3PY-vF8Zb3jRSA6tdhTePowN1lZpddikz8DDgudGGh1Mt54n40kFAm8dlPEojPBgwXT0ektF-Pz4xVxuPEzm4zdDwL8Ee8C5H-n39_dM2zVXHOlpWkrWTwZJOHoZrgc1nw9aLRBOf_u3MxaDnFSsWYa85Ej0QIjZ8AhD-HNQQN3pqVl8zxu-XxncbBHb3fx3DRsyb7mCHZaSctGdcWW-4BI2XVZFYbAEiGJwcYDNmtaENrlyzRSuGwRH13d358n5fIJ6SgxqDARdmAUmF_RS-ekjI0goxuS8l0Jypag', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=1-62654282-4f0073691b8735587c1bf3b2'}, 'origin': '107.178.237.17', 'url': 'https://httpbin.org/get'}
Note the JWT issuer and claims. This is an id_token
signed and issued by google
{
"alg": "RS256",
"kid": "d332ab545cc189df133efddb3a6c402ebf489ac2",
"typ": "JWT"
}.
{
"aud": "https://your_audience",
"azp": "107145139691231222712",
"email": "mineral-minutia-820@appspot.gserviceaccount.com",
"email_verified": true,
"exp": 1650806930,
"iat": 1650803330,
"iss": "https://accounts.google.com",
"sub": "107145139691231222712"
}
Finally, what we were actually after: a self-signed JWT
2022-04-24 12:28:51 py-frontend[1] {'args': {}, 'headers': {'Accept': 'application/json', 'Accept-Encoding': 'gzip, deflate', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjEzYTc0Njc3ZTI2YTcxNTkxZWE3ZTE1YjMyNGE0ZWIxYWU2ZmM2M2QiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiAibWluZXJhbC1taW51dGlhLTgyMEBhcHBzcG90LmdzZXJ2aWNlYWNjb3VudC5jb20iLCAiZW1haWwiOiAibWluZXJhbC1taW51dGlhLTgyMEBhcHBzcG90LmdzZXJ2aWNlYWNjb3VudC5jb20iLCAiYXVkIjogImh0dHBzOi8veW91cl9hdWRpZW5jZSIsICJzdWIiOiAibWluZXJhbC1taW51dGlhLTgyMEBhcHBzcG90LmdzZXJ2aWNlYWNjb3VudC5jb20iLCAiZXhwIjogMTY1MDgwMzMzMiwgImlhdCI6IDE2NTA4MDMzMzB9.uvAyRE9eMePUuajkOT4vNN-FKRg-dVUIAKb1G_7rtfE1W6S4p_QqMApoos6OjENZ456zEnrYwz7CiMbvkhhdwT7tbge1FZaQRpf1RZJwK7qaP6P2o9wYuuh8388bLYtTTLqf8739F5FiL3QRkr6HFx1HzoG0BgCrW9WxjqDJyy6Ff-NAglQjvD8OPqe0a-nx2RKyj3-cjigdzZWJHPlVmAibn4T5yHrurjsnUGJGCuQ8SZVVKWEpBB-K1AH3-fdHG0TkRtGLLyA4i9mhtcQGJB_SXmvhajGmSR8WiHwI14Of412nInB7nfRx3RBFAxQpgH7O9gy-ap11edbaGv4djw', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=1-62654282-47a877d07b375cf30ef7d84d'}, 'origin': '107.178.237.17', 'url': 'https://httpbin.org/get'}
Notice the issuer and claims. This is signed and issued by our service account
{
"alg": "RS256",
"kid": "13a74677e26a71591ea7e15b324a4eb1ae6fc63d",
"typ": "JWT"
}.
{
"iss": "mineral-minutia-820@appspot.gserviceaccount.com",
"email": "mineral-minutia-820@appspot.gserviceaccount.com",
"aud": "https://your_audience",
"sub": "mineral-minutia-820@appspot.gserviceaccount.com",
"exp": 1650803332,
"iat": 1650803330
}
Notice that the kid
field denotes which private key from the list of keys this service account has that was used to sign the JWT.
Cloud Endpoints can verify this JWT by access the “verification” endpoint for the certs here
(notice the key_id
is listed there 13a74677e26a71591ea7e15b324a4eb1ae6fc63d
)
Anyway, what you can do is inject this jwt
or id_token
into an AuthorizedSession
and make an api call to your app
session = google.auth.transport.requests.AuthorizedSession(jwt_credentials)
request_url = "https://httpbin.org/get"
response = session.get(request_url, headers = {'Accept': 'application/json'})
print(response.json())
main.py
app.yaml
runtime: python37
service: py-frontend
#service_account: SA2@Project.iam.gserviceaccount.com
requirements.txt
Flask
Werkzeug
google-auth
google-cloud-iam
appengine_config.py
:import os
import sys
import logging
from google.appengine.ext import vendor
vendor.add('lib')
sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
logging.basicConfig(level=logging.INFO)
This site supports webmentions. Send me a mention via this form.