Home > other >  Terraform use each.value.policy_name in data to retrieve speciffic policy dynamicly
Terraform use each.value.policy_name in data to retrieve speciffic policy dynamicly

Time:12-22

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.

  • Related