Terraform: Patterns and Anti-Patterns [Part 2] - Account Constraints

Background

In my last post, I talked about creating lean providers for maximum flexibility. As I closed out the post, I mentioned the potential peril of performing operations against the wrong account by virtue of having the AWS_PROFILE variable set for a profile matching an account other than the one you’re intending to work with (and believe me, if you work with more than a handful of accounts, this is a very easy mistake to make).

Terraform provides us with a mechanism that covers this scenario, achieved by the use of the allowed_account_ids parameter in the provider block. The parameter takes the form of a list of account numbers, restricting any Terraform operations using that provider from interacting with accounts not in the list. Building on the example from the previous post, now our provider looks like this:

provider "aws" {
  region = "us-east-1"
  allowed_account_ids = ["000011110000", "111100001111"]
}

This setup affords us the flexibility, portability, and security perks covered in the last post, yet it also keeps us from errant runs in accounts that this code was never intended to manage.

One important aspect of this setup is to know which subcommands this does not prohibit using; subcommands like init, taint, untaint, and show will still work in the same directory where this provider is defined even if your environment is pointing to a disallowed account. However, plan, apply, and destroy – as we would expect – will not work if the current profile doesn’t match against one of the accounts defined in the list.

What It Looks Like

Now that we’ve talked about how and when we can use this, you might be wondering what this actually looks like if you try to plan or destroy against a different account. The next few screenshots give you an idea:

Terraform Plan Disallowed

Terraform Plan Disallowed

Terraform Destroy Disallowed

Terraform Destroy Disallowed

As a bit of context, I had already initialized an s3 backend against a bucket in my account using the init command. The outputs captured above were running against a main.tf that had these contents:

provider "aws" {
  region = "us-east-1"
  allowed_account_ids = ["000011110000"]
}

resource "aws_vpc" "foo" {
  cidr_block = "10.0.0.0/16"
}

terraform {
  backend "s3" {}
}

Additionally, I had AWS_PROFILE env var set to default, which points to a profile for my personal account. Since 000011110000 isn’t my personal account number, the operations fail!

Now you know how to limit where your Terraform operations run for a given repo/project with one small change to your Terraform code. As useful as it is to document something like “Only run this code against client X’s account (# …)” in a README file, this is a powerful self-documenting mechanism that is honored by the application. Also, don’t forget the forbidden_account_ids counterpart attribute, if that’s more within your use case (you can’t use both at the same time, though!).

Lastly, for both failed operations, the exit code returned to the shell by the Terraform executable is 1 (even with -detailed-exitcode set on the plan operation), just in case you’re utilizing code like this in some sort of CI/CD pipeline.

Happy building!

comments powered by Disqus