Concat 2 lists within AWS IAM role policy statement

I’m having some issues writing a concat statement to dynamically build an AWS policy statement.

I need to add a dynamic list of Secrets Manager secrets to an IAM Role statement, but I’m getting stuck here.

Can anyone help?

I’m getting this error:

Error: Invalid template interpolation value

  on terraform/lambda.tf line 89, in resource "aws_iam_role_policy" "iam_policy_for_lambda_secrets":
  88:
  89:               concat(
  90:                 ["arn:aws:secretsmanager:${lookup(local.region, local.environment)}:111111111111:secret:customer*"],
  91:                 [
  92:                   for name in lookup(local.lambda_secrets_access, local.environment):
  93:                   "arn:aws:secretsmanager:${lookup(local.region, local.environment)}:111111111111:secret:${name}"
  94:                 ]
  95:               )
  96:

    |----------------
    | local.environment is "dev"
    | local.lambda_secrets_access is object with 2 attributes
    | local.region is object with 2 attributes

Cannot include the given value in a string template: string required.

My variables:

  // Lookup for Region
  region = {
    "dev"         = "eu-west-1"
  }
  // Region lookup relation to workspace
  environment     = "${terraform.workspace}"
  // List of extra Secrets Manager secrets to gain access to
  lambda_secrets_access = {
    "dev"         = ["my-secret-i-want-access-JHKku8"]
  }

This is my problematic code:

resource "aws_iam_role_policy" "iam_policy_for_lambda_secrets" {
  name = "policy_name"
  role = aws_iam_role.iam_role_for_lambda.id

  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": ${
              concat(
                ["arn:aws:secretsmanager:${lookup(local.region, local.environment)}:111111111111:secret:customer*"],
                [
                  for name in lookup(local.lambda_secrets_access, local.environment):
                  "arn:aws:secretsmanager:${lookup(local.region, local.environment)}:111111111111:secret:${name}"
                ]
              )
            }
        }
    ]
}
EOF
}

Hi @KptnKMan,

concat returns a list, so you’d need to convert that to a string somehow in order to use it as part of a string template. For example, you could use jsonencode to turn it into JSON array syntax.

With that said, generating JSON using string concatenation like this is generally hard to get right and the result is never nice to read. Instead, I’d recommend using the jsonencode function to build the whole thing at once:

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = "secretsmanager.GetSecretValue"
        Resource = concat(
          ["arn:aws:secretsmanager:${local.region[local.environment]}:111111111111:secret:customer*"],
          [
            for name in local.lambda_secrets_access[local.environment] :
            "arn:aws:secretsmanager:${local.region[local.environment]}:111111111111:secret:${name}"
          ],
        )
      },
    ]
  })

For more information on this function, see the jsonencode function documentation.

Hi @apparentlymart this works great in my testing here, thanks very much.

However, (And I think this is a tangent here), I’m not sure how I can make this conditional because when I leave the list empty, I end up with an empty policy. Any idea how I can get around this?
If I use:

locals {
  lambda_secrets_access = {
    "dev" = [""]
}

I get:

Terraform will perform the following actions:

  # aws_iam_role_policy.iam_policy_for_lambda_secrets will be updated in-place
  ~ resource "aws_iam_role_policy" "iam_policy_for_lambda_secrets" {
        id     = "policy_id:policy_id"
        name   = "policy_name"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                        Action   = "secretsmanager:GetSecretValue"
                        Effect   = "Allow"
                      ~ Resource = [
                          - "arn:aws:secretsmanager:eu-west-1:111111111111:secret:my-secret-i-want-access-JHKku8",
                          + "arn:aws:secretsmanager:eu-west-1:111111111111:secret:",
                        ]
                    },
                ]
                Version   = "2012-10-17"
            }
        )
        role   = "role_name"
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Do you want to perform these actions in workspace "dev"?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

I tried adding a count statement on the resource:

count = local.lambda_secrets_access[local.environment] != "" ? 1 : 0

But this does not seem to help, the resource still is created incomplete, rather than not at all.

I googled and I’m not sure how conditional resources are implemented into Terraform.
It seems that this isnt a feature at all, and count is used by some to get around it.