How to Grant Specific IAM Roles to a Google Service Account Using Terraform
The most effective way to grant specific IAM roles to a google_service_account is to use the google_service_account_iam_member resource, which adds granular, non-destructive role bindings without overwriting existing permissions.
When managing infrastructure as code in Google Cloud, you need a reliable method to assign precise permissions to service accounts. In the hashicorp/terraform repository, the Google provider implements IAM resources that treat permissions as declarative bindings between principals and roles. This approach allows you to grant specific IAM roles to a Google service account using dedicated resources that integrate with Terraform's core dependency graph and state management systems.
Understanding Terraform's IAM Resource Model
Terraform models IAM permissions as resources that describe the desired relationship between a principal (the service account) and a Google Cloud role. The Google provider offers several resource types to manage these bindings, each serving different use cases:
google_service_account– Creates the service account identity itself.google_service_account_iam_member– Adds a single role binding for a specific service account. This is the preferred method for most scenarios because it is granular and non-destructive.google_service_account_iam_binding– Sets the entire IAM policy for a specific role on a service account, replacing any existing members for that role.google_service_account_iam_policy– Replaces the complete IAM policy of a service account, removing all other bindings.google_project_iam_member– Grants roles at the project level rather than the service account level.
Recommended Approach for Granting IAM Roles
To grant specific IAM roles to a Google service account effectively, follow this three-step pattern that leverages Terraform's declarative nature and prevents configuration drift.
Step 1: Create the Service Account
First, define the service account resource. This establishes the identity that will receive the IAM roles.
resource "google_service_account" "my_sa" {
account_id = "my-application-sa"
display_name = "Service Account for My Application"
}
Step 2: Grant Roles Using google_service_account_iam_member
Use the google_service_account_iam_member resource to attach specific roles. This resource is additive, meaning it only manages the specific binding you define without affecting other permissions.
resource "google_service_account_iam_member" "sa_compute_viewer" {
service_account_id = google_service_account.my_sa.name
role = "roles/compute.viewer"
member = "serviceAccount:${google_service_account.my_sa.email}"
}
resource "google_service_account_iam_member" "sa_storage_admin" {
service_account_id = google_service_account.my_sa.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.my_sa.email}"
}
Step 3: Maintain Declarative Configuration
Avoid mixing google_service_account_iam_member with google_service_account_iam_binding for the same role. The binding resource uses authoritative semantics and will remove any members not defined in that specific block, potentially causing configuration drift or permission revocation.
Practical Code Examples
These examples demonstrate common patterns for granting specific IAM roles to Google service accounts in production environments.
Basic Role Assignment
This complete configuration creates a service account and grants two distinct roles using separate resources:
provider "google" {
project = var.project_id
region = var.region
}
resource "google_service_account" "app_sa" {
account_id = "app-service-account"
display_name = "Application Service Account"
}
resource "google_service_account_iam_member" "compute_viewer" {
service_account_id = google_service_account.app_sa.name
role = "roles/compute.viewer"
member = "serviceAccount:${google_service_account.app_sa.email}"
}
resource "google_service_account_iam_member" "storage_admin" {
service_account_id = google_service_account.app_sa.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.app_sa.email}"
}
Dynamic Role Assignment with Variables
For scenarios requiring flexible role assignments, use for_each to iterate over a list of roles:
variable "sa_roles" {
type = list(string)
default = [
"roles/compute.viewer",
"roles/storage.objectAdmin",
"roles/pubsub.publisher",
]
}
resource "google_service_account" "dynamic_sa" {
account_id = "dynamic-sa"
display_name = "Dynamic Role Service Account"
}
resource "google_service_account_iam_member" "dynamic_roles" {
for_each = toset(var.sa_roles)
service_account_id = google_service_account.dynamic_sa.name
role = each.value
member = "serviceAccount:${google_service_account.dynamic_sa.email}"
}
Project-Level IAM Bindings
When you need to grant a service account access at the project level rather than the service account level, use google_project_iam_member:
resource "google_project_iam_member" "project_viewer" {
project = var.project_id
role = "roles/viewer"
member = "serviceAccount:${google_service_account.my_sa.email}"
}
How Terraform Manages IAM Resources Internally
Understanding Terraform's internal architecture helps explain why the google_service_account_iam_member approach is reliable and deterministic.
Provider Plugin Architecture
Terraform loads the Google provider as a separate binary through the provider registry system. According to the source code in internal/getproviders/registry_client.go, Terraform resolves provider requirements and downloads the appropriate plugin that implements the Google Cloud API interactions.
The provider itself communicates with Terraform core via the Terraform Provider Protocol, implemented in internal/tfplugin6/tfplugin6_grpc.pb.go. This gRPC interface allows the Google provider to receive CRUD (Create, Read, Update, Delete) requests for resources like google_service_account_iam_member and translate them into Google Cloud IAM API calls.
State Management and Dependency Graph
Terraform's core engine, orchestrated through files in internal/command/*.go, maintains a dependency graph that ensures IAM resources are applied only after the service account exists. When you reference google_service_account.my_sa.email in an IAM member resource, Terraform implicitly creates this dependency edge.
The state package records the exact IAM bindings in the Terraform state file, enabling deterministic updates. When you modify a role assignment, Terraform compares the desired configuration against the stored state and the actual GCP infrastructure, then generates a precise execution plan to reconcile differences.
Summary
- Use
google_service_account_iam_memberfor granular, non-destructive role assignments that don't interfere with existing permissions. - Create the service account first using
google_service_account, then reference itsnameandemailattributes in IAM member resources. - Avoid mixing resource types for the same role—don't use both
*_memberand*_bindingresources simultaneously to prevent configuration drift. - Leverage
for_eachfor dynamic role assignments when managing multiple permissions through variable lists. - Understand the provider architecture—Terraform loads the Google provider via
internal/getproviders/registry_client.goand manages IAM state through its core graph engine.
Frequently Asked Questions
What is the difference between google_service_account_iam_member and google_service_account_iam_binding?
The google_service_account_iam_member resource is additive and manages a single principal-role pair. It only affects the specific binding you define, leaving all other IAM permissions on the service account untouched. Conversely, google_service_account_iam_binding is authoritative for a specific role—it sets the complete list of members for that role, removing any members not defined in your Terraform configuration. Use *_member for granular control and *_binding only when you must enforce an exact list of members for a role.
Can I use google_project_iam_member instead of service account specific resources?
Yes, google_project_iam_member grants roles at the project level rather than attaching them directly to the service account resource. Use this when the service account needs broad access to project-wide resources or when your organization requires centralized IAM management at the project level. However, for least-privilege principles, prefer google_service_account_iam_member when granting specific roles that the service account assumes for particular resources, as it keeps the permission logic co-located with the service account definition.
How does Terraform track IAM role changes in the state file?
Terraform stores the desired state of IAM bindings in its state file (managed by the state package) and compares it against the actual infrastructure during the planning phase. For each google_service_account_iam_member resource, Terraform records the service account ID, role, and member principal. When you run terraform plan, the core engine (orchestrated via internal/command/*.go) detects drift by querying the Google Cloud IAM API and generates an execution plan to create, update, or delete bindings to match your configuration.
What happens if I mix binding and member resources for the same service account?
Mixing google_service_account_iam_member (additive) and google_service_account_iam_binding (authoritative) resources for the same role creates configuration conflicts and non-deterministic behavior. During apply cycles, the binding resource will attempt to remove any members not explicitly listed in its configuration, potentially undoing permissions granted by member resources. This leads to perpetual drift, where Terraform plans continuously show changes as the resources fight to enforce their respective desired states. Choose one pattern per role and service account combination—either manage the complete member list with *_binding or manage individual memberships with *_member.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →