From 9be0b44a1320e9f40bf32ab3288d52b161561885 Mon Sep 17 00:00:00 2001 From: Andre de Vries Date: Fri, 27 Jan 2023 10:25:24 +0000 Subject: [PATCH 01/11] Add Fargate module --- modules/aws_ecs_fargate/README.md | 13 ++ modules/aws_ecs_fargate/loadbalancers.tf | 53 +++++ modules/aws_ecs_fargate/locals.tf | 51 ++++ modules/aws_ecs_fargate/main.tf | 230 ++++++++++++++++++ modules/aws_ecs_fargate/outputs.tf | 44 ++++ modules/aws_ecs_fargate/roles.tf | 96 ++++++++ modules/aws_ecs_fargate/secrets.tf | 59 +++++ modules/aws_ecs_fargate/security.tf | 54 +++++ modules/aws_ecs_fargate/variables.tf | 284 +++++++++++++++++++++++ 9 files changed, 884 insertions(+) create mode 100644 modules/aws_ecs_fargate/README.md create mode 100644 modules/aws_ecs_fargate/loadbalancers.tf create mode 100644 modules/aws_ecs_fargate/locals.tf create mode 100644 modules/aws_ecs_fargate/main.tf create mode 100644 modules/aws_ecs_fargate/outputs.tf create mode 100644 modules/aws_ecs_fargate/roles.tf create mode 100644 modules/aws_ecs_fargate/secrets.tf create mode 100644 modules/aws_ecs_fargate/security.tf create mode 100644 modules/aws_ecs_fargate/variables.tf diff --git a/modules/aws_ecs_fargate/README.md b/modules/aws_ecs_fargate/README.md new file mode 100644 index 0000000..eea7572 --- /dev/null +++ b/modules/aws_ecs_fargate/README.md @@ -0,0 +1,13 @@ +# AWS ECS + Fargate Module + +This module deploys and ECS cluster with AWS Fargate. This eliminates the need to manually provision, scale and manage compute instances. + +# Usage + +1. Directly use our module in your existing Terraform configuration and provide the required variables + +``` +module "retool" { + ... +} +``` diff --git a/modules/aws_ecs_fargate/loadbalancers.tf b/modules/aws_ecs_fargate/loadbalancers.tf new file mode 100644 index 0000000..0d5a9d2 --- /dev/null +++ b/modules/aws_ecs_fargate/loadbalancers.tf @@ -0,0 +1,53 @@ +resource "aws_lb" "this" { + name = "${var.deployment_name}-alb" + idle_timeout = var.alb_idle_timeout + + security_groups = [aws_security_group.alb.id] + subnets = var.subnet_ids +} + +resource "aws_lb_listener" "this" { + load_balancer_arn = aws_lb.this.arn + port = 80 + protocol = "HTTP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.this.arn + } +} + +resource "aws_lb_listener_rule" "this" { + listener_arn = aws_lb_listener.this.arn + priority = 1 + + action { + type = "forward" + target_group_arn = aws_lb_target_group.this.arn + } + + condition { + path_pattern { + values = ["/"] + } + } +} + +resource "aws_lb_target_group" "this" { + name = "${var.deployment_name}-target" + vpc_id = var.vpc_id + deregistration_delay = 30 + port = 80 + protocol = "HTTP" + target_type = "ip" + + + health_check { + interval = 10 + path = "/api/checkHealth" + protocol = "HTTP" + timeout = 5 + healthy_threshold = 3 + unhealthy_threshold = 2 + } +} \ No newline at end of file diff --git a/modules/aws_ecs_fargate/locals.tf b/modules/aws_ecs_fargate/locals.tf new file mode 100644 index 0000000..b45ed69 --- /dev/null +++ b/modules/aws_ecs_fargate/locals.tf @@ -0,0 +1,51 @@ +locals { + environment_variables = concat( + var.additional_env_vars, # add additional environment variables + [ + { + name = "NODE_ENV" + value = var.node_env + }, + { + name = "FORCE_DEPLOYMENT" + value = tostring(var.force_deployment) + }, + { + name = "POSTGRES_DB" + value = "hammerhead_production" + }, + { + name = "POSTGRES_HOST" + value = aws_db_instance.this.address + }, + { + name = "POSTGRES_SSL_ENABLED" + value = "true" + }, + { + name = "POSTGRES_PORT" + value = "5432" + }, + { + "name" = "POSTGRES_USER", + "value" = var.rds_username + }, + { + "name" = "POSTGRES_PASSWORD", + "value" = random_string.rds_password.result + }, + { + "name" : "JWT_SECRET", + "value" : random_string.jwt_secret.result + }, + { + "name" : "ENCRYPTION_KEY", + "value" : random_string.encryption_key.result + }, + { + "name" : "LICENSE_KEY", + "value" : var.retool_license_key + } + ] + ) +} diff --git a/modules/aws_ecs_fargate/main.tf b/modules/aws_ecs_fargate/main.tf new file mode 100644 index 0000000..b151605 --- /dev/null +++ b/modules/aws_ecs_fargate/main.tf @@ -0,0 +1,230 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +resource "aws_ecs_cluster" "this" { + name = "${var.deployment_name}-ecs" + + setting { + name = "containerInsights" + value = var.ecs_insights_enabled + } +} + +resource "aws_cloudwatch_log_group" "this" { + name = "${var.deployment_name}-ecs-log-group" + retention_in_days = var.log_retention_in_days +} + +resource "aws_db_instance" "this" { + identifier = "${var.deployment_name}-rds-instance" + allocated_storage = 80 + instance_class = var.rds_instance_class + engine = "postgres" + engine_version = "13.7" + db_name = "hammerhead_production" + username = aws_secretsmanager_secret_version.rds_username.secret_string + password = aws_secretsmanager_secret_version.rds_password.secret_string + port = 5432 + publicly_accessible = var.rds_publicly_accessible + vpc_security_group_ids = [aws_security_group.rds.id] + performance_insights_enabled = var.rds_performance_insights_enabled + + skip_final_snapshot = true + apply_immediately = true +} + +resource "aws_ecs_service" "retool" { + name = "${var.deployment_name}-main-service" + cluster = aws_ecs_cluster.this.id + task_definition = aws_ecs_task_definition.retool.arn + desired_count = var.ecs_service_count + deployment_maximum_percent = var.maximum_percent + deployment_minimum_healthy_percent = var.minimum_healthy_percent + launch_type = "FARGATE" + + load_balancer { + target_group_arn = aws_lb_target_group.this.arn + container_name = "retool" + container_port = 3000 + } + + network_configuration { + subnets = var.subnet_ids + security_groups = [aws_security_group.alb.id] + } +} + +resource "aws_ecs_task_definition" "retool" { + family = "${var.deployment_name}-backend" + requires_compatibilities = ["FARGATE"] + network_mode = var.ecs_task_network_mode + cpu = var.ecs_task_cpu + memory = var.ecs_task_memory + task_role_arn = aws_iam_role.task_role.arn + execution_role_arn = aws_iam_role.execution_role.arn + container_definitions = < Date: Wed, 22 Feb 2023 16:01:58 +0000 Subject: [PATCH 02/11] Add NAT gateway and variables --- modules/aws_ecs_fargate/main.tf | 17 +++++++++++++++++ modules/aws_ecs_fargate/variables.tf | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/modules/aws_ecs_fargate/main.tf b/modules/aws_ecs_fargate/main.tf index b151605..2ccf1db 100644 --- a/modules/aws_ecs_fargate/main.tf +++ b/modules/aws_ecs_fargate/main.tf @@ -64,6 +64,23 @@ resource "aws_ecs_service" "retool" { } } +resource "aws_eip" "retool_ip" { + vpc = true +} + +resource "aws_nat_gateway" "retool_nat_gateway" { + allocation_id = aws_eip.retool_ip.id + subnet_id = var.public_subnet + + tags = { + "Name" = "retool-nat" + } + + depends_on = [ + var.internet_gateway + ] +} + resource "aws_ecs_task_definition" "retool" { family = "${var.deployment_name}-backend" requires_compatibilities = ["FARGATE"] diff --git a/modules/aws_ecs_fargate/variables.tf b/modules/aws_ecs_fargate/variables.tf index 77a8e85..61c619b 100644 --- a/modules/aws_ecs_fargate/variables.tf +++ b/modules/aws_ecs_fargate/variables.tf @@ -25,7 +25,15 @@ variable "deployment_name" { description = "Name prefix for created resources. Defaults to `retool`." default = "retool" } +variable "public_subnet" { + type = string + description = "Public subnet for NAT Gateway." +} +variable "internet_gateway" { + type = string + description = "Internet gateway attached to VPC." +} variable "ecs_insights_enabled" { type = string default = "enabled" From 20d60a36cb5d4f100a2573e40dabf1c33257eb8e Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:07:38 +0000 Subject: [PATCH 03/11] Tidy variables.tf --- modules/aws_ecs_fargate/variables.tf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/aws_ecs_fargate/variables.tf b/modules/aws_ecs_fargate/variables.tf index 61c619b..03f19a1 100644 --- a/modules/aws_ecs_fargate/variables.tf +++ b/modules/aws_ecs_fargate/variables.tf @@ -25,6 +25,7 @@ variable "deployment_name" { description = "Name prefix for created resources. Defaults to `retool`." default = "retool" } + variable "public_subnet" { type = string description = "Public subnet for NAT Gateway." @@ -34,6 +35,7 @@ variable "internet_gateway" { type = string description = "Internet gateway attached to VPC." } + variable "ecs_insights_enabled" { type = string default = "enabled" @@ -182,7 +184,6 @@ variable "alb_ingress_rules" { description = "Ingress rules for load balancer" } - variable "alb_egress_rules" { type = list( object({ @@ -289,4 +290,4 @@ variable "retool_ecs_tasks_log_prefix" { description = "Associate a log stream with the specified prefix, the container name, and the ID of the Amazon ECS task that the container belongs to" type = string default = "SERVICE_RETOOL" -} \ No newline at end of file +} From 70e1ff4faa408ad44f520804e3744cf5278889d0 Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:08:18 +0000 Subject: [PATCH 04/11] Correct default rds_publicly_accessible default to match description --- modules/aws_ecs_fargate/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aws_ecs_fargate/variables.tf b/modules/aws_ecs_fargate/variables.tf index 03f19a1..a8427d5 100644 --- a/modules/aws_ecs_fargate/variables.tf +++ b/modules/aws_ecs_fargate/variables.tf @@ -92,7 +92,7 @@ variable "rds_instance_class" { variable "rds_publicly_accessible" { type = bool - default = true + default = false description = "Whether the RDS instance should be publicly accessible. Defaults to false." } From 79722d92d93d426017a45e7485a018536222d4fc Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:08:32 +0000 Subject: [PATCH 05/11] Move locals from variables --- modules/aws_ecs_fargate/locals.tf | 20 ++++++++++++++++++++ modules/aws_ecs_fargate/variables.tf | 22 ---------------------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/modules/aws_ecs_fargate/locals.tf b/modules/aws_ecs_fargate/locals.tf index b45ed69..7968c3c 100644 --- a/modules/aws_ecs_fargate/locals.tf +++ b/modules/aws_ecs_fargate/locals.tf @@ -48,4 +48,24 @@ locals { } ] ) + + stack_name = "${var.deployment_name}" + database_name = aws_db_instance.this.db_name + retool_image = "${var.ecs_retool_image}" + retool_alb_ingress_port = var.alb_listener_certificate_arn != null ? "443" : var.retool_alb_ingress_port + retool_alb_listener_protocol = var.alb_listener_certificate_arn != null ? "HTTPS" : var.aws_lb_listener_protocol + retool_alb_listener_ssl_policy = var.alb_listener_certificate_arn != null ? var.alb_listener_ssl_policy : null + retool_alb_listener_certificate_arn = var.alb_listener_certificate_arn + retool_url_port = local.retool_alb_ingress_port != "443" ? ":${local.retool_alb_ingress_port}" : "" + + retool_jwt_secret = { + password = aws_secretsmanager_secret_version.jwt_secret + } + retool_encryption_key_secret = { + password = random_string.encryption_key.result + } + retool_rds_secret = { + username = "retool" + password = aws_secretsmanager_secret.rds_password + } } diff --git a/modules/aws_ecs_fargate/variables.tf b/modules/aws_ecs_fargate/variables.tf index a8427d5..3b94185 100644 --- a/modules/aws_ecs_fargate/variables.tf +++ b/modules/aws_ecs_fargate/variables.tf @@ -138,28 +138,6 @@ variable "alb_listener_certificate_arn" { default = null } -locals { - stack_name = "${var.deployment_name}" - database_name = aws_db_instance.this.db_name - retool_image = "${var.ecs_retool_image}" - retool_alb_ingress_port = var.alb_listener_certificate_arn != null ? "443" : var.retool_alb_ingress_port - retool_alb_listener_protocol = var.alb_listener_certificate_arn != null ? "HTTPS" : var.aws_lb_listener_protocol - retool_alb_listener_ssl_policy = var.alb_listener_certificate_arn != null ? var.alb_listener_ssl_policy : null - retool_alb_listener_certificate_arn = var.alb_listener_certificate_arn - retool_url_port = local.retool_alb_ingress_port != "443" ? ":${local.retool_alb_ingress_port}" : "" - - retool_jwt_secret = { - password = aws_secretsmanager_secret_version.jwt_secret - } - retool_encryption_key_secret = { - password = random_string.encryption_key.result - } - retool_rds_secret = { - username = "retool" - password = aws_secretsmanager_secret.rds_password - } -} - variable "alb_ingress_rules" { type = list( object({ From a3ffb592417b806b58450750bd398a7e5ee9e12e Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Wed, 1 Mar 2023 16:18:01 +0000 Subject: [PATCH 06/11] Move RDS instance from default VPC into same VPC as other bits Allow choice of subnets --- modules/aws_ecs_fargate/locals.tf | 1 + modules/aws_ecs_fargate/main.tf | 6 ++++++ modules/aws_ecs_fargate/security.tf | 1 + modules/aws_ecs_fargate/variables.tf | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/modules/aws_ecs_fargate/locals.tf b/modules/aws_ecs_fargate/locals.tf index 7968c3c..a13bd66 100644 --- a/modules/aws_ecs_fargate/locals.tf +++ b/modules/aws_ecs_fargate/locals.tf @@ -51,6 +51,7 @@ locals { stack_name = "${var.deployment_name}" database_name = aws_db_instance.this.db_name + db_subnet_group_name = "${var.deployment_name}-subnet-group" retool_image = "${var.ecs_retool_image}" retool_alb_ingress_port = var.alb_listener_certificate_arn != null ? "443" : var.retool_alb_ingress_port retool_alb_listener_protocol = var.alb_listener_certificate_arn != null ? "HTTPS" : var.aws_lb_listener_protocol diff --git a/modules/aws_ecs_fargate/main.tf b/modules/aws_ecs_fargate/main.tf index 2ccf1db..487b628 100644 --- a/modules/aws_ecs_fargate/main.tf +++ b/modules/aws_ecs_fargate/main.tf @@ -25,6 +25,11 @@ resource "aws_cloudwatch_log_group" "this" { retention_in_days = var.log_retention_in_days } +resource "aws_db_subnet_group" "this" { + name = local.db_subnet_group_name + subnet_ids = var.rds_subnet_ids +} + resource "aws_db_instance" "this" { identifier = "${var.deployment_name}-rds-instance" allocated_storage = 80 @@ -36,6 +41,7 @@ resource "aws_db_instance" "this" { password = aws_secretsmanager_secret_version.rds_password.secret_string port = 5432 publicly_accessible = var.rds_publicly_accessible + db_subnet_group_name = local.db_subnet_group_name vpc_security_group_ids = [aws_security_group.rds.id] performance_insights_enabled = var.rds_performance_insights_enabled diff --git a/modules/aws_ecs_fargate/security.tf b/modules/aws_ecs_fargate/security.tf index aed5606..66496ed 100644 --- a/modules/aws_ecs_fargate/security.tf +++ b/modules/aws_ecs_fargate/security.tf @@ -1,6 +1,7 @@ resource "aws_security_group" "rds" { name = "${var.deployment_name}-rds-security-group" description = "Retool database security group" + vpc_id = var.vpc_id ingress { description = "Retool ECS Postgres Inbound" diff --git a/modules/aws_ecs_fargate/variables.tf b/modules/aws_ecs_fargate/variables.tf index 3b94185..b706de3 100644 --- a/modules/aws_ecs_fargate/variables.tf +++ b/modules/aws_ecs_fargate/variables.tf @@ -102,6 +102,11 @@ variable "rds_performance_insights_enabled" { description = "Whether to enable Performance Insights for RDS. Defaults to true." } +variable "rds_subnet_ids" { + type = list(string) + description = "Select at least two subnets for the RDS instance." +} + variable "secret_length" { type = number default = 48 From d39116b3382013bb1a0796deae94c4b5999777e5 Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Fri, 3 Mar 2023 09:09:52 +0000 Subject: [PATCH 07/11] Clarify design decisions in README --- modules/aws_ecs_fargate/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/aws_ecs_fargate/README.md b/modules/aws_ecs_fargate/README.md index eea7572..084f077 100644 --- a/modules/aws_ecs_fargate/README.md +++ b/modules/aws_ecs_fargate/README.md @@ -2,6 +2,10 @@ This module deploys and ECS cluster with AWS Fargate. This eliminates the need to manually provision, scale and manage compute instances. +# Key Design Decisions + +The module will deploy an RDS instance in the same VPC as the Fargate cluster. + # Usage 1. Directly use our module in your existing Terraform configuration and provide the required variables From 78237fd123deea7208717d2e24aaacc2bfa1e04a Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Fri, 3 Mar 2023 09:10:03 +0000 Subject: [PATCH 08/11] Enable clean teardown --- modules/aws_ecs_fargate/main.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/aws_ecs_fargate/main.tf b/modules/aws_ecs_fargate/main.tf index 487b628..2fe135f 100644 --- a/modules/aws_ecs_fargate/main.tf +++ b/modules/aws_ecs_fargate/main.tf @@ -47,6 +47,10 @@ resource "aws_db_instance" "this" { skip_final_snapshot = true apply_immediately = true + + depends_on = [ + aws_db_subnet_group.this + ] } resource "aws_ecs_service" "retool" { From 01e71b2713bb51cd7efe4ceac79a68978b490962 Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Fri, 3 Mar 2023 09:14:09 +0000 Subject: [PATCH 09/11] Remove NAT gateway and EIP from module --- modules/aws_ecs_fargate/README.md | 6 ++++++ modules/aws_ecs_fargate/main.tf | 26 +++++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/modules/aws_ecs_fargate/README.md b/modules/aws_ecs_fargate/README.md index 084f077..e720b65 100644 --- a/modules/aws_ecs_fargate/README.md +++ b/modules/aws_ecs_fargate/README.md @@ -2,6 +2,12 @@ This module deploys and ECS cluster with AWS Fargate. This eliminates the need to manually provision, scale and manage compute instances. +# Prerequisites + +- A VPC + - with 2+ private and 2+ public subnets, + - and a route from the private subnets to the Internet (perhaps via a NAT gateway with EIP) + # Key Design Decisions The module will deploy an RDS instance in the same VPC as the Fargate cluster. diff --git a/modules/aws_ecs_fargate/main.tf b/modules/aws_ecs_fargate/main.tf index 2fe135f..eefcef5 100644 --- a/modules/aws_ecs_fargate/main.tf +++ b/modules/aws_ecs_fargate/main.tf @@ -74,22 +74,22 @@ resource "aws_ecs_service" "retool" { } } -resource "aws_eip" "retool_ip" { - vpc = true -} +# resource "aws_eip" "retool_ip" { +# vpc = true +# } -resource "aws_nat_gateway" "retool_nat_gateway" { - allocation_id = aws_eip.retool_ip.id - subnet_id = var.public_subnet +# resource "aws_nat_gateway" "retool_nat_gateway" { +# allocation_id = aws_eip.retool_ip.id +# subnet_id = var.public_subnet - tags = { - "Name" = "retool-nat" - } +# tags = { +# "Name" = "retool-nat" +# } - depends_on = [ - var.internet_gateway - ] -} +# depends_on = [ +# var.internet_gateway +# ] +# } resource "aws_ecs_task_definition" "retool" { family = "${var.deployment_name}-backend" From c7efe17d0c0e77b4c08db521fd822e0dafbc5d8b Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Mon, 13 Mar 2023 08:31:19 +0000 Subject: [PATCH 10/11] Fix: Remove NAT gateway and EIP from module --- modules/aws_ecs_fargate/main.tf | 17 ----------------- modules/aws_ecs_fargate/variables.tf | 5 ----- 2 files changed, 22 deletions(-) diff --git a/modules/aws_ecs_fargate/main.tf b/modules/aws_ecs_fargate/main.tf index eefcef5..b0eeffa 100644 --- a/modules/aws_ecs_fargate/main.tf +++ b/modules/aws_ecs_fargate/main.tf @@ -74,23 +74,6 @@ resource "aws_ecs_service" "retool" { } } -# resource "aws_eip" "retool_ip" { -# vpc = true -# } - -# resource "aws_nat_gateway" "retool_nat_gateway" { -# allocation_id = aws_eip.retool_ip.id -# subnet_id = var.public_subnet - -# tags = { -# "Name" = "retool-nat" -# } - -# depends_on = [ -# var.internet_gateway -# ] -# } - resource "aws_ecs_task_definition" "retool" { family = "${var.deployment_name}-backend" requires_compatibilities = ["FARGATE"] diff --git a/modules/aws_ecs_fargate/variables.tf b/modules/aws_ecs_fargate/variables.tf index b706de3..11e5a55 100644 --- a/modules/aws_ecs_fargate/variables.tf +++ b/modules/aws_ecs_fargate/variables.tf @@ -31,11 +31,6 @@ variable "public_subnet" { description = "Public subnet for NAT Gateway." } -variable "internet_gateway" { - type = string - description = "Internet gateway attached to VPC." -} - variable "ecs_insights_enabled" { type = string default = "enabled" From 5d770fa87ec9658429ce02ad40195e311aa6cb2b Mon Sep 17 00:00:00 2001 From: Dave Gregory <117833775+davegregoryatperspectum@users.noreply.github.com> Date: Mon, 13 Mar 2023 08:33:04 +0000 Subject: [PATCH 11/11] Split up SGs, rearrange subnets Add new SG for tasks, predefine cross-SG rules, multiple public subnets, move LB to public subnets --- modules/aws_ecs_fargate/loadbalancers.tf | 2 +- modules/aws_ecs_fargate/main.tf | 8 +- modules/aws_ecs_fargate/security.tf | 122 +++++++++++++-------- modules/aws_ecs_fargate/variables.tf | 132 ++++++++++++++++------- 4 files changed, 174 insertions(+), 90 deletions(-) diff --git a/modules/aws_ecs_fargate/loadbalancers.tf b/modules/aws_ecs_fargate/loadbalancers.tf index 0d5a9d2..0efeb49 100644 --- a/modules/aws_ecs_fargate/loadbalancers.tf +++ b/modules/aws_ecs_fargate/loadbalancers.tf @@ -3,7 +3,7 @@ resource "aws_lb" "this" { idle_timeout = var.alb_idle_timeout security_groups = [aws_security_group.alb.id] - subnets = var.subnet_ids + subnets = var.alb_subnet_ids } resource "aws_lb_listener" "this" { diff --git a/modules/aws_ecs_fargate/main.tf b/modules/aws_ecs_fargate/main.tf index b0eeffa..a9b647a 100644 --- a/modules/aws_ecs_fargate/main.tf +++ b/modules/aws_ecs_fargate/main.tf @@ -69,8 +69,8 @@ resource "aws_ecs_service" "retool" { } network_configuration { - subnets = var.subnet_ids - security_groups = [aws_security_group.alb.id] + subnets = var.ecs_tasks_subnet_ids + security_groups = [aws_security_group.ecs_tasks.id] } } @@ -130,8 +130,8 @@ resource "aws_ecs_service" "jobs_runner" { launch_type = "FARGATE" network_configuration { - subnets = var.subnet_ids - security_groups = [aws_security_group.alb.id] + subnets = var.ecs_tasks_subnet_ids + security_groups = [aws_security_group.ecs_tasks.id] } } diff --git a/modules/aws_ecs_fargate/security.tf b/modules/aws_ecs_fargate/security.tf index 66496ed..63161a0 100644 --- a/modules/aws_ecs_fargate/security.tf +++ b/modules/aws_ecs_fargate/security.tf @@ -1,55 +1,87 @@ resource "aws_security_group" "rds" { name = "${var.deployment_name}-rds-security-group" description = "Retool database security group" - vpc_id = var.vpc_id - - ingress { - description = "Retool ECS Postgres Inbound" - from_port = "5432" - to_port = "5432" - protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] - } - - # Allow all outbound - modify if necessary - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = [ - "0.0.0.0/0" - ] - ipv6_cidr_blocks = ["::/0"] - } + vpc_id = var.vpc_id +} + +resource "aws_vpc_security_group_ingress_rule" "rds_ingress_from_ecs_tasks" { + security_group_id = aws_security_group.rds.id + + description = "Postgres ingress from ECS tasks" + from_port = "5432" + to_port = "5432" + ip_protocol = "tcp" + referenced_security_group_id = aws_security_group.ecs_tasks.id } resource "aws_security_group" "alb" { name = "${var.deployment_name}-alb-security-group" description = "Retool load balancer security group" vpc_id = var.vpc_id +} + +resource "aws_vpc_security_group_ingress_rule" "alb_ingress" { + for_each = var.alb_ingress_rules_map + + security_group_id = aws_security_group.alb.id + + description = each.value.description + from_port = each.value.from_port + to_port = each.value.to_port + ip_protocol = each.value.ip_protocol + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id +} + +resource "aws_vpc_security_group_egress_rule" "alb_egress_to_ecs_tasks" { + security_group_id = aws_security_group.alb.id + + description = "HTTP to ECS tasks" + from_port = var.retool_task_container_port + to_port = var.retool_task_container_port + ip_protocol = "tcp" + referenced_security_group_id = aws_security_group.ecs_tasks.id +} + +resource "aws_security_group" "ecs_tasks" { + name = "${var.deployment_name}-ecs-tasks-security-group" + description = "Retool ECS tasks security group" + vpc_id = var.vpc_id +} + +resource "aws_vpc_security_group_ingress_rule" "ecs_tasks_ingress_from_alb" { + security_group_id = aws_security_group.ecs_tasks.id + + description = "HTTP from ALB" + from_port = var.retool_task_container_port + to_port = var.retool_task_container_port + ip_protocol = "tcp" + referenced_security_group_id = aws_security_group.alb.id +} - dynamic "ingress" { - for_each = var.alb_ingress_rules - content { - description = ingress.value["description"] - from_port = ingress.value["from_port"] - to_port = ingress.value["to_port"] - protocol = ingress.value["protocol"] - cidr_blocks = ingress.value["cidr_blocks"] - ipv6_cidr_blocks = ingress.value["ipv6_cidr_blocks"] - } - } - - dynamic "egress" { - for_each = var.alb_egress_rules - - content { - description = egress.value["description"] - from_port = egress.value["from_port"] - to_port = egress.value["to_port"] - protocol = egress.value["protocol"] - cidr_blocks = egress.value["cidr_blocks"] - ipv6_cidr_blocks = egress.value["ipv6_cidr_blocks"] - } - } -} \ No newline at end of file +resource "aws_vpc_security_group_egress_rule" "ecs_tasks_egress_to_rds" { + security_group_id = aws_security_group.ecs_tasks.id + + description = "Postgres egress to RDS" + from_port = "5432" + to_port = "5432" + ip_protocol = "tcp" + referenced_security_group_id = aws_security_group.rds.id +} + +resource "aws_vpc_security_group_egress_rule" "ecs_tasks_egress_extra" { + for_each = var.ecs_tasks_extra_egress_rules_map + + security_group_id = aws_security_group.ecs_tasks.id + + description = each.value.description + from_port = each.value.from_port + to_port = each.value.to_port + ip_protocol = each.value.ip_protocol + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id +} diff --git a/modules/aws_ecs_fargate/variables.tf b/modules/aws_ecs_fargate/variables.tf index 11e5a55..86c25ad 100644 --- a/modules/aws_ecs_fargate/variables.tf +++ b/modules/aws_ecs_fargate/variables.tf @@ -15,9 +15,9 @@ variable "vpc_id" { description = "Select a VPC that allows instances access to the Internet." } -variable "subnet_ids" { +variable "ecs_tasks_subnet_ids" { type = list(string) - description = "Select at least two subnets in your selected VPC." + description = "Subnets for Fargate tasks (probably private)." } variable "deployment_name" { @@ -26,9 +26,9 @@ variable "deployment_name" { default = "retool" } -variable "public_subnet" { - type = string - description = "Public subnet for NAT Gateway." +variable "alb_subnet_ids" { + type = list(string) + description = "Public subnets for Load Balancer." } variable "ecs_insights_enabled" { @@ -138,52 +138,104 @@ variable "alb_listener_certificate_arn" { default = null } -variable "alb_ingress_rules" { - type = list( +variable "alb_ingress_rules_map" { + type = map( object({ - description = string - from_port = string - to_port = string - protocol = string - cidr_blocks = list(string) - ipv6_cidr_blocks = list(string) + description = string + from_port = string + to_port = string + ip_protocol = string + cidr_ipv4 = string # preferably optional(string) but reqs tf v1.3+ + cidr_ipv6 = string # preferably optional(string) but reqs tf v1.3+ + prefix_list_id = string # preferably optional(string) but reqs tf v1.3+ + referenced_security_group_id = string # preferably optional(string) but reqs tf v1.3+ }) ) - default = [ - { - description = "Global HTTP inbound" - from_port = "80" - to_port = "80" - protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] + default = { + global_http_in = { + description = "Global HTTP inbound" + from_port = "80" + to_port = "80" + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + cidr_ipv6 = "::/0" + prefix_list_id = null # not needed if optional(string) implemented above + referenced_security_group_id = null # not needed if optional(string) implemented above } - ] + } description = "Ingress rules for load balancer" } -variable "alb_egress_rules" { - type = list( +variable "alb_extra_egress_rules_map" { + type = map( + object({ + description = string + from_port = string + to_port = string + ip_protocol = string + cidr_ipv4 = string + cidr_ipv6 = string + prefix_list_id = string + referenced_security_group_id = string + }) + ) + default = {} + description = "Extra egress rules (beyond connectivity to Fargate tasks) for load balancer" +} + +variable "ecs_tasks_extra_ingress_rules_map" { + type = map( object({ - description = string - from_port = string - to_port = string - protocol = string - cidr_blocks = list(string) - ipv6_cidr_blocks = list(string) + description = string + from_port = string + to_port = string + ip_protocol = string + cidr_ipv4 = string # preferably optional(string) but reqs tf v1.3+ + cidr_ipv6 = string # preferably optional(string) but reqs tf v1.3+ + prefix_list_id = string # preferably optional(string) but reqs tf v1.3+ + referenced_security_group_id = string # preferably optional(string) but reqs tf v1.3+ }) ) - default = [ - { - description = "Global outbound" - from_port = "0" - to_port = "0" - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] + default = {} + description = "Extra ingress rules for ECS tasks (beyond connectivity from ALB)" +} + +variable "ecs_tasks_extra_egress_rules_map" { + type = map( + object({ + description = string + from_port = string + to_port = string + ip_protocol = string + cidr_ipv4 = string + cidr_ipv6 = string + prefix_list_id = string + referenced_security_group_id = string + }) + ) + default = { + global_https_out_ipv4 = { + description = "Global HTTPS outbound IPv4" + from_port = "443" + to_port = "443" + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + cidr_ipv6 = null # not needed if optional(string) implemented above + prefix_list_id = null # not needed if optional(string) implemented above + referenced_security_group_id = null # not needed if optional(string) implemented above + } + global_https_out_ipv6 = { + description = "Global HTTPS outbound IPv6" + from_port = "443" + to_port = "443" + ip_protocol = "tcp" + cidr_ipv4 = null # not needed if optional(string) implemented above + cidr_ipv6 = "::/0" + prefix_list_id = null # not needed if optional(string) implemented above + referenced_security_group_id = null # not needed if optional(string) implemented above } - ] - description = "Egress rules for load balancer" + } + description = "Extra egress rules for Fargate tasks (beyond connectivity to RDS) (must allow outbound to container registry; also see https://docs.retool.com/docs/network-storage-requirements)" } variable "alb_idle_timeout" {