gcp

Cloud Build Authentication to Cloud Run, Cloud Functions, IAP

2022-02-16

This article describes how you can acquire a google-issued OpenID Connect (OIDC) token within a Cloud Build step and securely invoke Cloud Run, Cloud Functions or any general system that accepts an id_token.

A clear usecase for this is to have a cloud build step invoke an external secure system as part of the workflow. If the secure service is running in Cloud Run or Cloud Function or behind IAP, the Cloud Build will need some way to acquire a id_token and specify that within the API call.

This article will not cover the id_tokens in any detail and will just describe how to get an use an id_token from within cloud build.

For information on id_token see Authenticating using Google OpenID Connect Tokens.

For information on security configuration for Cloud Run, IAP and Cloud Function, see


As background, Cloud Build uses a default service account as its identity for authenticating to GCP APIs. The way that cloud build acquires its access_token is through its local metadata sever similar to Compute Engine. Critically, while that metadata server can return an access_token, it does NOT provide the /identity endpoint that returns an id_token.

Since Cloud Run, Cloud Function applications you deploy are secured by id_tokens, this prevents easy authentication.

One workaround is to furbish Cloud Build with a service account credentials file as described here. While that works, thats also a problem since now you have just downloaded a key (don’t download keys).

Another approach is to use the default account’s access_token and use that to make a new API call to projects.serviceAccounts.generateIdToken. Basically enable ‘self-impersonation’ and use that API by the default service account.

This approach sounds feasible if it weren’t for a nit: the default service account is managed by google’s infrastructure and is not “assignable” to the required IAM role that enables self impersonation (eg, roles/iam.serviceAccountTokenCreator)

However, Cloud build support user-specified service accounts which you can control and can assign the role.

…and thats just what we’re going to do here

The iam.serviceAccountTokenCreator role includes concentric set of permission (see Concentric IAMCredentials Permissions: The secret life of signBlob. If you just need to generate an access_token or an id_token, consider generating a custom role with that specific capability.


Setup

First acquire some environment variables and enable the required APIs

export GCLOUD_USER=`gcloud config get-value core/account`
export PROJECT_ID=`gcloud config get-value core/project`
export PROJECT_NUMBER=`gcloud projects describe $PROJECT_ID --format='value(projectNumber)'`

gcloud services enable cloudbuild.googleapis.com iam.googleapis.com

Create a service account that Cloud Build will run as and then assign the IAM permissions to run Cloud Build steps, write logs and most importantly, enable ‘self-impersonation’

gcloud iam service-accounts create generic-server

gcloud iam service-accounts add-iam-policy-binding generic-server@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/iam.serviceAccountTokenCreator \
    --member "serviceAccount:generic-server@$PROJECT_ID.iam.gserviceaccount.com"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member=serviceAccount:generic-server@$PROJECT_ID.iam.gserviceaccount.com  \
  --role=roles/logging.logWriter


gcloud iam service-accounts add-iam-policy-binding generic-server@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/iam.serviceAccountUser \
    --member "user:$GCLOUD_USER"


gsutil iam ch serviceAccount:generic-server@$PROJECT_ID.iam.gserviceaccount.com:objectAdmin gs://$PROJECT_ID\_cloudbuild 

Once thats done, use gcloud to impersonate and get an id_token (you can do this in any number of ways programmatically but we’re just using gcloud for simplicity)

steps:
- name: gcr.io/cloud-builders/gcloud
  id: get_id_token
  entrypoint: /bin/bash
  args: ['-c', 'gcloud auth print-identity-token --include-email --audiences=https://foo.com --impersonate-service-account=generic-server@$PROJECT_ID.iam.gserviceaccount.com >/workspace/id_token.txt']

- name: launcher.gcr.io/google/ubuntu1604
  id: print_id_token
  entrypoint: bash
  args:
    - -c
    - |
      curl -s -H "Authorization: Bearer $(cat /workspace/id_token.txt)" https://httpbin.org/get      

serviceAccount: 'projects/$PROJECT_ID/serviceAccounts/generic-server@$PROJECT_ID.iam.gserviceaccount.com'
options:
  logging: CLOUD_LOGGING_ONLY

Note that we’ve specified the serviceAccount as part of the build and wrote the output token to a temp file to reuse between steps

Then invoke and view results

gcloud builds submit

images/cb_output.png

The JWT that is generated will be in the expected form

{
  "alg": "RS256",
  "kid": "f4bb220cd094b0ae90dd73e10c10e7db54b89280",
  "typ": "JWT"
}
{
  "aud": "https://foo.com",
  "azp": "108446643221242807280",
  "email": "generic-server@cr-cb-341501.iam.gserviceaccount.com",
  "email_verified": true,
  "exp": 1645014638,
  "iat": 1645011038,
  "iss": "https://accounts.google.com",
  "sub": "108446643221242807280"
}

and the output of a given run will invoke httpbin in this case but you can substitute IAP,Cloud Run or Cloud Functions there

This site supports webmentions. Send me a mention via this form.