Doug
Doug

Reputation: 123

Terraform pass a list for identifiers in an IAM policy Document

We are using Terraform to store secrets inside AWS secrets manager. We would like to expand our Terraform to add resource access policy to each secret to only allow certain IAM roles or user access the secret and get it is value. We are defining each secret and it is metadata using YAML. Terraform will then decode the yaml and store all the contents in a map. We then have a for_each to iterate through each map and create the secrets. Below is the yaml definition for a secret

nonprod:
- name: my-super-secret
  metadata:
    description: my-super-secret
  value: somesecret
  policy: true #This is the feature we trying to add. It will tell TF to add resource access policy
  iam_roles:
    - "arn:aws:iam::account-id:role/sagemaker"
    - "arn:aws:iam::account-id:user/jon.doe"
    - "arn:aws:iam::account-id:role/test"
  tags:
    purpose: sagemaker

The YAML is decoded and then stored in a var.secrets map. Using TF console this is what TF store in var.secrets after decoding YAML.

{
    "metadata" = {
      "description" = "my-super-secret"
    }
    "name" = "my-super-secret"
    "policy" = true
    "iam_roles" = [
      "arn:aws:iam::account-id:role/sagemaker",
      "arn:aws:iam::account-id:user/jane.doe",
      "arn:aws:iam::account-id:role/test",
    ]
    "tags" = {
      "purpose" = "sagemkaer"
    }
    "value" = "somesecret"
  }

on the main.tf file, I added the following code for the IAM policy document:

data "aws_iam_policy_document" "example" {
  for_each = { for item in var.secrets : item.name => item }
  statement {
    sid = "EnableAccessFor${each.value.name}"

    principals {
      type        = "AWS"
      identifiers = [lookup(each.value, "iam_roles")]
    }

    actions = [
      "secretsmanager:GetSecretValue",
    ]

    resources = [
      "*",
    ]
  }

}

then I am passing the policy document to aws_secretsmanager_secret_policy resource to attach the policy to the secret that has policy set as true

resource "aws_secretsmanager_secret_policy" "policy" {
  depends_on = [aws_secretsmanager_secret.secret]
  for_each = { for item in var.secrets : item.name => item }
  secret_arn = each.key

  policy = data.aws_iam_policy_document.example.json
}

no matter what way I use, I always get errors when I run a plan. I have used the following functions with no luck:

join, concat, toset, splat, jsonencode and for expressions

I get the following errors:

  1. using for expression identifiers = [for r in lookup(each.value, "iam_roles") : r] produces this error: Invalid value for "inputMap" parameter: the given object has no attribute "iam_roles"
  2. using splat identifiers = "${each.value[*].iam_roles}" produces this error: Inappropriate value for attribute "identifiers": element 0: string required.
  3. using toset with lookup identifiers = "${toset(lookup(each.value, "iam_roles", ""))}" produces this error Invalid value for "v" parameter: cannot convert string to set of any single type.
  4. using join with lookup identifiers = ["${join(", ", lookup(each.value, "iam_roles", ""))}"] produces this error Invalid value for "lists" parameter: list of string required.
  5. using jsonencode identifiers = [jsonencode(each.value.iam_roles)] produces this error This object does not have an attribute named "iam_roles".
  6. using just each.value without lookup identifiers = [each.value.iam_roles] produces this error Inappropriate value for attribute "identifiers": element 0: string required.

Any idea?

Update

I got rid of the iam_policy_document and instead opt-in to use the aws_secretsmanager_secret_policy resource with a json-policy. See example below:

resource "aws_secretsmanager_secret_policy" "policy" {
  depends_on = [aws_secretsmanager_secret.secret]
  for_each = { for item in var.secrets : item.name => item if try(item.policy, false)}
  secret_arn = aws_secretsmanager_secret.secret[each.key].arn 

  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnableAccessFor${each.value.name}",
      "Effect": "Allow",
      "Principal": {
        "AWS": [
        "${join(",", formatlist("\"%s\"", each.value.iam_roles))}"
        ]
      },
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "*"
    }
  ]
}
POLICY
}

The part I am having issue is with the each.value.iam_roles as that is a tuple vs. a string. I tried multiple ways to convert that into string but it is not working. Perhaps someone can help me with that.

Upvotes: 0

Views: 965

Answers (1)

Doug
Doug

Reputation: 123

issue was resolved with encoding the entire policy to JSON using the `jsonencode function in Terraform.

Upvotes: 0

Related Questions