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:
- create gcloud credential folder
- write “${CIRCLE_OIDC_TOKEN}” to a token file
- 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