2
votes

I'm looking to create the following statement in my IAM policy through the terraform module:

{
    "Effect": "Allow",
    "Action": [
        "ec2:CreateNetworkInterfacePermission"
    ],
    "Resource": "arn:aws:ec2:us-east-1:<acc-id>:network-interface/*",
    "Condition": {
        "StringEquals": {
            "ec2:Subnet": [
                "subnet-id1",
                "subnet-id2"
            ],
            "ec2:AuthorizedService": "codebuild.amazonaws.com"
        }
    }
}

The problem I'm facing is that I'm unable to figure out which interpolation will get me [ "subnet1", "subnet2" ].

Here's a little background. I'm created subnets with TF module (pubic_subnets) and output code is this:

   output "subnet_ids" { value = "${join(",", aws_subnet.public.*.id)}" }

which results in a string like "subnet-1,subnet-2,subnet-3". This is where I get my subnet ids from and I want to pass them to another module.

I have another module for the IAM policy (code below). I'm trying to take subnet_ids from above and pass them to the iam-policy module, so they can be placed into my statement, however, I can't figure out how to use any of the interpolations from TF. I tried about a dozen different interpolations but no luck. The only way I'm able to pass subnet ids to my IAM policy is by extracting items from the string (0, 1) and passing them as a separate variable. This is not perfect because I would like to include all subnet ids and there are times when I will have 3 subnets or more.

Has anyone been successful in doing such magic? How can I update my code so all subnet ids are passed in format ["subnet1", "subnet2", "subnet3"] ?

Here's my code for the iam-policy module in 2 files:

  1. main.tf
    data "template_file" "policy" {
       template = "${file("${var.policy_document}")}"
    
       vars = {
          account_id           = "${var.account_id}"
          role_name            = "${var.role_name}"
          log_group            = "${var.log_group}"
          repository_arn       = "${var.repository_arn}"
          website_s3_bucket    = "${var.website_s3_bucket}"
          pipeline_s3_bucket   = "${var.pipeline_s3_bucket}"
          subnet1              = "${var.subnet1}"
          subnet2              = "${var.subnet1}"
       }
    }
    
    resource "aws_iam_policy" "policy" {
    
       name        = "${var.policy_name}"
       description = "${var.description}"
       path        = "${var.path}"
       policy      = "${data.template_file.policy.rendered}"
    }
  1. variables.tf
variable "policy_name"     { default = "" }
variable "description"     { }
variable "path"            { default = "" }
variable "policy_document" {
   type  = "string"
   default = ""
}
# variables for policy
variable "log_group"          { }
variable "account_id"         { }
variable "role_name"          { }
variable "repository_arn"     { }
variable "website_s3_bucket"  { }
variable "pipeline_s3_bucket" { }
variable "subnet1"            { }
variable "subnet2"            { }

In my project file (project.tf) I have:

module "iam_policy_for_codebuild_service_role" {
   source = "../aws-policy.local"

   policy_name        = "${var.name}CodeBuildServiceRolePolicy1${lookup(var.environment, var.region)}"
   description        = "Policy for codebuild service role."
   path               = "/"
   policy_document    = "./policies/policy1.json"
   account_id         = "${module.account_id.id}"
   log_group          = "${var.name}Build${lookup(var.environment, var.region)}logGroup"
   role_name          = "${var.name}CodeBuildServiceRole${lookup(var.environment, var.region)}"
   repository_arn     = "${aws_codecommit_repository.ce_repo.arn}"
   website_s3_bucket  = "${aws_s3_bucket.website_bucket.arn}"
   pipeline_s3_bucket = "${aws_s3_bucket.codepipeline_bucket.arn}"

   subnet1             = "${element(split(",", module.public_subnet.subnet_ids), 0)}"
   subnet2             = "${element(split(",", module.public_subnet.subnet_ids), 1)}"

}

Here's what I have in my ./policies/policy1.json:

        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateNetworkInterfacePermission"
            ],
            "Resource": "arn:aws:ec2:us-east-1:${account_id}:network-interface/*",
            "Condition": {
                "StringEquals": {
                    "ec2:Subnet": ["${subnet1}", "${subnet2}"],
                    "ec2:AuthorizedService": "codebuild.amazonaws.com"
                }
            }
        }

I begin to think this is not possible because IAM policy must have [] after "ec2:Subnet".

If there's a Wizard out there who can perform such sorcery, please share the ingredients to making this spell work in Terraform version 11x. :)

Thank you

2

2 Answers

2
votes

You could you pass them arround as a string or just list, rather then individual subnets subnet1, subnet2 and so on.

For example, in your variables.tf you can use variable instead of individual subents:

variable "subnet_ids"            { }

Then in project.tf

subnet_ids             = "${module.public_subnet.subnet_ids}"

which will make subnet_ids="subnet1,subnet2". Then you pass this variable into your file template through jsonencode which should result in string ["subnet1","subnet2"]:

    data "template_file" "policy" {
       template = "${file("${var.policy_document}")}"
    
       vars = {
          account_id           = "${var.account_id}"
          role_name            = "${var.role_name}"
          log_group            = "${var.log_group}"
          repository_arn       = "${var.repository_arn}"
          website_s3_bucket    = "${var.website_s3_bucket}"
          pipeline_s3_bucket   = "${var.pipeline_s3_bucket}"
          subnet_ids           = "${jsonencode(split(",", {var.subnet_ids)}"
       }
    }

Finally, in ./policies/policy1.json

"ec2:Subnet": ${subnet_ids},

which should expand into:

"ec2:Subnet": ["subnet1","subnet2"],

I haven't verified the above, so some changes may be required to make it fully work. But at least the core idea on how you could attempt to solve your issue should be clearer.

0
votes

After having another go with interpolation I got it to work and here's my solution. Hopefully it will help someone in the future.

The core of it is to use replace function for string manipulation:

    ${replace(replace(replace(module.public_subnet.subnet_ids, ",", "\","), "subnet", "\"arn:aws:ec2:${var.region}:${module.account_id.id}:subnet"), "/$/", "\"")}

Here's how my module looks like now:

module "iam_policy" {
   source = "../aws-policy.local"

   policy_name        = "${var.name}CodeBuildServiceRolePolicy1${lookup(var.environment, var.region)}"
   description        = "Policy for codebuild service role."
   path               = "/"
   policy_document    = "./policies/policy1.json"
   account_id         = "${module.account_id.id}"
   log_group          = "${var.name}Build${lookup(var.environment, var.region)}logGroup"
   role_name          = "${var.name}CodeBuildServiceRole${lookup(var.environment, var.region)}"
   repository_arn     = "${aws_codecommit_repository.ce_repo.arn}"
   website_s3_bucket  = "${aws_s3_bucket.website_bucket.arn}"
   pipeline_s3_bucket = "${aws_s3_bucket.codepipeline_bucket.arn}"
   subnets             = "${replace(replace(replace(module.public_subnet.subnet_ids, ",", "\","), "subnet", "\"arn:aws:ec2:${var.region}:${module.account_id.id}:subnet"), "/$/", "\"")}"
}

However I will try implement jsonencode from comment above to shorten this code because it looks pretty ugly.