Terraform IAM modules for Google Cloud
Overview
Identity and Access Management (IAM) in Google Cloud can be configured at different levels, ranked from the most restrictive to the least restrictive as follows::
- resource
- project
- folder
- organization
It’s important to note that any IAM assignment made at a higher level gets inherited by lower levels. For instance, an IAM assignment given at the organization level will be inherited by underlying folders, projects, and resources. For more detailed information, refer to the official documentation.
Terraform module
When using terraform to manage your project(s), folder(s) or organization we can use up to three different modules, I’ll focus on the organization modules here:
google_organization_iam_policy
Starting with the most authorative module, the google_organization_iam_policy becames the single source of truth when used.
Whenever an organization user with the right priviledges adds or removes roles, Google Cloud updates the organization policy. But if the organizationn user makes a mistake, only that assignment fails.
When a policy gets applied by google_organization_iam_policy it affects the whole policy. Therefore, a mistake can cause a total loss of privileges and access to Google Cloud. This becames even more risky if the existing policy gets imported to be managed by terraform.
resource "google_project_iam_policy" "mypolicy" {
project = "example-project-id"
policy_data = "${data.google_iam_policy.admin.policy_data}"
}
data "google_iam_policy" "admin" {
binding {
role = "roles/compute.admin"
members = [
"user:john.doe@mydomain.co",
]
condition {
title = "expires_after_2019_12_31"
description = "Expiring at midnight of 2019-12-31"
expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")"
}
}
}
The above config when applied results in
Plan: 1 to add, 0 to change, 0 to destroy.
If we import the existing policy before in order for it to be managed by terraform, this would result in 1 to change where all IAM assignment would be removed from the project.
google_project_iam_policy.mypolicy example-project-id
data.google_iam_policy.admin: Reading...
data.google_iam_policy.admin: Read complete after 0s [id=2636224499]
google_project_iam_policy.mypolicy: Importing from ID "example-project-id"...
google_project_iam_policy.mypolicy: Import prepared!
Prepared google_project_iam_policy for import
google_project_iam_policy.mypolicy: Refreshing state... [id=example-project-id]
Import successful!
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
The above example demonstrates the import for a project level resource, but the equivalend can be done for the organization. Replacing the IAM policy with an incorrect policy would result in getting the in line message as the whole policy that contained the other roles was removed. If this is a project, it can be recovered by using a folder access or organization access. At organization level, all is lost.
Error: Error setting IAM policy for project "example-project-id": googleapi: Error 403: Policy update access denied., forbidden
google_organization_iam_binding
The google_organization_iam_binding module targets specific roles and associated members. It replaces any external configurations, making it authoritative for those roles. Note that changes will overwrite existing non-Terraform configurations.
resource "google_project_iam_binding" "mybinding" {
project = "example-project-id"
role = "roles/container.admin"
members = [
"user:john.doe@mydomain.co",
]
condition {
title = "expires_after_2019_12_31"
description = "Expiring at midnight of 2019-12-31"
expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")"
}
}
The above code will assign user John Doe the Container Admin role with a condition. Adding more users would be done by expanding the members and adding new principals. This approach does not allow for different principals to have a different set of conditions for the same role.
google_organization_iam_member
Unlike the previous modules, google_organization_iam_member is non-authoritative. It peacefully coexists with manual changes made outside Terraform, providing flexibility.
Example:
resource "google_project_iam_member" "user1" {
project = "example-project-id"
role = "roles/run.admin"
member = "user:john.doe@mydomain.co"
condition {
title = "expires_after_2019_12_31"
description = "Expiring at midnight of 2019-12-31"
expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")"
}
}
resource "google_project_iam_member" "user2" {
project = "example-project-id"
role = "roles/run.admin"
member = "user:lisa.monroe@mydomain.co"
}
In an empty project, this will prompt the creation of two resources:
Plan: 2 to add, 0 to change, 0 to destroy.
Upon applying the above configuration, the following is deployed:
$ gcloud projects get-iam-policy example-project-id --flatten=bindings --filter=bindings.role:roles/run.admin --format='value(bindings)' --format="yaml(bindings)"
---
bindings:
members:
- user:lisa.monroe@mydomain.co
role: roles/run.admin
---
bindings:
condition:
description: Expiring at midnight of 2019-12-31
expression: request.time < timestamp("2020-01-01T00:00:00Z")
title: expires_after_2019_12_31
members:
- user:john.doe@mydomain.co
role: roles/run.admin
Subsequently, adding another resource for the user lisa.monroe@mydomain.co with a condition will succeed:
resource "google_project_iam_member" "user3" {
project = "example-project-id"
role = "roles/run.admin"
member = "user:lisa.monroe@mydomain.co"
condition {
title = "expires_after_2032_12_31"
description = "Expiring at midnight of 2032-12-31"
expression = "request.time < timestamp(\"2024-01-01T00:00:00Z\")"
}
}
The deployment will result in success, and lisa.monroe will have two entries for the same role: one without a condition and the second one with a condition.
$ gcloud projects get-iam-policy example-project-id --flatten=bindings --filter=bindings.role:roles/run.admin --format='value(bindings)' --format="yaml(bindings)"
---
bindings:
members:
- user:lisa.monroe@mydomain.co
role: roles/run.admin
---
bindings:
condition:
description: Expiring at midnight of 2019-12-31
expression: request.time < timestamp("2020-01-01T00:00:00Z")
title: expires_after_2019_12_31
members:
- user:john.doe@mydomain.co
role: roles/run.admin
---
bindings:
condition:
description: Expiring at midnight of 2023-12-31
expression: request.time < timestamp("2024-01-01T00:00:00Z")
title: expires_after_2023_12_31
members:
- user:lisa.monroe@mydomain.co
role: roles/run.admin
Making the right choice
The choice of module depends on your approach:
- google_organization_iam_policy: Ideal for comprehensive control at the organization level.
- google_organization_iam_binding: Best for role-specific authoritative configurations.
- google_organization_iam_member: Offers freedom with non-authoritative setup, coexisting with manual changes.
By understanding these modules, you can effectively manage IAM configurations in Google Cloud using Terraform.