Terraform Service Account Configuration for Google Cloud Platform

Introduction

Terraform automation against Google Cloud Platform requires a dedicated identity. Personal Google accounts are sufficient for experimentation but automated deployments require a service account with specific permissions. The configuration of the Google provider to use a service account involves creation of the service account, assignment of roles, activation of APIs, and selection of an authentication method. Reference materials describe a Terraform module that supports granting multiple roles to a service account and creating a private key. The module is meant for use with Terraform 0.13+ and tested using Terraform 1.0+. The module source is [email protected]:serhatteker/gcp-service-account-terraform.git?ref=master. The workflow for adoption is terraform init to get the plugins, terraform plan to see the infrastructure plan, terraform apply to apply the infrastructure build, and terraform destroy to destroy the built infrastructure.

Service Account Fundamentals on GCP

A GCP service account is a special type of Google account that belongs to a project rather than to an individual user. It has an email address like [email protected], a key pair for authentication, and IAM role bindings that determine its permissions. Service accounts are the standard way to authenticate automated workloads including Terraform, CI/CD pipelines, and applications running on GCP.

The service account email format is NAME@PROJECT_ID.iam.gserviceaccount.com. Verification of creation is performed with gcloud iam service-accounts list.

Creation using gcloud CLI is performed after setting the project.

export PROJECT_ID="my-project-123" gcloud config set project $PROJECT_ID gcloud iam service-accounts create terraform \ --display-name="Terraform Service Account" \ --description="Used by Terraform to manage GCP infrastructure"

The description field is used to document purpose. The display name provides a human readable label.

Role Assignment and API Activation

Assigning roles grants the service account the permissions it needs. The reference example shows granting roles/cloudsql.admin with gcloud projects add-iam-policy-binding.

For a general-purpose Terraform service account, the Editor role might be used during initial setup and tightened later.

gcloud projects add-iam-policy-binding $PROJECT_ID \ --member="serviceAccount:terraform@${PROJECT_ID}.iam.gserviceaccount.com" \ --role="roles/editor"

The broad access for initial setup is not recommended for production.

Listing current roles for the service account can be done with:

gcloud projects get-iam-policy $PROJECT_ID \ --flatten="bindings[].members" \ --filter="bindings.members:terraform@${PROJECT_ID}.iam.gserviceaccount.com" \ --format="table(bindings.role)"

API enablement is required before using services. The error message "API not enabled" indicates missing API activation. Common APIs can be enabled with:

gcloud services enable compute.googleapis.com gcloud services enable container.googleapis.com gcloud services enable sqladmin.googleapis.com gcloud services enable storage.googleapis.com

The error "Service account does not exist" indicates a need to double-check the email format. It should be NAME@PROJECT_ID.iam.gserviceaccount.com.

Terraform Module for Service Account Provisioning

The module supports granting multiple roles to the service account and creating a private key. The module is meant for use with Terraform 0.13+ and tested using Terraform 1.0+.

Example for creating a Storage Bucket Admin Service Account:

module "storage_service_account" { source = "[email protected]:serhatteker/gcp-service-account-terraform.git?ref=master" project_id = "some-project-id" account_id = "bucket-admin" description = "Bucket Admin" roles = ["roles/storage.admin"] }

If Google Service API activation is also required, add gcpservicelist:

module "storage_service_account" { source = "[email protected]:serhatteker/gcp-service-account-terraform.git?ref=master" gcp_service_list = ["storage.googleapis.com"] project_id = "some-project-id" account_id = "bucket-admin" description = "Bucket Admin" roles = ["roles/storage.admin"] }

Input parameters for the module include projectid, accountid, description, managed-by-terraform, roles, and gcpservicelist.

project_id
- Description: The related project ID
- Type: string
- Default: -
- Required: yes

account_id
- Description: The service account ID
- Type: string
- Default: -
- Required: yes

description
- Description: The description for the service account
- Type: string
- Default: -
- Required: not specified in the table excerpt

managed-by-terraform
- Description: no
- Type: not specified
- Default: not specified
- Required: not specified

roles
- Description: The roles that will be granted
- Type: list
- Default: []
- Required: no

gcpservicelist
- Description: The necessary GCP services
- Type: list
- Default: []
- Required: no

Outputs from the module include email, name, accountid, privatekey, and decodedprivatekey.

email
- Description: The e-mail address of the service account

name
- Description: The fully-qualified name of the service account

account_id
- Description: The unique id of the service account

private_key
- Description: The private key that was created for the account (sensitive)

decodedprivatekey
- Description: The base64 decoded private key (sensitive)

Authentication Methods for Terraform Google Provider

There are several ways to authenticate Terraform with the service account.

Method 1 - Service Account Key File

Generate a JSON key and point Terraform at it.

gcloud iam service-accounts keys create terraform-key.json \ --iam-account="terraform@${PROJECT_ID}.iam.gserviceaccount.com"

The file contains private key material - handle it securely.

chmod 600 terraform-key.json

Provider configuration with credentials file:

provider "google" { project = var.project_id region = "us-central1" credentials = file("terraform-key.json") }

Or better, use the GOOGLE_CREDENTIALS environment variable:

export GOOGLE_CREDENTIALS=$(cat terraform-key.json)

Or point to the file path:

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/terraform-key.json"

Provider configuration with no credentials in code:

```
provider "google" {
project = var.project_id
region = "us-central1"

Reads from GOOGLECREDENTIALS or GOOGLEAPPLICATION_CREDENTIALS

}
```

Important security note: Service account keys are long-lived credentials.

Workload Identity Federation and CI/CD Integration

Service accounts are the foundation of secure Terraform automation on GCP. Create a dedicated service account with the minimum required roles, prefer keyless authentication through Workload Identity Federation or impersonation, and fall back to key files only when necessary. Whichever method is chosen, keep credentials out of Terraform code and use environment variables or the provider's built-in credential chain.

GitHub Actions with Workload Identity Federation example:

Terraform on: push: branches: [main] permissions: contents: read id-token: write # Required for Workload Identity Federation jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - id: auth uses: google-github-actions/auth@v3 with: workload_identity_provider: "projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/ci-pool/providers/github" service_account: "[email protected]" - uses: hashicorp/setup-terraform@v3 - name: Terraform Init run: terraform init - name: Terraform Plan run: terraform plan -out=tfplan - name: Terraform Apply if: github.ref == 'refs/heads/main' run: terraform apply tfplan

GitHub Actions with Key File:

jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - name: Setup GCP Credentials run: echo '${{ secrets.GCP_CREDENTIALS }}' > /tmp/gcp-key.json - name: Terraform Init env: GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-key.json run: terraform init - name: Terraform Apply env: GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-key.json run: terraform apply -auto-approve

Remote state with service account:

Store Terraform state in a GCS bucket, authenticated with the same service account.

gsutil mb -p $PROJECT_ID -l us-central1 gs://my-terraform-state-bucket gsutil versioning set on gs://my-terraform-state-bucket

The service account needs storage access (already granted if it has storage.admin).

terraform { backend "gcs"

Service Account Design for Cloud Functions and Multi-Project Systems

Some logic is built using Cloud Functions and Firebase. Terraform is used for managing infrastructure. To improve security, dedicated service accounts are created for specific functions so that they only have access to the resources they need.

Example with three cloud functions:

payment processor
- needs to publish to PubSub
- can publish logs to the log explorer
- has access to the Cloud Datastore (Firestore)
- has access to a specific Cloud storage bucket to upload legal documents
- has access to api_key secret

user creation flow
- has access to Firebase admin
- has access to the same Cloud Datastore (Firestore) database as the payment processor
- has access to userpasswordsalt secret

analytics
- for production needs to run BigQuery jobs but against a dataset in a different project
- for the test project, it uses the dataset from the same project

Three GCP projects exist: production, test, and data. The Cloud Functions run only in production and test.

Terraform allows defining reusable modules so that certain things can be abstracted away. Two main directories are modules and projects.

Summary and Security Guidance

Service accounts are the foundation of secure Terraform automation on GCP. Create a dedicated service account with the minimum required roles, prefer keyless authentication through Workload Identity Federation or impersonation, and fall back to key files only when necessary. Whichever method is chosen, keep credentials out of Terraform code and use environment variables or the provider's built-in credential chain.

The module described supports granting multiple roles and creating a private key. Input variables control projectid, accountid, description, roles, and gcpservicelist. Outputs provide email, name, accountid, privatekey, and decodedprivatekey. Authentication can be via key file with provider credentials or environment variables, or via Workload Identity Federation in CI/CD pipelines.

Conclusion

The reference materials demonstrate that Terraform service account management on GCP centers on identity creation, least privilege role assignment, API activation, and secure authentication. The SerhatTeker module provides a reusable pattern for creating service accounts with multiple roles and optional API enablement via gcpservicelist. The input parameters projectid, accountid, description, roles, and gcpservicelist map directly to GCP resource properties. The outputs email, name, accountid, privatekey, and decodedprivatekey expose the created identity for downstream use.

Authentication guidance emphasizes moving away from long-lived keys toward Workload Identity Federation and impersonation, with environment variables preferred over hard-coded credentials. CI/CD examples show both key file usage with GOOGLEAPPLICATIONCREDENTIALS and token-based auth with workloadidentityprovider. Error handling covers API not enabled and service account does not exist conditions. Multi-project, function-specific service account design illustrates how Terraform modules can enforce the principle of least privilege across production, test, and data projects.

Sources

  1. SerhatTeker/gcp-service-account-terraform
  2. How to configure gcp provider with service account
  3. How to define a service account for gcp cloud functions using terraform

Related Posts