Anthony Kong
Anthony Kong

Reputation: 40784

Do I need two aws_iam_policy_document in this ecs fargate script?

Here is my terraform script

variable "aws_region" { }
variable "flavor" { }  # test or prod
variable "task_worker_service_name" { }
variable "task_cpu" {}
variable "task_memory" {}
variable "az_count" {}

terraform {
  required_version = "= 0.12.6"
}

provider "aws" {
  version = "~> 2.21.1"
  region = "${var.aws_region}"
}

data "aws_availability_zones" "available" {}

data "aws_iam_policy_document" "ecs_service_policy" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type = "Service"
      identifiers = [ "ecs.amazonaws.com" ]
    }
  }
}

data "aws_iam_policy_document" "task_worker_iam_role_policy" {
  statement {
    actions   = [ "sts:AssumeRole" ]
    principals {
      type = "Service"
      identifiers = [
        "ecs-tasks.amazonaws.com"
      ]
    }
  }
}

data "aws_iam_policy_document" "assume_role_policy" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type = "Service"
      identifiers = [ "ecs-tasks.amazonaws.com" ]
    }
  }
}

resource "aws_iam_role" "ecs_service_role" {
  name = "${var.flavor}-task-ecs-service-role"
  path = "/"
  assume_role_policy = "${data.aws_iam_policy_document.ecs_service_policy.json}"
}

resource "aws_iam_role_policy_attachment" "ecs_service_role_attachment" {
  role = "${aws_iam_role.ecs_service_role.name}"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole"
}

resource "aws_vpc" "ecs" {
    cidr_block  = "10.0.0.0/16"
    enable_dns_hostnames = true
    enable_dns_support = true
    instance_tenancy = "default"

    tags = {
      Name = "ecs"
    }
}

resource "aws_security_group" "vpc_ecs_task_worker" {
    name        = "${var.flavor}-vpc_ecs_task_worker"
    description = "ECS Allowed Ports"

    ingress {
        from_port       = 32768
        to_port         = 65535
        protocol        = "tcp"
        cidr_blocks     = ["0.0.0.0/0"]
    }


    egress {
        from_port       = 0
        to_port         = 0
        protocol        = "-1"
        cidr_blocks     = ["0.0.0.0/0"]
    }

}

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "${var.flavor}-ecs-task-worker-task-execution-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
}

resource "aws_iam_role" "task_worker_iam_role" {
    name = "${var.flavor}-task-worker-role"
    path = "/"
    assume_role_policy = data.aws_iam_policy_document.task_worker_iam_role_policy.json
}

# Create var.az_count private subnets, each in a different AZ
resource "aws_subnet" "private" {
  count             = "${var.az_count}"
  cidr_block        = "${cidrsubnet(aws_vpc.ecs.cidr_block, 8, count.index)}"
  availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
  vpc_id            = "${aws_vpc.ecs.id}"
}

resource "aws_ecs_task_definition" "task_worker" {
  family = "${var.flavor}-${var.task_worker_service_name}"
  network_mode = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu = var.task_cpu
  memory = var.task_memory
  execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
  task_role_arn = aws_iam_role.task_worker_iam_role.arn
  container_definitions = <<JSON
[
{
      "dnsSearchDomains": null,
      "logConfiguration": null,
      "entryPoint": null,
      "portMappings": [],
      "command": null,
      "linuxParameters": null,
      "cpu": ${var.task_cpu},
      "environment": [],
      "resourceRequirements": null,
      "ulimits": null,
      "dnsServers": null,
      "mountPoints": [],
      "workingDirectory": null,
      "secrets": null,
      "dockerSecurityOptions": null,
      "memory": null,
      "memoryReservation": ${var.task_memory},
      "volumesFrom": [],
      "stopTimeout": null,
      "image": "us-west-2.amazonaws.com/task:4383669",
      "startTimeout": null,
      "dependsOn": null,
      "disableNetworking": null,
      "interactive": null,
      "healthCheck": null,
      "essential": true,
      "links": null,
      "hostname": null,
      "extraHosts": null,
      "pseudoTerminal": null,
      "user": null,
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "systemControls": null,
      "privileged": null,
      "name": "task-worker"
    }
  ]
JSON
}

resource "aws_ecs_cluster" "task_pool" {
  name = "${var.flavor}-task-pool"
}

resource "aws_ecs_service" "task_service" {
  name = "${var.flavor}-task-worker-service"

  cluster = "${aws_ecs_cluster.task_pool.id}"

  task_definition = "${aws_ecs_task_definition.task_worker.arn}"

  launch_type = "FARGATE"

  desired_count = 2

  network_configuration {
    subnets = "${aws_subnet.private[*].id}"
    security_groups = ["${aws_security_group.vpc_ecs_task_worker.id}" ]
    assign_public_ip = "true"
  }

}

In this script I have added two 'aws_iam_policy_document':

data "aws_iam_policy_document" "pdf_conversion_iam_role_policy", and data "aws_iam_policy_document" "assume_role_policy" respectively

They are basically identical i.e.

data "aws_iam_policy_document" "assume_role_policy" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type = "Service"
      identifiers = [ "ecs-tasks.amazonaws.com" ]
    }
  }
}

I used assume_role_policy in aws_iam_role.ecs_task_execution_role and pdf_conversion_iam_role_policy in aws_iam_role.pdf_conversion_iam_role.

My question is: Is it necessary to have two aws_iam_policy_document in this case? Also do you have a good suggestion for a naming convention for this kind of terraform resource names?

Upvotes: 2

Views: 1011

Answers (1)

Martin Atkins
Martin Atkins

Reputation: 74544

If you are using a recent version of Terraform then you can potentially write this using the resource for_each feature to produce multiple instances of aws_iam_policy_document from a single data block:

data "aws_iam_policy_document" "assume_role" {
  for_each = toset([
    "ecs-tasks.amazonaws.com",
    "ecs.amazonaws.com",
  ])

  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type = "Service"
      identifiers = [each.value]
    }
  }
}

resource "aws_iam_role" "service" {
  for_each = data.aws_iam_policy_document.assume_role

  name               = "${var.flavor}-${each.key}-service-role"
  assume_role_policy = each.value.json
}

resource "aws_iam_role_policy_attachment" "ecs_service" {
  role = aws_iam_role.service["ecs.amazonaws.com"].name

  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole"
}

for_each inside a data or resource block makes references to the resource produce a map of resource objects. In this case, our map keys are the service identifiers. That gives us some resources with the following addresses:

  • data.aws_iam_policy_document.assume_role["ecs.amazonaws.com"]
  • data.aws_iam_policy_document.assume_role["ecs-tasks.amazonaws.com"]
  • aws_iam_role.service["ecs.amazonaws.com"]
  • aws_iam_role.service["ecs-tasks.amazonaws.com"]

The aws_iam_role_policy_attachment.ecs_service resource then refers directly to the specific role object for ecs.amazonaws.com, allowing it to represent the policy that is uniquely attached to that role even though we generalized the creation of the roles themselves.

We can add more service identifiers to the for_each in data "aws_iam_policy_document" "assume_role", or possibly even factor this pattern out into a module that takes the set of service hostnames as an input variable and exports the role names via an output value, if you find yourself creating lots of these.

Upvotes: 3

Related Questions