diff --git a/modules/aws_ecs/ecs.tf b/modules/aws_ecs/ecs.tf index 4e16958..8cca01c 100644 --- a/modules/aws_ecs/ecs.tf +++ b/modules/aws_ecs/ecs.tf @@ -44,7 +44,7 @@ resource "aws_launch_configuration" "this" { instance_type = var.instance_type # e.g. t2.medium enable_monitoring = true - associate_public_ip_address = true + associate_public_ip_address = var.associate_public_ip_address # This user data represents a collection of “scripts” that will be executed the first time the machine starts. # This specific example makes sure the EC2 instance is automatically attached to the ECS cluster that we create earlier diff --git a/modules/aws_ecs/loadbalancers.tf b/modules/aws_ecs/loadbalancers.tf index 1d0d2ca..3ac2231 100644 --- a/modules/aws_ecs/loadbalancers.tf +++ b/modules/aws_ecs/loadbalancers.tf @@ -1,6 +1,7 @@ resource "aws_lb" "this" { name = "${var.deployment_name}-alb" idle_timeout = var.alb_idle_timeout + internal = var.alb_internal security_groups = [aws_security_group.alb.id] subnets = var.public_subnet_ids diff --git a/modules/aws_ecs/locals.tf b/modules/aws_ecs/locals.tf index a58c2bb..b11da5b 100644 --- a/modules/aws_ecs/locals.tf +++ b/modules/aws_ecs/locals.tf @@ -23,10 +23,48 @@ locals { // Use var.ecs_telemetry_fluentbit_image if defined, otherwise fallback to the same tag as var.ecs_retool_image ecs_telemetry_fluentbit_image = var.ecs_telemetry_fluentbit_image != "" ? var.ecs_telemetry_fluentbit_image : format("%s:%s", "tryretool/retool-aws-for-fluent-bit", split(":", var.ecs_retool_image)[1]) + secret_environment_variables = concat( + [ + { + name = "POSTGRES_USER" + valueFrom = aws_secretsmanager_secret.rds_username.arn + }, + { + name = "POSTGRES_PASSWORD" + valueFrom = aws_secretsmanager_secret.rds_password.arn + }, + { + name = "JWT_SECRET" + valueFrom = aws_secretsmanager_secret.jwt_secret.arn + }, + { + name = "ENCRYPTION_KEY" + valueFrom = aws_secretsmanager_secret.encryption_key.arn + } + ], + var.retool_license_key != "" ? [ + { + name = "LICENSE_KEY" + valueFrom = aws_secretsmanager_secret.retool_license_key[0].arn + } + ] : [], + var.temporal_cluster_config.tls_enabled && var.temporal_cluster_config.tls_crt != null ? [ + { + name = "WORKFLOW_TEMPORAL_TLS_CRT" + valueFrom = aws_secretsmanager_secret.temporal_tls_crt[0].arn + } + ] : [], + var.temporal_cluster_config.tls_enabled && var.temporal_cluster_config.tls_key != null ? [ + { + name = "WORKFLOW_TEMPORAL_TLS_KEY" + valueFrom = aws_secretsmanager_secret.temporal_tls_key[0].arn + } + ] : [] + ) + environment_variables = concat( var.additional_env_vars, # add additional environment variables local.base_environment_variables, - local.temporal_mtls_config, var.code_executor_enabled ? [ { name = "CODE_EXECUTOR_INGRESS_DOMAIN" @@ -68,26 +106,6 @@ locals { 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 - }, # Workflows-specific { "name" : "WORKFLOW_BACKEND_HOST", @@ -123,7 +141,7 @@ locals { auto_create_group = "true" log_stream_prefix = "SERVICE_RETOOL/" } - } : { + } : { logDriver = "awslogs" options = { awslogs-group = aws_cloudwatch_log_group.this.id @@ -143,7 +161,7 @@ locals { memory = var.launch_type == "EC2" ? var.ecs_task_resource_map["fluentbit"]["memory"] : null firelensConfiguration = { - type = "fluentbit" + type = "fluentbit" options = { config-file-type = "file" config-file-value = "/extra.conf" @@ -152,7 +170,7 @@ locals { logConfiguration = { logDriver = "awslogs" - options = { + options = { awslogs-group = aws_cloudwatch_log_group.this.id awslogs-region = var.aws_region awslogs-stream-prefix = "SERVICE_RETOOL" @@ -171,19 +189,4 @@ locals { } ] : [] ) - - temporal_mtls_config = ( - var.temporal_cluster_config.tls_enabled && var.temporal_cluster_config.tls_crt != null && var.temporal_cluster_config.tls_key != null ? - [ - { - "name" : "WORKFLOW_TEMPORAL_TLS_CRT", - "value" : var.temporal_cluster_config.tls_crt - }, - { - "name" : "WORKFLOW_TEMPORAL_TLS_KEY", - "value" : var.temporal_cluster_config.tls_key - } - ] : - [] - ) } diff --git a/modules/aws_ecs/main.tf b/modules/aws_ecs/main.tf index b89b2a7..649da48 100644 --- a/modules/aws_ecs/main.tf +++ b/modules/aws_ecs/main.tf @@ -42,6 +42,8 @@ resource "aws_db_instance" "this" { storage_throughput = var.rds_storage_throughput iops = var.rds_iops multi_az = var.rds_multi_az + backup_retention_period = var.rds_backup_retention_period + backup_window = var.rds_backup_window skip_final_snapshot = true apply_immediately = true @@ -79,7 +81,7 @@ resource "aws_ecs_service" "retool" { security_groups = [ aws_security_group.containers.id ] - assign_public_ip = true + assign_public_ip = var.assign_public_ip } } } @@ -107,7 +109,7 @@ resource "aws_ecs_service" "jobs_runner" { security_groups = [ aws_security_group.containers.id ] - assign_public_ip = true + assign_public_ip = var.assign_public_ip } } } @@ -140,7 +142,7 @@ resource "aws_ecs_service" "workflows_backend" { security_groups = [ aws_security_group.containers.id ] - assign_public_ip = true + assign_public_ip = var.assign_public_ip } } } @@ -169,7 +171,7 @@ resource "aws_ecs_service" "workflows_worker" { security_groups = [ aws_security_group.containers.id ] - assign_public_ip = true + assign_public_ip = var.assign_public_ip } } } @@ -201,7 +203,7 @@ resource "aws_ecs_service" "code_executor" { security_groups = [ aws_security_group.containers.id ] - assign_public_ip = true + assign_public_ip = var.assign_public_ip } } } @@ -234,7 +236,7 @@ resource "aws_ecs_service" "telemetry" { security_groups = [ aws_security_group.containers.id ] - assign_public_ip = true + assign_public_ip = var.assign_public_ip } } } @@ -278,7 +280,8 @@ resource "aws_ecs_task_definition" "retool_jobs_runner" { value = "JOBS_RUNNER" } ] - ) + ), + secrets = local.secret_environment_variables } ] )) @@ -328,7 +331,8 @@ resource "aws_ecs_task_definition" "retool" { "value" = tostring(var.cookie_insecure) } ] - ) + ), + secrets = local.secret_environment_variables } ] )) @@ -379,7 +383,8 @@ resource "aws_ecs_task_definition" "retool_workflows_backend" { "value" = tostring(var.cookie_insecure) } ] - ) + ), + secrets = local.secret_environment_variables } ] )) @@ -434,7 +439,8 @@ resource "aws_ecs_task_definition" "retool_workflows_worker" { "value" = tostring(var.cookie_insecure) } ] - ) + ), + secrets = local.secret_environment_variables } ] )) @@ -454,12 +460,12 @@ resource "aws_ecs_task_definition" "retool_code_executor" { local.common_containers, [ { - name = "retool-code-executor" - essential = true - image = local.ecs_code_executor_image - cpu = var.launch_type == "EC2" ? var.ecs_task_resource_map["code_executor"]["cpu"] : null - memory = var.launch_type == "EC2" ? var.ecs_task_resource_map["code_executor"]["memory"] : null - user = var.launch_type == "EC2" ? null : "1001:1001" + name = "retool-code-executor" + essential = true + image = local.ecs_code_executor_image + cpu = var.launch_type == "EC2" ? var.ecs_task_resource_map["code_executor"]["cpu"] : null + memory = var.launch_type == "EC2" ? var.ecs_task_resource_map["code_executor"]["memory"] : null + user = var.launch_type == "EC2" ? null : "1001:1001" # required to use nsjail sandboxing, which is required for custom libraries for JS and Python # Learn more here: https://docs.retool.com/self-hosted/concepts/architecture#code-executor # If not using nsjail sandboxing, update this to be false and use user = "1001:1001" @@ -486,7 +492,7 @@ resource "aws_ecs_task_definition" "retool_code_executor" { local.base_environment_variables, [ { - name = "NODE_OPTIONS", + name = "NODE_OPTIONS", value = "--max_old_space_size=1024" } ], @@ -580,7 +586,8 @@ resource "aws_ecs_task_definition" "retool_telemetry" { value = base64encode(file(var.telemetry_custom_config_path)) } ] : [] - ) + ), + secrets = local.secret_environment_variables } ] ) @@ -635,7 +642,7 @@ resource "aws_service_discovery_service" "retool_code_executor_service" { resource "aws_service_discovery_service" "retool_telemetry_service" { count = var.telemetry_enabled ? 1 : 0 - name = "telemetry" + name = "telemetry" dns_config { namespace_id = aws_service_discovery_private_dns_namespace.retool_namespace[0].id @@ -701,5 +708,6 @@ module "temporal" { aws_ecs_capacity_provider_name = var.launch_type == "EC2" ? aws_ecs_capacity_provider.this[0].name : null task_propagate_tags = var.task_propagate_tags service_discovery_namespace = local.service_discovery_namespace + assign_public_ip = var.assign_public_ip iam_partition = var.iam_partition } diff --git a/modules/aws_ecs/roles.tf b/modules/aws_ecs/roles.tf index b242bd2..edc5dfe 100644 --- a/modules/aws_ecs/roles.tf +++ b/modules/aws_ecs/roles.tf @@ -93,6 +93,28 @@ resource "aws_iam_role_policy_attachment" "execution_role" { policy_arn = "arn:${var.iam_partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" } +data "aws_iam_policy_document" "execution_role_secrets_policy" { + count = var.launch_type == "FARGATE" ? 1 : 0 + statement { + actions = [ + "secretsmanager:GetSecretValue", + ] + resources = [for secret in local.secret_environment_variables : secret.valueFrom] + } +} + +resource "aws_iam_policy" "execution_role_secrets_policy" { + count = var.launch_type == "FARGATE" ? 1 : 0 + name = "${var.deployment_name}-execution-role-secrets-policy" + policy = data.aws_iam_policy_document.execution_role_secrets_policy[0].json +} + +resource "aws_iam_role_policy_attachment" "execution_role_secrets" { + count = var.launch_type == "FARGATE" ? 1 : 0 + role = aws_iam_role.execution_role[0].name + policy_arn = aws_iam_policy.execution_role_secrets_policy[0].arn +} + # IAM Role for EC2 instances resource "aws_iam_instance_profile" "ec2" { count = var.launch_type == "EC2" ? 1 : 0 diff --git a/modules/aws_ecs/secrets.tf b/modules/aws_ecs/secrets.tf index 8223abf..7be0e61 100644 --- a/modules/aws_ecs/secrets.tf +++ b/modules/aws_ecs/secrets.tf @@ -57,3 +57,40 @@ resource "aws_secretsmanager_secret_version" "encryption_key" { secret_id = aws_secretsmanager_secret.encryption_key.id secret_string = random_string.encryption_key.result } + +resource "aws_secretsmanager_secret" "retool_license_key" { + count = var.retool_license_key != "" ? 1 : 0 + name = "${var.deployment_name}-retool-license-key" + description = "This is the Retool license key" + recovery_window_in_days = 0 +} + +resource "aws_secretsmanager_secret_version" "retool_license_key" { + count = var.retool_license_key != "" ? 1 : 0 + secret_id = aws_secretsmanager_secret.retool_license_key[0].id + secret_string = var.retool_license_key +} + +resource "aws_secretsmanager_secret" "temporal_tls_crt" { + count = var.temporal_cluster_config.tls_enabled && var.temporal_cluster_config.tls_crt != null ? 1 : 0 + name = "${var.deployment_name}-temporal-tls-crt" + description = "Temporal client certificate" +} + +resource "aws_secretsmanager_secret_version" "temporal_tls_crt" { + count = var.temporal_cluster_config.tls_enabled && var.temporal_cluster_config.tls_crt != null ? 1 : 0 + secret_id = aws_secretsmanager_secret.temporal_tls_crt[0].id + secret_string = var.temporal_cluster_config.tls_crt +} + +resource "aws_secretsmanager_secret" "temporal_tls_key" { + count = var.temporal_cluster_config.tls_enabled && var.temporal_cluster_config.tls_key != null ? 1 : 0 + name = "${var.deployment_name}-temporal-tls-key" + description = "Temporal client key" +} + +resource "aws_secretsmanager_secret_version" "temporal_tls_key" { + count = var.temporal_cluster_config.tls_enabled && var.temporal_cluster_config.tls_key != null ? 1 : 0 + secret_id = aws_secretsmanager_secret.temporal_tls_key[0].id + secret_string = var.temporal_cluster_config.tls_key +} diff --git a/modules/aws_ecs/temporal/locals.tf b/modules/aws_ecs/temporal/locals.tf index e4ef558..37ca26e 100644 --- a/modules/aws_ecs/temporal/locals.tf +++ b/modules/aws_ecs/temporal/locals.tf @@ -22,14 +22,6 @@ locals { "name": "POSTGRES_PORT", "value": tostring(module.temporal_aurora_rds.cluster_port) }, - { - "name": "POSTGRES_USER", - "value": var.temporal_aurora_username - }, - { - "name": "POSTGRES_PASSWORD", - "value": random_string.temporal_aurora_password.result - }, { "name": "DBNAME", "value": "temporal" diff --git a/modules/aws_ecs/temporal/main.tf b/modules/aws_ecs/temporal/main.tf index 9d9ef7d..e2cdfa7 100644 --- a/modules/aws_ecs/temporal/main.tf +++ b/modules/aws_ecs/temporal/main.tf @@ -93,7 +93,7 @@ resource "aws_ecs_service" "retool_temporal" { security_groups = [ var.container_sg_id ] - assign_public_ip = true + assign_public_ip = var.assign_public_ip } } } @@ -153,7 +153,17 @@ resource "aws_ecs_task_definition" "retool_temporal" { "value" : "${var.temporal_cluster_config.hostname}.${var.service_discovery_namespace}:${var.temporal_cluster_config.port}" } ] : [] - ) + ), + secrets = [ + { + name = "POSTGRES_USER", + valueFrom = aws_secretsmanager_secret.temporal_aurora_username.arn + }, + { + name = "POSTGRES_PASSWORD", + valueFrom = aws_secretsmanager_secret.temporal_aurora_password.arn + } + ] } ] ) diff --git a/modules/aws_ecs/temporal/roles.tf b/modules/aws_ecs/temporal/roles.tf index 6d6a23e..d004dd3 100644 --- a/modules/aws_ecs/temporal/roles.tf +++ b/modules/aws_ecs/temporal/roles.tf @@ -74,4 +74,30 @@ resource "aws_iam_role_policy_attachment" "execution_role" { count = var.launch_type == "FARGATE" ? 1 : 0 role = aws_iam_role.execution_role[0].name policy_arn = "arn:${var.iam_partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +data "aws_iam_policy_document" "secrets_manager_access" { + count = var.launch_type == "FARGATE" ? 1 : 0 + + statement { + effect = "Allow" + actions = ["secretsmanager:GetSecretValue"] + resources = [ + aws_secretsmanager_secret.temporal_aurora_password.arn, + aws_secretsmanager_secret.temporal_aurora_username.arn, + ] + } +} + +resource "aws_iam_policy" "secrets_manager_access" { + count = var.launch_type == "FARGATE" ? 1 : 0 + name = "${var.deployment_name}-secrets-manager-access" + path = "/" + policy = data.aws_iam_policy_document.secrets_manager_access[0].json +} + +resource "aws_iam_role_policy_attachment" "execution_role_secrets_manager_access" { + count = var.launch_type == "FARGATE" ? 1 : 0 + role = aws_iam_role.execution_role[0].name + policy_arn = aws_iam_policy.secrets_manager_access[0].arn } \ No newline at end of file diff --git a/modules/aws_ecs/temporal/variables.tf b/modules/aws_ecs/temporal/variables.tf index 9d29346..d5f98f2 100644 --- a/modules/aws_ecs/temporal/variables.tf +++ b/modules/aws_ecs/temporal/variables.tf @@ -74,6 +74,12 @@ variable "launch_type" { } } +variable "assign_public_ip" { + type = bool + description = "Whether to assign a public IP address to Temporal Fargate tasks. Defaults to false." + default = true +} + variable "temporal_aurora_username" { type = string default = "retool" diff --git a/modules/aws_ecs/variables.tf b/modules/aws_ecs/variables.tf index 81651d9..98da0e6 100644 --- a/modules/aws_ecs/variables.tf +++ b/modules/aws_ecs/variables.tf @@ -49,6 +49,18 @@ variable "min_instance_count" { default = 3 } +variable "associate_public_ip_address" { + type = bool + description = "Whether to associate a public IP address with an EC2 instance in a VPC. Defaults to true." + default = true +} + +variable "assign_public_ip" { + type = bool + description = "Whether to assign a public IP address to Fargate tasks. Defaults to false." + default = true +} + variable "deployment_name" { type = string description = "Name prefix for created resources. Defaults to `retool`." @@ -258,6 +270,18 @@ variable "rds_multi_az" { description = "Whether the RDS instance should have Multi-AZ enabled. Defaults to false." } +variable "rds_backup_retention_period" { + type = number + default = null + description = "The number of days to retain backups for. Must be between 0 and 35." +} + +variable "rds_backup_window" { + type = string + default = null + description = "The daily time range (in UTC) during which automated backups are created if automated backups are enabled." +} + variable "use_existing_temporal_cluster" { type = bool default = false @@ -421,6 +445,12 @@ variable "alb_http_redirect" { description = "Boolean for if http should redirect to https" } +variable "alb_internal" { + type = bool + default = false + description = "Whether to create an internal load balancer. Defaults to false." +} + variable "cookie_insecure" { type = bool default = true diff --git a/modules/aws_ecs_ec2/loadbalancers.tf b/modules/aws_ecs_ec2/loadbalancers.tf index a3eee4f..80c5c52 100644 --- a/modules/aws_ecs_ec2/loadbalancers.tf +++ b/modules/aws_ecs_ec2/loadbalancers.tf @@ -1,6 +1,7 @@ resource "aws_lb" "this" { name = "${var.deployment_name}-alb" idle_timeout = var.alb_idle_timeout + internal = var.alb_internal security_groups = [aws_security_group.alb.id] subnets = var.subnet_ids diff --git a/modules/aws_ecs_ec2/main.tf b/modules/aws_ecs_ec2/main.tf index 72f55ab..4a3ccde 100644 --- a/modules/aws_ecs_ec2/main.tf +++ b/modules/aws_ecs_ec2/main.tf @@ -42,7 +42,7 @@ resource "aws_launch_configuration" "this" { instance_type = var.instance_type # e.g. t2.medium enable_monitoring = true - associate_public_ip_address = true + associate_public_ip_address = var.associate_public_ip_address # This user data represents a collection of “scripts” that will be executed the first time the machine starts. # This specific example makes sure the EC2 instance is automatically attached to the ECS cluster that we create earlier @@ -157,6 +157,8 @@ resource "aws_db_instance" "this" { vpc_security_group_ids = [aws_security_group.rds.id] performance_insights_enabled = var.rds_performance_insights_enabled multi_az = var.rds_multi_az + backup_retention_period = var.rds_backup_retention_period + backup_window = var.rds_backup_window skip_final_snapshot = true apply_immediately = true diff --git a/modules/aws_ecs_ec2/variables.tf b/modules/aws_ecs_ec2/variables.tf index d0a43e0..6bc08b8 100644 --- a/modules/aws_ecs_ec2/variables.tf +++ b/modules/aws_ecs_ec2/variables.tf @@ -43,6 +43,12 @@ variable "min_instance_count" { default = 3 } +variable "associate_public_ip_address" { + type = bool + description = "Whether to associate a public IP address with an EC2 instance in a VPC. Defaults to true." + default = true +} + variable "deployment_name" { type = string description = "Name prefix for created resources. Defaults to `retool`." @@ -121,6 +127,18 @@ variable "rds_multi_az" { description = "Whether the RDS instance should have Multi-AZ enabled. Defaults to false." } +variable "rds_backup_retention_period" { + type = number + default = null + description = "The number of days to retain backups for. Must be between 0 and 35." +} + +variable "rds_backup_window" { + type = string + default = null + description = "The daily time range (in UTC) during which automated backups are created if automated backups are enabled." +} + variable "log_retention_in_days" { type = number default = 14 @@ -139,6 +157,12 @@ variable "cookie_insecure" { description = "Whether to allow insecure cookies. Should be turned off when serving on HTTPS. Defaults to true." } +variable "alb_internal" { + type = bool + default = false + description = "Whether to create an internal load balancer. Defaults to false." +} + variable "maximum_percent" { type = number default = 250