Using 'dynamic' block to generate IAM policy statements in Terraform
Tags: devops, aws, terraform, tutorial
Published: 2022-01-27 14:01:17

Terraform has a cool resource block called the 'dynamic' block that allows generating multiple nested blocks for a resource.

This tutorial will show you how to generate multiple  IAM policy statements using this dynamic block.

In this example we have a list of AWS Principals that we want to allow access to our bucket named _dev-to-multi-account-bucket_.

```tf
# variables.tf
variable "aws_accounts" {
  type        = map(list(string))
  description = "A map of lists of AWS Principals"
  default     = {
    "mgmt" = ["arn:aws:iam::123456789876:root"]
    "prod" = ["arn:aws:iam::098765432123:root"]
  }
}
```


```tf
# bucket_policy.tf
resource "aws_s3_bucket_policy" "cross_account_policy" {
    bucket = "dev-to-multi-account-bucket"
    policy = data.aws_iam_policy_document.allow_access_from_another_account.json
}

# dynamic policy statement block for each account specified
data "aws_iam_policy_document" "allow_access_from_another_account" {
    dynamic "statement" {
        for_each = var.aws_accounts
        content {
            actions   = ["s3:*"]
            resources = [
                "arn:aws:s3:::dev-to-multi-account-bucket/${statement.key}*",
                ]
            principals {
                type = "AWS"
                identifiers = "${flatten(statement.value)}"
            } 
        }
    }
}
```

**Explaination**

So what's really happening here? Let's look at the _aws_iam_policy_document.allow_access_from_another_account_ "data" resource block. 

We are leveraging the dynamic block within this resource to iterate through the _aws_accounts_ variable. This generates multiple statements inside the policy allowing us to rely on the variable's length.

The variable _statement_ has two important attributes: _key_ and _value_. These allow you to target the respective section of the key-value map.

We can access these attributes through the `statement` variable. Terraform automatically assigns this variable name from the dynamic block's configuration. In this case, it's named `statement`.

After running `terraform plan`, we see a plan that looks something like this:

```tf
  # aws_s3_bucket_policy.cross_account_policy will be created
  + resource "aws_s3_bucket_policy" "cross_account_policy" {
      + bucket = "dev-to-multi-account-bucket"
      + id     = (known after apply)
      + policy = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "s3:*"
                      + Effect    = "Allow"
                      + Principal = {
                          + AWS = "arn:aws:iam::123456789876:root"
                        }
                      + Resource  = [
                          + "arn:aws:s3:::dev-to-multi-account-bucket/mgmt*",
                        ]
                      + Sid       = ""
                    },
                  + {
                      + Action    = "s3:*"
                      + Effect    = "Allow"
                      + Principal = {
                          + AWS = "arn:aws:iam::098765432123:root"
                        }
                      + Resource  = [
                          + "arn:aws:s3:::dev-to-multi-account-bucket/prod*",
                        ]
                      + Sid       = ""
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
    }
```

Nice!

Find more about dynamic blocks in [Terraform's official documentation page.](https://www.terraform.io/language/expressions/dynamic-blocks)