Walk-through - OIDC to GCP

Walk-through - OIDC to GCP

Because of multiple requests to help with OIDC for GCP, I decided to release a short walk-through. Feel free to comment. Note: We moved on using Vault…

IAM credential API

Let’s ensure the IAM Credential Service is enabled

gcloud services enable iamcredentials.googleapis.com \
    --project "${GCP_PROJECT}"
gcloud components update

Service Account

The entity we want to work with at GCP is a Service Account, so we are going to create one.

gcloud iam service-accounts create circleci-oidc \
    --project "${GCP_PROJECT}"

PoLP

In order to only allow the Service Account to describe a cluster and request an OAuth2 token credential for it, we are going to create a custom role definition and bind the GCP SA to it.

custom-role.yaml

title: CircleCIOIDC
description: CircleCI OIDC role
stage: GA
includedPermissions:
  - container.clusters.get
  - container.clusters.getCredentials
gcloud iam roles create container.circleciOIDC \
        --file custom-role.yaml
gcloud projects add-iam-policy-binding "${GCP_PROJECT}" \
        --member "serviceAccount:circleci-oidc@${GCP_PROJECT}.iam.gserviceaccount.com" \
        --project "${GCP_PROJECT}" \
        --role "projects/${GCP_PROJECT}/roles/container.circleciOIDC"

Workload Identity Pool

The service which offers short-lived credentials is the workload identity pool.

gcloud iam workload-identity-pools create circleci-oidc \
    --display-name circleci-oidc \
    --location global \
    --project "${GCP_PROJECT}"

OIDC Provider

To allow CircleCI logging into GCP we need an OIDC provider configuration.
Regarding the token format description we are going to set the audience to the CircleCI Org UUID (reflects ClientIDList in AWS).
The attribute mappings need to be explicitly defined. All possible claims are enlisted here.

gcloud iam workload-identity-pools providers update-oidc circleci 
    --allowed-audiences "${CIRCLECI_ORG_UUID}" \
    --attribute-mapping google.subject=assertion.sub,attribute.audience=assertion.aud,attribute.project=assertion['oidc.circleci.com/project-id'],attribute.context=assertion['oidc.circleci.com/context-ids'] \
    --display-name circleci-oidc \
    --issuer-uri "https://oidc.circleci.com/org/${CIRCLECI_ORG_UUID}" \
    --location global \
    --project "${GCP_PROJECT}" \
    --workload-identity-pool circleci-oidc

Service Account Mapping

Finally we allow the Service Account to be impersonated if attribute.audience matches the aud claim ("${CIRCLCI_ORG_UUID}"). Feel free to add the project and context claim as well.

gcloud iam service-accounts add-iam-policy-binding "circleci-oidc@${GCP_PROJECT}.iam.gserviceaccount.com" \
    --project "${GCP_PROJECT}" \
    --role roles/iam.workloadIdentityUser \
    --member "principalSet://iam.googleapis.com/projects/${GCP_PROJECT_ID}/locations/global/workloadIdentityPools/circleci-oidc/providers/circleci/attribute.audience/${CIRCLECI_ORG_UUID}"

CI Implementation

This is a plain implementation to configure the default application credentials.
Application path:

  1. create gcloud credential folder
  2. write “${CIRCLE_OIDC_TOKEN}” to a token file
  3. render the application default credential file using this token
    (runtime will retrieve the temp token on demand)
- run:
    name: GCP OIDC auth
    command: |
      GCP_OIDC_PROVIDER_ID="circleci"
      GCP_PROJECT_ID="${GCP_PROJECT_ID}"
      GCP_SERVICE_ACCOUNT_EMAIL="circleci-oidc@${GCP_PROJECT}.iam.gserviceaccount.com"
      GCP_WORKLOAD_IDENTITY_POOL_ID="circleci-oidc"
      GCP_OIDC_AUDIENCE="projects/${GCP_PROJECT_ID}/locations/global/workloadIdentityPools/${GCP_WORKLOAD_IDENTITY_POOL_ID}/providers/${GCP_OIDC_PROVIDER_ID}"
      GCP_IMPERSONATION_URL="https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${GCP_SERVICE_ACCOUNT_EMAIL}:generateAccessToken"

      mkdir -p ~/.config/gcloud
      echo "${CIRCLE_OIDC_TOKEN}" > $HOME/.config/gcloud/oidc_access_token

      cat >> $HOME/.config/gcloud/application_default_credentials.json \<<- EOF
      {
        "type": "external_account",
        "audience": "//iam.googleapis.com/${GCP_OIDC_AUDIENCE}",
        "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
        "token_url": "https://sts.googleapis.com/v1/token",
        "credential_source": {
          "file": "$HOME/.config/gcloud/oidc_access_token"
        },
        "service_account_impersonation_url": "${GCP_IMPERSONATION_URL}"
      }
      EOF
2 Likes