← Back to list

Creating a CI/CD pipeline with Workload Identity Federation in GCP and Github Actions

j.xcvii · 2025-07-21 06:01 · 2 claps · 4.0 min read
#ci-cd-pipeline #google-cloud-platform #github #workload-identity #docker
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud 🔓 · Open Source

Creating a CI/CD pipeline to Cloud Run with Workload Identity Federation in GCP and Github Actions

This is the preferred method of handling authentication between Google Cloud and Github Actions. In the following I will create two examples, one deploying from source using Cloud Build and the secondary one is creating the image bypassing Cloud Build.

Instead of downloading long lived service key credentials which have to be downloaded and can pose security threats. Workload Identity Federations allows you to “use Identity and Access Management (IAM) to grant to external identities IAM roles, direct access on Google Cloud resources. You can also grant access through service account impersonation.” [1]

Something to note: “Workload Identity Federation follows the OAuth 2.0 token exchange specification.”

You can view more information on the following steps we are about to take here [2]

First your principal that will create the workload identity pool will need the following : roles/iam.workloadIdentityPoolAdmin

You need to also enable the following APIs in your project:

  • Identity and Access Management (IAM) API
  • Cloud Resource Manager API
  • IAM Service Account Credentials API
  • Security Token Service API

Following the Github Actions readme [3] we will create an identity pool for Github:

// To create the idenity pool: 

PROJECT_ID=YOUR_PROJECT_ID

gcloud iam workload-identity-pools create "github" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --display-name="GitHub Actions Pool"
// To verify if the idenity pool was created: 

gcloud iam workload-identity-pools describe "github" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --format="value(name)"

The the third step is to create a Workload Identity Provider in the pool we just made:

The one in the documentation sets the restriction of the attribute-condition to the Github Organization but we will set it to the repository owner for people who don’t have Github Orgs set up:

// Create a Workload Identity Provider

REPO_OWNER=YOUR_GH_USERNAME

gcloud iam workload-identity-pools providers create-oidc "my-repo" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="github" \
  --display-name="My GitHub repo Provider" \
  --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
  --attribute-condition="assertion.repository_owner == '${REPO_OWNER}'" \
  --issuer-uri="https://token.actions.githubusercontent.com"

Extract the Workload Identity Provider resource name:

gcloud iam workload-identity-pools providers describe "my-repo" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="github" \
  --format="value(name)"

Grab the output, it should be something like this:

"projects/123456789/locations/global/workloadIdentityPools/github/providers/my-repo"

Now let’s go to Github Actions on the repository that you would like create the pipeline in. In this one we will set one up that allows us to build from source and deploy to Cloud Run:

If the Workload Identity Pool is missing permission you can add them like so:

WORKLOAD_IDENTITY_POOL_ID=OUTPUT_FROM_PREVIOUS_STEP
REPO=USERNAME_OR_GH_ORG/REPO_NAME

gcloud projects add-iam-policy-binding \
  --project="${PROJECT_ID}" \
  --role="roles/run.sourceDeveloper" \
  --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}"

Here is the Github Workflow yaml file:

# Change the values in the "env" block to match your values.

name: 'Deploy to Cloud Run from Source'

on:
  push:
    branches:
      - "main"

env:
  PROJECT_ID: '' # TODO: update to your Google Cloud project ID
  REGION: '' # TODO: update to your region
  SERVICE: '' # TODO: update to your service name
  WORKLOAD_IDENTITY_PROVIDER: ''


jobs:
  deploy:
    runs-on: 'ubuntu-latest'

    permissions:
      contents: 'read'
      id-token: 'write'

    steps:
      - name: 'Checkout'
        uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4

      # Configure Workload Identity Federation and generate an access token.
      #
      # See https://github.com/google-github-actions/auth for more options,
      # including authenticating via a JSON credentials file.
      - id: 'auth'
        name: 'Authenticate to Google Cloud'
        uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2
        with:
          project_id: '${{env.PROJECT_ID}}'
          workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' # TODO: replace with your workload identity provider

      - name: 'Deploy to Cloud Run'
        uses: 'google-github-actions/deploy-cloudrun@33553064113a37d688aa6937bacbdc481580be17' # google-github-actions/deploy-cloudrun@v2
        with:
          service: '${{ env.SERVICE }}'
          region: '${{ env.REGION }}'
          # NOTE: If using a different source folder, update the source dir below:
          source: './'

      # If required, use the Cloud Run URL output in later steps
      - name: 'Show output'
        run: |-
          echo ${{ steps.deploy.outputs.url }}

As you can see no service account key credentials were needed instead we pass in the project_id and workload_identity_provider.

For the second example all steps from the first one are the same the only difference is the yaml since we need to to create a docker image on the GitHub side and push to A.R. As well as use the impersonated service account:

# Change the values in the "env" block to match your values.

name: 'Deploy to Cloud Run through managed images'

on:
  push:
    branches:
      - "main"

env:
  REGION: '' # TODO: update to your region
  SERVICE: '' # TODO: update to your service name
  REPO: ''
  SERVICE_ACCOUNT: ''
  WORKLOAD_IDENTITY_PROVIDER: ''

jobs:
  deploy:
    runs-on: 'ubuntu-latest'

    permissions:
      contents: 'read'
      id-token: 'write'

    steps:
      - name: 'Checkout'
        uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4

      # Configure Workload Identity Federation and generate an access token.
      #
      # See https://github.com/google-github-actions/auth for more options,
      # including authenticating via a JSON credentials file.
      - id: 'auth'
        name: 'Authenticate to Google Cloud'
        uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2
        with:
          service_account: '${{env.SERVICE_ACCOUNT}}'
          workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' # TODO: replace with your workload identity provider

      - name: build Docker Image
        run: docker build -t case-management-backend:latest .

      - name: Configure Docker Client of Gcloud
        run:  |-
          gcloud auth configure-docker --quiet
          gcloud auth configure-docker us-central1-docker.pkg.dev --quiet

      - name: 'Build and Push Container'
        run: |-
          DOCKER_TAG="${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPO }}/${{env.SERVICE}}"
          docker build --tag "${DOCKER_TAG}" .
          docker push "${DOCKER_TAG}"
      - name: 'Deploy to Cloud Run'

        # END - Docker auth and build

        uses: 'google-github-actions/deploy-cloudrun@33553064113a37d688aa6937bacbdc481580be17' # google-github-actions/deploy-cloudrun@v2
        with:
          service: '${{ env.SERVICE }}'
          region: '${{ env.REGION }}'
          # NOTE: If using a pre-built image, update the image name below:

          image: '${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPO }}/${{ env.SERVICE }}'

      # If required, use the Cloud Run URL output in later steps
      - name: 'Show output'
        run: |-
          echo ${{ steps.deploy.outputs.url }}

It is just that easy, to verify you are impersonating a service account you can give a permission to your impersonated service account that your workload identity providers principal does not have.

WORKLOAD_IDENTITY_POOL_ID=OUTPUT_FROM_PREVIOUS_STEP
REPO=USERNAME_OR_GH_ORG/REPO_NAME

gcloud projects remove-iam-policy-binding \
  --project="${PROJECT_ID}" \
  --role="roles/run.sourceDeveloper" \
  --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}" 

// AND || OR 

gcloud projects remove-iam-policy-binding \
  --project="${PROJECT_ID}" \
  --role="roles/artifactregistry.writer" \
  --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}"

If you’re not impersonating a service account and your identity pool principal does not have appropriate permissions to push to A.R or deploy to Cloud Run then your pipeline should fail.

[1] https://cloud.google.com/iam/docs/workload-identity-federation?_ga=2.114275588.-285296507.1634918453#why

[2] https://cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines

[3] https://github.com/google-github-actions/auth/blob/main/README.md


메타데이터
post_id
222eaf01a671
slug
creating-a-ci-cd-pipeline-with-workload-identity-federation-in-gcp-and-github-actions-222eaf01a671
url
https://medium.com/@jayarch/creating-a-ci-cd-pipeline-with-workload-identity-federation-in-gcp-and-github-actions-222eaf01a671
canonical_url
https://medium.com/@jayarch/creating-a-ci-cd-pipeline-with-workload-identity-federation-in-gcp-and-github-actions-222eaf01a671
author_url
https://medium.com/@jayarch
status
ok
fetched_at
2026-07-18 19:47:45