🔐 Google Cloud Authentication with Workload Identity Federation for GitHub Actions
Tags: terraform, githubactions, gcp, googlecloud
Published: 2023-12-21 21:04:57

##Introduction

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 is not 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:

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

- 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).

```terraform
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.

```terraform
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.

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 5: Full Terraform code
To sum up, this is the full terraform code we needed for the setup.

```terraform
# 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 organization, 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 6: 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. 

```yaml
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 7: GitHub Action in Action

Find here how the output of the run looks like:

![GitHub Action running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mit5azygi9h47ta0fixm.png)

## More Ressources

For more information on best practices for using WIF on Google Cloud: https://cloud.google.com/iam/docs/best-practices-for-using-workload-identity-federation

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

♻️ Share this article, if you like the content.

💜 [Find me everywhere](https://linktr.ee/laysauchoa) | [X](https://twitter.com/laysauchoa) | [LinkedIn](https://www.linkedin.com/in/laysauchoa/)