Generate IAM JSON Policy via Map

Team,
I am trying to create a IAM assume policy in JSON format from a map. I will be grateful if someone can help me here.

locals {
test = {
    cluster1 = {
      ns = "ns1"
      oidc = "oidc1"
    },
    cluster2 = {
      ns = "ns2"
      oidc = "oidc2"
    },
    cluster3 = {
      ns = "ns3"
      oidc = "oidc3"
    }
  }
}

Expected Output

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::12312:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/<oidc1>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "oidc.eks.us-west-2.amazonaws.com/id/<oidc1>:sub": "system:serviceaccount:<ns1>:*"
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::12312:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/<oidc2>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "oidc.eks.us-west-2.amazonaws.com/id/<oidc2>:sub": "system:serviceaccount:<ns2>:*"
        }
      }
    },
   {
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::12312:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/<oidc3>"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringLike": {
      "oidc.eks.us-west-2.amazonaws.com/id/<oidc3>:sub": "system:serviceaccount:<ns3>:*"
    }
  }
},
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

The above expected policy will then be attached to a IAM role.

1 Like

Hi @smitjainsj,

Can you share what you tried already and what happened when you tried it?

@apparentlymart
I wasn’t able to loop through maps in “jsonencode” in tmpl files, finally I had to use data to iterate over and get the work Done . However, I am not sure if jsonencode can handle complex JSON objects with iterations.

Whenever I tried with iteration over via function jsonencode , it didn’t took the input.


{
  "Version": "2012-10-17",
  "Statement": ${ jsonencode( [ for oidc in oidc_list = 
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam:::oidc-provider/oidc.eks.amazonaws.com/id/${oidc}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "oidc.eks.us-west-2.amazonaws.com/id/${oidc}:sub": "system:serviceaccount:${ns}:*"
        }
      }
    },)]}
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}


The above always throws syntax errors, I wasn’t very sure on how to insert complex object in side jsonencode function.

Here is what I did with data resource


data "aws_iam_policy_document" "test" {
  dynamic "statement" {
    for_each = var.testvar
    content {
        effect = "Allow"
        principals {
          identifiers = [
            "arn:aws:iam:::oidc-provider/oidc.eks..amazonaws.com/id/${statement.value["oidc"]}"]
          type = "Federated"
        }
        actions = [
          "sts:AssumeRoleWithWebIdentity"]
        condition {
          test = "StringLike"
          values = [
            "system:serviceaccount:xxxx:*"]
          variable = "oidc.eks..amazonaws.com/id/${statement.value["oidc"]}:sub"
        }
    }
  }
  statement {
    sid = "CustomRole"
    effect = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      identifiers = ["arn:aws:iam::340345076445:role/custom_role"]
      type = "AWS"
    }
    principals {
      identifiers = ["ec2.amazonaws.com"]
      type = "Service"
    }
  }
}

Hi @smitjainsj,

I think the problem with your first example is incorrect for expression syntax, rather than something specific to jsonencode. Specifically, the separator between the for clause and the result clause is supposed to be a colon, not an equals sign.

Since your whole result is JSON anyway, I’d recommend writing the whole thing as a single jsonencode call, rather than mixing template interpolation with jsonencode, because that way the result is guaranteed to always be valid JSON syntax:

${jsonencode({
  "Version": "2012-10-17",
  "Statement": concat(
    [
      for oidc in oidc_list : {
        "Effect": "Allow",
        "Principal": {
          "Federated": "arn:aws:iam:::oidc-provider/oidc.eks.amazonaws.com/id/${oidc}",
        },
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
          "StringLike": {
            "oidc.eks.us-west-2.amazonaws.com/id/${oidc}:sub": "system:serviceaccount:${ns}:*"
          },
        },
      }
    ],
    [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "ec2.amazonaws.com",
        },
        "Action": "sts:AssumeRole",
      },
    ],
  ),
})}

With all of that said, the aws_iam_policy_document is helpful in that it is aware of the IAM policy structure and so it can also help guarantee your result is valid IAM policy syntax, in addition to being valid JSON syntax, so if you’re happy with the data source solution then I’d say that’s fine too.

1 Like

thanks @apparentlymart this worked, also it fulfilled the MAP use case as well.

However, at times I see data is useful and at times template file resource can be very handy for quick changes. Thanks for your support.

${jsonencode({
  "Version": "2012-10-17",
  "Statement": concat(
    [
      for k, v  in test : {
        "Effect": "Allow",
        "Principal": {
          "Federated": "arn:aws:iam:::oidc-provider/oidc.eks.amazonaws.com/id/${v.oidc}",
        },
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
          "StringLike": {
            "oidc.eks.us-west-2.amazonaws.com/id/${v.oidc}:sub": "system:serviceaccount:${v.asl}:*"
          },
        },
      }
    ],
    [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "ec2.amazonaws.com",
        },
        "Action": "sts:AssumeRole",
      },
    ],
  ),
})}

-----

locals { 
  account_id = "1234567890"
  test = {
    cluster1 = {
      asl = "ap-admin"
      oidc = "123456"
    },
    cluster2 = {
      asl = "test"
      oidc = "5678"
    },
    cluster3 = {
      asl = "test12"
      oidc = "abcdc"
    }
  }

  file = templatefile("test.tmpl", { test = local.test , account_id = local.account_id , region = var.region })
}


{
  "Statement": [
    {
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "oidc.eks.us-west-2.amazonaws.com/id/123456:sub": "system:serviceaccount:ap-admin:*"
        }
      },
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam:::oidc-provider/oidc.eks.amazonaws.com/id/123456"
      }
    },
    {
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "oidc.eks.us-west-2.amazonaws.com/id/5678:sub": "system:serviceaccount:test:*"
        }
      },
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam:::oidc-provider/oidc.eks.amazonaws.com/id/5678"
      }
    },
    {
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "oidc.eks.us-west-2.amazonaws.com/id/abcdc:sub": "system:serviceaccount:test12:*"
        }
      },
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam:::oidc-provider/oidc.eks.amazonaws.com/id/abcdc"
      }
    },
    {
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      }
    }
  ],
  "Version": "2012-10-17"
}