Search code examples
google-cloud-platformgoogle-cloud-functionsservice-accountsgoogle-iam

Generate JWT for service account using compute metadata from cloud function


I'm trying to generate a JWT for a given service account serviceA from a Google/Firebase Cloud function. Service account serviceB is running the function.

I got it working by using the account keys from JSON.

Given that the CF is running within Google Cloud, I want to leverage compute metadata to not having to store the private key with the functions.

I've been trying to access the metadata server for serviceA while serviceB is executing the CF. I deliberately don't want serviceA to run the CF.

The code

const request = require('request-promise');
const serviceAccountEmail = 'serviceA@<projectA>.iam.gserviceaccount.com';
const metadataServerTokenURL = `http://metadata/computeMetadata/v1/instance/service-accounts/${serviceAccountEmail}/identity?audience=<audience>`;
const tokenRequestOptions = {
    uri: metadataServerTokenURL,
    headers: {
        'Metadata-Flavor': 'Google'
    }
};
const token = await request(tokenRequestOptions);

The error

I'm currently getting a 404 not found error for the email provided

I guess it's
a) not possible what I'm trying to do, or
b) I'm missing some IAM permissions for serviceA


Solution

  • You can do this with the metadata server because they can only generate ID Token for the service account loaded with your instance (in this case the serviceB).

    You can use another API for this: Service Account Credentials API, especially the generateIdToken method

    In your case, you can do something like this (in python here)

    import google.auth
    from google.auth.transport.requests import AuthorizedSession
    import json
    
    
    # IAP audience is the ClientID of IAP-App-Engine-app in 
    # the API->credentials page
    # Cloud Function and Cloud Run need the base URL of the service
    audience = 'YOUR AUDIENCE'
    # #1 Get the default credential to generate the access token
    credentials, project_id = google.auth.default(
                scopes='https://www.googleapis.com/auth/cloud-platform')
    
    # #2 To use the current service account email
    service_account_email = credentials.service_account_email
    # Don't work with user account, so define manually the email
    # service_account_email = 'MY SERVICE ACCOUNT EMAIL'
    # #3 prepare the call the the service account credentials API
    sa_credentials_url =  f'https://iamcredentials.googleapis.com/' \
                          f'v1/projects/-/serviceAccounts/'  \
                          f'{service_account_email}:generateIdToken'
    headers = {'Content-Type': 'application/json'}
    
    # Create an AuthorizedSession that includes 
    # automatically the access_token based on your credentials
    authed_session = AuthorizedSession(credentials)
    # Define the audience in the request body
    # add the parameter "'includeEmail':true" for IAP access
    body = json.dumps({'audience': audience})
    # Make the call 
    token_response = authed_session.request('POST',sa_credentials_url,
                                            data=body, headers=headers)
    
    jwt = token_response.json()
    id_token = jwt['token']
    

    I wrote an article on this, this week