Let’s talk about keyless authorization from GitHub Actions in GCP using IdP. How should we auth into GCP from GitHub Actions (it doesn't matter if we use our runners or GitHub runners)? What if we use terraform to configure GCP and GitHub Actions for our automation operations? The easiest and not the most secure way — is to create a primary GCP Service Account key (JSON) with specific permission and authorization. This is a great way to start. Let’s see how it works:
The service account key will be compromised?
We want to limit the origins from where the key can be used.
We want to reissue a new service account key automatically.
Google has a great solution —
Traditionally, applications running outside Google Cloud have used
service account keys to access Google Cloud resources. Service account keys are powerful credentials, and can represent a security risk if they are not managed correctly.With identity federation, you can use Identity and Access Management (IAM) to grant external identities
IAM roles , including the ability to impersonate service accounts. This lets you access resources directly, using ashort-lived access token , and eliminates the maintenance and security burden associated with service account keys.
Aworkload identity pool provider is an entity that describes a relationship between Google Cloud and an external identity provider, such as the following:
But let’s look at how to use this keyless authorization paired with GitHub Actions.
We will perform all the actions using terraform, but you can also use the UI GCP.
First, let’s create Workload Identity Pool:
resource "google_iam_workload_identity_pool" "github_actions" {
provider = google-beta
project = "my-gcp-project"
workload_identity_pool_id = "github-actions"
display_name = "GitHub Actions pool"
description = "Workload Identity Pool managed by Terraform"
disabled = false
}
Then we need to create a Workload Identity Pool Provider (many providers can be assigned to one Workload Identity Pool):
resource "google_iam_workload_identity_pool_provider" "github_actions" {
provider = google-beta
project = "my-gcp-project"
workload_identity_pool_id = google_iam_workload_identity_pool.github_actions.workload_identity_pool_id
workload_identity_pool_provider_id = "github-actions"
display_name = "GitHub Actions provider"
description = "Workload Identity Pool Provider managed by Terraform"
attribute_condition = "attribute.repository_owner==\"arslanbekov\""
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.aud" = "assertion.aud"
"attribute.repository" = "assertion.repository"
"attribute.repository_owner" = "assertion.repository_owner"
}
oidc {
allowed_audiences = []
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
Notice the parameter attribute_condition, a condition by which we can prohibit actions that do not fall under our filter.
You can see all available values in the official
GitHub documentation .
We also need a service account (don’t worry, we won’t need the keys anywhere); I did not describe it in the terraform code for simplicity; I think that you can easily do it yourself or using CLI gcloud:
gcloud iam service-accounts create SA_NAME \
--description="DESCRIPTION" \
--display-name="DISPLAY_NAME"
After we have an email service account, it will need to be given a role. roles/iam.workloadIdentityUser
resource "google_service_account_iam_member" "wif-sa" {
service_account_id = "projects/my-gcp-project/serviceAccounts/[email protected]"
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_actions.name}/*"
}
Don’t forget to replace my-gcp-project and [email protected]
After applying this terraform in the output, you will have two values (please save them, you will need them later):
output "pool_name" {
description = "Pool name"
value = google_iam_workload_identity_pool.github_actions.name
}
output "provider_name" {
description = "Provider name"
value = google_iam_workload_identity_pool_provider.github_actions.name
}
Example output:
pool_name = "projects/${redacted_number}/locations/global/workloadIdentityPools/github-actions"
provider_name = "projects/${redacted_number}/locations/global/workloadIdentityPools/github-actions/providers/github-actions"
Let’s create 2 GitHub actions secrets:
GCP_WORKLOAD_IDENTITY_PROVIDER_NAME
(need to specify the entire line, for example: projects/123456789/locations/global/workloadIdentityPools/github-actions/providers/github-actions
)GCP_WORKLOAD_IDENTITY_SA_EMAIL
(service account email that we created earlier and indicated in the terraform)
GitHub workflow example:
name: "Example Workload Identity"
on:
push:
branches:
- "master"
jobs:
run:
name: "Workload Identity Job"
permissions:
id-token: write
contents: read
runs-on: "ubuntu-latest"
steps:
- name: "Auth in GCP"
id: "auth"
uses: "google-github-actions/auth@v1"
with:
token_format: "access_token"
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER_NAME }}
service_account: ${{ secrets.GCP_WORKLOAD_IDENTITY_SA_EMAIL }}
- name: "Docker login"
run: |
echo '${{ steps.auth.outputs.access_token }}' | docker login -u oauth2accesstoken --password-stdin https://gcr.io
Finally, we can authorize in the docker access_token
which will be in the output of the step id: auth
.
If you need interaction with gsutils
— then this is also available after authorization.
By switching to authorization through a workload identity provider, we can restrict access from specific origins for approval. We also stop caring about key rotation and can control the key's lifetime.
They are also published here.