Terraform Automation Foundations with Dedicated GCP Service Accounts

Terraform automation against Google Cloud in production requires a service account. A personal Google account works fine for experimentation, but automated deployments need a dedicated identity with specific permissions. This guide covers creating a service account, assigning the right roles, and configuring the Terraform Google provider to use it.

The material expands the practical steps for creating a service account with gcloud CLI, binding IAM roles, authenticating the provider with key files or environment variables, and defining the same lifecycle declaratively with Terraform resources. It also covers the design pattern of per-function service accounts for Cloud Functions workloads across production, test, and data projects, and the module structure that makes those patterns reusable.

What Is a GCP Service Account

A GCP service account is a special type of Google account that belongs to your project rather than to an individual user.

It has:

  • An email address (like [email protected])
  • A key pair for authentication
  • 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 project-owned nature of the identity means permissions are scoped to the project and can be audited independently of human users. The email address provides a stable identifier for IAM bindings. The key pair enables programmatic authentication when keyless methods are not available. The IAM role bindings translate the identity into actionable permissions.

Creating a Service Account with gcloud CLI

Using gcloud CLI provides an imperative path to create the identity before Terraform manages it.

bash export PROJECT_ID="my-project-123" gcloud config set project $PROJECT_ID

bash gcloud iam service-accounts create terraform \ --display-name="Terraform Service Account" \ --description="Used by Terraform to manage GCP infrastructure"

bash gcloud iam service-accounts list

The display name is human readable and the description documents the intended use. Verification with list confirms creation and surfaces the generated email address in the format NAME@PROJECT_ID.iam.gserviceaccount.com.

Assigning Roles and Permissions

Grant the service account the permissions it needs.

A common initial binding for broad access is:

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

For a general-purpose Terraform service account, you might use the Editor role during initial setup and tighten it later. Broad access for initial setup is not recommended for production.

A more targeted role example is:

bash --role="roles/cloudsql.admin"

Check the error message for the specific permission needed:

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

Error conditions to handle:

  • "API not enabled"

    GCP requires you to enable APIs before using them:

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

  • "Service account does not exist"

    Double-check the email format. It should be NAME@PROJECT_ID.iam.gserviceaccount.com.

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:

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

bash chmod 600 terraform-key.json

The file contains private key material - handle it securely.

Provider configuration with inline credentials:

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

Or better, use the GOOGLE_CREDENTIALS environment variable:

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

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

Provider configuration without credentials in code:

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

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 you choose, keep credentials out of your Terraform code and use environment variables or the provider's built-in credential chain.

Creating Service Accounts and Roles with Terraform

To create a GCP service account with roles using Terraform, you'll generally follow these steps:

Define the service account:

bash resource "google_service_account" "default" { account_id = "your-service-account-id" display_name = "Your Service Account Name" }

Replace "your-service-account-id" with a unique identifier for your service account.

Replace "Your Service Account Name" with a descriptive name.

The example code includes explanations and important considerations for managing service accounts and their permissions effectively.

Role Definition and Variable Organization

You can either list roles directly or use variables for better organization:

bash variable "roles" { type = list(string) default = [ "roles/storage.objectViewer", "roles/pubsub.publisher", ] }

Variable organization allows the same service account definition to be reused with different permission sets per environment. The list type makes it easy to add or remove roles without editing resource blocks.

Binding Roles with googleprojectiam_member

Assign roles using googleprojectiam_member:

bash resource "google_project_iam_member" "service_account_roles" { for_each = toset(var.roles) role = each.value member = "serviceAccount:${google_service_account.default.email}" }

This iterates through the roles list and grants each role to the service account.

Explanation:

  • googleserviceaccount: This resource creates the service account itself.
  • googleprojectiam_member: This resource binds roles to members at the project level.
  • for_each: This Terraform construct allows you to create multiple instances of a resource based on a collection.

The guide explains how to create a Google Cloud Platform service account and assign it roles using Terraform. We'll cover defining the service account, specifying roles, and using the googleprojectiam_member resource to grant the roles.

A summary table of key aspects:

Feature | Description | Terraform Resource | Example
Service Account Creation | Defines the service account with a unique ID and display name. | googleserviceaccount | resource "googleserviceaccount" "default" { accountid = "your-service-account-id" displayname = "Your Service Account Name" }
Role Definition | Specifies the roles to be assigned, either directly or using variables for better organization |

Troubleshooting and Propagation Delays

IAM Propagation Delays: Be aware that IAM changes might take a few minutes to propagate fully. If you encounter permission issues immediately after creating or modifying a service account, wait a short period and try again.

Error Messages: Pay close attention to Terraform error messages, as they often provide valuable clues for resolving issues related to service account creation or role assignment.

Troubleshooting helps with troubleshooting and security audits.

Alternatives and Security Considerations

Alternatives:

  • Workload Identity Federation: For applications running outside of GCP, consider using Workload Identity Federation to grant them temporary credentials to access GCP resources without needing a service account key.
  • Terraform Google Provider Documentation: Refer to the official Terraform Google provider documentation for the most up-to-date information on resources and their usage: https://registry.terraform.io/providers/hashicorp/google/latest/docs

The example code includes explanations and important considerations for managing service accounts and their permissions effectively.

Multi-Project Service Account Patterns with Cloud Functions

Some of the logic is built using Cloud Functions and Firebase. We use Terraform for managing our infrastructure. To improve the security of the system, we wanted to create dedicated service accounts for specific functions so that they only have access to the resources they need.

For the sake of this article let's assume we have 3 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.

We also have 3 GCP projects: production, test, and data. The Cloud Functions run only in production and test.

Here’s a visual representation of the system described above:

Dedicated per-function service accounts enforce least privilege. The payment processor service account requires PubSub publisher permission, log explorer write permission, Firestore access, a specific Cloud Storage bucket access for legal documents, and access to the apikey secret. The user creation flow service account requires Firebase Admin access, shared Firestore access, and access to the userpassword_salt secret. The analytics service account requires BigQuery job execution permission, with cross-project dataset access in production and same-project dataset access in test.

Module Organization for Terraform

Terraform allows you to define reusable modules so that we can abstract away certain things. We have two main directories: modules and projects.

Module abstraction enables the service account definitions, role bindings, and Cloud Functions resources to be parameterized by project name, environment, and function-specific permissions. The projects directory instantiates those modules for production, test, and data.

Conclusion

Service account creation with gcloud CLI establishes the identity with a project-scoped email, display name, and description, then verifies existence. Role assignment follows via IAM policy bindings, with targeted roles preferred over broad Editor access. Authentication for the Terraform Google provider can use a JSON key file referenced directly or via GOOGLECREDENTIALS and GOOGLEAPPLICATION_CREDENTIALS environment variables, with the security note that service account keys are long-lived credentials.

Declarative management with Terraform replaces imperative steps with googleserviceaccount for identity creation, a variable-driven roles list for organization, and googleprojectiammember with foreach for binding. Propagation delays and error messages remain operational concerns during IAM changes. Workload Identity Federation offers a keyless alternative for external workloads.

The per-function service account pattern for Cloud Functions, with separate identities for payment processor, user creation flow, and analytics across production, test, and data projects, demonstrates how Terraform modules and projects directories support least privilege and reuse. The combination of dedicated service accounts, minimum required roles, keyless preference, and environment-based credential handling forms the core of secure Terraform automation on GCP.

Sources

  1. How to configure gcp provider with service account
  2. Terraform GCP service account creation with roles
  3. How to define a service account for gcp cloud functions using terraform

Related Posts