Google Cloud Keyless Authentication in GitHub Actions
What is keyless authentication, and why would you care?
Let’s say you want a full CI/CD flow run directly from GitHub Actions. As the last steps of the deployment, you might need to authenticate to Google Cloud to deploy any changes to infrastructure and services. Since you don’t have a UI, authentication is usually performed via a service account.
For example, when running either of the following two scenarios in GitHub Actions, you would need to provide credentials for Google Cloud Platform (GCP):
- apply infra changes with Terraform
- make changes to your cloud service with gcloud CLI
GCP provides a simple mechanism: download a service account’s key in JSON format, and offer it to any running clients via the GOOGLE_APPLICATION_CREDENTIALS
environment variable.
This mechanism works, but it relies on long-lived credentials. It works, but it is also insecure since, if somehow exposed, a third party would get access to everything the service account has access to, providing an attacker with ample time to steal data and/or generally do some damage.
If you think you’d spot any such attacks, think again! A motivated and skilled attacker could get far before triggering any alarms that would prompt you to investigate.
Enter workload identity federation. It allows granting external identities (a.k.a., your GitHub principal) access to Google Cloud resources. It not only does so magically without requiring any service key secrets but also sets a token expiry of one hour — a much-reduced window of attack.
Ok, so we now know ‘the why.’ Let’s move on to ‘the how’.
How do I configure keyless auth between GitHub and GCP?
Before starting, you’ll need to decide on a few names and variables:
$PROJECT
: your Google Cloud Project’s identifier (i.e.some-project-123
)$NAME
: the name was given to the identity pool (i.e.,my-identity-pool
)$PROVIDER_NAME
: the name was given to the identity provider (i.e.,my-provider
)$SERVICE_ACCOUNT
: the name of the service account user that has access to your project/resources$GITHUB_ORG
: the GitHub organization that hosts your code$GITHUB_REPO
: the GitHub repository that holds your code$PROJECT_NUMBER
: retrieved from Google Cloud, see below:
gcloud projects list \
--filter="project_id:$PROJECT" \
--format="get(project_number)"
Now, let’s configure GCP keyless authentication in five easy steps:
- Create an identity pool
gcloud iam workload-identity-pools create "$NAME" \
--project="$PROJECT" \
--location="global" \
--display-name="$NAME (or a description of your choosing)"
2. Create an identity provider
gcloud iam workload-identity-pools providers create-oidc "$PROVIDER_NAME" \
--project="$PROJECT" \
--location="global" \
--workload-identity-pool="$NAME" \
--display-name="$NAME (or a description of your choosing)" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.aud=assertion.aud,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
As configured, this identity provider will allow your workflows to obtain OIDC tokens restricted to the current (calling) repository — when it comes to security, always apply the principle_of_least_privilege!
3. Bind the policy to your service account:
gcloud iam service-accounts add-iam-policy-binding "$SERVICE_ACCOUNT@$PROJECT.iam.gserviceaccount.com" \
--project="$PROJECT" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$NAME/attribute.repository/$GITHUB_ORG/$GITHUB_REPO"
Over on the GitHub Workflows side:
4. You must give the id-token permission
to your job’s GITHUB_TOKEN
, so that the running workflow can read/write OIDC tokens and interact with the Google Identity Provider.
This is easily accomplished by adding the following permissions block to your job (or entire workflow).
permissions:
contents: read
id-token: write
When specifying explicit permissions in GH workflows you will in turn remove all the defaults. This is why you need the “contents: read” line.
5. Finally, use the Google Auth GitHub Action to retrieve the necessary credentials for your job — all further steps in your job will be able to use these.
- name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v0.4.0'
with:
workload_identity_provider: 'projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$NAME/providers/$PROVIDER_NAME'
service_account: '$SERVICE_USER@$PROJECT.iam.gserviceaccount.com'
That’s it!
Good luck with all your automation!