Introduction
In this post, we'll go over some tips for managing GCP Project IAM resources using Terraform. These tips are drawn from my experience working with 10 GCP projects and approximately 500 IAM resources. I hope that regardless of whether you're a beginner or a seasoned Terraform user, you might find something of interest or value in this post.
For groups, users
for_each with map objects for arguments
In the following code, we utilize for_each with map objects for arguments. This approach significantly reduces resource declaration and we can focus on "for whom" and "to what".
This helped me when I collaborated with an engineer who is not familiar with terraform.
google_project_iam_member
locals {
group_list = [
{ group: "developer@example.com", role: "roles/viewer", project: "your-project-id" },
{ group: "admin@example.com", role: "roles/owner", project: "your-project-id" },
]
}
resource "google_project_iam_member" "this" {
# By making inex like that, state is uniqu
for_each = { for i in local.group_list : "${i.group}_${i.role}_${i.project}" => i }
project = each.value.project_id
role = each.value.role
member = "group:${each.value.group}"
}
- pros: it is very simple. this tip can be applyed to other iam_mambers resourses not only google_project_iam_member.
- cons: it tends to increase map object size because iam member resoueces do not allaw multiple members.
google_project_iam_bindings
locals {
group_list = [
# projects can be parameterize if there are a lot.
{ role : "roles/viewer", projects : ["your-project-id-1","your-project-id-2"], members : [
"group:admin@example.com",
"group:developer@example.com",
] },
{ role : "roles/owner", projects : ["your-project-id-1","your-project-id-2"], members : [
"group:admin@example.com",
] },
]
# need some tricks for nested map object
group_list_flattened = flatten([
for i in local.group_list : [
for project in i.projects : {
role = i.role
project = project
members = i.members
}
]
])
}
resource "google_project_iam_binding" "this" {
for_each = { for i in local.group_list_flattened : "${i.role}_${i.project}" => i }
project = each.value.project_id
role = each.value.role
members = each.value.members
}
- pros: reduce more code compared with google_project_iam_member if you need to create common iam for a lot of projects.
- cons: the nature of iam_binding, there is a risk to override iam settings outside of this resource.
For service accounts
Types of service accounts
there are generally two types of service accounts.
- service accounts for tools(cicd pipeline,ETL pipeline, monitoring tool, something like that).
- service accounts for application code.
Service accounts for tools
For this type of service accounts, calling resources directly or using a simple custom module is good because these service accounts need limited permission to one project. Sometimes we need to add an extra role for a specific purpose like accessing a BigQuery dataset that is located in other GCP projects (e.g. data analysts need specific logs for the application or something similar). In those cases, using a single IAM member/IAM binding resource is sufficient because it emphasizes that those roles are exceptional.
Here's an example:
locals {
projects = [
"your-projec-id-1",
"your-projec-id-2",
]
}
module "service_account" {
source = "terraform-google-modules/service-accounts/google"
project_id = "service-account-project-id"
prefix = ""
names = ["service-account-name"]
project_roles = concat(
# for custom role
formatlist("%s=>projects/%s/roles/custom_role_name", local.projects, local.projects),
# for default role
formatlist("%s=>roles/viewer", local.projects),
}
Service accounts for application code
- For this type of service accounts, calling resources directly or using a simple custom module is good because this service accounts need limited permission to one projects. sometimes we need to add an extra role for a specific purpose like accessing bigquery dataset that is located to other gcp projects.(data analysts need specific log for application or something like that). In those cases, using single iam member/iam binding resource is sufficient because that emphasize those roles are unusual.
Here's an example:
# module definition.
resource "google_service_account" "this" {
project = var.project
account_id = var.name
display_name = var.name
}
resource "time_sleep" "this" {
# add some deletion because google_project_iam_member sometimes fail if immediately add iam after service account creation.
depends_on = [
google_service_account.this
]
triggers = {
email = google_service_account.this.email
}
create_duration = "10s"
}
resource "google_project_iam_member" "this" {
depends_on = [time_sleep.this]
for_each = toset(var.roles)
project = var.project
role = each.key
member = "serviceAccount:${google_service_account.this.email}"
}
variable "project" {
type = string
}
variable "name" {
type = string
}
variable "roles" {
type = list(string)
default = []
}
# call module
module "serviceaccount" {
source = "your-module-path"
name = "service-account-name"
project = "your-project-id"
roles = [
"roles/viewer",
]
}
Wrapping Up
We've walked through a few different ways to juggle Project IAM resources in GCP using Terraform, This is just a small tip But Hopefully, you've found a trick or two you can use in your own Terraform codes.
Actually, this is my first tech post, so I welcome your feedback.
Thank you for reading!
Top comments (0)