A lesson or two( okay four) when using terraform

A slightly different type of post for a change which doesn’t involve talking about security!

I haven’t used terraform in anger for a while. By this I mean creating a full config from a blankish page rather than updating ones I created before or using a framework to help build out an environment on Google Cloud. I had reason to create a config from scratch recently that involved GCS, Cloud functions, pub/sub and DLP. It was an enjoyable thing to do but as I was doing this there were a few observations I made along the way. My observations may be obvious to some of you but then again I don’t use terraform every day in my day job so as is my wont I am writing it here for my own reference and also to share with others whom it may save some time.

IAM permissions

As I was creating terraform for a tutorial I needed to add a couple of additional roles to the default app engine service account that the cloud functions were using as their identity. Don’t judge me but this was for a tutorial and for production use you’d create a custom service account and grant it only the permissions it needs.

My confusion was where to start and to be fair it was resolved after reading the second sentence of IAM policy for service account of the Hashicorp registry docs.

This terraform resource is to be used for when you are treating the service account as an actual Google cloud resource so you will be using one of the service account roles such as granting the ServiceAccountUser role to a user.

IAM policy for projects is to be used when you want to update or create an IAM policy attached to a project.

For my use case it was the latter as I needed to grant permissions to a default service account i.e the service account is an identity. Sounds simple enough but then there are four different resources available to help you configure IAM policies for projects .

The key decision point is whether you want to override existing IAM policies or add to them . I wanted to add a new member (the service account) to existing roles and I wanted to preserve the existing members in the policies so I needed to use the resource google_project_iam_member This way I could roll back any changes I made but leave things as they were before I granted the roles to the default app engine service account.

Cloud storage object level acl’s and uniform access

When you create buckets via the gcloud command or via the console it defaults to uniform access which is where Object access is controlled via bucket-level permissions using IAM however if you create a bucket via terraform using the following resource definition :

resource "google_storage_bucket" "your_bucket" {

 name = "${var.project_id}-quarantine"

 location = var.region

}

It defaults to Fine-grained which is a legacy access control system where Object access is controlled through both bucket-level (IAM) and object-level permissions (ACLs)

I just assumed that it would default to the recommended approach which is uniform . it was only when I tried to update the cloud function zip in a bucket that I realised the problem

The way to get the behavior you are expecting is to add

    uniform_bucket_level_access = true 

To the resource definition.

Force destroy

If you use Cloud storage you find out pretty quickly that you can’t delete a bucket unless it’s empty. So even if you create a bucket via Terraform and then run terraform destroy to destroy the entire configuration or just the target resource unless the bucket is empty it will remain stubbornly in place and the terraform destroy run will exit with a failure.

However Is this a problem personally for production? It depends as personally I think it’s always a good thing to double check what is in those buckets ( but obviously your use cases may differ) but having a failing terraform run may cause issues ( for example it may be part of your automated testing process). To ensure your bucket is destroyed as part of the terraform destroy process you just need to add

force_destroy = true

To the resource definition

Data sources and ${}

A data source is used to provide data about a resource that exists outside of terraform such as a default service account.

For example when I needed to add the default App Engine account as a member to some DLP specific IAM policies I needed to first define the data source that would be used to return data about the default app engine account similar to this :

data "google_app_engine_default_service_account" "default" {
}
output "default_account" {
 value = data.google_app_engine_default_service_account.default.email
}

I was then able to interpolate ( sorry that may not translate well but it is a nice word and basically means insert) the identity of the service account as a member into a resource declaration for the Iam role i wanted to update by adding the default app engine to the IAM policy

resource "google_project_iam_member" "set_dlp_roles" {

project  =  var.project_id

count = length(var.dlp_rolesList)

role =  var.dlp_rolesList[count.index]

member   = "serviceAccount:${data.google_app_engine_default_service_account.default.email}"