We've built several services using Cloud Run. Our goal is to build an internal CLI that allows our developers to make calls to these services. We're having trouble generating an id_token
to use with the Identity Aware Proxy that sits in front of Cloud Run services.
According to the docs, making calls to your Cloud Run services can be accomplished by using gcloud
and the gcloud auth print-identity-token
command. This works great. This also avoids having to download and pass around service account credentials to our developers as this method leverages your application default credentials.
We've tried implementing something to replicate this print-identity-token
functionality in Go with no luck. The id_token
generated returns 401's to all of our Cloud Run API's. Example code for generating the token:
func GetIDToken() string {
ctx := context.Background()
tokenSource, err := google.DefaultTokenSource(ctx, "openid", "email")
if err != nil {
log.Fatal(err)
}
token, err := tokenSource.Token()
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%v", token.Extra("id_token"))
}
This returns an id_token
but it doesn't work with the API's. The scopes seem to be correct according to the docs.
This leaves us with two questions:
This answer is for creating an Identity Token from a service account. This example is in Python. If requested, I will write this in Go. I just had this code already written in Python.
In the code below, the first code block is the section that takes a service account and requests the Identity Token from Google. Notice that I do not use any scopes. Scopes are used when requesting Google OAuth Access Tokens. Identity Tokens have identity stored in them. Instead you need to specify the audience (URL) that the Identity Token is destined for. Not all services require a valid audience value.
My code also shows how to decode an Identity Token to see the Header and Payload JSON. The Payload contains the identity that Google IAP validates.
Once you have the Identity Token, include the HTTP Header authorization: bearer TOKEN
when making requests to Cloud Run.
import google.auth.transport.requests
import google.oauth2.service_account
credentials = google.oauth2.service_account.IDTokenCredentials.from_service_account_file(
json_filename,
target_audience=aud)
request = google.auth.transport.requests.Request()
credentials.refresh(request)
aud
to be your Cloud Run URL.Full Source Code Example:
'''
This program creates an OIDC Identity Token from a service account
'''
import json
import base64
import google.auth.transport.requests
import google.oauth2.service_account
# The service account JSON key file to use to create the Identity Token
json_filename = '/config/service-account.json'
# The audience that this ID token is intended for (example Google Cloud Run service URL)
aud = 'http://localhost'
def pad(data):
""" pad base64 string """
missing_padding = len(data) % 4
data += '=' * (4 - missing_padding)
return data
def print_jwt(signed_jwt):
""" Print a JWT Header and Payload """
s = signed_jwt.decode('utf-8').split('.')
print('Header:')
h = base64.urlsafe_b64decode(pad(s[0])).decode('utf-8')
print(json.dumps(json.loads(h), indent=4))
print('Payload:')
p = base64.urlsafe_b64decode(pad(s[1])).decode('utf-8')
print(json.dumps(json.loads(p), indent=4))
if __name__ == '__main__':
credentials = google.oauth2.service_account.IDTokenCredentials.from_service_account_file(
json_filename,
target_audience=aud)
request = google.auth.transport.requests.Request()
credentials.refresh(request)
#print(dir(credentials))
# This is debug code to show how to decode Identity Token
print('Decoded Identity Token:')
print_jwt(credentials.token.encode())
# This is the actual Identity Token
print()
print('Identity Token:')
print(credentials.token)