Terraform, GCP, GCP IAM

Terraform, GCP and GCP IAM.

Introduction

I’m between two customer projects, and our CEO asked me to work on one internal financial application. I worked to the point where I needed a runtime environment in GCP. I decided to use GCP Cloud Run which seems a good fit for this particular application. I used the admin project / infra project pattern in which I created:

  • an admin project for hosting the service account that I use when running Terraform, and storing the Terraform module state files, and
  • an infra project with all actual infra resources.

I have explained this pattern in more detail in my previous blog post GCP Kubernetes Exercise. This is a nice pattern; it provides an excellent way for implementing the least privilege principle: give Terraform only the rights it needs to create the resources your infrastructure needs. There are some caveats, however.

Some Observations

Implementing other parts of the infrastructure: common GCP artifact repository for all infrastructure side projects, GCP infrastructure project, and GCP cloud run service, were relatively easy. Easy, except the GCP IAM part to glue everything together. The point of this observation is that in various examples, it is assumed that you are giving the gcloud commands or running Terraform using your own gcp user principal. This makes examples easy since using the editor basic role you can do basically what ever you like.

In real life it is a bit more complicated. If you want to comply with the principle of least privilege it is a better practice to create a dedicated service account for terraform and assign to this service account only those rights that it needs for creating the cloud resources. Then things get a bit hairy since with this terraform service account you need to be able to create other service accounts (e.g. a dedicated service account for running the cloud run service) and modify the rights of that service account. We need to give appropriate rights for the terraform service account, e.g.:

# NOTE: We need this policy to give the terraform service account rights to assign cloud-run service account other rights for the
gcloud projects add-iam-policy-binding ${TF_VAR_ADMIN_PROJ_ID} \
  --member serviceAccount:${SA_NAME} \
  --role roles/resourcemanager.projectIamAdmin

I.e. the roles/resourcemanager.projectIamAdmin role.

Another oddity. I decided to create the GCP artifact registry for storing the Docker images, in the admin project side, since I need only one Docker repository. In the infra side I create (using terraform) all environments as exact copies of each other: the GCP project, Cloud Run service etc. So, I could have e.g. the following environments:

  • dev: for development
  • test: for testing
  • prod: for production

So, the artifact registry needs to be hosted in a GCP project, but if you have several identical infrastructure projects - which one? That’s the rationale to host the artifact registry in the admin project side (one admin project common for all infrastructure projects - common resources in the admin project are: the terraform service account, the bucket for hosting terraform state files for infrastructure modules, and now the artifact registry).

In all these infrastructure environments (projects) the Cloud Run service needs to pull the image from the common GCP artifact registry (from the admin project). The GCP documentation gives some instructions for this: Deploying images from other Google Cloud projects. Here comes an oddity: you have to give rights for some weird XXX@serverless-robot-prod.iam.gserviceaccount.com service account that you actually didn’t create as part of your infrastructure terraform code. How to get a hook to that service account? I decided to use Terraform External Provider. First let’s create a bash script to get the name of this weird service account:

λ> cat get-serverless-service-account-id.sh 
MY_ENV=$(terraform workspace show)
MY_SA_NAME=$(gcloud projects get-iam-policy ${TF_VAR_PREFIX}-${MY_ENV}-${TF_VAR_INFRA_PROJ_ID} --flatten='bindings[].members' | grep serverless | grep -o 'serviceAccount.*serverless-robot-prod\.iam\.gserviceaccount\.com')
JSON_STRING="{\"sa\":\"$MY_SA_NAME\"}"
echo $JSON_STRING

Then we can use this bash script in our terraform code:

# See: https://cloud.google.com/run/docs/deploying#other-projects
# We have the artifact registry in the admin project since we need only one.
data "external" "serverless_sa" {
  program = ["bash", "${path.module}/get-serverless-service-account-id.sh"]
}

# NOTE: The artifact registry is in the admin project side, see artifact-registry README.md for rationale.
# We use the serverless SA of this project which we get using the external resource.
resource "google_project_iam_binding" "serverless_service_account_artifactregistryviewer_binding" {
  project = var.ADMIN_PROJ_ID
  role    = "roles/artifactregistry.reader"
  members = [ data.external.serverless_sa.result.sa ]
}

So, you have to give this weird XXX@serverless-robot-prod.iam.gserviceaccount.com service account the role roles/artifactregistry.reader. Now we have done all the magic so that our infrastructure project (that we have created using terraform) can now pull the docker image using this robot service account (that we didn’t create using terraform). Now we are able to run the Cloud Run service (which we created using terraform, of course) using the dedicated cloud-run service account (which we created using terraform, of course). So, we are using the terraform external provider to make things smoother: this way we can create the Cloud Run module of our infrastructure solution using just terraform (and there is no need to first do some magic using gcloud and then use terraform).

Conclusions

This blog post explained some oddities working with GCP IAM (in a something-else-than-a-trivial-example-using-your-own-principal).

The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a cloud or Clojure project in Finland or you are interested getting cloud or Clojure training in Finland you can contact me by sending an email to my Metosin email address or contact me via LinkedIn.

Kari Marttila

Kari Marttila’s Home Page in LinkedIn: https://www.linkedin.com/in/karimarttila/