# How to Grant Specific IAM Roles to a Google Service Account Using Terraform

> Grant specific IAM roles to a Google service account with Terraform using the google_service_account_iam_member resource for granular, non-destructive role bindings. Learn the effective method now.

- Repository: [HashiCorp/terraform](https://github.com/hashicorp/terraform)
- Tags: how-to-guide
- Published: 2026-02-16

---

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

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

```hcl
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:

```hcl
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:

```hcl
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`:

```hcl
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`](https://github.com/hashicorp/terraform/blob/main/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`](https://github.com/hashicorp/terraform/blob/main/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_member`** for granular, non-destructive role assignments that don't interfere with existing permissions.
- **Create the service account first** using `google_service_account`, then reference its `name` and `email` attributes in IAM member resources.
- **Avoid mixing resource types** for the same role—don't use both `*_member` and `*_binding` resources simultaneously to prevent configuration drift.
- **Leverage `for_each`** for dynamic role assignments when managing multiple permissions through variable lists.
- **Understand the provider architecture**—Terraform loads the Google provider via [`internal/getproviders/registry_client.go`](https://github.com/hashicorp/terraform/blob/main/internal/getproviders/registry_client.go) and 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`.