DEV Community

Takahiro Sakai
Takahiro Sakai

Posted on • Updated on

terraform tips: gcp project IAM

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}"
}

Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode
  • 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),   
}
Enter fullscreen mode Exit fullscreen mode

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",
  ]
}

Enter fullscreen mode Exit fullscreen mode

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)