mellifluous
mellifluous

Reputation: 2975

How do I attach a managed IAM policy and an inline/custom IAM policy to IAM roles?

I want to attach a managed IAM Policy ARN (like AmazomS3FullAccess) and an inline/custom IAM policy (written in JSON in terraform file) to a single IAM Role.

by using aws_iam_role_policy_attachment I am able to attach only one policy, what's the way to attach both?

variables.tf
------------

variable "iam_policy_arn" {
  description = "IAM Policy to be attached to role"
  type        = list(string)
  default     = ["arn:aws:iam::aws:policy/AWSLambdaFullAccess", "arn:aws:iam::aws:policy/AmazonSSMFullAccess", "arn:aws:iam::aws:policy/AmazonSageMakerFullAccess"]
}




main.tf
-------


resource "aws_iam_role" "test_role" {
  name = "test_role"

  assume_role_policy = <<-EOF
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Effect":"Allow",
      "Principal":{
        "Service":"ec2.amazonaws.com"
      },
      "Action":"sts:AssumeRole"
    },
    {
      "Effect":"Allow",
      "Principal":{
        "Service":"sagemaker.amazonaws.com",
        "AWS":"*"
      },
      "Action":"sts:AssumeRole"
    }
  ]
}    
  EOF
}
resource "aws_iam_role_policy_attachment" "role_policy_attachment" {
  role       = "${aws_iam_role.test_role.name}"
  count      = "${length(var.iam_policy_arn)}"
  policy_arn = "${element(var.iam_policy_arn,count.index)}"

}

resource "aws_iam_instance_profile" "test_profile" {
  name = "test_profile"
  role = "${aws_iam_role.test_role.name}"
}

now I want to attach a custom policy like below to the role

resource "aws_iam_role_policy" "test_policy" {
  name = "test_policy"
  role = aws_iam_role.test_role.id

  policy = <<-EOF
  {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": [
          "ec2:Describe*"
        ],
        "Effect": "Allow",
        "Resource": "*"
      }
    ]
  }
  EOF
}

How do I attach a managed IAM policy and a custom IAM policy to IAM roles?

Upvotes: 5

Views: 16992

Answers (5)

sogyals429
sogyals429

Reputation: 375

You might need to modify the policy to your needs but that's what it would look like. You can do the following:

data "template_file" "test_role_template" {
 template = "${file("pathToRoleJson")}"
}

data "template_file" "test_policy_template" {
    template = "${file("pathToPolicyJson")}"
    vars = {
      customParam    = "${var.ValueOfParam}"
    }
}

resource "aws_iam_role" "test_role" {
    name     = "roleName"
    assume_role_policy = "${data.template_file.test_role.rendered}"
}

#-----------------------------------------
resource "aws_iam_policy" "test_role_policy" {
  name   = "policyName"
  policy = "${data.template_file.test_policy_template.rendered}"
}

# Attach policy to role nat_ec2_role
#-----------------------------------------
resource "aws_iam_role_policy_attachment" "nat_ec2_role_policy-attachment" {
  role       = "${aws_iam_role.test_role.name}"
  policy_arn = "${aws_iam_policy.test_role_policy.arn}"
}



# Policy Template File
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Effect":"Allow",
      "Principal":{
        "Service":"ec2.amazonaws.com"
      },
      "Action":"sts:AssumeRole"
    },
    {
      "Effect":"Allow",
      "Principal":{
        "Service":"sagemaker.amazonaws.com",
        "AWS":"*"
      },
       {
        "Action": [
          "ec2:Describe*"
        ],
        "Effect": "Allow",
        "Resource": "*"
      }
      "Action":"sts:AssumeRole"
    }
  ]
}    


resource "aws_iam_instance_profile" "test_profile" {
  name = "test_profile"
  role = "${aws_iam_role.test_role.name}"
}

Upvotes: 1

dur
dur

Reputation: 17009

Background

AWS offers different identity-based policies, see Identity-based policies:

  • Managed policies – Standalone identity-based policies that you can attach to multiple users, groups, and roles in your AWS account. There are two types of managed policies:

    • AWS managed policies – Managed policies that are created and managed by AWS.

    • Customer managed policies – Managed policies that you create and manage in your AWS account. Customer managed policies provide more precise control over your policies than AWS managed policies.

  • Inline policies – Policies that you add directly to a single user, group, or role. Inline policies maintain a strict one-to-one relationship between a policy and an identity. They are deleted when you delete the identity.

You can mix these policies, see Choosing between managed policies and inline policies:

You can use both managed and inline policies together to define common and unique permissions for a principal entity.

Terraform can add a policy to a role with:

But you can't mix them arbitrarily, see aws_iam_role_policy:

For a given role, this resource is incompatible with using the aws_iam_role resource inline_policy argument. When using that argument and this resource, both will attempt to manage the role's inline policies and Terraform will show a permanent difference.

and aws_iam_role_policy_attachment:

The usage of this resource conflicts with the aws_iam_policy_attachment resource and will permanently show a difference if both are defined.

and aws_iam_role:

If you use this resource's managed_policy_arns argument or inline_policy configuration blocks, this resource will take over exclusive management of the role's respective policy types (e.g., both policy types if both arguments are used). These arguments are incompatible with other ways of managing a role's policies, such as aws_iam_policy_attachment, aws_iam_role_policy_attachment, and aws_iam_role_policy. If you attempt to manage a role's policies by multiple means, you will get resource cycling and/or errors.

Solution

You can use aws_iam_role_policy_attachment and aws_iam_role_policy together.

Example

resource "aws_iam_role" "my_role" {
  name               = "my-role"
  assume_role_policy = data.aws_iam_policy_document.instance-assume-role-policy.json
}

resource "aws_iam_role_policy_attachment" "rekognition" {
  role       = aws_iam_role.my_role.name
  policy_arn = data.aws_iam_policy.rekognition.arn
}

resource "aws_iam_role_policy" "s3" {
  name = "my-inline-policy"
  role = aws_iam_role.my_role.name
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "s3:GetObject",
          "s3:ListBucket",
        ]
        Effect = "Allow"
        Resource = [
          "my-bucket",
          "my-bucket/*",
        ]
      }
    ]
  })
}

Additional information

If you want to validate your custom managed / inline policy by Terraform, you could use aws_iam_policy_document, see Refactor your policy:

The aws_iam_policy_document data source uses HCL to generate a JSON representation of an IAM policy document. Writing the policy as a Terraform configuration has several advantages over defining your policy inline in the aws_iam_policy resource.

  • Terraform data sources makes applying policies to your AWS resources more flexible. You can overwrite, append, or update policies with this resource by using the source_policy_documents and override_policy_documents arguments.
  • Terraform data sources make it easier to reuse policies throughout your environment.
  • Terraform error checking automatically formats your policy document into correct JSON when you run your apply.

Upvotes: 1

Alain O&#39;Dea
Alain O&#39;Dea

Reputation: 21716

You can add the inline policy with embedded JSON as follows:

resource "aws_iam_role_policy" "test_policy" {
  name = "test_policy"
  role = aws_iam_role.test_role.id

  policy = <<-EOF
  {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": [
          "ec2:Describe*"
        ],
        "Effect": "Allow",
        "Resource": "*"
      }
    ]
  }
  EOF
}

Or you can use a aws_iam_policy_document to get better error-checking in IDEs like IntelliJ IDEA:

resource "aws_iam_role_policy" "policy" {
  name        = "test-policy"
  description = "A test policy"

  policy = data.aws_iam_policy_document.allow_ec2_describe
}

data "aws_iam_policy_document" "allow_ec2_describe" {
  version = "2012-10-17"

  statement {
    actions = [
      "ec2:Describe*",
    ]
    effect = "Allow"
    resources = [
      "*",
    ]
  }
}

Side note: you can more cleanly attach the Amazon Managed Policies using an aws_iam_role_policy_attachment resource with for_each like this:

resource "aws_iam_role_policy_attachment" "managed_policy_attachments" {
  for_each   = {for arn in var.iam_policy_arns : arn => arn}
  role       = aws_iam_role.test_role.name
  policy_arn = data.aws_iam_policy.managed_policies[each.key]
}

Side note: you can also use aws_iam_role_policy_attachment for cleaner assume_role_policy setup:

resource "aws_iam_role" "test_role" {
  name = "test_role"

  assume_role_policy = data.aws_iam_policy_document.allow_ec2_and_sagemaker
}

data "aws_iam_policy_document" "allow_ec2_and_sagemaker" {
  version = "2012-10-17"

  statement {
    sid    = "AllowEC2AndSageMaker"
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "ec2.amazonaws.com",
        "sagemaker.amazonaws.com",
      ]
    }
  }
}

Upvotes: 2

mellifluous
mellifluous

Reputation: 2975

I was able to attach a managed IAM policy and an inline/custom IAM policy to IAM role using the below code.

# variables.tf
variable "cloudwatch_lambda_iam_policy_arn" {
  type        = list(string)
  description = "IAM Policy to be attached to AWS CloudWatch Lambda role"
  default     = ["arn:aws:iam::aws:policy/AmazonEC2FullAccess", "arn:aws:iam::aws:policy/AWSLambdaExecute", "arn:aws:iam::aws:policy/AmazonCloudDirectoryFullAccess", "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]
}

#------------------------------------------------------------

# lambda.tf
resource "aws_iam_role" "awsmetrics_exec_role" {
  name = "awsmetrics-exec-role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

# custom/inline policy
resource "aws_iam_role_policy" "sts_assumerole_lambda" {
  name = "sts-assumerole-lambda"
  role = aws_iam_role.awsmetrics_exec_role.id

  policy = <<-EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
        "sts:AssumeRole",
        "sts:DecodeAuthorizationMessage",
        "sts:AssumeRoleWithSAML",
        "sts:AssumeRoleWithWebIdentity"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}

# AWS managed policies
resource "aws_iam_role_policy_attachment" "awsmetrics_role_policy_attachment" {
  role       = aws_iam_role.awsmetrics_exec_role.name
  count      = length(var.cloudwatch_lambda_iam_policy_arn)
  policy_arn = element(var.cloudwatch_lambda_iam_policy_arn, count.index)
}

Upvotes: 4

user4093955
user4093955

Reputation:

Just pass them as variable or declare them as a local value, and then iterate over such variable.

For example:

resource "aws_iam_role_policy_attachment" "attach" {
  count      = length(var.policies)
  role       = aws_iam_role.my_role.name
  policy_arn = ${var.policies[count.index]}
}

where var.policies is a list of policies ["arn:aws:iam::aws:policy/AmazonS3FullAccess", "arn:aws:iam::<your_account>:policy/your_policy"]

Upvotes: 0

Related Questions