From 18f464dc06992b7e7bd3a74cc83c2932c54d4172 Mon Sep 17 00:00:00 2001 From: Isaac T Date: Tue, 28 Oct 2025 22:46:27 -0400 Subject: [PATCH 1/3] feat(route53): add Route 53 A records - Use data source to fetch hosted zone and create apex A record for each subdomain (dev/demo) --- route53_records.tf | 17 +++++++++++++++++ variables.tf | 23 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 route53_records.tf diff --git a/route53_records.tf b/route53_records.tf new file mode 100644 index 0000000..261c7f5 --- /dev/null +++ b/route53_records.tf @@ -0,0 +1,17 @@ +data "aws_route53_zone" "env" { + name = var.route53_zone_name + private_zone = false +} + +# Safety check — only create the record if an IP is found +resource "aws_route53_record" "env_apex_a" { + count = aws_instance.app.public_ip != "" ? 1 : 0 + + zone_id = data.aws_route53_zone.env.zone_id + name = var.record_name + type = "A" + ttl = var.record_ttl + records = aws_instance.app.public_ip + + allow_overwrite = true +} diff --git a/variables.tf b/variables.tf index 291aca1..e894f72 100644 --- a/variables.tf +++ b/variables.tf @@ -150,6 +150,29 @@ variable "s3_prefix" { default = "" } +variable "route53_zone_name" { + description = "Public hosted zone name for this environment (e.g., dev.domain.tld or demo.domain.tld)" + type = string +} + +variable "app_public_ip" { + description = "Fallback IP if no in-plan EC2 resource is referenced" + type = string + default = "" +} + +variable "record_name" { + description = "Record name inside the zone; empty string for apex" + type = string + default = "" +} + +variable "record_ttl" { + description = "TTL for A records" + type = number + default = 300 +} + variable "tags" { description = "Common tags applied to all resources" type = map(string) From 4482b26a89b4a995bfd27dc28f853826fe21c73d Mon Sep 17 00:00:00 2001 From: Isaac T Date: Thu, 30 Oct 2025 14:38:03 -0400 Subject: [PATCH 2/3] feat(terraform): add conditional Route53 DNS creation and secret cleanup on destroy - Added `create_dns_record` variable to toggle DNS creation per environment. --- rds.tf | 5 +++-- route53_records.tf | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/rds.tf b/rds.tf index 60ecabb..704426d 100644 --- a/rds.tf +++ b/rds.tf @@ -26,8 +26,9 @@ resource "random_password" "rds" { # Store the generated pwd in AWS Secrets Manager resource "aws_secretsmanager_secret" "rds" { - name = "${var.name_prefix}-rds-master-password" - description = "Master password for the ${var.name_prefix} RDS instance" + name = "${var.name_prefix}-rds-master-strong-password" + description = "Master password for the ${var.name_prefix} RDS instance" + recovery_window_in_days = 0 } resource "aws_secretsmanager_secret_version" "rds" { diff --git a/route53_records.tf b/route53_records.tf index 261c7f5..ee05697 100644 --- a/route53_records.tf +++ b/route53_records.tf @@ -1,3 +1,10 @@ +# Decide at plan time if we create DNS (e.g., public subnet envs) +variable "create_dns_record" { + type = bool + description = "Whether to create the Route53 A record for the app." + default = true +} + data "aws_route53_zone" "env" { name = var.route53_zone_name private_zone = false @@ -5,13 +12,13 @@ data "aws_route53_zone" "env" { # Safety check — only create the record if an IP is found resource "aws_route53_record" "env_apex_a" { - count = aws_instance.app.public_ip != "" ? 1 : 0 + count = var.create_dns_record != "" ? 1 : 0 zone_id = data.aws_route53_zone.env.zone_id name = var.record_name type = "A" ttl = var.record_ttl - records = aws_instance.app.public_ip + records = [aws_instance.app.public_ip] allow_overwrite = true } From 6f1aecd8b44396829e9d5ae7dd9708306e0eca1c Mon Sep 17 00:00:00 2001 From: Isaac T Date: Thu, 30 Oct 2025 14:49:09 -0400 Subject: [PATCH 3/3] feat(cloudwatch): Add cloudwatch agent --- iam_role.tf | 18 ++++++++++++++++++ scripts/user_data.sh | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/iam_role.tf b/iam_role.tf index 661f53e..41d7f14 100644 --- a/iam_role.tf +++ b/iam_role.tf @@ -82,3 +82,21 @@ resource "aws_iam_role_policy_attachment" "s3_access" { role = aws_iam_role.app_ec2_role.name policy_arn = aws_iam_policy.s3_app_policy.arn } + +# ----------- +# CloudWatch +# ----------- + +# Grants CloudWatch Agent permissions to create log groups/streams and put log events, +# and to send custom metrics (PutMetricData). +resource "aws_iam_role_policy_attachment" "cloudwatch_agent" { + role = aws_iam_role.app_ec2_role.name + policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy" +} + +# Enables SSM connectivity (optional but highly recommended to manage the instance/agent +# without SSH and to fetch agent binaries or run commands). +resource "aws_iam_role_policy_attachment" "ssm_core" { + role = aws_iam_role.app_ec2_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} diff --git a/scripts/user_data.sh b/scripts/user_data.sh index 2012d99..4727a6d 100644 --- a/scripts/user_data.sh +++ b/scripts/user_data.sh @@ -32,3 +32,36 @@ systemctl enable "${service_name}.service" systemctl restart "${service_name}.service" info "=== Web App started successfully ===" + +# --------------------------------------------------------------------------- +# CloudWatch Agent: Refresh and start using baked config (from Packer) +# --------------------------------------------------------------------------- +CWA_BIN="/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl" +CWA_CFG="/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json" + +info "=== Preparing CloudWatch Agent ===" + +# Ensure application log directory exists (for ${app_dir}/log/app.log) +install -d -m 0755 -o "${app_user}" -g "${app_group}" "${app_dir}/log" || true + +if [ -x "$CWA_BIN" ] && [ -f "$CWA_CFG" ]; then + info "CloudWatch Agent binary and config found. Enabling and refreshing..." + + # Enable on boot (idempotent) + systemctl enable amazon-cloudwatch-agent || true + + # Stop agent if running (safe if it's not) + "$CWA_BIN" -a stop || true + + # Fetch baked config and start + "$CWA_BIN" -a fetch-config -m ec2 -c file:"$CWA_CFG" -s + + # Check service status (non-fatal) + systemctl status amazon-cloudwatch-agent --no-pager || true + + info "CloudWatch Agent started with baked config." +else + err "CloudWatch Agent binary or config not found. Skipping agent start." +fi + +info "=== User data script completed successfully ==="