Terraform Service Account Creation and Role Assignment on Google Cloud Platform

Infrastructure as Code for Google Cloud Platform service accounts requires precise definition of identity resources and permission bindings. Terraform provides declarative constructs for creating service accounts, specifying roles, and binding those roles at project scope. The reference material documents patterns for defining a service account with a unique accountid and displayname, defining roles via variables, and applying those roles with googleprojectiammember using foreach. It also documents Infra Manager service account configuration patterns and service account design for Cloud Functions with least-privilege access.

The approach centers on googleserviceaccount and googleprojectiam_member. The service account resource creates the identity. The IAM member resource binds roles to the serviceAccount: email member. Iteration over a roles list enables multiple role grants from a single block. Propagation delays and error message interpretation are noted as operational considerations. Alternatives such as Workload Identity Federation are mentioned for external workloads.

The Infra Manager configuration introduces service agents with email patterns service-INFRAMANAGERPROJECT[email protected] and service-INFRAMANAGERPROJECT[email protected]. Granting roles/iam.serviceAccountTokenCreator to Cloud Build service agent and roles/config.agent to the deployment service account are required steps for cross-project Terraform execution via Cloud Build. User access requires roles/iam.serviceAccountUser on the service account.

Cloud Functions use case demonstrates dedicated service accounts per function with tightly scoped roles. Payment processor requires PubSub publishing, log explorer publishing, Cloud Datastore Firestore access, specific Cloud Storage bucket access, and access to apikey secret. User creation flow requires Firebase admin access, same Firestore access, and userpassword_salt secret access. Analytics requires BigQuery job execution against a dataset in a different project for production and same project for test. Projects involved are production, test, and data. Terraform modules abstract reusable patterns across projects and environments.

Service Account Definition with Terraform

The core resource for creating an identity is googleserviceaccount.

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

The accountid must be replaced with a unique identifier for the service account. The identifier becomes part of the service account email and must be unique within the project. The displayname is a descriptive name used for human readability in the GCP console and audit logs.

Impact layer: Choosing a meaningful accountid reduces operational confusion during troubleshooting and security audits. The displayname provides context for auditors without exposing internal naming conventions.

Contextual layer: This definition feeds directly into IAM bindings. The resource outputs an email attribute that is referenced as serviceAccount:${googleserviceaccount.default.email} in member bindings.

Variable based role definition improves organization.

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

Defining roles as a variable decouples policy from resource configuration. Default values allow quick prototyping. Variable usage enables reuse across environments.

Impact layer: Centralized role lists simplify compliance reviews and allow changes to be audited in version control rather than console edits.

Contextual layer: The variable is consumed by the for_each construct in the IAM member resource, creating a one-to-one mapping between role strings and IAM binding instances.

Role Assignment Using googleprojectiam_member

Role binding at project scope uses googleprojectiam_member.

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

The foreach iterator creates multiple instances of googleprojectiammember, one per role in the set. each.value supplies the role name. The member field uses the service account email prefix.

Explanation of components:

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

Impact layer: Granting each role individually enables fine-grained least privilege. The iteration pattern prevents duplication and makes adding or removing roles a single-line change in the variable list.

Contextual layer: The binding is project scoped. If cross-project access is required, such as analytics accessing a dataset in the data project, a separate binding must be created in the target project or using googleprojectiammember with projectid set to the target project.

Operational Considerations and Troubleshooting

IAM Propagation Delays are a real-world constraint. 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.

Impact layer: Immediate test failures after apply can lead to unnecessary rollback. Understanding propagation avoids false negatives during CI/CD pipelines.

Contextual layer: Propagation interacts with Cloud Functions deployment timing. A function may start before its service account permissions are fully effective, causing cold start errors.

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

Impact layer: Specific errors such as Error 403 Permission denied point to missing roles on the Terraform execution identity. Errors about invalid member format point to incorrect service account email construction.

Contextual layer: The Terraform Google provider requires the execution identity to have resourcemanager.projects.getIamPolicy and resourcemanager.projects.setIamPolicy for setting project-level policies. For Secret Manager access, secretmanager.secrets.getIamPolicy and secretmanager.secrets.setIamPolicy are required to use googlesecretmanagersecretiam_member. Other permissions that end with *.setIamPolicy may be required for resource-level policies.

Alternative Access Patterns

Workload Identity Federation is presented as an alternative for applications running outside of GCP. Workload Identity Federation allows granting temporary credentials to access GCP resources without needing a service account key.

Impact layer: Eliminating long-lived keys reduces secret management burden and improves security posture for external systems.

Contextual layer: When Terraform manages service accounts for Cloud Functions, Workload Identity Federation is not applicable internally because the functions run on GCP and use the attached service account directly.

Terraform Google Provider Documentation is referenced for up-to-date information on resources and their usage: https://registry.terraform.io/providers/hashicorp/google/latest/docs

Impact layer: Provider version changes can alter resource argument names and behavior. Referencing official documentation prevents drift.

Feature Summary Table

Feature description and Terraform mapping.

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 variable "roles" { type = list(string) default = [ "roles/storage.objectViewer", "roles/pubsub.publisher", ] }

The table summarizes the key aspects of creating GCP service accounts and assigning them roles using Terraform.

Infrastructure Manager Service Account Configuration

Infra Manager executes Terraform using the identity of a service account. You do not need a service account to view information about Infra Manager deployments.

Before you begin:

  • Enable the Infra Manager service.
  • Create a service account or identify an existing service account to use with Infra Manager.

Grant access to the service account.

To use Infrastructure Manager to create, update, or delete a deployment, an individual user needs access to the service account. Grant the user the Service Account User (roles/iam.serviceAccountUser) IAM role for the service account.

Grant permissions for Infra Manager.

To work with Infra Manager, the service account needs the Infra Manager Agent (roles/config.agent) role. This grants permissions for Infra Manager to create, update, and delete resources:

bash gcloud projects add-iam-policy-binding INFRA_MANAGER_PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \ --role="roles/config.agent"

Replace:

INFRAMANAGERPROJECTID: The project ID of the project where you're creating deployments.
SERVICE
ACCOUNT_EMAIL: The email of the service account.

The service agent email pattern forms as service-INFRAMANAGER[email protected]

Grant the Service Account Token Creator (roles/iam.serviceAccountTokenCreator) role to the Cloud Build service agent in the project where you're creating deployments. To allow Infra Manager to execute Terraform using Cloud Build, the Cloud Build service agent in the project containing the service account needs additional permissions as part of the cross-project set up:

bash gcloud projects add-iam-policy-binding SERVICE_ACCOUNT_PROJECT_ID \ --member="serviceAccount:service-INFRA_MANAGER_PROJECT_NUMBER@gcp-sa-cloudbuild.iam.gserviceaccount.com" \ --role="roles/iam.serviceAccountTokenCreator"

Impact layer: Correct agent role assignment determines whether Infra Manager can actually apply Terraform plans. Missing roles/config.agent results in permission denied during deployment.

Contextual layer: The cross-project setup separates the project containing the service account from the project where deployments are created. Token creator role enables Cloud Build to impersonate the service account for Terraform execution.

Grant read permission for the storage bucket.

You can use a storage bucket to store the Terraform configurations that are deployed by Infra Manager. If you use a storage bucket...

Impact layer: Storage access controls prevent configuration leakage and ensure Infra Manager can fetch source.

Contextual layer: Bucket IAM must be aligned with the service account used by Infra Manager, mirroring the principle of least privilege applied to service account roles in general Terraform patterns.

Cloud Functions Service Account Design Pattern

The article shares a solution to define a service account for Cloud Functions using Terraform. Prerequisites for this to work include using the hashicorp/google Terraform provider. Also, the service account that Terraform uses needs to have appropriate permissions: permissions to create resources – that depends on what exactly is in the Terraform code; resourcemanager.projects.getIamPolicy and resourcemanager.projects.setIamPolicy for setting project-level policies; secretmanager.secrets.getIamPolicy and secretmanager.secrets.setIamPolicy to use googlesecretmanagersecretiam_member; potentially other permissions that end with *.setIamPolicy for resource-level policies.

Context: The system is based on Google Cloud Platform. Some of the logic is built using Cloud Functions and Firebase. Terraform is used for managing infrastructure. To improve security, dedicated service accounts for specific functions are created so they only have access to resources they need.

The assumed system has 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.

Projects involved are production, test, and data. The Cloud Functions run only in production and test.

Terraform allows you to define reusable modules so that certain things can be abstracted away. There are two main directories: modules and projects.

Impact layer: Dedicated service accounts per function enforce least privilege and limit blast radius if a function is compromised.

Contextual layer: The analytics function demonstrates cross-project access requirements, which require IAM bindings in the data project for the production service account, and bindings in the same project for test. This mirrors the general pattern of googleprojectiammember with projectid targeting.

Conclusion

Terraform service account management on GCP combines identity creation, role definition, and IAM binding into a declarative workflow. The googleserviceaccount resource establishes the identity with a stable accountid and human readable displayname. The googleprojectiammember resource with foreach iteration binds roles to the service account email. Variables centralize role lists for maintainability and auditability.

Operational realities include IAM propagation delays that require waiting periods before validation, and error messages that guide remediation of permission gaps on the Terraform execution identity. The execution identity must hold resourcemanager.projects.getIamPolicy and setIamPolicy, secretmanager.secrets.getIamPolicy and setIamPolicy, and other *.setIamPolicy permissions as required by the resources being managed.

Infra Manager introduces service agents with predictable email patterns and requires roles/config.agent on the deployment service account and roles/iam.serviceAccountTokenCreator on the Cloud Build service agent for cross-project execution. User access is granted via roles/iam.serviceAccountUser.

Cloud Functions workloads benefit from per-function service accounts with tightly scoped roles for PubSub, Firestore, Cloud Storage, Secret Manager, Firebase Admin, and BigQuery. Cross-project dataset access demands IAM bindings in the target project, illustrating how project-scoped bindings compose across environments.

The pattern of defining reusable modules under modules and projects directories supports consistent service account and role definitions across production, test, and data projects while preserving least privilege principles.

Sources

  1. nulldog.com
  2. Google Cloud Documentation
  3. softwarephilosopher.com

Related Posts