Skip to content

Terraform#1232

Open
gyoge0 wants to merge 18 commits intodevfrom
terraform-yogesh
Open

Terraform#1232
gyoge0 wants to merge 18 commits intodevfrom
terraform-yogesh

Conversation

@gyoge0
Copy link
Copy Markdown
Collaborator

@gyoge0 gyoge0 commented Mar 31, 2026

Add a terraform implementation to recreate our stack from scratch.

I recommend adding a "Owner" tag in main.tf before you try to deploy this, so that you can see who created each resource. You also need "thecourseforumtest" to be an active zone in the account which you try it out in, which for now is only in the dev account in the AWS org.

Please merge with "rebase" (and not any of the other merge methods) + lmk if any issues

Summary by CodeRabbit

  • New Features

    • App deployed to AWS with HTTPS on a custom domain, CDN-backed static assets, Cognito-based auth with hosted UI branding, PostgreSQL storage, ECS containers behind an ALB, CloudWatch logging, and a lightweight /health readiness endpoint.
  • Chores

    • Infrastructure added for networking, IAM, secrets, ECR, RDS, S3, CloudFront, Route53, outputs, and Terraform configuration.
    • Container startup clarified: container runs the startup script and listens on port 8000.
  • Style

    • IaC .gitignore added to ignore Terraform/local artifacts.

@gyoge0 gyoge0 requested a review from Jay-Lalwani March 31, 2026 19:53
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a complete Terraform-based AWS infrastructure (VPC, subnets, IGW, route tables, ALB, CloudFront, S3, ECR, ECS Fargate, RDS, Cognito, ACM, Secrets Manager, IAM, CloudWatch), plus container/runtime tweaks (Docker ENTRYPOINT, Gunicorn port 8000, health-check middleware, small prod settings change).

Changes

Cohort / File(s) Summary
Container & Django
Dockerfile, scripts/container-startup.sh, tcf_core/settings/health_check_middleware.py, tcf_core/settings/base.py, tcf_core/settings/prod.py
Add Docker ENTRYPOINT to run /app/scripts/container-startup.sh; change Gunicorn bind to 0.0.0.0:8000; add HealthCheckMiddleware and register it; remove reading AWS creds in prod; add STORAGES["staticfiles"].OPTIONS.location.
Terraform root & config
iac/terraform.tf, iac/main.tf, iac/variables.tf, iac/locals.tf, iac/.gitignore, iac/data.tf, iac/outputs.tf
Initialize Terraform settings/providers (aws ~>6.0, random ~>3.6, required_version >=1.2), add AWS provider + us-east-1 alias and default tags, declare variables/locals, add Route53/CloudFront data sources, and many outputs.
Networking & security
iac/networking.tf, iac/security-groups.tf
Create VPC, public/private subnets per AZ, IGW, public/private route tables, and security groups for ALB (CloudFront prefix list), ECS tasks (port 8000 from ALB), and RDS (5432 from ECS).
Load balancing & TLS
iac/alb.tf, iac/acm.tf, iac/route53.tf
Provision internet-facing ALB, HTTP→HTTPS redirect listener, HTTPS listener using ACM cert; request ACM cert in us-east-1 with DNS validation and create Route53 alias for CloudFront.
CDN & static hosting
iac/cloudfront.tf, iac/s3.tf
Add CloudFront distribution with S3 and ALB origins, OAC, behavior rules for dynamic/static content; add S3 bucket for static assets with public-access-block, CORS, versioning, and bucket policy.
ECR, ECS & logging
iac/ecr.tf, iac/ecs.tf, iac/cloudwatch.tf, iac/iam.tf
Create ECR repo + lifecycle policy; ECS cluster, task definition (runs /app/scripts/container-startup.sh, uses secrets, logs to CloudWatch) and Fargate service registered to ALB; add CloudWatch log group and IAM roles/policies for task execution, secrets access, and S3 access.
Database & secrets
iac/rds.tf, iac/secrets.tf
Provision RDS PostgreSQL with subnet group and final-snapshot suffix; generate passwords and create Secrets Manager secrets/versions for Django SECRET_KEY, DB credentials, and Cognito credentials.
Authentication & branding
iac/cognito.tf, iac/branding-settings.json
Add Cognito user pool, app client, hosted UI domain, managed login branding (branding-settings JSON).

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant CF as CloudFront
    participant ALB as ALB
    participant ECS as ECS (Fargate)
    participant RDS as RDS
    participant S3 as S3
    participant Cognito as Cognito

    Client->>CF: Request for example.com/*
    CF->>CF: Route by cache behavior / path
    alt Static asset
        CF->>S3: GET object (OAC)
        S3-->>CF: Object
        CF-->>Client: Return static asset
    else Dynamic app request
        CF->>ALB: Forward (HTTPS)
        ALB->>ECS: Forward to target group (port 8000)
        ECS->>ECS: HealthCheckMiddleware handles /health
        ECS->>RDS: Query DB as needed
        RDS-->>ECS: DB response
        ECS-->>ALB: Response
        ALB-->>CF: Response
        CF-->>Client: Rendered response
    end

    alt Authentication flow
        Client->>Cognito: Hosted UI /token
        Cognito-->>Client: Tokens
        Client->>ALB: Requests with token
        ALB->>ECS: Forward authenticated request
    end
Loading

Estimated Code Review Effort

🎯 5 (Critical) | ⏱️ ~120 minutes

"I hopped through code and cloud with cheer,
New services sprout, the infra's here.
Containers hum on port eight-thousand,
Certs and caches all well-founded.
—A joyful rabbit 🐇"

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description deviates significantly from the template by omitting required sections (GitHub Issues addressed, Screenshots, Testing) and providing minimal context about what was implemented, though it does explain deployment prerequisites and merge instructions. Expand the description to include: (1) GitHub Issues this closes, (2) a 'What I did' section detailing the major infrastructure components added (VPC, RDS, ECS, CloudFront, Cognito, etc.), (3) testing instructions for reviewers, and (4) any configuration steps needed before deployment.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Terraform' is extremely vague and generic, failing to communicate the specific purpose or scope of the changes despite this being a substantial infrastructure-as-code implementation. Replace with a more descriptive title such as 'Add Terraform IaC for complete AWS infrastructure stack' or 'Implement Terraform configuration to provision AWS infrastructure (VPC, RDS, ECS, CloudFront, etc.)' to clearly convey the PR's primary objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch terraform-yogesh

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (4)
test_django_app/scripts/container-startup.sh-6-8 (1)

6-8: ⚠️ Potential issue | 🟡 Minor

Consider migration strategy for multi-replica ECS deployments.

Running migrations at container startup can cause race conditions when multiple ECS tasks start simultaneously, potentially leading to migration conflicts or duplicate migration attempts.

Consider either:

  1. Running migrations as a separate one-off ECS task before deployment
  2. Adding a migration lock mechanism
  3. Using ECS deployment circuit breaker with a dedicated migration container

Do you want me to help design a separate migration task approach for the Terraform ECS configuration?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/scripts/container-startup.sh` around lines 6 - 8, The startup
script currently runs migrations unconditionally (python manage.py migrate
--noinput) which causes race conditions in multi-replica ECS; remove that line
from container-startup.sh and instead implement one of the suggested strategies:
(A) create a separate one-off ECS task in your Terraform config that runs
manage.py migrate once before service deployment and remove migrations from the
container image, or (B) if you must run in-container, add a migration guard such
as an environment-controlled flag (e.g., RUN_MIGRATIONS) and a distributed lock
(DynamoDB/SSM parameter or django-locking) around the migrate call to ensure
only one replica runs migrations (wrap logic that invokes python manage.py
migrate). Ensure the chosen approach is wired into Terraform ECS deployment
(task definition for the one-off task or env var/lock resources for guarded
in-container runs).
iac/s3.tf-2-9 (1)

2-9: ⚠️ Potential issue | 🟡 Minor

force_destroy = true will delete all objects when bucket is destroyed.

This is dangerous for production as it allows Terraform to delete the bucket even when it contains static assets. Consider setting to false for production environments or using a variable to control this behavior per environment.

♻️ Proposed fix
 resource "aws_s3_bucket" "static" {
   bucket_prefix  = "${local.name_prefix}-static-"
-  force_destroy  = true
+  force_destroy  = var.environment == "dev" ? true : false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/s3.tf` around lines 2 - 9, The aws_s3_bucket resource
aws_s3_bucket.static currently sets force_destroy = true which allows Terraform
to delete all objects when the bucket is destroyed; change this to a safer
default by replacing the hardcoded true with a configurable value (e.g., use a
variable like var.s3_force_destroy or an environment-specific local) and set the
production default to false so production runs do not purge bucket contents
unless explicitly overridden; update any relevant variable declarations and
documentation to reflect the new var (and ensure the resource uses
aws_s3_bucket.static.force_destroy = var.s3_force_destroy).
test_django_app/README.md-91-113 (1)

91-113: ⚠️ Potential issue | 🟡 Minor

Project structure shows config/ but actual directory is tcf_core/.

The directory tree references config/ and config/settings/, but based on the actual file paths in this PR (e.g., tcf_core/settings/base.py), the Django project directory is tcf_core/.

📝 Proposed fix
 ## Project Structure
 
-```
+```text
 .
-├── config/                 # Django project configuration
-│   ├── settings/          # Settings modules
+├── tcf_core/               # Django project configuration
+│   ├── settings/           # Settings modules
 │   │   ├── __init__.py
-│   │   ├── base.py       # Base settings
-│   │   ├── local.py      # Local development settings
-│   │   └── prod.py       # Production settings
+│   │   ├── base.py        # Base settings
+│   │   ├── local.py       # Local development settings
+│   │   └── prod.py        # Production settings
 │   ├── asgi.py
 │   ├── urls.py
 │   └── wsgi.py
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/README.md` around lines 91 - 113, Update the README project
tree to reflect the actual Django package name: replace references to config/
and config/settings/ with tcf_core/ and tcf_core/settings/ so paths match the
real files (e.g., tcf_core/settings/base.py, tcf_core/settings/local.py,
tcf_core/settings/prod.py) and adjust the brief comments accordingly; ensure
entries for asgi.py, urls.py and wsgi.py remain under tcf_core/ in the diagram.
test_django_app/tcf_core/settings/prod.py-16-18 (1)

16-18: ⚠️ Potential issue | 🟡 Minor

Address the reported line-length lint failure.

CI reports pylint C0301 for this file. Please wrap the flagged long setting assignment(s) so lint passes consistently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/tcf_core/settings/prod.py` around lines 16 - 18, The long
setting assignments (SECURE_HSTS_SECONDS, SECURE_HSTS_INCLUDE_SUBDOMAINS,
SECURE_HSTS_PRELOAD) are triggering pylint C0301; break the env.*(...) calls
across multiple lines using parentheses and one parameter per line so each line
is under the length limit (e.g. call env.int(...) or env.bool(...) with the name
and default on separate lines and a trailing comma) to satisfy line-length
linting while preserving the same variable names and values.
🧹 Nitpick comments (16)
iac/.gitignore (1)

29-31: Remove .terraform.lock.hcl from .gitignore or document the exception.

The lock file is currently being ignored at line 31, but Terraform best practices recommend committing .terraform.lock.hcl to version control for team and CI/CD workflows. This ensures consistent provider versions across environments and prevents the provider drift you've identified. If there's a specific reason to ignore it, document that decision in the README.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/.gitignore` around lines 29 - 31, The .gitignore currently excludes
.terraform.lock.hcl which conflicts with Terraform best practices; remove the
.terraform.lock.hcl entry from .gitignore (so the lockfile is committed) or, if
you intentionally want to ignore it, add a clear documented exception in the
repo README explaining why the lockfile is excluded and the implications for
provider/version consistency; reference the .terraform.lock.hcl entry in your
change to ensure reviewers can verify the fix.
test_django_app/requirements/base.txt (1)

2-13: Consider pinning production dependencies for reproducible builds.

prod.txt inherits from base.txt, which uses open-ended version ranges (≥) for most dependencies. This allows drift in production builds over time. A dedicated lock file or pinned constraints file would ensure consistent deployments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/requirements/base.txt` around lines 2 - 13, The base.txt
dependency ranges (django>=6.0,<7.0, psycopg[binary]>=3.3.0,
django-storages[s3]>=1.14.0, boto3>=1.42.0, gunicorn>=25.0.0) are open-ended and
can cause drift in production; update the build to use pinned versions by
producing a locked constraints file or lockfile (e.g., pip freeze style or
pip-tools/poetry lock) and have prod.txt reference that constraints file, or
replace the >= ranges in base.txt with exact pinned versions so deployments use
reproducible, deterministic package versions.
iac/s3.tf (1)

2-9: Consider adding server-side encryption for data at rest.

The bucket lacks explicit encryption configuration. While S3 now encrypts objects by default with SSE-S3, explicitly configuring encryption ensures compliance and allows for CMK usage if required.

🔒 Add encryption configuration
resource "aws_s3_bucket_server_side_encryption_configuration" "static" {
  bucket = aws_s3_bucket.static.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/s3.tf` around lines 2 - 9, The S3 bucket resource aws_s3_bucket.static
has no explicit server-side encryption; add a server-side encryption
configuration by creating an aws_s3_bucket_server_side_encryption_configuration
resource that references aws_s3_bucket.static.id and sets
apply_server_side_encryption_by_default with an sse_algorithm (e.g., "AES256" or
a CMK algorithm) so objects are explicitly encrypted at rest and can be switched
to a KMS CMK if needed.
test_django_app/Dockerfile (1)

16-17: Pin wait-for-it.sh to a specific commit for reproducible builds.

Downloading from master branch means the script content can change unexpectedly, introducing supply chain risk and breaking reproducibility.

♻️ Proposed fix
-    curl -sSL https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > /wait-for-it.sh && \
+    curl -sSL https://raw.githubusercontent.com/vishnubob/wait-for-it/81b1373f17855a4dc21156cfe1694c31d7d1792e/wait-for-it.sh > /wait-for-it.sh && \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/Dockerfile` around lines 16 - 17, Pin the wait-for-it.sh curl
URL to a specific commit instead of the master branch to ensure reproducible
builds; update the Dockerfile's curl invocation that writes to /wait-for-it.sh
(and keeps the subsequent chmod +x /wait-for-it.sh) to fetch the script from the
raw URL for a chosen commit hash (replace the current master-based URL) and
document the commit hash in a comment so future reviewers can verify provenance.
iac/alb.tf (1)

2-14: Enable drop_invalid_header_fields to prevent HTTP request smuggling.

The ALB should be configured to drop invalid HTTP headers, which helps protect against HTTP request smuggling attacks and header injection vulnerabilities.

🛡️ Proposed fix
 resource "aws_lb" "main" {
   name               = "${local.name_prefix}-alb"
   internal           = false
   load_balancer_type = "application"
   security_groups    = [aws_security_group.alb.id]
   subnets            = aws_subnet.public[*].id

   enable_deletion_protection = false
+  drop_invalid_header_fields = true

   tags = {
     Name = "${local.name_prefix}-alb"
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/alb.tf` around lines 2 - 14, The ALB resource aws_lb.main is missing the
drop_invalid_header_fields setting; enable it by adding
drop_invalid_header_fields = true to the aws_lb "main" resource so the load
balancer drops invalid HTTP header fields and mitigates request smuggling/header
injection risks.
iac/variables.tf (2)

85-89: Consider marking db_username as sensitive.

While the username itself may not be highly sensitive, marking it as sensitive = true prevents it from appearing in logs and console output, following the principle of least exposure.

♻️ Proposed change
 variable "db_username" {
   description = "Database master username"
   type        = string
   default     = "tcf_admin"
+  sensitive   = true
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/variables.tf` around lines 85 - 89, Mark the Terraform variable
db_username as sensitive to prevent it from being printed in logs/console
output: update the variable "db_username" block to include sensitive = true
alongside the existing description/type/default so the value is treated as
sensitive by Terraform.

67-71: Using latest tag for ECR images reduces deployment reproducibility.

The default ecr_image_tag = "latest" makes deployments non-deterministic. If the same Terraform configuration is applied twice, different image versions could be deployed. Consider using explicit version tags or commit SHAs.

♻️ Suggested approach

Remove the default and require explicit tags:

 variable "ecr_image_tag" {
   description = "ECR image tag to deploy"
   type        = string
-  default     = "latest"
 }

Or use a timestamp-based default that's clearly transient:

 variable "ecr_image_tag" {
   description = "ECR image tag to deploy (use git SHA or semantic version)"
   type        = string
+  # No default - must be explicitly provided for reproducible deployments
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/variables.tf` around lines 67 - 71, The variable ecr_image_tag currently
defaults to "latest", which makes deployments non-deterministic; remove the
default from variable "ecr_image_tag" so it becomes a required input (or
alternatively set a clearly transient default like a timestamp-derived value)
and update any callers or CI/Terraform var files to supply an explicit image
tag/commit SHA; ensure references to ecr_image_tag in your Terraform code remain
unchanged so callers must pass the tag.
iac/cloudfront.tf (2)

106-127: Path pattern *.* may inadvertently route dynamic URLs to S3.

The pattern *.* matches any path containing a dot, which could include dynamic routes like /api/v1.0/resource or /user.json. These would be incorrectly routed to S3 instead of the ALB.

Consider using more explicit patterns for static assets:

♻️ Suggested patterns
   ordered_cache_behavior {
-    path_pattern     = "*.*"
+    path_pattern     = "/static/*"
+    # ... config
+  }
+
+  ordered_cache_behavior {
+    path_pattern     = "/media/*"
     # ... config
   }

Or use file extension patterns like *.css, *.js, *.png, etc.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cloudfront.tf` around lines 106 - 127, The current ordered_cache_behavior
using path_pattern="*.*" will match any dotted path and send dynamic routes to
the S3 origin (target_origin_id = "S3-${aws_s3_bucket.static.id}"); replace this
single catch-all with explicit ordered_cache_behavior entries for known static
file extensions (e.g., "*.css", "*.js", "*.png", "*.jpg", "*.svg", "*.woff2",
etc.) each pointing to the S3 origin, and ensure the default_cache_behavior (or
an ordered_cache_behavior targeting the ALB origin) remains to handle all other
routes so dynamic APIs and user.json style URLs go to the ALB instead of S3.

29-146: Consider adding WAF protection for production deployment.

The CloudFront distribution lacks a Web Application Firewall (WAF). While acceptable for a test environment, production should include WAF with rules for common attack vectors (OWASP Top 10, rate limiting, Log4j mitigation).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cloudfront.tf` around lines 29 - 146, The CloudFront distribution
(aws_cloudfront_distribution.main) needs WAF protection for production: create
an AWS WAFv2 Web ACL resource (e.g., aws_wafv2_web_acl.main) configured with
appropriate managed rule groups (OWASP/AWSManagedRules, rate limiting, and any
custom rules like Log4j signature or IP allow/deny) and logging, then attach it
by setting web_acl_id = aws_wafv2_web_acl.main.arn on
aws_cloudfront_distribution.main; ensure the ACL scope is "CLOUDFRONT", include
sensible default action and rule priority, and enable logging/metrics for
monitoring.
iac/secrets.tf (1)

39-39: Use dynamic port reference instead of hardcoded value.

The port is hardcoded as "5432" while other values reference the RDS instance dynamically. Use tostring(aws_db_instance.postgres.port) for consistency and to handle any future port changes.

♻️ Proposed fix
   secret_string = jsonencode({
     username = var.db_username
     password = random_password.db_password.result
     host     = aws_db_instance.postgres.address
-    port     = "5432"
+    port     = tostring(aws_db_instance.postgres.port)
     dbname   = var.db_name
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/secrets.tf` at line 39, Replace the hardcoded port string with a dynamic
reference to the RDS instance port: instead of the literal "5432" in
iac/secrets.tf, use tostring(aws_db_instance.postgres.port) so the secret stays
consistent with the actual aws_db_instance.postgres configuration and adapts if
the port changes.
iac/ecs.tf (1)

59-62: Hardcoded DEBUG log level should be configurable.

AWS_LOG_LEVEL = "DEBUG" is hardcoded, which may generate excessive logs in production, increasing CloudWatch costs and potentially exposing sensitive information.

♻️ Proposed fix

Add a variable in variables.tf:

variable "log_level" {
  description = "Application log level"
  type        = string
  default     = "INFO"
}

Then update the environment variable:

         {
           name  = "AWS_LOG_LEVEL"
-          value = "DEBUG"
+          value = var.log_level
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/ecs.tf` around lines 59 - 62, Replace the hardcoded AWS_LOG_LEVEL value
with a configurable Terraform variable: add a variable "log_level" (string,
default "INFO") in variables.tf and update the container environment entry that
sets AWS_LOG_LEVEL to use var.log_level instead of the literal "DEBUG" (locate
the environment map containing the AWS_LOG_LEVEL entry in the ECS task/container
definition).
iac/rds.tf (2)

38-39: Consider enabling deletion protection for database safety.

The RDS instance has deletion_protection disabled (default). While this may be intentional for a test environment to allow easy teardown, production deployments should enable this to prevent accidental deletion.

♻️ Proposed addition
   multi_az               = false
   publicly_accessible    = false
+  deletion_protection    = var.environment == "prod" ? true : false
   db_subnet_group_name   = aws_db_subnet_group.main.name
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/rds.tf` around lines 38 - 39, The RDS resource currently sets multi_az
and publicly_accessible but omits deletion_protection; add deletion_protection =
true to the same resource block (e.g., the aws_db_instance or aws_rds_cluster
that contains multi_az/publicly_accessible) to prevent accidental deletions, or
gate it behind a boolean variable (e.g., var.enable_deletion_protection) so
non-prod environments can opt out; ensure the attribute is set at the resource
level and update any related docs/variables accordingly.

14-16: RDS subnet group uses public subnets - consider using private subnets.

The database subnet group references public subnets (aws_subnet.public[*].id). While publicly_accessible = false prevents direct internet access, best practice is to place databases in private subnets with no route to an internet gateway. This provides defense-in-depth.

For a test environment this is acceptable, but production should use dedicated private subnets.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/rds.tf` around lines 14 - 16, The RDS subnet group
aws_db_subnet_group.main is referencing public subnets via
aws_subnet.public[*].id; change it to use private subnet IDs instead (e.g.,
aws_subnet.private[*].id or a dedicated private subnet set) and ensure the DB
resource (where publicly_accessible is set, if present) remains false; update
any locals/variables (like local.name_prefix usage) or create a new
aws_subnet.private resource group and reference those IDs in
aws_db_subnet_group.main so the DB sits in private subnets with no IGW route for
production.
iac/iam.tf (1)

91-98: ACL-related S3 permissions may be unnecessary.

s3:PutObjectAcl and s3:GetObjectAcl are included, but with the bucket policy granting public read access and the ECS role write access, these ACL operations may not be needed. Modern S3 best practice is to use bucket policies over object ACLs.

Consider removing if not required:

♻️ Simplified permissions
         Action = [
           "s3:PutObject",
-          "s3:GetObjectAcl",
           "s3:GetObject",
           "s3:DeleteObject",
-          "s3:PutObjectAcl",
           "s3:HeadObject"
         ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/iam.tf` around lines 91 - 98, The IAM policy's Action list currently
includes ACL operations s3:PutObjectAcl and s3:GetObjectAcl which are likely
unnecessary given the bucket policy and ECS role; remove "s3:PutObjectAcl" and
"s3:GetObjectAcl" from the Actions array in the IAM policy block so the policy
only grants the required object operations (e.g., s3:PutObject, s3:GetObject,
s3:DeleteObject, s3:HeadObject) and re-run plan to verify no other code relies
on object ACLs.
iac/cognito.tf (1)

73-77: Consider using a custom domain for Cognito hosted UI in production.

The Cognito prefix domain (${local.name_prefix}-auth) uses the shared amazoncognito.com domain. For production, consider configuring a custom domain (e.g., auth.${var.domain_name}) with an ACM certificate for better branding and trust. This is optional for a test environment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cognito.tf` around lines 73 - 77, Replace the default amazoncognito.com
prefix in the aws_cognito_user_pool_domain.main resource with a production-ready
custom domain by adding a domain like "auth.${var.domain_name}" and wiring it to
an ACM certificate; create or reference an ACM certificate (e.g.,
aws_acm_certificate.cognito_cert) in the same region (or use a validated
certificate ARN) and set its ARN on aws_cognito_user_pool_domain.main via
certificate_arn, and ensure you provision the required DNS validation record
(e.g., via aws_route53_record) and point the custom domain to Cognito so the
hosted UI uses the custom domain instead of local.name_prefix.
test_django_app/tcf_core/settings/prod.py (1)

9-9: Prefer fail-fast for host/origin env vars.

Lines 9 and 21 default to empty lists, which can lead to avoidable 400/403 behavior in prod when env wiring is incomplete. Requiring these vars at startup is safer operationally.

💡 Proposed fix
-ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])
+ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
@@
-CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS', default=[])
+CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS')

Also applies to: 21-21

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/tcf_core/settings/prod.py` at line 9, ALLOWED_HOSTS currently
uses env.list('ALLOWED_HOSTS', default=[]) which silently falls back to an empty
list; change both ALLOWED_HOSTS and the similar CORS_ALLOWED_ORIGINS usage to
require the environment variable and fail-fast by removing the default and
raising/letting the env loader throw if the variable is missing (i.e., call
env.list('ALLOWED_HOSTS') and env.list('CORS_ALLOWED_ORIGINS') or explicitly
validate and raise a clear RuntimeError during settings import), so startup will
fail when these host/origin env vars are not provided.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Dockerfile`:
- Line 26: The ENTRYPOINT in the Dockerfile causes docker-compose command
overrides to be treated as arguments to /app/scripts/container-startup.sh which
never forwards them (container-startup.sh always execs gunicorn), so change the
Dockerfile ENTRYPOINT to use CMD instead or modify
/app/scripts/container-startup.sh to detect passed-in arguments and exec them
(i.e., if there are args, run them and return) before falling back to the
existing gunicorn startup path; reference ENTRYPOINT, CMD, and
/app/scripts/container-startup.sh (the section that currently execs gunicorn).

In `@iac/acm.tf`:
- Around line 1-13: The ACM certificate is currently created with the default
provider (which may not be us-east-1) causing CloudFront failures; add an
aliased AWS provider for us-east-1 (e.g., provider "aws" with alias "us_east_1"
and region = "us-east-1") and update the ACM resources to use that provider by
setting provider = aws.us_east_1 on aws_acm_certificate.main and also on
aws_acm_certificate_validation.main so the certificate and its validation are
created in us-east-1.

In `@iac/cloudfront.tf`:
- Around line 8-14: The policy statement with Sid "PublicReadGetObject"
currently grants Principal = "*" s3:GetObject on Resource =
"${aws_s3_bucket.static.arn}/*", exposing user-uploaded media; change the policy
so public read only applies to static assets (e.g., tighten Resource to
"${aws_s3_bucket.static.arn}/static/*" or the appropriate static prefix) or
remove Principal="*" and instead use a CloudFront origin access identity /
signed URLs for private media, and move user uploads into a separate private
bucket (update aws_s3_bucket.static usage and any references to the media bucket
accordingly).

In `@iac/cognito.tf`:
- Around line 48-49: The OAuth redirect URIs in the Cognito client are
incorrect: update the callback_urls and logout_urls settings so they match the
Django app's expected endpoints by replacing the current paths ("/callback" and
"/logout") with "/cognito-callback" for callback_urls and "/" for logout_urls
while keeping the same host interpolation (i.e., using the existing
"${var.domain_name}" pattern) so the full redirect URIs match the Django
configuration.

In `@iac/ecr.tf`:
- Around line 2-5: The ECR repository resource aws_ecr_repository "app" is using
image_tag_mutability = "MUTABLE" which allows tags to be overwritten; change
image_tag_mutability to "IMMUTABLE" in that resource to ensure tags are
immutable for reliable ECS rollbacks and reproduction, and (optionally) add or
adjust an aws_ecr_lifecycle_policy attached to the repository to avoid pruning
digests referenced by older task definitions.

In `@iac/main.tf`:
- Around line 4-8: The provider default_tags block currently only sets ManagedBy
and was suggested to merge values from common_tags, but the proposed refactor
references a non-existent var.owner; either add an input variable named owner
(variable "owner") and use it in default_tags (e.g., include Owner = var.owner)
or remove the Owner entry and instead merge only existing common_tags values
like Project and Environment from the locals (referencing local.common_tags or
the specific locals Project/Environment keys) in the default_tags block; update
the default_tags block and/or create the variable declaration to ensure
var.owner is defined before referencing it.

In `@iac/networking.tf`:
- Around line 13-23: The current aws_subnet.public resource is creating only
public subnets (resource "aws_subnet" "public") and the RDS DB subnet group is
built from aws_subnet.public[*].id, exposing the DB to the internet; add a
separate set of private subnets and private route table resources and switch the
DB subnet group to reference the private subnet IDs. Specifically, create a new
resource "aws_subnet" "private" (same count/availability_zone pattern, different
cidr_block range or offset from public), create an "aws_route_table" "private"
and "aws_route_table_association" entries to ensure private subnets do NOT have
a route to the internet gateway, keep the existing "aws_route_table" "public"
and its associations for the public subnets, and update the DB subnet group
(where it currently references aws_subnet.public[*].id) to reference
aws_subnet.private[*].id instead so the RDS sits in private subnets. Ensure tag
names mirror the public pattern (e.g., -private-subnet-) and reuse
local.name_prefix for consistency.

In `@iac/s3.tf`:
- Around line 11-19: The aws_s3_bucket_public_access_block resource "static"
currently disables all public access; change its flags (block_public_acls,
block_public_policy, ignore_public_acls, restrict_public_buckets) to true to
re-enable S3 public access blocks, then replace the wide bucket policy that
grants s3:GetObject to Principal:"*" by restricting access to CloudFront only:
create and configure a CloudFront Origin Access Control (OAC) resource and
attach it to your CloudFront distribution (the distribution resource in
cloudfront.tf), and update the bucket policy to allow s3:GetObject only for the
CloudFront OAC (or CloudFront service principal/identity) instead of "*". Ensure
the bucket reference remains aws_s3_bucket.static.id and update any references
in aws_cloudfront_distribution to use the new OAC.

In `@iac/secrets.tf`:
- Around line 54-63: The secret currently stores only the domain prefix
(aws_cognito_user_pool_domain.main.domain) but the app expects a full Cognito
hosted UI base URL; update the aws_secretsmanager_secret_version
"cognito_credentials" secret_string to encode the full URL (e.g. build
https://{aws_cognito_user_pool_domain.main.domain}.auth.{var.aws_region}.amazoncognito.com)
instead of just the domain prefix so the Django code using COGNITO_DOMAIN as a
base URL will work; modify the jsonencode payload in
aws_secretsmanager_secret_version.cognito_credentials to include this
constructed cognito_base_url value (or replace the domain field) while keeping
other fields (user_pool_id, app_client_id, app_client_secret, region) intact.

In `@iac/security-groups.tf`:
- Around line 7-21: The two ingress blocks allowing 0.0.0.0/0 should be
restricted to CloudFront: replace cidr_blocks = ["0.0.0.0/0"] in both ingress
blocks with prefix_list_ids = [data.aws_prefix_list.cloudfront.id] and add a
data "aws_prefix_list" resource named "cloudfront" that targets the CloudFront
origin-facing managed prefix list; alternatively implement an ALB listener rule
or WAF rule that rejects requests missing the CloudFront header (so update the
ALB listener rule that checks X-Forwarded-Proto/X-Forwarded-For). Target the
ingress blocks shown in the diff and the ALB listener rule that currently
accepts direct-origin traffic.

In `@iac/terraform.tf`:
- Around line 1-14: The terraform configuration currently lacks a remote backend
which causes local state to be used; update the terraform { ... } block (the
existing terraform and required_providers stanza) to include a backend
configuration (for example an "s3" backend with bucket, key, region and a
"dynamodb_table" for state locking, or configure a "remote" backend for
Terraform Cloud) so state is stored remotely and locking is enabled; ensure you
add required backend variables or a separate backend.hcl as appropriate and
document any new environment variables or IAM permissions needed for the
backend.

In `@tcf_core/settings/health_check_middleware.py`:
- Around line 5-6: HealthCheckMiddleware triggers pylint too-few-public-methods;
add a simple public method to satisfy the linter and keep middleware
functional—implement a pass-through __call__(self, request) or
process_request(self, request) that returns None (or forwards to get_response if
you have it) inside the HealthCheckMiddleware class so the class has at least
one public method (reference HealthCheckMiddleware and the new __call__ or
process_request method).

In `@tcf_core/settings/prod.py`:
- Around line 24-27: The STORAGES dict entry for "staticfiles" is a single long
line that exceeds pylint's max-line-length; reformat the STORAGES definition so
each nested mapping is split across multiple lines (e.g., break the
"staticfiles" value into separate lines for "BACKEND" and "OPTIONS", and put the
"location": "static" pair on its own line) so the symbol STORAGES and its nested
dictionaries comply with line-length rules while keeping the same keys and
values.

In `@test_django_app/Dockerfile`:
- Around line 1-37: The container currently relies on the manage.py default
('tcf_core.settings.local') so migrations/collectstatic run with dev settings;
set the DJANGO_SETTINGS_MODULE environment variable
(DJANGO_SETTINGS_MODULE='tcf_core.settings.production' or your production
module) in the Dockerfile (e.g., next to ENV PYTHONUNBUFFERED) so the runtime
invoked by ENTRYPOINT ["/app/scripts/container-startup.sh"] uses the correct
production settings instead of tcf_core.settings.local.

In `@test_django_app/manage.py`:
- Around line 7-18: The manage.py soft-sets DJANGO_SETTINGS_MODULE to
'tcf_core.settings.local' in main(), which can silently mask a missing
production setting; change main() to hard-fail when DJANGO_SETTINGS_MODULE is
not present by removing os.environ.setdefault and instead verify presence of
'DJANGO_SETTINGS_MODULE' (or access it via os.environ[...] semantics) and raise
a clear error (RuntimeError or similar) if it's missing so the process fails
fast; leave the rest of main() (importing execute_from_command_line and calling
it) unchanged.

In `@test_django_app/pyproject.toml`:
- Around line 6-14: The build is non-deterministic because dependencies in the
dependencies block in pyproject.toml (and requirements/base.txt used by the
Dockerfile) use open-ended ranges and uv.lock is not used; either pin exact
versions in requirements/base.txt to match the resolved versions in uv.lock, or
modify the Dockerfile to install from uv.lock (e.g., run uv sync or use uv pip
install with the uv.lock file) so container builds are reproducible; update the
pyproject.toml dependencies only if you also update requirements/base.txt and
ensure the Dockerfile uses uv.lock during the install step.

In `@test_django_app/tcf_core/settings/base.py`:
- Around line 18-22: The production settings currently inherit the insecure
default SECRET_KEY from base.py; add an explicit override in prod.py by calling
env.str('SECRET_KEY') with no default so the app fails fast when the env var is
missing (i.e., set SECRET_KEY = env.str('SECRET_KEY') in prod.py), ensuring
SECRET_KEY is required in production and preventing the insecure fallback from
being used.
- Around line 40-48: The MIDDLEWARE list in base.py is missing the
HealthCheckMiddleware, so ALB health checks to /health won't be handled; add
'tcf_core.settings.health_check_middleware.HealthCheckMiddleware' into the
MIDDLEWARE list (place it early, e.g., immediately after
'django.middleware.security.SecurityMiddleware') so the HealthCheckMiddleware
class runs before other middleware and handles health check requests.

In `@test_django_app/tcf_core/settings/prod.py`:
- Line 13: SECURE_SSL_REDIRECT is currently defaulting to False which weakens
HTTPS enforcement; update the env.bool call that sets SECURE_SSL_REDIRECT to
default to True (i.e., change the default argument on the SECURE_SSL_REDIRECT
assignment) so production enables HTTPS redirects by default while still
allowing an override via the environment variable.
- Line 4: The wildcard import from .base is causing F403/F405 for the env
symbol; replace the "from .base import *" with explicit imports that include env
and any other names used in this module (e.g., "from .base import env, DEBUG,
DATABASES, ...") so env is defined unambiguously and Ruff errors are resolved;
locate the prod settings file and update the import to list required identifiers
instead of using a star import.

---

Minor comments:
In `@iac/s3.tf`:
- Around line 2-9: The aws_s3_bucket resource aws_s3_bucket.static currently
sets force_destroy = true which allows Terraform to delete all objects when the
bucket is destroyed; change this to a safer default by replacing the hardcoded
true with a configurable value (e.g., use a variable like var.s3_force_destroy
or an environment-specific local) and set the production default to false so
production runs do not purge bucket contents unless explicitly overridden;
update any relevant variable declarations and documentation to reflect the new
var (and ensure the resource uses aws_s3_bucket.static.force_destroy =
var.s3_force_destroy).

In `@test_django_app/README.md`:
- Around line 91-113: Update the README project tree to reflect the actual
Django package name: replace references to config/ and config/settings/ with
tcf_core/ and tcf_core/settings/ so paths match the real files (e.g.,
tcf_core/settings/base.py, tcf_core/settings/local.py,
tcf_core/settings/prod.py) and adjust the brief comments accordingly; ensure
entries for asgi.py, urls.py and wsgi.py remain under tcf_core/ in the diagram.

In `@test_django_app/scripts/container-startup.sh`:
- Around line 6-8: The startup script currently runs migrations unconditionally
(python manage.py migrate --noinput) which causes race conditions in
multi-replica ECS; remove that line from container-startup.sh and instead
implement one of the suggested strategies: (A) create a separate one-off ECS
task in your Terraform config that runs manage.py migrate once before service
deployment and remove migrations from the container image, or (B) if you must
run in-container, add a migration guard such as an environment-controlled flag
(e.g., RUN_MIGRATIONS) and a distributed lock (DynamoDB/SSM parameter or
django-locking) around the migrate call to ensure only one replica runs
migrations (wrap logic that invokes python manage.py migrate). Ensure the chosen
approach is wired into Terraform ECS deployment (task definition for the one-off
task or env var/lock resources for guarded in-container runs).

In `@test_django_app/tcf_core/settings/prod.py`:
- Around line 16-18: The long setting assignments (SECURE_HSTS_SECONDS,
SECURE_HSTS_INCLUDE_SUBDOMAINS, SECURE_HSTS_PRELOAD) are triggering pylint
C0301; break the env.*(...) calls across multiple lines using parentheses and
one parameter per line so each line is under the length limit (e.g. call
env.int(...) or env.bool(...) with the name and default on separate lines and a
trailing comma) to satisfy line-length linting while preserving the same
variable names and values.

---

Nitpick comments:
In `@iac/.gitignore`:
- Around line 29-31: The .gitignore currently excludes .terraform.lock.hcl which
conflicts with Terraform best practices; remove the .terraform.lock.hcl entry
from .gitignore (so the lockfile is committed) or, if you intentionally want to
ignore it, add a clear documented exception in the repo README explaining why
the lockfile is excluded and the implications for provider/version consistency;
reference the .terraform.lock.hcl entry in your change to ensure reviewers can
verify the fix.

In `@iac/alb.tf`:
- Around line 2-14: The ALB resource aws_lb.main is missing the
drop_invalid_header_fields setting; enable it by adding
drop_invalid_header_fields = true to the aws_lb "main" resource so the load
balancer drops invalid HTTP header fields and mitigates request smuggling/header
injection risks.

In `@iac/cloudfront.tf`:
- Around line 106-127: The current ordered_cache_behavior using
path_pattern="*.*" will match any dotted path and send dynamic routes to the S3
origin (target_origin_id = "S3-${aws_s3_bucket.static.id}"); replace this single
catch-all with explicit ordered_cache_behavior entries for known static file
extensions (e.g., "*.css", "*.js", "*.png", "*.jpg", "*.svg", "*.woff2", etc.)
each pointing to the S3 origin, and ensure the default_cache_behavior (or an
ordered_cache_behavior targeting the ALB origin) remains to handle all other
routes so dynamic APIs and user.json style URLs go to the ALB instead of S3.
- Around line 29-146: The CloudFront distribution
(aws_cloudfront_distribution.main) needs WAF protection for production: create
an AWS WAFv2 Web ACL resource (e.g., aws_wafv2_web_acl.main) configured with
appropriate managed rule groups (OWASP/AWSManagedRules, rate limiting, and any
custom rules like Log4j signature or IP allow/deny) and logging, then attach it
by setting web_acl_id = aws_wafv2_web_acl.main.arn on
aws_cloudfront_distribution.main; ensure the ACL scope is "CLOUDFRONT", include
sensible default action and rule priority, and enable logging/metrics for
monitoring.

In `@iac/cognito.tf`:
- Around line 73-77: Replace the default amazoncognito.com prefix in the
aws_cognito_user_pool_domain.main resource with a production-ready custom domain
by adding a domain like "auth.${var.domain_name}" and wiring it to an ACM
certificate; create or reference an ACM certificate (e.g.,
aws_acm_certificate.cognito_cert) in the same region (or use a validated
certificate ARN) and set its ARN on aws_cognito_user_pool_domain.main via
certificate_arn, and ensure you provision the required DNS validation record
(e.g., via aws_route53_record) and point the custom domain to Cognito so the
hosted UI uses the custom domain instead of local.name_prefix.

In `@iac/ecs.tf`:
- Around line 59-62: Replace the hardcoded AWS_LOG_LEVEL value with a
configurable Terraform variable: add a variable "log_level" (string, default
"INFO") in variables.tf and update the container environment entry that sets
AWS_LOG_LEVEL to use var.log_level instead of the literal "DEBUG" (locate the
environment map containing the AWS_LOG_LEVEL entry in the ECS task/container
definition).

In `@iac/iam.tf`:
- Around line 91-98: The IAM policy's Action list currently includes ACL
operations s3:PutObjectAcl and s3:GetObjectAcl which are likely unnecessary
given the bucket policy and ECS role; remove "s3:PutObjectAcl" and
"s3:GetObjectAcl" from the Actions array in the IAM policy block so the policy
only grants the required object operations (e.g., s3:PutObject, s3:GetObject,
s3:DeleteObject, s3:HeadObject) and re-run plan to verify no other code relies
on object ACLs.

In `@iac/rds.tf`:
- Around line 38-39: The RDS resource currently sets multi_az and
publicly_accessible but omits deletion_protection; add deletion_protection =
true to the same resource block (e.g., the aws_db_instance or aws_rds_cluster
that contains multi_az/publicly_accessible) to prevent accidental deletions, or
gate it behind a boolean variable (e.g., var.enable_deletion_protection) so
non-prod environments can opt out; ensure the attribute is set at the resource
level and update any related docs/variables accordingly.
- Around line 14-16: The RDS subnet group aws_db_subnet_group.main is
referencing public subnets via aws_subnet.public[*].id; change it to use private
subnet IDs instead (e.g., aws_subnet.private[*].id or a dedicated private subnet
set) and ensure the DB resource (where publicly_accessible is set, if present)
remains false; update any locals/variables (like local.name_prefix usage) or
create a new aws_subnet.private resource group and reference those IDs in
aws_db_subnet_group.main so the DB sits in private subnets with no IGW route for
production.

In `@iac/s3.tf`:
- Around line 2-9: The S3 bucket resource aws_s3_bucket.static has no explicit
server-side encryption; add a server-side encryption configuration by creating
an aws_s3_bucket_server_side_encryption_configuration resource that references
aws_s3_bucket.static.id and sets apply_server_side_encryption_by_default with an
sse_algorithm (e.g., "AES256" or a CMK algorithm) so objects are explicitly
encrypted at rest and can be switched to a KMS CMK if needed.

In `@iac/secrets.tf`:
- Line 39: Replace the hardcoded port string with a dynamic reference to the RDS
instance port: instead of the literal "5432" in iac/secrets.tf, use
tostring(aws_db_instance.postgres.port) so the secret stays consistent with the
actual aws_db_instance.postgres configuration and adapts if the port changes.

In `@iac/variables.tf`:
- Around line 85-89: Mark the Terraform variable db_username as sensitive to
prevent it from being printed in logs/console output: update the variable
"db_username" block to include sensitive = true alongside the existing
description/type/default so the value is treated as sensitive by Terraform.
- Around line 67-71: The variable ecr_image_tag currently defaults to "latest",
which makes deployments non-deterministic; remove the default from variable
"ecr_image_tag" so it becomes a required input (or alternatively set a clearly
transient default like a timestamp-derived value) and update any callers or
CI/Terraform var files to supply an explicit image tag/commit SHA; ensure
references to ecr_image_tag in your Terraform code remain unchanged so callers
must pass the tag.

In `@test_django_app/Dockerfile`:
- Around line 16-17: Pin the wait-for-it.sh curl URL to a specific commit
instead of the master branch to ensure reproducible builds; update the
Dockerfile's curl invocation that writes to /wait-for-it.sh (and keeps the
subsequent chmod +x /wait-for-it.sh) to fetch the script from the raw URL for a
chosen commit hash (replace the current master-based URL) and document the
commit hash in a comment so future reviewers can verify provenance.

In `@test_django_app/requirements/base.txt`:
- Around line 2-13: The base.txt dependency ranges (django>=6.0,<7.0,
psycopg[binary]>=3.3.0, django-storages[s3]>=1.14.0, boto3>=1.42.0,
gunicorn>=25.0.0) are open-ended and can cause drift in production; update the
build to use pinned versions by producing a locked constraints file or lockfile
(e.g., pip freeze style or pip-tools/poetry lock) and have prod.txt reference
that constraints file, or replace the >= ranges in base.txt with exact pinned
versions so deployments use reproducible, deterministic package versions.

In `@test_django_app/tcf_core/settings/prod.py`:
- Line 9: ALLOWED_HOSTS currently uses env.list('ALLOWED_HOSTS', default=[])
which silently falls back to an empty list; change both ALLOWED_HOSTS and the
similar CORS_ALLOWED_ORIGINS usage to require the environment variable and
fail-fast by removing the default and raising/letting the env loader throw if
the variable is missing (i.e., call env.list('ALLOWED_HOSTS') and
env.list('CORS_ALLOWED_ORIGINS') or explicitly validate and raise a clear
RuntimeError during settings import), so startup will fail when these
host/origin env vars are not provided.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e43d80ad-54db-4503-9fb3-c9d81be7eab0

📥 Commits

Reviewing files that changed from the base of the PR and between 6eda060 and 4ca036b.

⛔ Files ignored due to path filters (1)
  • test_django_app/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (44)
  • Dockerfile
  • iac/.gitignore
  • iac/acm.tf
  • iac/alb.tf
  • iac/cloudfront.tf
  • iac/cloudwatch.tf
  • iac/cognito.tf
  • iac/data.tf
  • iac/ecr.tf
  • iac/ecs.tf
  • iac/iam.tf
  • iac/locals.tf
  • iac/main.tf
  • iac/networking.tf
  • iac/outputs.tf
  • iac/rds.tf
  • iac/route53.tf
  • iac/s3.tf
  • iac/secrets.tf
  • iac/security-groups.tf
  • iac/terraform.tf
  • iac/variables.tf
  • scripts/container-startup.sh
  • tcf_core/settings/base.py
  • tcf_core/settings/health_check_middleware.py
  • tcf_core/settings/prod.py
  • test_django_app/.dockerignore
  • test_django_app/.gitignore
  • test_django_app/Dockerfile
  • test_django_app/README.md
  • test_django_app/manage.py
  • test_django_app/pyproject.toml
  • test_django_app/requirements/base.txt
  • test_django_app/requirements/dev.txt
  • test_django_app/requirements/prod.txt
  • test_django_app/scripts/container-startup.sh
  • test_django_app/tcf_core/__init__.py
  • test_django_app/tcf_core/asgi.py
  • test_django_app/tcf_core/settings/__init__.py
  • test_django_app/tcf_core/settings/base.py
  • test_django_app/tcf_core/settings/local.py
  • test_django_app/tcf_core/settings/prod.py
  • test_django_app/tcf_core/urls.py
  • test_django_app/tcf_core/wsgi.py

Comment thread Dockerfile
Comment thread iac/acm.tf
Comment thread iac/cloudfront.tf
Comment thread iac/cognito.tf Outdated
Comment thread iac/ecr.tf
Comment thread test_django_app/pyproject.toml Outdated
Comment on lines +18 to +22
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env.str('SECRET_KEY', default='django-insecure-change-this-in-production')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool('DEBUG', default=True)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify prod.py requires SECRET_KEY without default
rg -n 'SECRET_KEY' test_django_app/tcf_core/settings/prod.py

Repository: thecourseforum/theCourseForum2

Length of output: 56


🏁 Script executed:

# List the settings directory structure
ls -la test_django_app/tcf_core/settings/

Repository: thecourseforum/theCourseForum2

Length of output: 418


🏁 Script executed:

# Check if prod.py exists and read its content
[ -f test_django_app/tcf_core/settings/prod.py ] && cat -n test_django_app/tcf_core/settings/prod.py || echo "File does not exist"

Repository: thecourseforum/theCourseForum2

Length of output: 3563


🏁 Script executed:

# Search for how settings are imported/used in the project
rg -n 'from.*settings' test_django_app/ --type py -m 5

Repository: thecourseforum/theCourseForum2

Length of output: 56


Add explicit SECRET_KEY enforcement to prod.py without a fallback default.

The prod.py file inherits the insecure SECRET_KEY default from base.py. While other production-critical settings like AWS credentials are explicitly required in environment variables without fallbacks, SECRET_KEY should receive the same treatment. Add an explicit override in prod.py such as SECRET_KEY = env.str('SECRET_KEY') to prevent the insecure default from being used in production.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/tcf_core/settings/base.py` around lines 18 - 22, The
production settings currently inherit the insecure default SECRET_KEY from
base.py; add an explicit override in prod.py by calling env.str('SECRET_KEY')
with no default so the app fails fast when the env var is missing (i.e., set
SECRET_KEY = env.str('SECRET_KEY') in prod.py), ensuring SECRET_KEY is required
in production and preventing the insecure fallback from being used.

Comment thread test_django_app/tcf_core/settings/base.py Outdated
"""
Django production settings for AWS deployment.
"""
from .base import *
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make env explicit to fix Ruff root-cause errors.

Line 4’s wildcard import triggers F403 and causes downstream F405 ambiguity for env. Fixing this at the import layer is cleaner than suppressing each usage warning.

💡 Proposed fix
-from .base import *
+from .base import *  # noqa: F403
+from .base import env
🧰 Tools
🪛 Ruff (0.15.7)

[error] 4-4: from .base import * used; unable to detect undefined names

(F403)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/tcf_core/settings/prod.py` at line 4, The wildcard import
from .base is causing F403/F405 for the env symbol; replace the "from .base
import *" with explicit imports that include env and any other names used in
this module (e.g., "from .base import env, DEBUG, DATABASES, ...") so env is
defined unambiguously and Ruff errors are resolved; locate the prod settings
file and update the import to list required identifiers instead of using a star
import.


# Security settings for production
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=False)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Default SECURE_SSL_REDIRECT to enabled in production.

Line 13 currently defaults to False; that weakens HTTPS enforcement if infra redirects are missed/misconfigured.

💡 Proposed fix
-SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=False)
+SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=True)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=False)
SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=True)
🧰 Tools
🪛 Ruff (0.15.7)

[error] 13-13: env may be undefined, or defined from star imports

(F405)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_django_app/tcf_core/settings/prod.py` at line 13, SECURE_SSL_REDIRECT is
currently defaulting to False which weakens HTTPS enforcement; update the
env.bool call that sets SECURE_SSL_REDIRECT to default to True (i.e., change the
default argument on the SECURE_SSL_REDIRECT assignment) so production enables
HTTPS redirects by default while still allowing an override via the environment
variable.

Comment thread iac/variables.tf
Comment thread iac/cloudfront.tf
Comment thread iac/cognito.tf Outdated
Comment thread iac/cognito.tf
id_token_validity = 60
refresh_token_validity = 30

token_validity_units {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make refresh valid longer if possible

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now users stay logged in for 1 month at a time (refresh token validity is 30 days). Do you want that to be longer? Or make the access/ID tokens validity longer? I'd caution against lengthening the access and ID token validity for security reasons; users won't be signed out while the refresh token is still valid.

Comment thread iac/cognito.tf
Comment thread iac/rds.tf
@gyoge0 gyoge0 force-pushed the terraform-yogesh branch from 4ca036b to ab921aa Compare March 31, 2026 20:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
iac/s3.tf (2)

21-28: Consider enabling versioning or adding a lifecycle policy.

Versioning is disabled, which is fine for cost optimization on static assets. However, consider adding a lifecycle policy to automatically clean up incomplete multipart uploads or transition older objects to cheaper storage classes if the bucket grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/s3.tf` around lines 21 - 28, The bucket's versioning is explicitly
disabled in aws_s3_bucket_versioning.static for aws_s3_bucket.static; add a
lifecycle policy resource (or lifecycle rules on the aws_s3_bucket) to handle
cleanup and cost optimization: include an abort_incomplete_multipart_upload rule
to remove incomplete uploads after N days and add transition rules to move older
objects (e.g., >30/60/90 days) to cheaper storage classes; reference
aws_s3_bucket.static and aws_s3_bucket_versioning.static when adding the
lifecycle block so the rules apply to the same bucket.

2-9: Consider force_destroy implications for production.

force_destroy = true allows Terraform to delete the bucket even when it contains objects. This is convenient for dev/test teardown but risky for production environments where accidental destruction could cause data loss.

💡 Suggested approach

Consider making this configurable via a variable:

 resource "aws_s3_bucket" "static" {
   bucket_prefix  = "${local.name_prefix}-static-"
-  force_destroy  = true
+  force_destroy  = var.environment == "dev" ? true : false

   tags = {
     Name = "${local.name_prefix}-static"
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/s3.tf` around lines 2 - 9, resource "aws_s3_bucket" "static" currently
sets force_destroy = true which is unsafe for production; change the bucket to
use a configurable variable (e.g., var.s3_force_destroy) instead of a hardcoded
true, update the resource to reference that variable (aws_s3_bucket.static ->
force_destroy = var.s3_force_destroy), add a corresponding variable declaration
with a safe default (false) and a clear description, and document or wire the
variable into your environment/configuration so devs can opt into true when
tearing down non-prod environments.
iac/cloudfront.tf (1)

55-58: X-Forwarded-Proto: https does not lock the ALB behind CloudFront.

If the goal is “CloudFront only,” this header is public and trivial to spoof. AWS’s documented pattern is a random origin custom header plus an ALB listener rule that only forwards when that secret header is present; otherwise direct requests can still bypass CloudFront and any edge-only controls added later. (docs.aws.amazon.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cloudfront.tf` around lines 55 - 58, The CloudFront custom_header block
currently sets a public X-Forwarded-Proto header which can be trivially spoofed;
replace this with a private, randomly-generated secret header (e.g.,
custom_header { name = "X-CloudFront-Secret" value = "<strong-random-secret>" })
and then enforce that secret on the ALB by adding a listener rule or security
check that only accepts requests containing that exact header value; update the
custom_header block and coordinate the ALB listener rule (or target
group/ingress rule) to validate the new secret header so direct requests are
rejected unless they originate from CloudFront.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@iac/alb.tf`:
- Around line 2-14: The ALB resource aws_lb.main is missing the
drop_invalid_header_fields setting needed to mitigate HTTP request smuggling;
update resource "aws_lb" "main" to include drop_invalid_header_fields = true
(add the attribute inside the resource block for aws_lb.main) so the ALB will
drop invalid HTTP header fields.

In `@iac/cloudfront.tf`:
- Around line 45-57: The ALB origin (domain_name = aws_lb.main.dns_name,
origin_id = "ALB-${aws_lb.main.name}") is using legacy forwarded_values which
causes CloudFront to set Host to the ALB DNS name and break TLS SNI; replace the
forwarded_values blocks for behaviors that point to this ALB origin with an
origin_request_policy that forwards the viewer Host header (use the AWS-managed
"AllViewer" origin request policy or create a custom Origin Request Policy that
includes Host), and update the behavior blocks to use origin_request_policy_id
instead of forwarded_values so the Host header from the viewer is preserved for
TLS validation.

---

Nitpick comments:
In `@iac/cloudfront.tf`:
- Around line 55-58: The CloudFront custom_header block currently sets a public
X-Forwarded-Proto header which can be trivially spoofed; replace this with a
private, randomly-generated secret header (e.g., custom_header { name =
"X-CloudFront-Secret" value = "<strong-random-secret>" }) and then enforce that
secret on the ALB by adding a listener rule or security check that only accepts
requests containing that exact header value; update the custom_header block and
coordinate the ALB listener rule (or target group/ingress rule) to validate the
new secret header so direct requests are rejected unless they originate from
CloudFront.

In `@iac/s3.tf`:
- Around line 21-28: The bucket's versioning is explicitly disabled in
aws_s3_bucket_versioning.static for aws_s3_bucket.static; add a lifecycle policy
resource (or lifecycle rules on the aws_s3_bucket) to handle cleanup and cost
optimization: include an abort_incomplete_multipart_upload rule to remove
incomplete uploads after N days and add transition rules to move older objects
(e.g., >30/60/90 days) to cheaper storage classes; reference
aws_s3_bucket.static and aws_s3_bucket_versioning.static when adding the
lifecycle block so the rules apply to the same bucket.
- Around line 2-9: resource "aws_s3_bucket" "static" currently sets
force_destroy = true which is unsafe for production; change the bucket to use a
configurable variable (e.g., var.s3_force_destroy) instead of a hardcoded true,
update the resource to reference that variable (aws_s3_bucket.static ->
force_destroy = var.s3_force_destroy), add a corresponding variable declaration
with a safe default (false) and a clear description, and document or wire the
variable into your environment/configuration so devs can opt into true when
tearing down non-prod environments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02b40d4f-7c23-49f1-bae3-d10638655f97

📥 Commits

Reviewing files that changed from the base of the PR and between 4ca036b and ab921aa.

📒 Files selected for processing (26)
  • Dockerfile
  • iac/.gitignore
  • iac/acm.tf
  • iac/alb.tf
  • iac/cloudfront.tf
  • iac/cloudwatch.tf
  • iac/cognito.tf
  • iac/data.tf
  • iac/ecr.tf
  • iac/ecs.tf
  • iac/iam.tf
  • iac/locals.tf
  • iac/main.tf
  • iac/networking.tf
  • iac/outputs.tf
  • iac/rds.tf
  • iac/route53.tf
  • iac/s3.tf
  • iac/secrets.tf
  • iac/security-groups.tf
  • iac/terraform.tf
  • iac/variables.tf
  • scripts/container-startup.sh
  • tcf_core/settings/base.py
  • tcf_core/settings/health_check_middleware.py
  • tcf_core/settings/prod.py
✅ Files skipped from review due to trivial changes (12)
  • Dockerfile
  • iac/data.tf
  • tcf_core/settings/prod.py
  • iac/main.tf
  • scripts/container-startup.sh
  • tcf_core/settings/base.py
  • iac/locals.tf
  • iac/.gitignore
  • tcf_core/settings/health_check_middleware.py
  • iac/terraform.tf
  • iac/acm.tf
  • iac/variables.tf
🚧 Files skipped from review as they are similar to previous changes (4)
  • iac/route53.tf
  • iac/cognito.tf
  • iac/iam.tf
  • iac/outputs.tf

Comment thread iac/alb.tf
Comment thread iac/cloudfront.tf
Comment on lines +45 to +57
domain_name = aws_lb.main.dns_name
origin_id = "ALB-${aws_lb.main.name}"

custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}

custom_header {
name = "X-Forwarded-Proto"
value = "https"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "cloudfront.tf" -type f

Repository: thecourseforum/theCourseForum2

Length of output: 93


🏁 Script executed:

cat -n ./iac/cloudfront.tf | head -120

Repository: thecourseforum/theCourseForum2

Length of output: 4116


🏁 Script executed:

cat -n ./iac/cloudfront.tf | tail -40

Repository: thecourseforum/theCourseForum2

Length of output: 1268


🌐 Web query:

CloudFront forwarded_values headers Host header custom origin HTTPS

💡 Result:

  • You can’t add or override the Host header as a “custom origin header” in CloudFront. Host is on the list of headers that CloudFront won’t let you configure as an origin custom header. [1]
  • Which Host CloudFront sends to a custom origin is controlled by the Origin Request Policy (and related managed policies), not by custom headers. For example, AWS’s managed policy AllViewerExceptHostHeader removes the viewer’s Host; CloudFront then adds a new Host header equal to the origin’s domain name when it makes the origin request. [2]
  • If you need the viewer’s hostname at the origin: the common pattern is to forward a different header (for example X-Forwarded-Host) and allowlist it in the cache/origin request policy. [3]
  • When using HTTPS to a custom origin: CloudFront’s TLS SNI value follows the Host value CloudFront chose for the origin request, and the origin certificate must match that name. [4]

Sources
[1] CloudFront Developer Guide PDF (custom headers you can’t forward includes Host) (s3.amazonaws.com)
[2] Managed origin request policies (behavior when removing viewer Host) (docs.aws.amazon.com)
[3] AWS re:Post: forward host header / use X-Forwarded-Host (repost.aws)
[4] HTTPS to origin: SNI tied to chosen Host (stackoverflow.com)

Citations:


🌐 Web query:

CloudFront AllViewer origin request policy ALB HTTPS custom origin

💡 Result:

To use an ALB as a CloudFront custom origin over HTTPS, you typically want:

  1. Origin settings (CloudFront → Origin)
  • Origin type: Custom origin (your ALB DNS name)
  • Origin protocol policy: HTTPS Only (so CloudFront connects to the ALB via HTTPS) [2]
  1. Origin request policy (Behavior → Cache key and origin requests)
  • Use AWS managed AllViewer if you need CloudFront to forward all viewer headers/cookies/query strings, including the Host header [1].
  • AWS specifically notes that for HTTPS to an ALB, you must forward the Host header to the origin via the origin request policy; AllViewer is an acceptable choice [2].
  1. Certificates
  • Your ALB must have an HTTPS listener with an ACM cert that matches the domain name routed to the ALB [2].
  • If viewers also use HTTPS to CloudFront with your custom domain, the CloudFront cert must be in us-east-1, while the ALB’s cert is in the ALB’s region (both matching the routed domain) [2].

Important practical note: AllViewer can significantly reduce cacheability because it forwards everything [1]. If you don’t truly need all headers/cookies/query strings at the origin, use a tighter policy (but still ensure Host is forwarded when required for your ALB setup) [2].

Sources: AWS managed origin request policies docs [1], AWS “Restrict access to Application Load Balancers” (CloudFront↔ALB over HTTPS + Host header + cert requirements) [2].


Switch ALB-backed behaviors to an origin request policy that forwards Host, or this HTTPS origin will 502.

For custom origins over HTTPS, CloudFront's TLS connection uses the Host header to validate the origin certificate via SNI. When using legacy forwarded_values (lines 62–80 and 84–103), CloudFront sets Host to the origin domain name (aws_lb.main.dns_name)—but your ALB certificate is likely issued for a different domain. This certificate mismatch breaks the TLS handshake and returns 502s.

The fix is to replace forwarded_values with an origin request policy (e.g., AWS managed AllViewer) that explicitly forwards the viewer's Host header. Alternatively, you can use a dedicated ALB hostname/certificate pair matching the ALB's DNS name, though this complicates certificate management.

Also applies to: lines 84–103

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cloudfront.tf` around lines 45 - 57, The ALB origin (domain_name =
aws_lb.main.dns_name, origin_id = "ALB-${aws_lb.main.name}") is using legacy
forwarded_values which causes CloudFront to set Host to the ALB DNS name and
break TLS SNI; replace the forwarded_values blocks for behaviors that point to
this ALB origin with an origin_request_policy that forwards the viewer Host
header (use the AWS-managed "AllViewer" origin request policy or create a custom
Origin Request Policy that includes Host), and update the behavior blocks to use
origin_request_policy_id instead of forwarded_values so the Host header from the
viewer is preserved for TLS validation.

@gyoge0 gyoge0 force-pushed the terraform-yogesh branch from ab921aa to 3bae275 Compare March 31, 2026 21:21
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
iac/s3.tf (1)

4-4: Consider disabling force_destroy for production.

force_destroy = true allows Terraform to delete the bucket even when it contains objects. While convenient for development, this could lead to accidental data loss in production if terraform destroy is run.

🛡️ Suggested change for production safety
 resource "aws_s3_bucket" "static" {
   bucket_prefix  = "${local.name_prefix}-static-"
-  force_destroy  = true
+  force_destroy  = false  # Prevent accidental deletion of bucket with contents
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/s3.tf` at line 4, The S3 bucket currently sets force_destroy = true which
allows Terraform to delete non-empty buckets; change this for production by
removing or setting force_destroy = false (or gating it behind an environment
variable such as var.force_destroy) and add a safer safeguard like a lifecycle
rule or Terraform prevent_destroy behavior for the S3 resource (refer to the
force_destroy setting on the S3 bucket resource) so production buckets are
protected from accidental terraform destroy.
iac/rds.tf (1)

24-50: Good RDS security posture with optional hardening opportunities.

Positives:

  • storage_encrypted = true
  • publicly_accessible = false
  • backup_retention_period = 7
  • Uses private subnet and RDS security group ✓

Static analysis suggests enabling IAM authentication (CKV_AWS_161) and deletion protection (CKV_AWS_293). These are good-to-have for production but not blockers for initial deployment.

🛡️ Optional: Enable deletion protection for production
   backup_retention_period   = 7
   skip_final_snapshot       = false
   final_snapshot_identifier = "${local.name_prefix}-final-snapshot-${random_id.db_snapshot.hex}"
+  deletion_protection       = true  # Prevent accidental deletion

   tags = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/rds.tf` around lines 24 - 50, Enable recommended RDS hardening by
updating the aws_db_instance "postgres" resource: set deletion_protection = true
and enable IAM DB authentication via iam_database_authentication_enabled = true
on the aws_db_instance.postgres resource so deletion protection and IAM-based
authentication are enforced for production deployments.
iac/cloudfront.tf (2)

84-91: Consider migrating from deprecated forwarded_values to cache/origin request policies.

The forwarded_values block is legacy and deprecated by AWS. While functional, migrating to the newer cache policy and origin request policy resources provides better maintainability and access to newer features.

For the ALB behaviors, you could use the AWS-managed CachingDisabled cache policy and AllViewer origin request policy to achieve the same effect.

Example migration for ALB behaviors
# Add data sources for managed policies
data "aws_cloudfront_cache_policy" "caching_disabled" {
  name = "Managed-CachingDisabled"
}

data "aws_cloudfront_origin_request_policy" "all_viewer" {
  name = "Managed-AllViewer"
}

# Then in the behavior block, replace forwarded_values with:
default_cache_behavior {
  # ... existing config ...
  
  cache_policy_id          = data.aws_cloudfront_cache_policy.caching_disabled.id
  origin_request_policy_id = data.aws_cloudfront_origin_request_policy.all_viewer.id
  
  # Remove forwarded_values block entirely
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cloudfront.tf` around lines 84 - 91, The CloudFront distribution uses the
deprecated forwarded_values block; replace it for the ALB behaviors by removing
forwarded_values and instead attach a cache_policy_id and
origin_request_policy_id (e.g., set cache_policy_id to the
Managed-CachingDisabled policy and origin_request_policy_id to the
Managed-AllViewer policy). Add data sources for data.aws_cloudfront_cache_policy
(name = "Managed-CachingDisabled") and data.aws_cloudfront_origin_request_policy
(name = "Managed-AllViewer"), then reference their .id values from the
default_cache_behavior (or relevant behavior) to replicate query string, header
and cookie forwarding behavior.

45-51: Consider adding WAF protection for production deployments.

Static analysis correctly identifies the absence of a WAF. While this may be acceptable for a dev/test environment, production deployments should attach a WAFv2 WebACL to protect against common web exploits (OWASP Top 10, Log4j, etc.).

Additionally, consider configuring origin failover for improved availability if the ALB becomes unreachable.

Example WAF attachment
resource "aws_wafv2_web_acl_association" "cloudfront" {
  resource_arn = aws_cloudfront_distribution.main.arn
  web_acl_arn  = aws_wafv2_web_acl.main.arn
}

You would also need to create an aws_wafv2_web_acl resource with appropriate rules (consider AWS managed rule groups like AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cloudfront.tf` around lines 45 - 51, The CloudFront distribution resource
aws_cloudfront_distribution.main lacks WAF protection and origin failover; add
an aws_wafv2_web_acl resource (e.g., aws_wafv2_web_acl.main) configured with AWS
managed rule groups (AWSManagedRulesCommonRuleSet,
AWSManagedRulesKnownBadInputsRuleSet, etc.) and attach it using
aws_wafv2_web_acl_association.cloudfront with resource_arn =
aws_cloudfront_distribution.main.arn; also update
aws_cloudfront_distribution.main to configure origin failover (use an
origin_group with primary and failover origin definitions pointing to your ALB
and a standby origin, plus appropriate failover criteria) so the distribution
has both WAF protection and improved availability.
iac/ecs.tf (1)

153-157: Single subnet limits availability and uses fragile hardcoded index.

The service is deployed to only one subnet (aws_subnet.public[1]), which means:

  1. No multi-AZ redundancy—an AZ outage takes down the service
  2. The hardcoded index [1] is fragile if subnet definitions change

For production resilience, consider deploying across all public subnets:

Recommended fix for multi-AZ deployment
   network_configuration {
-    subnets          = [aws_subnet.public[1].id]
+    subnets          = aws_subnet.public[*].id
     security_groups  = [aws_security_group.ecs_tasks.id]
     assign_public_ip = true
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/ecs.tf` around lines 153 - 157, The configuration pins ECS
network_configuration.subnets to a single subnet aws_subnet.public[1].id which
removes multi‑AZ redundancy and is brittle; change the subnets value to
reference all created public subnets (e.g., use a splat or for-expression over
aws_subnet.public to collect every id) so the service is deployed across all
public subnets/ AZs instead of the hardcoded index, keeping assign_public_ip and
security_groups as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@iac/rds.tf`:
- Around line 14-21: The aws_db_subnet_group resource
("aws_db_subnet_group.main") currently only lists one subnet
(aws_subnet.private[1].id) which violates RDS requirement for at least two AZs;
update the subnet_ids to include both private subnets (e.g.,
aws_subnet.private[0].id and aws_subnet.private[1].id or the appropriate private
subnet index names) so the DB subnet group spans two AZs and the Terraform apply
will succeed.

---

Nitpick comments:
In `@iac/cloudfront.tf`:
- Around line 84-91: The CloudFront distribution uses the deprecated
forwarded_values block; replace it for the ALB behaviors by removing
forwarded_values and instead attach a cache_policy_id and
origin_request_policy_id (e.g., set cache_policy_id to the
Managed-CachingDisabled policy and origin_request_policy_id to the
Managed-AllViewer policy). Add data sources for data.aws_cloudfront_cache_policy
(name = "Managed-CachingDisabled") and data.aws_cloudfront_origin_request_policy
(name = "Managed-AllViewer"), then reference their .id values from the
default_cache_behavior (or relevant behavior) to replicate query string, header
and cookie forwarding behavior.
- Around line 45-51: The CloudFront distribution resource
aws_cloudfront_distribution.main lacks WAF protection and origin failover; add
an aws_wafv2_web_acl resource (e.g., aws_wafv2_web_acl.main) configured with AWS
managed rule groups (AWSManagedRulesCommonRuleSet,
AWSManagedRulesKnownBadInputsRuleSet, etc.) and attach it using
aws_wafv2_web_acl_association.cloudfront with resource_arn =
aws_cloudfront_distribution.main.arn; also update
aws_cloudfront_distribution.main to configure origin failover (use an
origin_group with primary and failover origin definitions pointing to your ALB
and a standby origin, plus appropriate failover criteria) so the distribution
has both WAF protection and improved availability.

In `@iac/ecs.tf`:
- Around line 153-157: The configuration pins ECS network_configuration.subnets
to a single subnet aws_subnet.public[1].id which removes multi‑AZ redundancy and
is brittle; change the subnets value to reference all created public subnets
(e.g., use a splat or for-expression over aws_subnet.public to collect every id)
so the service is deployed across all public subnets/ AZs instead of the
hardcoded index, keeping assign_public_ip and security_groups as-is.

In `@iac/rds.tf`:
- Around line 24-50: Enable recommended RDS hardening by updating the
aws_db_instance "postgres" resource: set deletion_protection = true and enable
IAM DB authentication via iam_database_authentication_enabled = true on the
aws_db_instance.postgres resource so deletion protection and IAM-based
authentication are enforced for production deployments.

In `@iac/s3.tf`:
- Line 4: The S3 bucket currently sets force_destroy = true which allows
Terraform to delete non-empty buckets; change this for production by removing or
setting force_destroy = false (or gating it behind an environment variable such
as var.force_destroy) and add a safer safeguard like a lifecycle rule or
Terraform prevent_destroy behavior for the S3 resource (refer to the
force_destroy setting on the S3 bucket resource) so production buckets are
protected from accidental terraform destroy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9a464ef-07a9-4fe1-a5c0-e8fdf152909c

📥 Commits

Reviewing files that changed from the base of the PR and between ab921aa and 3bae275.

📒 Files selected for processing (26)
  • Dockerfile
  • iac/.gitignore
  • iac/acm.tf
  • iac/alb.tf
  • iac/cloudfront.tf
  • iac/cloudwatch.tf
  • iac/cognito.tf
  • iac/data.tf
  • iac/ecr.tf
  • iac/ecs.tf
  • iac/iam.tf
  • iac/locals.tf
  • iac/main.tf
  • iac/networking.tf
  • iac/outputs.tf
  • iac/rds.tf
  • iac/route53.tf
  • iac/s3.tf
  • iac/secrets.tf
  • iac/security-groups.tf
  • iac/terraform.tf
  • iac/variables.tf
  • scripts/container-startup.sh
  • tcf_core/settings/base.py
  • tcf_core/settings/health_check_middleware.py
  • tcf_core/settings/prod.py
✅ Files skipped from review due to trivial changes (4)
  • scripts/container-startup.sh
  • iac/cloudwatch.tf
  • iac/.gitignore
  • iac/variables.tf
🚧 Files skipped from review as they are similar to previous changes (12)
  • Dockerfile
  • tcf_core/settings/base.py
  • iac/locals.tf
  • tcf_core/settings/health_check_middleware.py
  • iac/route53.tf
  • iac/data.tf
  • iac/main.tf
  • iac/cognito.tf
  • iac/secrets.tf
  • iac/acm.tf
  • iac/iam.tf
  • iac/outputs.tf

Comment thread iac/rds.tf
@gyoge0 gyoge0 force-pushed the terraform-yogesh branch from 3bae275 to ce83276 Compare April 1, 2026 14:28
@gyoge0 gyoge0 requested a review from Jay-Lalwani April 1, 2026 14:40
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
iac/cloudfront.tf (1)

123-144: Route only the static prefix to S3.

*.* is broader than the bucket’s contract and will also send dotted app routes to S3. Matching the actual static prefix (/static/* or whatever STATIC_URL resolves to) keeps dynamic routes on the ALB and avoids having to carve out exceptions like /favicon.ico.

Based on learnings: In theCourseForum2, the S3 bucket (aws_s3_bucket.static) is used exclusively for Django static files with no private user-uploaded content.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cloudfront.tf` around lines 123 - 144, The cache behavior currently uses
a broad path_pattern "*.*" which routes dotted app routes to S3; change
ordered_cache_behavior's path_pattern to only match your static prefix (e.g.,
"static/*" or the value derived from STATIC_URL without leading slash) so only
Django static files go to aws_s3_bucket.static; keep target_origin_id =
"S3-${aws_s3_bucket.static.id}" and the rest of the settings, and update any
comments to reflect the precise static prefix used.
iac/security-groups.tf (1)

15-20: Tighten ALB egress to the target SG.

The load balancer only needs to reach the ECS tasks on port 8000, so 0.0.0.0/0 is broader than necessary for the public edge security group.

♻️ Suggested narrowing
  egress {
-    description = "Allow all outbound traffic"
-    from_port   = 0
-    to_port     = 0
-    protocol    = "-1"
-    cidr_blocks = ["0.0.0.0/0"]
+    description     = "Allow traffic to ECS tasks on port 8000"
+    from_port       = 8000
+    to_port         = 8000
+    protocol        = "tcp"
+    security_groups = [aws_security_group.ecs_tasks.id]
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/security-groups.tf` around lines 15 - 20, The ALB egress rule currently
allows 0.0.0.0/0; replace that egress block so it only allows TCP port 8000 to
the ECS tasks' security group by setting protocol = "tcp", from_port = 8000,
to_port = 8000 and target the task SG (use security_groups =
[aws_security_group.<task_sg_resource_name>.id] or a self-referencing
aws_security_group rule referencing the task SG) instead of cidr_blocks; update
the ALB/security group resource's egress block (the egress in the ALB/public
edge SG) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@iac/rds.tf`:
- Around line 38-45: The RDS resource block that contains multi_az,
publicly_accessible, db_subnet_group_name, vpc_security_group_ids and
final_snapshot_identifier is missing deletion_protection; add
deletion_protection = true to that aws_db_instance resource (the same block that
uses final_snapshot_identifier and skip_final_snapshot) to prevent accidental
DeleteDBInstance API calls, and if you need to allow controlled deletion expose
a boolean var (e.g., var.enable_rds_deletion) to set deletion_protection
conditionally and document the process to disable it before intentional
destroys.

---

Nitpick comments:
In `@iac/cloudfront.tf`:
- Around line 123-144: The cache behavior currently uses a broad path_pattern
"*.*" which routes dotted app routes to S3; change ordered_cache_behavior's
path_pattern to only match your static prefix (e.g., "static/*" or the value
derived from STATIC_URL without leading slash) so only Django static files go to
aws_s3_bucket.static; keep target_origin_id = "S3-${aws_s3_bucket.static.id}"
and the rest of the settings, and update any comments to reflect the precise
static prefix used.

In `@iac/security-groups.tf`:
- Around line 15-20: The ALB egress rule currently allows 0.0.0.0/0; replace
that egress block so it only allows TCP port 8000 to the ECS tasks' security
group by setting protocol = "tcp", from_port = 8000, to_port = 8000 and target
the task SG (use security_groups =
[aws_security_group.<task_sg_resource_name>.id] or a self-referencing
aws_security_group rule referencing the task SG) instead of cidr_blocks; update
the ALB/security group resource's egress block (the egress in the ALB/public
edge SG) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b3d905f-0b92-4eda-8f1f-ab5a1a0a08a7

📥 Commits

Reviewing files that changed from the base of the PR and between 3bae275 and ce83276.

📒 Files selected for processing (7)
  • iac/cloudfront.tf
  • iac/data.tf
  • iac/networking.tf
  • iac/rds.tf
  • iac/s3.tf
  • iac/secrets.tf
  • iac/security-groups.tf
✅ Files skipped from review due to trivial changes (1)
  • iac/data.tf

Comment thread iac/rds.tf
Comment on lines +38 to +45
multi_az = false
publicly_accessible = false
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]

backup_retention_period = 7
skip_final_snapshot = false
final_snapshot_identifier = "${local.name_prefix}-final-snapshot-${random_id.db_snapshot.hex}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the iac/rds.tf file
find . -name "rds.tf" -type f

Repository: thecourseforum/theCourseForum2

Length of output: 86


🏁 Script executed:

# Once found, let's examine the full aws_db_instance resource definition
cat -n iac/rds.tf

Repository: thecourseforum/theCourseForum2

Length of output: 1803


🏁 Script executed:

# Search for deletion_protection mentions in the file
rg "deletion_protection" iac/rds.tf

Repository: thecourseforum/theCourseForum2

Length of output: 56


🌐 Web query:

AWS RDS deletion_protection behavior terraform aws_db_instance

💡 Result:

In the Terraform AWS provider's aws_db_instance resource, deletion_protection is an optional boolean argument that controls AWS RDS deletion protection for the DB instance. When set to true, the DB instance cannot be deleted via the AWS console, CLI, or API until deletion protection is first disabled. The default value is false, matching the AWS API default to maintain consistency. To use it: deletion_protection = true Terraform will respect this setting during destroy operations: if true, it first modifies the instance to set deletion_protection = false (causing an update), then deletes it. If deletion_protection = true in state but false in AWS (drift), Terraform detects and plans to update it to true. This behavior prevents accidental deletions while allowing managed lifecycle. Official AWS docs confirm deletion protection blocks DeleteDBInstance API calls when enabled, requiring a ModifyDBInstance first.

Citations:


Add deletion protection to prevent accidental database deletion.

The DB instance lacks deletion_protection, which means a terraform destroy or accidental replacement can remove it despite the final snapshot. Final snapshots enable recovery after deletion but do not prevent the delete operation itself. AWS blocks DeleteDBInstance API calls when deletion protection is enabled, requiring an explicit disable step first—this is the standard safeguard for production databases.

🛡️ Suggested safeguard
   multi_az               = false
   publicly_accessible    = false
+  deletion_protection    = true
   db_subnet_group_name   = aws_db_subnet_group.main.name
   vpc_security_group_ids = [aws_security_group.rds.id]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
multi_az = false
publicly_accessible = false
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
backup_retention_period = 7
skip_final_snapshot = false
final_snapshot_identifier = "${local.name_prefix}-final-snapshot-${random_id.db_snapshot.hex}"
multi_az = false
publicly_accessible = false
deletion_protection = true
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
backup_retention_period = 7
skip_final_snapshot = false
final_snapshot_identifier = "${local.name_prefix}-final-snapshot-${random_id.db_snapshot.hex}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/rds.tf` around lines 38 - 45, The RDS resource block that contains
multi_az, publicly_accessible, db_subnet_group_name, vpc_security_group_ids and
final_snapshot_identifier is missing deletion_protection; add
deletion_protection = true to that aws_db_instance resource (the same block that
uses final_snapshot_identifier and skip_final_snapshot) to prevent accidental
DeleteDBInstance API calls, and if you need to allow controlled deletion expose
a boolean var (e.g., var.enable_rds_deletion) to set deletion_protection
conditionally and document the process to disable it before intentional
destroys.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
iac/cognito.tf (3)

48-48: Consider removing the implicit OAuth flow.

The implicit flow is deprecated in OAuth 2.1 due to security concerns (tokens exposed in URLs/browser history). For web applications, the authorization code flow (code) with PKCE is the recommended approach. If implicit is not required for backward compatibility, consider removing it.

♻️ Proposed fix
-  allowed_oauth_flows                  = ["code", "implicit"]
+  allowed_oauth_flows                  = ["code"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cognito.tf` at line 48, The configuration currently enables the
deprecated implicit OAuth flow via allowed_oauth_flows = ["code", "implicit"];
remove "implicit" from the allowed_oauth_flows setting (in the Cognito
client/resource that declares allowed_oauth_flows) so only the authorization
code flow is permitted (["code"]) and ensure PKCE is enabled for the client
(check settings like support for PKCE or explicit_require_pkce if present) to
follow OAuth 2.1 guidance.

30-33: Consider SES for production email delivery.

COGNITO_DEFAULT has a daily email limit (50/day in sandbox mode). For a production environment with significant user sign-ups or OTP requests, consider configuring SES as the email sender to avoid rate limits.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cognito.tf` around lines 30 - 33, The user pool currently uses
email_sending_account = "COGNITO_DEFAULT" which is rate-limited; change the
email_configuration to use SES by setting email_sending_account = "DEVELOPER"
and add a source_arn that references a verified SES identity (e.g., the ARN of
an aws_ses_email_identity or aws_ses_domain_identity resource) and optionally
reply_to_email_address/from fields; also ensure you create and verify the SES
identity (and any required IAM role/policy) in Terraform so the referenced SES
resource exists and is usable by the Cognito User Pool.

79-79: Add variable validation to prevent Cognito domain deployment failures.

AWS Cognito domains must be lowercase alphanumeric with hyphens only. Since local.name_prefix is "${var.project_name}-${var.environment}", values with underscores or uppercase letters will cause deployment failures. Add validation blocks to project_name and environment variables to enforce the pattern: ^[a-z0-9]+(-[a-z0-9]+)*$

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@iac/cognito.tf` at line 79, Add validation for the variables used to build
local.name_prefix so Cognito domains stay lowercase alphanumeric with hyphens:
update the variable declarations for project_name and environment (the variables
referenced by local.name_prefix and used in cognito.tf) to include a validation
block enforcing the regex ^[a-z0-9]+(-[a-z0-9]+)*$ and a clear error_message;
this ensures values passed into local.name_prefix (and thus the Cognito domain
in domain = "${local.name_prefix}-auth") cannot contain underscores or uppercase
letters and will prevent Cognito deployment failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@iac/cognito.tf`:
- Line 48: The configuration currently enables the deprecated implicit OAuth
flow via allowed_oauth_flows = ["code", "implicit"]; remove "implicit" from the
allowed_oauth_flows setting (in the Cognito client/resource that declares
allowed_oauth_flows) so only the authorization code flow is permitted (["code"])
and ensure PKCE is enabled for the client (check settings like support for PKCE
or explicit_require_pkce if present) to follow OAuth 2.1 guidance.
- Around line 30-33: The user pool currently uses email_sending_account =
"COGNITO_DEFAULT" which is rate-limited; change the email_configuration to use
SES by setting email_sending_account = "DEVELOPER" and add a source_arn that
references a verified SES identity (e.g., the ARN of an aws_ses_email_identity
or aws_ses_domain_identity resource) and optionally reply_to_email_address/from
fields; also ensure you create and verify the SES identity (and any required IAM
role/policy) in Terraform so the referenced SES resource exists and is usable by
the Cognito User Pool.
- Line 79: Add validation for the variables used to build local.name_prefix so
Cognito domains stay lowercase alphanumeric with hyphens: update the variable
declarations for project_name and environment (the variables referenced by
local.name_prefix and used in cognito.tf) to include a validation block
enforcing the regex ^[a-z0-9]+(-[a-z0-9]+)*$ and a clear error_message; this
ensures values passed into local.name_prefix (and thus the Cognito domain in
domain = "${local.name_prefix}-auth") cannot contain underscores or uppercase
letters and will prevent Cognito deployment failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bcd83e84-15d8-4688-82ed-658bbf52451e

📥 Commits

Reviewing files that changed from the base of the PR and between ce83276 and 4a39eb4.

⛔ Files ignored due to path filters (4)
  • iac/assets/form_logo_light.svg is excluded by !**/*.svg
  • iac/assets/page_background_light.png is excluded by !**/*.png
  • iac/assets/page_footer_logo_light.png is excluded by !**/*.png
  • iac/assets/page_header_logo_light.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • iac/branding-settings.json
  • iac/cognito.tf
✅ Files skipped from review due to trivial changes (1)
  • iac/branding-settings.json

@gyoge0 gyoge0 force-pushed the terraform-yogesh branch from 4a39eb4 to 4741429 Compare April 10, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants