🔐 Google Cloud Authentication with Workload Identity Federation for GitHub Actions

Tech Community • 6 min read

10 February 2023

In the past, when you wanted to allow an application like GitHub to access your Google Cloud resources, you had to follow a few steps. First, you would create a separate IAM service account for each application. 

Then, you would download a long-lasting JSON service account key and store it in your GitHub repository's secrets. These service account keys could then be used to make GCP API calls from your application.

Managing these service account keys involved secure storage, regular rotation, and continuous monitoring to protect your Google resources from potential malicious actors. If a malicious actor gained access to your keys, they could maintain prolonged access to your Google Cloud resources. This is why this approach isn’t recommended.

So, what's the solution? 

The answer is Workload Identity Federation (WIF).

Through the Workload Identity Federation (WIF), you can grant external identities IAM roles, by allowing them to impersonate service accounts that have these roles.

This approach is superior for the following reasons:

1. You no longer need to manage key rotation or store service account keys.

2. The credentials have a short default lifespan and are configurable. This approach simplifies the management and security aspects associated with service account keys.

Steps to Set Up Workload Identity Federation 

Step 1: Create a Workload Identity Pool

According to the documentation, a Workload Identity Pool (WIP) is an entity that lets you manage external identities. In general, GCP recommends creating a new pool for each non-Google Cloud environment that needs to access Google Cloud resources, such as development, staging, or production environments. 

In this step, we are defining the locals to be used, and we are using a unique name for the Workload Identity Pool (WIP).

locals {
  project_id = "PROJECT_ID"
  service_account = "SERVICE_ACCOUNT"
  organization     = "YOUR_ORGANISATION_HERE"
  repository       = "YOUR_REPOSITORY_HERE"
}


resource "google_iam_workload_identity_pool" "github_pool" {
  project                   = local.project_id
  workload_identity_pool_id = "github-pool-oidc"
  display_name              = "GitHub pool"
  description               = "Identity pool for GitHub deployments"
}

Step 2: Configure the Workload Identity Pool Provider

In this step, we configure the Workload Identity Pool provider. Your provider should have a unique name. Then, we can set the "Attribute Mapping" to map the GitHub Actions attributes to GCP identity attributes. 

Here, we can also define an "Attribute Condition" which is recommended **to be more secure.** This condition specifies when the identity provider should be used based on GitHub repository attributes, for example, repository name, repository owner, actor, and others. 

We also set up the OIDC settings by providing the correct issuer URI.

resource "google_iam_workload_identity_pool_provider" "github" {
  project                            = local.project_id
  workload_identity_pool_id          = google_iam_workload_identity_pool.github_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
 
  attribute_mapping = {
    "attribute.aud"              = "assertion.aud"
    "google.subject"             = "assertion.sub"
    "attribute.sub"              = "assertion.sub"
    "attribute.actor"            = "assertion.actor"
    "attribute.repository"       = "assertion.repository"
    "attribute.repository_owner" = "assertion.repository_owner"
    "attribute.ref"              = "assertion.ref"
  }

  If you want to restrict to organisation, use:

"assertion.repository_owner==\"${local.organization}\""

  For more than one repository, use:

"assertion.repository==\"ORG/repository-1\" || assertion.repository==\"ORG/repository-2\""
 
  attribute_condition = "assertion.repository==\"${local.organization}/${local.repository}\""
 

  oidc {
    allowed_audiences = []
    issuer_uri        = "https://token.actions.githubusercontent.com"
  }
}

Step 3: Configure IAM Roles

In this step, we configure the GCP IAM role for our service account. This can be done by setting the roles/iam.workloadIdentityUser to the service account we want to impersonate.

resource "google_service_account_iam_member" "workload_identity_user" {
  service_account_id = 'projects/${local.project_name}/serviceAccounts/${local.service_account}' #Replace with your service account
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool.name}/attribute.repository_owner/${local.organization}"
}

What this role does is to allow the associated service account to assume identities from a trusted identity provider to access Google Cloud resources which this service account has permission for.

Step 4: Full Terraform code

To sum up, this is the full terraform code we needed for the setup.

# wif.tf

locals {
  project_id = "PROJECT_ID"
  service_account = "SERVICE_ACCOUNT"
  project_name    = "YOUR_PROJECT_NAME"
  organization     = "YOUR_ORGANISATION_HERE"
  repository       = "YOUR_REPOSITORY_HERE"
}
 
resource "google_iam_workload_identity_pool" "github_pool" {
  project                   = local.project_id
  workload_identity_pool_id = "github-pool-oidc"
  display_name              = "GitHub pool"
  description               = "Identity pool for GitHub deployments"
}
 
resource "google_iam_workload_identity_pool_provider" "github" {
  project                            = local.project_id
  workload_identity_pool_id          = google_iam_workload_identity_pool.github_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
 
  attribute_mapping = {
    "attribute.aud"              = "assertion.aud"
    "google.subject"             = "assertion.sub"
    "attribute.sub"              = "assertion.sub"
    "attribute.actor"            = "assertion.actor"
    "attribute.repository"       = "assertion.repository"
    "attribute.repository_owner" = "assertion.repository_owner"
    "attribute.ref"              = "assertion.ref"
  }

  If you want to restrict the organisation, use the:

"assertion.repository_owner==\"${local.organization}\""

  For more than one repository, use:

  "assertion.repository==\"ORG/repository-1\" || assertion.repository==\"ORG/repository-2\""

  For one repository:

  attribute_condition = "assertion.repository==\"${local.organization}/${local.repository}\""
 
  oidc {
    allowed_audiences = []
    issuer_uri        = "https://token.actions.githubusercontent.com"
  }

}
 
resource "google_service_account_iam_member" "workload_identity_user" {
  service_account_id = 'projects/${local.project_name}/serviceAccounts/${local.service_account}' #Replace with your service account
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool.name}/attribute.repository_owner/${local.organization}"
}

Step 5: Setup GitHub Actions Workflow

You need to save as secrets the ``provider_name`` as this is sensitive information. It has the following format:

WORKLOAD_IDENTITY_PROVIDER = projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool-oidc/providers/github-provider

Below is an example of a workflow using GCP WIF OpenID Connect (OIDC) in GitHub Actions. 

name: Example GCP WIF with GitHub Actions
 
on:
  push:
    branches:
      - setup-wif-oidc
 
jobs:
  job_id:
    runs-on: ubuntu-latest
    permissions:
      contents: 'read'
      id-token: 'write'
 
    steps:
      # actions/checkout MUST come before auth

      - uses: 'actions/checkout@v3'
 
      - id: "auth"
        name: "Authenticate to Google Cloud"
        uses: "google-github-actions/auth@v2"
        with:
          token_format: "access_token"
          workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}
          service_account: YOUR_SERVICE_ACCOUNT
          export_environment_variables: true
          audience: ${{ secrets.GCP_POOL_AUDIENCE }}
          create_credentials_file: true
          access_token_lifetime: 500

    Further steps are automatically authenticated
 
      - name: "Set up Cloud SDK"
        uses: "google-github-actions/setup-gcloud@v1"
        with:
          version: ">= 390.0.0"
       
      - name: Check currently authenticated user
        run: gcloud auth list
 
 Interact with google cloud

      - name: Run gcloud
        run: gcloud storage buckets list

The workflow authenticates with Google Cloud, sets up necessary tools, and performs a listing of storage buckets within the Google Cloud environment. 

Step 6: GitHub Action in Action

Find here how the output of the run looks like:

More Resources

For more information on best practices for using WIF on Google Cloud, you can check this Google resource

-

👋 Hello, I'm Laysa, a developer and cloud engineer, a public speaker, and a knowledge sharer as I go along.

♻️ If you like this article, why not give it a share?

💜 And you can follow me or reach out here | Laysa Uchoa on X | Laysa Uchoa on LinkedIn 

Get in Touch.

Let’s discuss how we can help with your cloud journey. Our experts are standing by to talk about your migration, modernisation, development and skills challenges.

Ilja Summala
Ilja’s passion and tech knowledge help customers transform how they manage infrastructure and develop apps in cloud.
Ilja Summala LinkedIn
Group CTO