From f7586cd87c732cb641083b0151352a8ec2e70646 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 20 Oct 2025 17:10:57 -0400 Subject: [PATCH 1/5] feat(misc): random updates and improvements --- terraform/.gitignore | 1 + terraform/.terraform.lock.hcl | 44 ------ terraform/main.tf | 1 + terraform/modules/bastion/user_data.sh | 158 ++++++++++--------- terraform/modules/compute/main.tf | 10 ++ terraform/modules/dns/main.tf | 13 ++ terraform/modules/load_balancer/main.tf | 62 ++++++-- terraform/modules/load_balancer/variables.tf | 7 +- terraform/modules/networking/main.tf | 149 ++++++++++++++++- terraform/modules/security/main.tf | 10 +- 10 files changed, 313 insertions(+), 142 deletions(-) delete mode 100644 terraform/.terraform.lock.hcl diff --git a/terraform/.gitignore b/terraform/.gitignore index 7b18919..09a803e 100644 --- a/terraform/.gitignore +++ b/terraform/.gitignore @@ -69,6 +69,7 @@ terraform.rc # ======================================== *.tfplan *tfplan* +plan.out # ======================================== # SSH Keys and Certificates diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl deleted file mode 100644 index 2ce4ab1..0000000 --- a/terraform/.terraform.lock.hcl +++ /dev/null @@ -1,44 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.100.0" - constraints = "~> 5.0" - hashes = [ - "h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=", - "zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644", - "zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2", - "zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274", - "zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b", - "zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862", - "zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93", - "zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2", - "zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e", - "zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421", - "zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4", - "zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9", - "zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9", - "zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70", - ] -} - -provider "registry.terraform.io/hashicorp/random" { - version = "3.7.2" - hashes = [ - "h1:KG4NuIBl1mRWU0KD/BGfCi1YN/j3F7H4YgeeM7iSdNs=", - "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", - "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", - "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", - "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", - "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", - "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", - "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", - "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", - "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", - "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", - ] -} diff --git a/terraform/main.tf b/terraform/main.tf index 2472d2f..907baf9 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -181,6 +181,7 @@ module "load_balancer" { app_name = var.app_name environment = var.environment aws_region = var.aws_region + domain_name = var.domain_name vpc_id = module.networking.vpc_id public_subnets = module.networking.public_subnets alb_security_group_id = module.networking.alb_security_group_id diff --git a/terraform/modules/bastion/user_data.sh b/terraform/modules/bastion/user_data.sh index a896199..c9c7133 100755 --- a/terraform/modules/bastion/user_data.sh +++ b/terraform/modules/bastion/user_data.sh @@ -1,57 +1,90 @@ #!/bin/bash -yum update -y +set -euo pipefail + +BOOTSTRAP_SCRIPT="/usr/local/bin/bastion-bootstrap.sh" +LOG_FILE="/var/log/bastion-bootstrap.log" + +cat > "$BOOTSTRAP_SCRIPT" <<'SCRIPT' +#!/bin/bash +set -euo pipefail + +LOG_FILE="/var/log/bastion-bootstrap.log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +timestamp() { + date -u +"%Y-%m-%dT%H:%M:%SZ" +} + +log() { + echo "[$(timestamp)] $*" +} + +retry() { + local attempts="$1" + local delay="$2" + shift 2 + + local n=1 + until "$@"; do + if [ "$n" -ge "$attempts" ]; then + log "Command failed after $attempts attempts: $*" + return 1 + fi + log "Command failed (attempt $n/$attempts): $*; retrying in $${delay}s" + n=$((n + 1)) + sleep "$delay" + done +} + +if [ -f /var/log/bastion-bootstrap.done ]; then + log "Bootstrap already completed; exiting" + exit 0 +fi + +log "Starting bastion bootstrap" + +retry 5 30 yum update -y %{ if install_mysql_client ~} -# Install MySQL client -yum install -y mysql +log "Installing MySQL client" +retry 5 30 yum install -y mysql %{ endif ~} %{ if install_redis_client ~} -# Install Redis client -amazon-linux-extras install -y redis6 +log "Installing Redis client" +retry 5 30 amazon-linux-extras install -y redis6 %{ endif ~} -# Install useful tools -yum install -y htop nano vim curl wget git unzip jq - -# ======================================== -# Install AWS CLI v2 -# ======================================== -cd /tmp -curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" -unzip awscliv2.zip -./aws/install -rm -rf aws awscliv2.zip - -# ======================================== -# Install Terraform -# ======================================== +log "Installing base utilities" +retry 5 30 yum install -y htop nano vim curl wget git unzip jq + +log "Installing AWS CLI v2" +retry 5 30 bash -c 'cd /tmp && curl -sSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip' +retry 5 30 bash -c 'cd /tmp && unzip -o awscliv2.zip' +retry 5 30 bash -c 'cd /tmp && ./aws/install' +rm -rf /tmp/aws /tmp/awscliv2.zip + +log "Installing Terraform" TERRAFORM_VERSION="1.10.5" -cd /tmp -wget "https://releases.hashicorp.com/terraform/$${TERRAFORM_VERSION}/terraform_$${TERRAFORM_VERSION}_linux_amd64.zip" -unzip "terraform_$${TERRAFORM_VERSION}_linux_amd64.zip" -mv terraform /usr/local/bin/ -rm "terraform_$${TERRAFORM_VERSION}_linux_amd64.zip" +retry 5 30 bash -c "cd /tmp && wget -q \"https://releases.hashicorp.com/terraform/$${TERRAFORM_VERSION}/terraform_$${TERRAFORM_VERSION}_linux_amd64.zip\"" +retry 5 30 bash -c "cd /tmp && unzip -o \"terraform_$${TERRAFORM_VERSION}_linux_amd64.zip\"" +mv /tmp/terraform /usr/local/bin/ +rm -f /tmp/terraform_$${TERRAFORM_VERSION}_linux_amd64.zip chmod +x /usr/local/bin/terraform -# ======================================== -# Install Docker -# ======================================== -amazon-linux-extras install -y docker +log "Installing Docker engine" +retry 5 30 amazon-linux-extras install -y docker systemctl start docker systemctl enable docker - -# Add ec2-user to docker group usermod -aG docker ec2-user -# Install Docker Compose +log "Installing Docker Compose" DOCKER_COMPOSE_VERSION="2.34.1" -curl -L "https://github.com/docker/compose/releases/download/v$${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +retry 5 30 curl -sSL "https://github.com/docker/compose/releases/download/v$${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose -ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose +ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose -# Create a welcome message -cat > /etc/motd << 'EOF' +cat > /etc/motd <<'MOTD' ================================= Bastion Host ================================= @@ -67,18 +100,12 @@ Installed Tools: Use SSH tunneling to connect to private resources. ================================= -EOF +MOTD %{ if setup_mysql_user ~} -# ======================================== -# Setup MySQL Application User -# ======================================== -echo "Setting up MySQL application user..." >> /var/log/bastion-setup.log - -# Wait for instance profile and AWS CLI to be ready +log "Configuring MySQL users" sleep 30 -# Get the master password from Secrets Manager MASTER_PASSWORD=$(aws secretsmanager get-secret-value \ --secret-id "${rds_master_password_secret_arn}" \ --region "${aws_region}" \ @@ -86,55 +113,44 @@ MASTER_PASSWORD=$(aws secretsmanager get-secret-value \ --output text | jq -r '.password') if [ -z "$MASTER_PASSWORD" ]; then - echo "ERROR: Failed to retrieve master password from Secrets Manager" >> /var/log/bastion-setup.log + log "ERROR: Failed to retrieve master password from Secrets Manager" exit 1 fi -# Wait for RDS to be available -echo "Waiting for RDS to be available..." >> /var/log/bastion-setup.log -for i in {1..30}; do +for i in $(seq 1 30); do if mysql -h "${rds_endpoint}" -u "${rds_master_username}" -p"$MASTER_PASSWORD" -e "SELECT 1;" 2>/dev/null; then - echo "RDS is ready" >> /var/log/bastion-setup.log + log "RDS is ready" break fi - echo "Waiting for RDS... attempt $i/30" >> /var/log/bastion-setup.log + log "Waiting for RDS... attempt $i/30" sleep 10 done -# Create application database user with limited privileges -echo "Creating application user '${app_db_username}'..." >> /var/log/bastion-setup.log - mysql -h "${rds_endpoint}" -u "${rds_master_username}" -p"$MASTER_PASSWORD" <> /var/log/bastion-setup.log +MYSQL_EXIT_CODE=$? +unset MASTER_PASSWORD + +if [ "$MYSQL_EXIT_CODE" -eq 0 ]; then + log "MySQL application and reporting users created successfully" else - echo "ERROR: Failed to create MySQL users" >> /var/log/bastion-setup.log + log "ERROR: Failed to create MySQL users" fi - -# Clear the password from memory -unset MASTER_PASSWORD %{ endif ~} -echo "Bastion host setup completed" > /var/log/bastion-setup.log +touch /var/log/bastion-bootstrap.done +log "Bastion bootstrap completed" +SCRIPT + +chmod +x "$BOOTSTRAP_SCRIPT" +nohup "$BOOTSTRAP_SCRIPT" >/dev/null 2>&1 & diff --git a/terraform/modules/compute/main.tf b/terraform/modules/compute/main.tf index 52dcdaf..a099ecd 100644 --- a/terraform/modules/compute/main.tf +++ b/terraform/modules/compute/main.tf @@ -210,6 +210,16 @@ resource "aws_ecs_service" "main" { container_port = 80 } + # Deployment configuration to minimize ENI requirements during rolling updates + deployment_configuration { + deployment_minimum_healthy_percent = 50 + deployment_maximum_percent = 100 + deployment_circuit_breaker { + enable = true + rollback = true + } + } + # Give Laravel time to boot before health checks start health_check_grace_period_seconds = 120 diff --git a/terraform/modules/dns/main.tf b/terraform/modules/dns/main.tf index c052864..26ef72d 100644 --- a/terraform/modules/dns/main.tf +++ b/terraform/modules/dns/main.tf @@ -28,6 +28,19 @@ resource "aws_route53_record" "wildcard" { } } +# WWW subdomain A record (for www to non-www redirect) +resource "aws_route53_record" "www" { + zone_id = var.route53_zone_id + name = "www.${var.domain_name}" + type = "A" + + alias { + name = var.alb_dns_name + zone_id = var.alb_zone_id + evaluate_target_health = true + } +} + # DMARC record (only created if dmarc_record is set) resource "aws_route53_record" "dmarc" { count = var.dmarc_record != "" ? 1 : 0 diff --git a/terraform/modules/load_balancer/main.tf b/terraform/modules/load_balancer/main.tf index 9c0e271..c0ba50c 100644 --- a/terraform/modules/load_balancer/main.tf +++ b/terraform/modules/load_balancer/main.tf @@ -35,8 +35,8 @@ resource "aws_wafv2_web_acl" "main" { visibility_config { cloudwatch_metrics_enabled = true - metric_name = "CommonRuleSetMetric" - sampled_requests_enabled = true + metric_name = "CommonRuleSetMetric" + sampled_requests_enabled = true } } @@ -58,8 +58,8 @@ resource "aws_wafv2_web_acl" "main" { visibility_config { cloudwatch_metrics_enabled = true - metric_name = "KnownBadInputsRuleSetMetric" - sampled_requests_enabled = true + metric_name = "KnownBadInputsRuleSetMetric" + sampled_requests_enabled = true } } @@ -81,8 +81,8 @@ resource "aws_wafv2_web_acl" "main" { visibility_config { cloudwatch_metrics_enabled = true - metric_name = "AmazonIpReputationListMetric" - sampled_requests_enabled = true + metric_name = "AmazonIpReputationListMetric" + sampled_requests_enabled = true } } @@ -104,8 +104,8 @@ resource "aws_wafv2_web_acl" "main" { visibility_config { cloudwatch_metrics_enabled = true - metric_name = "BotControlRuleSetMetric" - sampled_requests_enabled = true + metric_name = "BotControlRuleSetMetric" + sampled_requests_enabled = true } } @@ -127,15 +127,15 @@ resource "aws_wafv2_web_acl" "main" { visibility_config { cloudwatch_metrics_enabled = true - metric_name = "RateLimitRule" - sampled_requests_enabled = true + metric_name = "RateLimitRule" + sampled_requests_enabled = true } } visibility_config { cloudwatch_metrics_enabled = true - metric_name = "${var.app_name}-${var.environment}-waf" - sampled_requests_enabled = true + metric_name = "${var.app_name}-${var.environment}-waf" + sampled_requests_enabled = true } tags = merge(var.common_tags, { @@ -147,6 +147,10 @@ resource "aws_wafv2_web_acl" "main" { # Application Load Balancer # ======================================== +locals { + domain_starts_with_www = startswith(lower(var.domain_name), "www.") +} + resource "aws_lb" "main" { name = "${var.app_name}-${var.environment}-alb" internal = false @@ -178,7 +182,7 @@ resource "aws_lb_target_group" "main" { health_check { enabled = true healthy_threshold = 2 - interval = 30 + interval = 15 matcher = "200" path = "/health" port = "traffic-port" @@ -187,7 +191,7 @@ resource "aws_lb_target_group" "main" { unhealthy_threshold = 2 } - deregistration_delay = 30 + deregistration_delay = 15 tags = merge(var.common_tags, { Name = "${var.app_name}-${var.environment}-tg" @@ -227,6 +231,34 @@ resource "aws_lb_listener" "https" { } depends_on = [aws_lb_target_group.main] + tags = var.common_tags +} + +# HTTPS Listener Rule - Redirect www to non-www +resource "aws_lb_listener_rule" "redirect_www" { + count = local.domain_starts_with_www ? 0 : 1 + listener_arn = aws_lb_listener.https.arn + priority = 1 + + action { + type = "redirect" + + redirect { + host = var.domain_name + port = "443" + protocol = "HTTPS" + path = "/#{path}" + query = "#{query}" + status_code = "HTTP_301" + } + } + + condition { + host_header { + values = ["www.${var.domain_name}"] + } + } + tags = var.common_tags } @@ -234,4 +266,4 @@ resource "aws_lb_listener" "https" { resource "aws_wafv2_web_acl_association" "main" { resource_arn = aws_lb.main.arn web_acl_arn = aws_wafv2_web_acl.main.arn -} \ No newline at end of file +} diff --git a/terraform/modules/load_balancer/variables.tf b/terraform/modules/load_balancer/variables.tf index e4a65fe..7f5aa3d 100644 --- a/terraform/modules/load_balancer/variables.tf +++ b/terraform/modules/load_balancer/variables.tf @@ -13,6 +13,11 @@ variable "aws_region" { type = string } +variable "domain_name" { + description = "Primary domain name for the application" + type = string +} + variable "vpc_id" { description = "VPC ID" type = string @@ -47,4 +52,4 @@ variable "enable_access_logs" { variable "common_tags" { description = "Common tags for all resources" type = map(string) -} \ No newline at end of file +} diff --git a/terraform/modules/networking/main.tf b/terraform/modules/networking/main.tf index 17b6c89..9811645 100644 --- a/terraform/modules/networking/main.tf +++ b/terraform/modules/networking/main.tf @@ -10,15 +10,15 @@ module "vpc" { name = "${var.app_name}-${var.environment}-vpc" cidr = var.vpc_cidr - azs = slice(var.availability_zones, 0, 3) # Use only first 3 AZs + azs = slice(var.availability_zones, 0, 3) # Use only first 3 AZs private_subnets = [for k in range(3) : cidrsubnet(var.vpc_cidr, 4, k)] public_subnets = [for k in range(3) : cidrsubnet(var.vpc_cidr, 4, k + 4)] - enable_nat_gateway = true - single_nat_gateway = true - enable_vpn_gateway = false + enable_nat_gateway = true + single_nat_gateway = true + enable_vpn_gateway = false enable_dns_hostnames = true - enable_dns_support = true + enable_dns_support = true tags = var.common_tags } @@ -134,4 +134,141 @@ module "vpn_security_group" { tags = merge(var.common_tags, { Name = "${var.app_name}-${var.environment}-vpn-sg" }) -} \ No newline at end of file +} + +# VPC Endpoints Security Group +module "vpc_endpoints_security_group" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = "${var.app_name}-${var.environment}-vpc-endpoints-sg" + description = "Security group for VPC Interface Endpoints" + vpc_id = module.vpc.vpc_id + + ingress_rules = ["https-443-tcp"] + ingress_cidr_blocks = [var.vpc_cidr] + + egress_rules = ["all-all"] + egress_cidr_blocks = ["0.0.0.0/0"] + + tags = merge(var.common_tags, { + Name = "${var.app_name}-${var.environment}-vpc-endpoints-sg" + }) +} + +# ======================================== +# VPC Interface Endpoints +# ======================================== + +locals { + interface_endpoint_tags = merge(var.common_tags, { + TerraformComponent = "vpc-endpoints" + }) +} + +resource "aws_vpc_endpoint" "ssm" { + service_name = "com.amazonaws.${var.aws_region}.ssm" + vpc_id = module.vpc.vpc_id + vpc_endpoint_type = "Interface" + subnet_ids = module.vpc.private_subnets + security_group_ids = [module.vpc_endpoints_security_group.security_group_id] + private_dns_enabled = true + + tags = merge(local.interface_endpoint_tags, { + Name = "${var.app_name}-${var.environment}-ssm-endpoint" + }) +} + +resource "aws_vpc_endpoint" "ssmmessages" { + service_name = "com.amazonaws.${var.aws_region}.ssmmessages" + vpc_id = module.vpc.vpc_id + vpc_endpoint_type = "Interface" + subnet_ids = module.vpc.private_subnets + security_group_ids = [module.vpc_endpoints_security_group.security_group_id] + private_dns_enabled = true + + tags = merge(local.interface_endpoint_tags, { + Name = "${var.app_name}-${var.environment}-ssmmessages-endpoint" + }) +} + +resource "aws_vpc_endpoint" "ec2messages" { + service_name = "com.amazonaws.${var.aws_region}.ec2messages" + vpc_id = module.vpc.vpc_id + vpc_endpoint_type = "Interface" + subnet_ids = module.vpc.private_subnets + security_group_ids = [module.vpc_endpoints_security_group.security_group_id] + private_dns_enabled = true + + tags = merge(local.interface_endpoint_tags, { + Name = "${var.app_name}-${var.environment}-ec2messages-endpoint" + }) +} + +resource "aws_vpc_endpoint" "ecr_api" { + service_name = "com.amazonaws.${var.aws_region}.ecr.api" + vpc_id = module.vpc.vpc_id + vpc_endpoint_type = "Interface" + subnet_ids = module.vpc.private_subnets + security_group_ids = [module.vpc_endpoints_security_group.security_group_id] + private_dns_enabled = true + + tags = merge(local.interface_endpoint_tags, { + Name = "${var.app_name}-${var.environment}-ecr-api-endpoint" + }) +} + +resource "aws_vpc_endpoint" "ecr_dkr" { + service_name = "com.amazonaws.${var.aws_region}.ecr.dkr" + vpc_id = module.vpc.vpc_id + vpc_endpoint_type = "Interface" + subnet_ids = module.vpc.private_subnets + security_group_ids = [module.vpc_endpoints_security_group.security_group_id] + private_dns_enabled = true + + tags = merge(local.interface_endpoint_tags, { + Name = "${var.app_name}-${var.environment}-ecr-dkr-endpoint" + }) +} + +resource "aws_vpc_endpoint" "logs" { + service_name = "com.amazonaws.${var.aws_region}.logs" + vpc_id = module.vpc.vpc_id + vpc_endpoint_type = "Interface" + subnet_ids = module.vpc.private_subnets + security_group_ids = [module.vpc_endpoints_security_group.security_group_id] + private_dns_enabled = true + + tags = merge(local.interface_endpoint_tags, { + Name = "${var.app_name}-${var.environment}-logs-endpoint" + }) +} + +resource "aws_vpc_endpoint" "sqs" { + service_name = "com.amazonaws.${var.aws_region}.sqs" + vpc_id = module.vpc.vpc_id + vpc_endpoint_type = "Interface" + subnet_ids = module.vpc.private_subnets + security_group_ids = [module.vpc_endpoints_security_group.security_group_id] + private_dns_enabled = true + + tags = merge(local.interface_endpoint_tags, { + Name = "${var.app_name}-${var.environment}-sqs-endpoint" + }) +} + +# ======================================== +# VPC Gateway Endpoints +# ======================================== + +resource "aws_vpc_endpoint" "s3" { + service_name = "com.amazonaws.${var.aws_region}.s3" + vpc_id = module.vpc.vpc_id + vpc_endpoint_type = "Gateway" + route_table_ids = concat(module.vpc.private_route_table_ids, module.vpc.public_route_table_ids) + + tags = merge(var.common_tags, { + Name = "${var.app_name}-${var.environment}-s3-endpoint" + TerraformComponent = "vpc-endpoints" + }) +} diff --git a/terraform/modules/security/main.tf b/terraform/modules/security/main.tf index 3282181..2cdce8c 100644 --- a/terraform/modules/security/main.tf +++ b/terraform/modules/security/main.tf @@ -345,14 +345,14 @@ resource "aws_iam_role_policy" "github_actions_policy" { "s3:PutObject", "s3:DeleteObject" ] - Resource = "arn:aws:s3:::${var.app_name}-terraform-state-bucket/*" + Resource = "arn:aws:s3:::laravel-terraform-state-bucket/*" }, { Effect = "Allow" Action = [ "s3:ListBucket" ] - Resource = "arn:aws:s3:::${var.app_name}-terraform-state-bucket" + Resource = "arn:aws:s3:::laravel-terraform-state-bucket" }, { Effect = "Allow" @@ -361,7 +361,7 @@ resource "aws_iam_role_policy" "github_actions_policy" { "dynamodb:PutItem", "dynamodb:DeleteItem" ] - Resource = "arn:aws:dynamodb:${var.aws_region}:${var.caller_identity_account_id}:table/${var.app_name}-terraform-locks" + Resource = "arn:aws:dynamodb:${var.aws_region}:${var.caller_identity_account_id}:table/laravel-terraform-locks" }, { Effect = "Allow" @@ -395,8 +395,8 @@ resource "aws_iam_role_policy" "github_actions_policy" { "s3:*" ] Resource = [ - "arn:aws:s3:::${var.app_name}-*", - "arn:aws:s3:::${var.app_name}-*/*" + "arn:aws:s3:::laravel-*", + "arn:aws:s3:::laravel-*/*" ] }, { From 1698d9b5a4e820c4575e47a2e463512cdaef8693 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 20 Oct 2025 17:18:35 -0400 Subject: [PATCH 2/5] fix: using variable --- terraform/modules/security/main.tf | 16 ++++++++-------- terraform/variables.tf | 2 +- terraform/versions.tf | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/terraform/modules/security/main.tf b/terraform/modules/security/main.tf index 2cdce8c..5d4eabd 100644 --- a/terraform/modules/security/main.tf +++ b/terraform/modules/security/main.tf @@ -345,14 +345,14 @@ resource "aws_iam_role_policy" "github_actions_policy" { "s3:PutObject", "s3:DeleteObject" ] - Resource = "arn:aws:s3:::laravel-terraform-state-bucket/*" + Resource = "arn:aws:s3:::${var.app_name}-terraform-state-bucket/*" }, { Effect = "Allow" Action = [ "s3:ListBucket" ] - Resource = "arn:aws:s3:::laravel-terraform-state-bucket" + Resource = "arn:aws:s3:::${var.app_name}-terraform-state-bucket" }, { Effect = "Allow" @@ -361,7 +361,7 @@ resource "aws_iam_role_policy" "github_actions_policy" { "dynamodb:PutItem", "dynamodb:DeleteItem" ] - Resource = "arn:aws:dynamodb:${var.aws_region}:${var.caller_identity_account_id}:table/laravel-terraform-locks" + Resource = "arn:aws:dynamodb:${var.aws_region}:${var.caller_identity_account_id}:table/${var.app_name}-terraform-locks" }, { Effect = "Allow" @@ -395,8 +395,8 @@ resource "aws_iam_role_policy" "github_actions_policy" { "s3:*" ] Resource = [ - "arn:aws:s3:::laravel-*", - "arn:aws:s3:::laravel-*/*" + "arn:aws:s3:::${var.app_name}-*", + "arn:aws:s3:::${var.app_name}-*/*" ] }, { @@ -416,10 +416,10 @@ resource "aws_iam_role_policy" "github_actions_policy" { # IAM user for Laravel application with same permissions as ECS task role resource "aws_iam_user" "laravel_app_user" { - name = "${var.app_name}-${var.environment}-laravel-user" + name = "${var.app_name}-${var.environment}-${var.app_name}-user" tags = merge(var.common_tags, { - Name = "${var.app_name}-${var.environment}-laravel-user" + Name = "${var.app_name}-${var.environment}-${var.app_name}-user" }) } @@ -430,7 +430,7 @@ resource "aws_iam_access_key" "laravel_app_user" { # Attach the same policy as ECS task role to Laravel user resource "aws_iam_user_policy" "laravel_app_user_policy" { - name = "${var.app_name}-${var.environment}-laravel-user-policy" + name = "${var.app_name}-${var.environment}-${var.app_name}-user-policy" user = aws_iam_user.laravel_app_user.name policy = jsonencode({ diff --git a/terraform/variables.tf b/terraform/variables.tf index 82b0ef8..6e410e7 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -11,7 +11,7 @@ variable "aws_region" { variable "app_name" { description = "Application name" type = string - default = "laravel-app" + default = "${var.app_name}-app" } variable "app_key" { diff --git a/terraform/versions.tf b/terraform/versions.tf index 2cfb86c..5bca8a7 100644 --- a/terraform/versions.tf +++ b/terraform/versions.tf @@ -14,12 +14,12 @@ terraform { # S3 backend for state management # Uses Terraform workspaces for environment isolation backend "s3" { - bucket = "laravel-terraform-state-bucket" + bucket = "${var.app_name}-terraform-state-bucket" key = "laravel/terraform.tfstate" workspace_key_prefix = "env" region = "us-east-1" encrypt = true - # dynamodb_table = "laravel-terraform-locks" + # dynamodb_table = "${var.app_name}-terraform-locks" } } From 11a427272ed72aeb2a1353ad2adb2f46024cbb3b Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 20 Oct 2025 17:28:47 -0400 Subject: [PATCH 3/5] fix: deployment config --- terraform/modules/compute/main.tf | 13 ++++++------- terraform/versions.tf | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/terraform/modules/compute/main.tf b/terraform/modules/compute/main.tf index a099ecd..d5ec0e2 100644 --- a/terraform/modules/compute/main.tf +++ b/terraform/modules/compute/main.tf @@ -211,13 +211,12 @@ resource "aws_ecs_service" "main" { } # Deployment configuration to minimize ENI requirements during rolling updates - deployment_configuration { - deployment_minimum_healthy_percent = 50 - deployment_maximum_percent = 100 - deployment_circuit_breaker { - enable = true - rollback = true - } + deployment_minimum_healthy_percent = 50 + deployment_maximum_percent = 100 + + deployment_circuit_breaker { + enable = true + rollback = true } # Give Laravel time to boot before health checks start diff --git a/terraform/versions.tf b/terraform/versions.tf index 5bca8a7..9c29980 100644 --- a/terraform/versions.tf +++ b/terraform/versions.tf @@ -15,7 +15,7 @@ terraform { # Uses Terraform workspaces for environment isolation backend "s3" { bucket = "${var.app_name}-terraform-state-bucket" - key = "laravel/terraform.tfstate" + key = "${var.app_name}/terraform.tfstate" workspace_key_prefix = "env" region = "us-east-1" encrypt = true From e4a597833c38503b5e1aaa75600e14b840dc6e1c Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 20 Oct 2025 17:35:28 -0400 Subject: [PATCH 4/5] fix: self ref var --- terraform/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/variables.tf b/terraform/variables.tf index 6e410e7..82b0ef8 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -11,7 +11,7 @@ variable "aws_region" { variable "app_name" { description = "Application name" type = string - default = "${var.app_name}-app" + default = "laravel-app" } variable "app_key" { From 96051d81d72b3f87623276ac803059e6ebe6f586 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 20 Oct 2025 17:36:06 -0400 Subject: [PATCH 5/5] fix: var inter --- terraform/versions.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/versions.tf b/terraform/versions.tf index 9c29980..2cfb86c 100644 --- a/terraform/versions.tf +++ b/terraform/versions.tf @@ -14,12 +14,12 @@ terraform { # S3 backend for state management # Uses Terraform workspaces for environment isolation backend "s3" { - bucket = "${var.app_name}-terraform-state-bucket" - key = "${var.app_name}/terraform.tfstate" + bucket = "laravel-terraform-state-bucket" + key = "laravel/terraform.tfstate" workspace_key_prefix = "env" region = "us-east-1" encrypt = true - # dynamodb_table = "${var.app_name}-terraform-locks" + # dynamodb_table = "laravel-terraform-locks" } }