Terraform: correct way to attach AWS managed policies to a role?

Amazon Web-ServicesAmazon IamTerraform

Amazon Web-Services Problem Overview


I want to attach one of the pre-existing AWS managed roles to a policy, here's my current code:

resource "aws_iam_role_policy_attachment" "sto-readonly-role-policy-attach" {
  role       = "${aws_iam_role.sto-test-role.name}"
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

Is there a better way to model the managed policy and then reference it instead of hardcoding the ARN? It just seems like whenever I hardcode ARNs / paths or other stuff like this, I usually find out later there was a better way.

Is there something already existing in Terraform that models managed policies? Or is hardcoding the ARN the "right" way to do it?

Amazon Web-Services Solutions


Solution 1 - Amazon Web-Services

The IAM Policy data source is great for this. A data resource is used to describe data or resources that are not actively managed by Terraform, but are referenced by Terraform.

For your example, you would create a data resource for the managed policy as follows:

data "aws_iam_policy" "ReadOnlyAccess" {
  arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

The name of the data source, ReadOnlyAccess in this case, is entirely up to you. For managed policies I use the same name as the policy name for the sake of consistency, but you could just as easily name it readonly if that suits you.

You would then attach the IAM policy to your role as follows:

resource "aws_iam_role_policy_attachment" "sto-readonly-role-policy-attach" {
  role       = "${aws_iam_role.sto-test-role.name}"
  policy_arn = "${data.aws_iam_policy.ReadOnlyAccess.arn}"
}

Solution 2 - Amazon Web-Services

When using values that Terraform itself doesn't directly manage, you have a few options.

The first, simplest option is to just hard-code the value as you did here. This is a straightforward answer if you expect that the value will never change. Given that these "canned policies" are documented, built-in AWS features they likely fit this criteria.

The second option is to create a Terraform module and hard-code the value into that, and then reference this module from several other modules. This allows you to manage the value centrally and use it many times. A module that contains only outputs is a common pattern for this sort of thing, although you could also choose to make a module that contains an aws_iam_role_policy_attachment resource with the role set from a variable.

The third option is to place the value in some location that Terraform can retrieve values from, such as Consul, and then retrieve it from there using a data source. With only Terraform in play, this ends up being largely equivalent to the second option, though it means Terraform will re-read it on each refresh rather than only when you update the module using terraform init -upgrade, and thus this could be a better option for values that change often.

The fourth option is to use a specialized data source that can read the value directly from the source of truth. Terraform does not currently have a data source for fetching information on AWS managed policies, so this is not an option for your current situation, but can be used to fetch other AWS-defined data such as the AWS IP address ranges, service ARNs, etc.

Which of these is appropriate for a given situation will depend on how commonly the value changes, who manages changes to it, and on the availability of specialized Terraform data sources.

Solution 3 - Amazon Web-Services

I got a similar situation, and I don't want to use the arn in my terraform script for two reasons,

  • If we do it on the Web console, we don't really look at the arn, we search the role name, then attach it to the role
  • The arn is not easy to remember, looks like is not for human

I would rather use the policy name, but not the arn, here is my example

# Get the policy by name
data "aws_iam_policy" "required-policy" {
  name = "AmazonS3FullAccess"
}

# Create the role
resource "aws_iam_role" "system-role" {
  name = "data-stream-system-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Sid    = ""
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      },
    ]
  })
}


# Attach the policy to the role
resource "aws_iam_role_policy_attachment" "attach-s3" {
  role       = aws_iam_role.system-role.name
  policy_arn = data.aws_iam_policy.required-policy.arn
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionShornView Question on Stackoverflow
Solution 1 - Amazon Web-ServicesjorelliView Answer on Stackoverflow
Solution 2 - Amazon Web-ServicesMartin AtkinsView Answer on Stackoverflow
Solution 3 - Amazon Web-ServicesZhong DaiView Answer on Stackoverflow