68

I want to attach multiple IAM Policy ARNs to a single IAM Role.

One method is to create a new policy with privileges of all the policies (multiple policies).

But in AWS, we have some predefined IAM policies like AmazonEC2FullAccess, AmazomS3FullAccess, etc. I want to use a combination of these for my role.

I could not find a way to do so in the Terraform documentation.

As per documentation we can use aws_iam_role_policy_attachment to attach a policy to a role, but not multiple policies to a role as this is available via AWS console.

Please let me know if there is a method to do the same or is it still a feature to be added.

The Terraform version I use is v0.9.5

GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
Pranshu Verma
  • 1,482
  • 1
  • 12
  • 12

7 Answers7

68

For Terraform versions >= 0.12 the cleanest way to add multiple policies is probably something like this:

resource "aws_iam_role_policy_attachment" "role-policy-attachment" {
  for_each = toset([
    "arn:aws:iam::aws:policy/AmazonEC2FullAccess", 
    "arn:aws:iam::aws:policy/AmazonS3FullAccess"
  ])

  role       = var.iam_role_name
  policy_arn = each.value
}

As described in Pranshu Verma's answer, the list of policies can also be put into a variable.

Using for_each in favor of count has the advantage, that insertions to the list are properly recognized by terraform so that it would really only add one policy, while with count all policies after the insertion would be changed (this is described in detail in this blog post)

Falk Tandetzky
  • 5,226
  • 2
  • 15
  • 27
  • Note that using a variable for the list causes `plan` time errors, because TF doesn't know at the time how many items are in the list – J Hamm Mar 03 '21 at 11:25
  • 3
    Full error message for reference: `The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.` – J Hamm Mar 03 '21 at 11:26
  • 1
    @JHamm correct. This error appears in cases where the argument of for_each depends on outputs of resources (it works with variables though). In such a case, if the number of resources is known in advance, one can still list the resources one by one as in the answer of krishna_mee2004. I have not tested if using count, as in the accepted answer still works in situations where the number of resources depend on the resource output. – Falk Tandetzky Mar 03 '21 at 16:43
  • 3
    Indeed, `for_each` doesn't work in case there is a reference in a set. For example `for_each = toset(["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", aws_iam_policy.handy_ecs_tasks_execution_role_iam_policy.arn])`. However, `count` works fine in this case. – dzagorovsky Mar 31 '22 at 15:41
  • to fix for_each, terraform suggests to use a one off `-target` invocation, ie `terraform apply -target aws_iam_policy.handy_ecs_tasks_execution_role_iam_policy`. `terraform apply` should work afterwards – Alec Istomin Jun 30 '22 at 21:27
51

Thanks Krishna Kumar R for the hint.

A little more polished answer I reached from your answer.

# Define policy ARNs as list
variable "iam_policy_arn" {
  description = "IAM Policy to be attached to role"
  type = "list"
}

# Then parse through the list using count
resource "aws_iam_role_policy_attachment" "role-policy-attachment" {
  role       = "${var.iam_role_name}"
  count      = "${length(var.iam_policy_arn)}"
  policy_arn = "${var.iam_policy_arn[count.index]}"
}

And finally the list of policies should be specified in *.tfvars file or in command line using -var, for example:

iam_policy_arn = [ "arn:aws:iam::aws:policy/AmazonEC2FullAccess", "arn:aws:iam::aws:policy/AmazonS3FullAccess"]

maagdzia
  • 3
  • 3
Pranshu Verma
  • 1,482
  • 1
  • 12
  • 12
  • 1
    Only downside to this is that you can't set a default value with interpolation in variables so you can't reference `arn` from a `aws_iam_policy `'s `resource` or `data`. The default value will only take a text list. – GabLeRoux Feb 27 '19 at 13:53
  • 1
    This answer seems incomplete to me. How is the policy for e.g. AmazonEC2FullAccess brought in here? – Magnus Jan 19 '20 at 04:57
  • Is there a way to check if the managed policy is already attached to the target role or not? say a check before attaching and attach only if it not already attach to the Role. – Kamlendra Sharma Oct 15 '20 at 11:11
  • @magnus It is brought in via the `iam_policy_arn` list variable. Note that `AmazonEC2FullAccess` is an AWS *Managed* Policy - predefined by AWS HQ. :) – Jesse Adelman May 07 '21 at 16:11
  • Using Terraform's new syntax, this would be: `# Define policy ARNs as list variable "iam_policy_arn" { description = "IAM Policy to be attached to role" type = list(string) } # Then parse through the list using count resource "aws_iam_role_policy_attachment" "role-policy-attachment" { role = var.iam_role_name count = length(var.iam_policy_arn) policy_arn = var.iam_policy_arn[count.index] }` – Mark Sep 21 '21 at 21:15
33

Did you try something like this:

resource "aws_iam_role" "iam_role_name" {
  name = "iam_role_name"
}

resource "aws_iam_role_policy_attachment" "mgd_pol_1" {
  name       = "mgd_pol_attach_name"
  role       = "${aws_iam_role.iam_role_name.name}"
  policy_arn = "${aws_iam_policy.mgd_pol_1.arn}"
}

resource "aws_iam_role_policy_attachment" "mgd_pol_2" {
  name       = "mgd_pol_attach_name"
  role       = "${aws_iam_role.iam_role_name.name}"
  policy_arn = "${aws_iam_policy.mgd_pol_2.arn}"
}
GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
krishna_mee2004
  • 6,556
  • 2
  • 35
  • 45
  • 2
    Thanks that solves my purpose, but can there be a more cleaner way to pass the ARNs as a list. Actually I created a module in similar fashion to create a role attach multiple policies but executing it twice was overwriting previous attachments. – Pranshu Verma Aug 03 '17 at 14:12
  • 3
    assume_role_policy is a required field in "aws_iam_role" – Cnf271 Oct 30 '20 at 14:38
  • The argument "assume_role_policy" is required, but no definition was found. Not sure how you got this working? – Guy Rawsthorn Apr 28 '21 at 14:45
  • I understood the error about assume_role_policy. You need create or get a policy_document. This terraform doc example show how do that: – Rafael Gomes Francisco Jul 07 '21 at 01:52
  • https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role#example-of-using-data-source-for-assume-role-policy – Rafael Gomes Francisco Jul 07 '21 at 01:52
2

1.Use a datasource with for loop to get all the policies

data "aws_iam_policy" "management_group_policy" {
  for_each = toset(["Billing", "AmazonS3ReadOnlyAccess"])
  name     = each.value
}

2.Attach to role as so;

resource "aws_iam_role_policy_attachment" "dev_role_policy_attachment" {
  for_each   = data.aws_iam_policy.management_group_policy
  role       = aws_iam_role.role.name
  policy_arn = each.value.arn
}
Keynes
  • 109
  • 1
  • 4
1

Adding another option, which is similar to the excepted answer but instead of:

policy_arn = "${var.iam_policy_arn[count.index]}"

You can use the element function:

policy_arn = "${element(var.iam_policy_arn,count.index)}"

I think that in some cases (like a project with a large amount of code) this could be more readable.

Rot-man
  • 18,045
  • 12
  • 118
  • 124
1

In my case I added multiple statements in one policy document:

data "aws_iam_policy_document" "sns-and-sqs-policy" {
  statement {
    sid    = "AllowToPublishToSns"
    effect = "Allow"
    actions = [
      "sns:Publish",
    ]
    resources = [
      data.resource.arn,
    ]
  }
  statement {
    sid    = "AllowToSubscribeFromSqs"
    effect = "Allow"
    actions = [
      "sqs:changeMessageVisibility*",
      "sqs:SendMessage",
      "sqs:ReceiveMessage",
      "sqs:GetQueue*",
      "sqs:DeleteMessage",
    ]
    resources = [
      data.resource.arn,
    ]
  }
}

resource "aws_iam_policy" "sns-and-sqs" {
  name   = "sns-and-sqs-policy"
  policy = data.aws_iam_policy_document.sns-and-sqs-policy.json
}

resource "aws_iam_role_policy_attachment" "sns-and-sqs-role" {
  role       = "role_name"
  policy_arn = aws_iam_policy.sns-and-sqs.arn
}

simply combine your policies in one policy

Ahmed Badawy
  • 421
  • 4
  • 13
0

This is an example how i did it:

resource "aws_iam_group_policy_attachment" "policy_attach_example" {
  for_each   = aws_iam_policy.example
  group      = aws_iam_group.example.name
  policy_arn = each.value["arn"]
}

So basically "aws_iam_policy.example" is a list of policies that i have made in the same way, with for_each

Hope that this help you, i know i come late but i had this simillar issue