Attache different multiple policies to multiple users with for_each

Terraform Version

0.12.7

Terraform Configuration Files

resource "aws_iam_user" "test" {
  for_each = "${var.svcMap}"
  name = "${var.svcMap[each.key]}"

}

resource "aws_iam_user_policy_attachment" "test-attach" {
  for_each = "${var.svcMap}"
  user = "${var.svcMap[each.key]}"
  
  policy_arn = "??"
}

Debug Output

It’s just a question, How to

Crash Output

N/A

Expected Behavior

To be able to attach different policies to multiple users with for_each

Actual Behavior

Not possible as policy_arn accept only string not list

Steps to Reproduce

N/A

Additional Context

I need to automate creation of users using terraform, mainly i have map “svcMap” like that {user1=[policy_arn1, policy_arn2] user2=[policy_arn3, policy_arn4]}, it’s easy to create users but not easy to attach policies:

resource "aws_iam_user" "test" {
  for_each = "${var.svcMap}"
  name = "${var.svcMap[each.key]}"

}

resource "aws_iam_user_policy_attachment" "test-attach" {
  for_each = "${var.svcMap}"
  user = "${var.svcMap[each.key]}"
  
  policy_arn = "??"
}

Is there anyway to achieve that ?

Hi @mo-saeed!

The first step is to produce a flattened data structure where each element maps to only one instance of aws_iam_user_policy_attachment:

locals {
  policy_attachments = flatten([
    for username, policies in var.service_map : [
      for policy_arn in policies: {
        username   = username
        policy_arn = policy_arn
      }
    ]
  ])
}

The above will produce a list of objects looking like this:

[
  {"username": "user1", "policy_arn": "policy_arn1"},
  {"username": "user1", "policy_arn": "policy_arn2"},
  {"username": "user2", "policy_arn": "policy_arn3"},
  {"username": "user2", "policy_arn": "policy_arn4"},
]

for_each requires a map where the keys each uniquely identify one instance of the resource, so we’ll need to do one further transformation in the for_each expression to generate these unique keys:

resource "aws_iam_user_policy_attachment" "test-attach" {
  for_each = {
    for up in var.policy_attachments :
    "${up.username} ${up.policy_arn}" => up
  }

  user       = each.value.username
  policy_arn = each.value.policy_arn
}

This will produce instances with addresses like aws_iam_user_policy_attachment.test-attach["user1 policy_arn1"], which ensures that as you make changes to var.service_map in future Terraform will be able to correlate the existing instances with the elements in your map and decide which instances to create or destroy.

@apparentlymart Thanks a lot for your answer !

Would it be possible some day to support list of policies attachement per user so mainly policy_arn accepts list instead of string ? that would make it really easy and less confusing.

I have case that we need for each user to attach up to 10 policies which will lead to 10 terraform resources of type aws_iam_user_policy_attachment which will make terraform output hard to read.

Cloudformation supports list of policies attachement per IAM:USER, Does it make sense for you ?

The Terraform provider structure is reflecting the structure of the underlying AWS API. The provider development teams usually follow the API structures closely because previous experience has shown that when they try to add additional abstractions they tend to be broken later by the evolution of the underlying APIs.

In this particular case, aws_iam_user_policy_attachment is a Terraform interface to iam:AttachUserPolicy. You’ll see there that its signature matches the resource type signature exactly, aside from following Terraform’s naming conventions.

I don’t work on the AWS provider so I can’t speak to the specific design tradeoffs in this case. If you’d like to discuss further the specific design tradeoffs for this specific case, you could open a feature request in the AWS provider repository and see what the AWS provider team thinks.

Thanks @apparentlymart