I want to create IAM Role and Policies automatically and attach policies to the role respectively:
variables.tf
variable "roles" {
type = map(object({
role_name = string
role_description = string
policies = map(object({ policy_name = string, policy_description = string }))
})
)}
terraform.tfvars
roles = {
"aws-config-role-1" = {
role_name = "aws-config-s3"
role_description = "Custom AWSConfig Service Role for the Recorder to record s3 only"
policies = {
"s3" = {
policy_name = "s3",
policy_description = "Custom policy for AWSConfigRecorder Service Role to allow record only S3 resources"
},
"policy" = {
policy_name = "policy",
policy_description = "Custom policy for AWSConfigRecorder Service Role"
}
}
policy_description = "S3 Policy to get list of all s3 buckets in the account"
}
"aws-config-role-2" = {
role_name = "aws-config-ebs"
role_description = "Custom AWSConfig Service Role for the Recorder to allow record only ec2 ebs resources"
policies = {
"ebs" = {
policy_name = "ebs",
policy_description = "Custom policy for AWSConfigRecorder Service Role to record ebs volumes"
}
}
policy_description = "EBS Policy to get list of all ec2 ebs volumes in the account"
}
}
Each role can have different amount of policies, in my example aws-config-role-1 has 2 policies(s3 and policy) and aws-config-role-2 has only 1 policy(ebs)
Now I need to use locals and flatten function so each role has a list of policies respectively
locals.tf
locals {
policies = flatten([
for role_key, role in var.roles : [
for policy_key, policy in role.policies : {
role_key = role_key
role_name = role.role_name
role_description = role.role_description
policy_key = policy_key
policy_name = policy.policy_name
policy_description = policy.policy_description
}
]
])
}
in terraform console:
> local.policies
[
{
"policy_description" = "Custom policy for AWSConfigRecorder Service Role"
"policy_key" = "policy"
"policy_name" = "policy"
"role_description" = "Custom AWSConfig Service Role for the Recorder to record s3 only"
"role_key" = "aws-config-role-1"
"role_name" = "aws-config-s3"
},
{
"policy_description" = "Custom policy for AWSConfigRecorder"
"policy_key" = "s3"
"policy_name" = "s3"
"role_description" = "Custom AWSConfig Role for s3"
"role_key" = "aws-config-role-1"
"role_name" = "aws-config-s3"
},
{
"policy_description" = "Custom policy for AWSConfigRecorder"
"policy_key" = "ebs"
"policy_name" = "ebs"
"role_description" = "Custom AWSConfig Role for ebs"
"role_key" = "aws-config-role-2"
"role_name" = "aws-config-ebs"
},
]
Creating roles and policies
roles.tf
resource "aws_iam_role" "this" {
for_each = var.roles
name = "${var.project}-${var.env}-${each.value["role_name"]}-role"
path = "/${var.project}/${var.module_name}/"
description = each.value["role_description"]
assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "config.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
POLICY
}
then I create policies
resource "aws_iam_policy" "this" {
for_each = {
for policy in local.policies : "${policy.role_key}.${policy.policy_name}" => policy
}
name = "${var.project}-${var.env}-${each.value.policy_name}-Policy"
policy = "data.aws_iam_policy_document.${each.value.policy_name}.json"
path = "/${var.project}/${var.module_name}/"
description = each.value.policy_description
}
and data.tf where all policies defined
data "aws_iam_policy_document" "s3" {
statement {
sid = "GetListS3"
effect = "Allow"
actions = [
"s3:GetAccelerateConfiguration",
"s3:GetAccessPoint",
"s3:GetAccessPointPolicy",
"s3:GetAccessPointPolicyStatus",
"s3:GetAccountPublicAccessBlock",
"s3:GetBucketAcl",
"s3:GetBucketCORS",
"s3:GetBucketLocation",
"s3:GetBucketLogging",
"s3:GetBucketNotification",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketPolicy",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketRequestPayment",
"s3:GetBucketTagging",
"s3:GetBucketVersioning",
"s3:GetBucketWebsite",
"s3:GetEncryptionConfiguration",
"s3:GetLifecycleConfiguration",
"s3:GetReplicationConfiguration",
"s3:ListAccessPoints",
"s3:ListAllMyBuckets",
"s3:ListBucket"
]
resources = [
"arn:aws:s3:::*"
]
}
}
data "aws_iam_policy_document" "ebs" {
statement {
sid = "ListEBSVolumes"
effect = "Allow"
actions = [
"ec2:Describe*",
"ec2:GetEbsEncryptionByDefault"
]
resources = ["*"]
}
}
data "aws_iam_policy_document" "policy" {
statement {
sid = "Pol"
effect = "Allow"
actions = ["ec2:Describe*"]
resources = ["*"]
}
}
but when I run terraform plan
in aws_iam_policy.this policy field transformed into string instead of data value and I get an error
│ Error: "policy" contains an invalid JSON policy
│
│ with aws_iam_policy.this["aws-config-role-1.policy"],
│ on roles.tf line 31, in resource "aws_iam_policy" "this":
│ 31: policy = "data.aws_iam_policy_document.${each.value.policy_name}.json"
Basically if I look inside policy it contains string policy =data.aws_iam_policy_document.s3.json insted of actual data
Is there a way around this? Please advice.
CodePudding user response:
You can't dynamically create references to data sources in the following way:
policy = "data.aws_iam_policy_document.${each.value.policy_name}.json"
This will result in your policy
being literal string, e.g. "data.aws_iam_policy_document.s3.json"
, not its outcome the way you may think it should work.
You have to fully refactor your design, probably using for_each
with your aws_iam_policy_document
and dynamic blocks.