Tired of managing cumbersome API keys for your Terraform Cloud deployments to GCP? There’s a better, more secure way.
In my previous lab post, I showed how to provision a Google Cloud Storage (GCS) bucket using Terraform Cloud (TFC) — but with long-lived service account keys for authentication.
That method works… but it comes with real security baggage:
Static credentials that must be rotated manually
Higher risk if keys are leaked
Clumsy management overhead in CI/CD pipelines
This post is all about solving that problem — by ditching API keys and upgrading to Workload Identity Federation (WIF).
Why Workload Identity Federation? 🔐
Workload Identity Federation allows external identities (like Terraform Cloud, GitHub Actions, etc.) to authenticate to GCP without needing service account keys. Instead of uploading secrets, your CI/CD platform presents an OIDC identity that GCP trusts — and issues short-lived tokens in return.
Key Benefits of WIF Over API Keys:
✅ Better security: No long-lived secrets, short-lived tokens, automatic rotation
✅ Reduced credential sprawl: No need to manage and distribute service account keys
✅ Granular access control: Attribute-based identity mapping lets you apply least privilege per workspace
✅ Improved auditability: Authentication is tied to identity pools and issuers
Step-by-Step: Provision GCS Bucket with WIF
Let’s walk through exactly how to create a GCS bucket from Terraform Cloud using Workload Identity Federation.
🔧 Step 1: Enable Required GCP APIs
gcloud services enable \
iam.googleapis.com \
iamcredentials.googleapis.com \
cloudresourcemanager.googleapis.com☁️ Step 2: Create a Workload Identity Pool
gcloud iam workload-identity-pools create tfc-pool \
--location="global" \
--display-name="Terraform Cloud Pool"This creates a container for trusted external identities — like Terraform Cloud workspaces.
🪪 Step 3: Add an OIDC Provider for Terraform Cloud
gcloud iam workload-identity-pools providers create-oidc tfc-provider \
--location="global" \
--workload-identity-pool="tfc-pool" \
--display-name="Terraform Cloud Provider" \
--issuer-uri="https://app.terraform.io" \
--attribute-mapping="google.subject=assertion.sub" \
--attribute-condition="assertion.sub.startsWith("organization:<ORG-NAME>:project:<PROJECT_NAME>:workspace:<WORKSPACE_NAME>")"👤 Step 4: Create a Service Account
gcloud iam service-accounts create tfc-deployer \
--display-name="Terraform Cloud Deployer"This account will be impersonated by Terraform Cloud via WIF.
👥 Step 5: Allow Pool to Impersonate Service Account
gcloud iam service-accounts add-iam-policy-binding tfc-deployer@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--role=roles/iam.workloadIdentityUser \
--member="principal://iam.googleapis.com/projects/$PROJECT_NUM/locations/global/workloadIdentityPools/$POOL_ID/subject/organization:$ORG:project:$TFC_PROJECT:workspace:$WORKSPACE:run_phase:plan"gcloud iam service-accounts add-iam-policy-binding tfc-deployer@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--role=roles/iam.serviceAccountTokenCreator \
--member="principal://iam.googleapis.com/projects/$PROJECT_NUM/locations/global/workloadIdentityPools/$POOL_ID/subject/organization:$ORG:project:$TFC_PROJECT:workspace:$WORKSPACE:run_phase:apply"Replace the following and allow some time for permissions to sync.
YOUR_PROJECT_ID
PROJECT_NUM
POOL_ID
ORG
TFC_PROJECT
WORKSPACE
🗂️ Step 6: Grant Storage Admin role to Service Account
gcloud projects add-iam-iam-policy-binding PROJECT_ID --member="serviceAccount:tfc-deployer@PROJECT_ID.iam.gserviceaccount.com" --role="roles/storage.admin"This lets Terraform Cloud create buckets. For production use, consider more granular roles like storage.objectCreator.
⚙️ Step 7: Configure Terraform Cloud Workspace Variables
In your Terraform Cloud workspace, add the following environment variables:
Name | Value | Sensitive |
|---|---|---|
| Your GCP project ID | No |
|
| No |
| No | |
|
| No |
|
| No |
Marking sensitive variables is optional here as none of these contain secrets — but it’s good hygiene.
📦 Step 8: Terraform HCL for GCS Bucket
Here’s a minimal main.tf to test the setup:
terraform {
required_version = ">= 1.5.0"
required_providers {
google = {
source = "hashicorp/google"
version = "6.50.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
resource "google_storage_bucket" "example" {
name = "my-wif-secured-bucket-${random_id.bucket_suffix.hex}"
location = var.region
uniform_bucket_level_access = true
labels = {
environment = "test"
}
}
resource "random_id" "bucket_suffix" {
byte_length = 4
}
variable "project_id" {
description = "The GCP project ID where resources will be created"
type = string
}
variable "region" {
description = "The GCP region to deploy resources into"
type = string
}
✅ Testing & Verification
Push your Terraform code to GitHub (or your VCS).
Trigger a plan or apply in Terraform Cloud.
Confirm:
No API key or static credentials required
Authentication succeeds in run logs
GCS bucket appears in your GCP console
🧯 Troubleshooting & Best Practices
Double-check IAM roles and identity pool bindings
Ensure your
terraform_workspace_idin attribute mapping matches your TFC workspaceAlways prefer least privilege — avoid giving
storage.adminif your use case only needs object creationUse separate pools/providers for different environments (dev, staging, prod)
🔚 Conclusion: Make WIF Your Default
Workload Identity Federation represents the next step in secure, scalable Terraform-GCP workflows.
No more API key sprawl
Fine-grained access controls
Seamless integration with Terraform Cloud
If you're still using service account keys, now's the time to switch. WIF is what modern CI/CD pipelines need — especially when managing cloud infrastructure at scale.
📬 Want More Like This?
👉 Subscribe to DevOps Lab Notes on Beehiiv to stay in the loop.
#DevOps #TerraformCloud #GoogleCloud #WorkloadIdentityFederation #IaC #CloudSecurity #Terraform #GCP #CI/CD #SecurityBestPractices
