Can I set IAM roles for all accounts in one place in the terragrunt.hcl file?

Hi All,

I’m working on a terragrunt project in AWS where each environment (dev, test, and production) uses its own IAM role, and so the S3 remote backend and provider need to assume a unique role per environment. I’m having some trouble figuring out how to lay out my live folder in the correct way.

I’d like to have the live folder look like this:

live
├── terragrunt.hcl
├── dev
│   ├── dev1
│   │   └── app
│   │       └── terragrunt.hcl
│   ├── dev2
│   │   └── app
│   │       └── terragrunt.hcl
│   ├── dev3
│   │   └── app
│   │       └── terragrunt.hcl
│   └── main
│       └── app
│           └── terragrunt.hcl
├── prod
│   └── main
│       └── app
│           └── terragrunt.hcl
└── test
    ├── main
    │   └── app
    │       └── terragrunt.hcl
    └── test1
        └── app
            └── terragrunt.hcl

Where the top-level terragrunt.hcl has the backend and provider configured like this:

locals {
  aws_role_arn               = ???
  aws_region                 = "us-east-1"
  aws_credentials_file       = "$HOME/.aws/credentials"
}


remote_state {
  backend = "s3"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
  config = {
    dynamodb_table = "TABLE_NAME"
    bucket         = "BUCKET_NAME"
    key            = "${path_relative_to_include()}/terraform.tfstate"
    region         = "${local.aws_region}"
    role_arn       = "${local.aws_role_arn}"
    encrypt        = true
  }
}

generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
provider "aws" {
  region                  = "${local.aws_region}"
  shared_credentials_file = "${local.aws_credentials_file}"
  assume_role {
    role_arn = "${local.aws_role_arn}"
  }
}
EOF
}

I’d like the aws_role_arn to be coming from each environment, but I’m not sure how I can achieve this.

I’ve looked at the docs page on working with multiple AWS accounts, and it seems like the recommendation is to not include assume_role lines in the backend or provider blocks, and to just set the TERRAGRUNT_IAM_ROLE environment variable before running each terragrunt apply:

export TERRAGRUNT_IAM_ROLE="XXXX"
terraform apply

This works for me, but it makes the apply process a bit more manual and error-prone than I’d like. If I forget to set this environment variable, or if I try to deploy a prod stack while I still have the dev TERRAGRUNT_IAM_ROLE variable set, then I’ll run into issues.

Is there a way to automate this? Ideally, I’d like to be able to set roles automatically depending on whether terragrunt apply is being called from the test, dev, or prod folder. Is there any way I can configure a map of IAM roles in the terragrunt.hcl file and have terragrunt select the right value depending on where it is being called from?

Something like this is what I’m trying to do:

iam_role = "<Determine this based on child folder path>"

locals {
  aws_region                 = "us-east-1"
  aws_credentials_file       = "$HOME/.aws/credentials"
}


remote_state {
  backend = "s3"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
  config = {
    dynamodb_table = "TABLE_NAME"
    bucket         = "BUCKET_NAME"
    key            = "${path_relative_to_include()}/terraform.tfstate"
    region         = "${local.aws_region}"
    encrypt        = true
  }
}

generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
provider "aws" {
  region                  = "${local.aws_region}"
  shared_credentials_file = "${local.aws_credentials_file}"
}
EOF
}

For the time being, what I’ve done is create a separate make recipe for each environment which generates a file called role.yaml that has the correct role ARN, and then in the terragrunt.hcl file I have:

iam_role = yamldecode(file("../role.yaml"))["role"]

So I just have to run make set-dev-role or make set-test-role, etc, before running terragrunt commands. That’s a little better, but I was hoping for a way to do this without having an additional step before each apply.

Here’s an option for you to try: in each environment, define an env.hcl file that contains your role.

.
├── dev
│   └── env.hcl
├── prod
│   └── env.hcl
└── stage
    └── env.hcl

In each env.hcl, define your role as a local.

locals {
  iam_role = "arn:aws:iam:..."
}

In the root terragrunt.hcl, read in the env.hcl, and use the value with the iam_role attribute:

locals {
  # Automatically load env-level vars (not to be confused with environment variables!)
  env_vars = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}

iam_role = local.env_vars.locals.iam_role

This works because when you use:

# Include all settings from the root terragrunt.hcl file
include {
  path = find_in_parent_folders()
}

… in each of the app terragrunt.hcl files, it includes the parent in the configuration, and the parent will now be able to see the right env.hcl and hence the relevant IAM role.