diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml
index 287401792e..6c0346e007 100644
--- a/.github/workflows/run-ci-cd.yaml
+++ b/.github/workflows/run-ci-cd.yaml
@@ -64,6 +64,14 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
tflint_version: v0.60.0
+ - name: Install terraform-docs
+ run: |
+ TERRAFORM_DOCS_VERSION=0.20.0
+ curl -sSL \
+ "https://github.com/terraform-docs/terraform-docs/releases/download/v${TERRAFORM_DOCS_VERSION}/terraform-docs-v${TERRAFORM_DOCS_VERSION}-linux-amd64.tar.gz" \
+ | tar -xz terraform-docs
+ sudo mv terraform-docs /usr/local/bin/terraform-docs
+
- name: Run pre-commit
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 79c6f38cff..23245b0d94 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -40,6 +40,15 @@ repos:
args:
- --args=--config=__GIT_WORKING_DIR__/infrastructure/.tflint.hcl
+ - repo: local
+ hooks:
+ - id: terraform_docs
+ name: terraform-docs
+ entry: python infrastructure/scripts/terraform_docs.py
+ language: system
+ files: ^(infrastructure/.*\.tf|infrastructure/\.terraform-docs\.yml|infrastructure/scripts/terraform_docs\.py)$
+ pass_filenames: false
+
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: aca6d4c8045a504e2812ea4bedff1d0a09e437bc # v0.15.8
hooks:
diff --git a/infrastructure/.terraform-docs.yml b/infrastructure/.terraform-docs.yml
new file mode 100644
index 0000000000..d511479030
--- /dev/null
+++ b/infrastructure/.terraform-docs.yml
@@ -0,0 +1,15 @@
+formatter: markdown table
+output:
+ file: README.md
+ mode: inject
+sections:
+ show:
+ - providers
+ - requirements
+ - resources
+ - modules
+ - inputs
+ - outputs
+settings:
+ hide-empty: true
+ html: false
diff --git a/infrastructure/Makefile b/infrastructure/Makefile
index 452d16fa8f..85736baa62 100644
--- a/infrastructure/Makefile
+++ b/infrastructure/Makefile
@@ -1,5 +1,11 @@
##@ Infrastructure
+terraform-docs-infrastructure: ## Generate terraform-docs for infrastructure projects and modules
+ @for dir in infrastructure/bootstrap infrastructure/live infrastructure/state $$(find infrastructure/modules -type f -name "*.tf" -not -path "*/.terraform/*" -exec dirname {} \; | sort -u); do \
+ echo "Generating terraform-docs for $$dir..."; \
+ terraform-docs --config "$(CURDIR)/infrastructure/.terraform-docs.yml" "$$dir" || exit 1; \
+ done
+
test-infrastructure: ## Run infrastructure tests
@find infrastructure/modules \
-name "tests" \
diff --git a/infrastructure/README.md b/infrastructure/README.md
index 815ead189a..71e9d73436 100644
--- a/infrastructure/README.md
+++ b/infrastructure/README.md
@@ -16,6 +16,14 @@ Note: Refer to the respective `README.md` files for more information.
- [OWASP Nest DeepWiki](https://deepwiki.com/OWASP/Nest/4.3-aws-infrastructure)
+## Terraform Docs
+
+Refresh generated Terraform reference sections locally with:
+
+```bash
+make terraform-docs-infrastructure
+```
+
## Setting up the infrastructure
Follow these steps to set up the infrastructure:
diff --git a/infrastructure/bootstrap/README.md b/infrastructure/bootstrap/README.md
index e7f0691010..646946f6a5 100644
--- a/infrastructure/bootstrap/README.md
+++ b/infrastructure/bootstrap/README.md
@@ -76,3 +76,46 @@ Use the following inline permissions for the `nest-bootstrap` IAM User
]
}
```
+
+## Terraform Reference
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_iam_policy.part_one](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.part_two](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_role.terraform](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.attach_part_one](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.attach_part_two](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [aws\_region](#input\_aws\_region) | The AWS region to deploy resources in. | `string` | `"us-east-2"` | no |
+| [aws\_role\_external\_id](#input\_aws\_role\_external\_id) | The external ID for role assumption. | `string` | n/a | yes |
+| [environments](#input\_environments) | The environments to create Terraform roles for. | `list(string)` | ```[ "staging", "production" ]``` | no |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | `"nest"` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [terraform\_role\_arns](#output\_terraform\_role\_arns) | The ARNs of the Terraform IAM roles, keyed by environment. |
+
+
diff --git a/infrastructure/live/README.md b/infrastructure/live/README.md
index 4fda6f0c38..8e5a5d8bc0 100644
--- a/infrastructure/live/README.md
+++ b/infrastructure/live/README.md
@@ -32,3 +32,109 @@ Use the following inline permissions for the `nest-staging` IAM User.
]
}
```
+
+## Terraform Reference
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [alb](#module\_alb) | ../modules/alb | n/a |
+| [backend](#module\_backend) | ../modules/service | n/a |
+| [cache](#module\_cache) | ../modules/cache | n/a |
+| [database](#module\_database) | ../modules/database | n/a |
+| [frontend](#module\_frontend) | ../modules/service | n/a |
+| [kms](#module\_kms) | ../modules/kms | n/a |
+| [networking](#module\_networking) | ../modules/networking | n/a |
+| [parameters](#module\_parameters) | ../modules/parameters | n/a |
+| [security](#module\_security) | ../modules/security | n/a |
+| [storage](#module\_storage) | ../modules/storage | n/a |
+| [tasks](#module\_tasks) | ../modules/tasks | n/a |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [availability\_zones](#input\_availability\_zones) | A list of availability zones for the VPC. | `list(string)` | ```[ "us-east-2a", "us-east-2b", "us-east-2c" ]``` | no |
+| [aws\_region](#input\_aws\_region) | The AWS region to deploy resources in. | `string` | `"us-east-2"` | no |
+| [backend\_desired\_count](#input\_backend\_desired\_count) | The desired number of backend tasks. | `number` | `1` | no |
+| [backend\_enable\_auto\_scaling](#input\_backend\_enable\_auto\_scaling) | Whether to enable auto scaling for backend. | `bool` | `false` | no |
+| [backend\_image\_tag](#input\_backend\_image\_tag) | The Docker backend image tag. | `string` | n/a | yes |
+| [backend\_max\_count](#input\_backend\_max\_count) | The maximum number of backend tasks for auto scaling. | `number` | `6` | no |
+| [backend\_min\_count](#input\_backend\_min\_count) | The minimum number of backend tasks for auto scaling. | `number` | `2` | no |
+| [backend\_use\_fargate\_spot](#input\_backend\_use\_fargate\_spot) | Whether to use Fargate Spot for backend tasks. | `bool` | `true` | no |
+| [db\_allocated\_storage](#input\_db\_allocated\_storage) | The allocated storage for the RDS database in GB. | `number` | `20` | no |
+| [db\_backup\_retention\_period](#input\_db\_backup\_retention\_period) | The number of days to retain backups for. | `number` | `7` | no |
+| [db\_deletion\_protection](#input\_db\_deletion\_protection) | Specifies whether to prevent database deletion. | `bool` | `true` | no |
+| [db\_engine\_version](#input\_db\_engine\_version) | The version of the PostgreSQL engine. | `string` | `"16.10"` | no |
+| [db\_instance\_class](#input\_db\_instance\_class) | The instance class for the RDS database. | `string` | `"db.t3.micro"` | no |
+| [db\_name](#input\_db\_name) | The name of the RDS database. | `string` | `"nest_db"` | no |
+| [db\_password](#input\_db\_password) | The password for the RDS database. | `string` | `null` | no |
+| [db\_port](#input\_db\_port) | The port for the RDS database. | `number` | `5432` | no |
+| [db\_skip\_final\_snapshot](#input\_db\_skip\_final\_snapshot) | Whether a final DB snapshot is created before the DB instance is deleted. | `bool` | `false` | no |
+| [db\_storage\_type](#input\_db\_storage\_type) | The storage type for the RDS database. | `string` | `"gp3"` | no |
+| [db\_user](#input\_db\_user) | The username for the RDS database. | `string` | `"nest_db_user"` | no |
+| [django\_configuration](#input\_django\_configuration) | The name of the Django configuration to use (e.g., Staging, Production). | `string` | n/a | yes |
+| [django\_release\_version](#input\_django\_release\_version) | The Django release version. | `string` | `"0.0.0"` | no |
+| [django\_settings\_module](#input\_django\_settings\_module) | The location of the Django settings module to use (e.g., settings.staging, settings.production). | `string` | n/a | yes |
+| [domain\_name](#input\_domain\_name) | The domain name for the site. | `string` | n/a | yes |
+| [enable\_additional\_parameters](#input\_enable\_additional\_parameters) | Whether to enable additional parameters (e.g. for production). | `bool` | `false` | no |
+| [enable\_cron\_tasks](#input\_enable\_cron\_tasks) | Whether to enable scheduled cron tasks. | `bool` | n/a | yes |
+| [enable\_nat\_gateway](#input\_enable\_nat\_gateway) | Whether to enable a NAT Gateway. | `bool` | `true` | no |
+| [enable\_rds\_proxy](#input\_enable\_rds\_proxy) | Whether to create an RDS proxy. | `bool` | `false` | no |
+| [enable\_vpc\_cloudwatch\_logs\_endpoint](#input\_enable\_vpc\_cloudwatch\_logs\_endpoint) | Whether to create CloudWatch Logs VPC endpoint. | `bool` | `false` | no |
+| [enable\_vpc\_ecr\_api\_endpoint](#input\_enable\_vpc\_ecr\_api\_endpoint) | Whether to create ECR API VPC endpoint. | `bool` | `false` | no |
+| [enable\_vpc\_ecr\_dkr\_endpoint](#input\_enable\_vpc\_ecr\_dkr\_endpoint) | Whether to create ECR DKR VPC endpoint. | `bool` | `false` | no |
+| [enable\_vpc\_s3\_endpoint](#input\_enable\_vpc\_s3\_endpoint) | Whether to create S3 VPC endpoint (Gateway, free). | `bool` | `true` | no |
+| [enable\_vpc\_secretsmanager\_endpoint](#input\_enable\_vpc\_secretsmanager\_endpoint) | Whether to create Secrets Manager VPC endpoint. | `bool` | `false` | no |
+| [enable\_vpc\_ssm\_endpoint](#input\_enable\_vpc\_ssm\_endpoint) | Whether to create SSM VPC endpoint. | `bool` | `false` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [fixtures\_bucket\_name](#input\_fixtures\_bucket\_name) | The name of the S3 bucket for fixtures. | `string` | `null` | no |
+| [frontend\_desired\_count](#input\_frontend\_desired\_count) | The desired number of frontend tasks. | `number` | `1` | no |
+| [frontend\_enable\_auto\_scaling](#input\_frontend\_enable\_auto\_scaling) | Whether to enable auto scaling for frontend. | `bool` | `false` | no |
+| [frontend\_image\_tag](#input\_frontend\_image\_tag) | The Docker frontend image tag. | `string` | n/a | yes |
+| [frontend\_max\_count](#input\_frontend\_max\_count) | The maximum number of tasks for auto scaling. | `number` | `6` | no |
+| [frontend\_min\_count](#input\_frontend\_min\_count) | The minimum number of tasks for auto scaling. | `number` | `2` | no |
+| [frontend\_use\_fargate\_spot](#input\_frontend\_use\_fargate\_spot) | Whether to use Fargate Spot for frontend tasks. | `bool` | `true` | no |
+| [private\_subnet\_cidrs](#input\_private\_subnet\_cidrs) | A list of CIDR blocks for the private subnets. | `list(string)` | ```[ "10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24" ]``` | no |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | `"nest"` | no |
+| [public\_subnet\_cidrs](#input\_public\_subnet\_cidrs) | A list of CIDR blocks for the public subnets. | `list(string)` | ```[ "10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24" ]``` | no |
+| [redis\_engine\_version](#input\_redis\_engine\_version) | The version of the Redis engine. | `string` | `"7.0"` | no |
+| [redis\_node\_type](#input\_redis\_node\_type) | The node type for the Redis cache. | `string` | `"cache.t3.micro"` | no |
+| [redis\_num\_cache\_nodes](#input\_redis\_num\_cache\_nodes) | The number of cache nodes in the Redis cluster. | `number` | `1` | no |
+| [redis\_port](#input\_redis\_port) | The port for the Redis cache. | `number` | `6379` | no |
+| [secret\_recovery\_window\_in\_days](#input\_secret\_recovery\_window\_in\_days) | The number of days that Secrets Manager waits before it can delete the secret. Set to 0 to delete immediately. | `number` | `7` | no |
+| [slack\_bot\_token\_suffix](#input\_slack\_bot\_token\_suffix) | The Suffix for the Slack bot token. | `string` | `"T04T40NHX"` | no |
+| [tasks\_use\_fargate\_spot](#input\_tasks\_use\_fargate\_spot) | Whether to use Fargate Spot for ECS tasks. | `bool` | `true` | no |
+| [vpc\_cidr](#input\_vpc\_cidr) | The CIDR block for the VPC. | `string` | `"10.0.0.0/16"` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [acm\_certificate\_domain\_validation\_options](#output\_acm\_certificate\_domain\_validation\_options) | The DNS validation options for ACM certificate. |
+| [acm\_certificate\_status](#output\_acm\_certificate\_status) | The status of the ACM certificate. |
+| [alb\_dns\_name](#output\_alb\_dns\_name) | The DNS name of the ALB. |
+| [backend\_cluster\_name](#output\_backend\_cluster\_name) | The name of the ECS backend cluster. |
+| [backend\_ecr\_repository\_url](#output\_backend\_ecr\_repository\_url) | The URL of the backend ECR repository. |
+| [backend\_service\_name](#output\_backend\_service\_name) | The name of the ECS backend service. |
+| [fixtures\_bucket\_name](#output\_fixtures\_bucket\_name) | The name of the S3 bucket for database fixtures. |
+| [frontend\_cluster\_name](#output\_frontend\_cluster\_name) | The name of the ECS frontend cluster. |
+| [frontend\_ecr\_repository\_url](#output\_frontend\_ecr\_repository\_url) | The URL of the frontend ECR repository. |
+| [frontend\_service\_name](#output\_frontend\_service\_name) | The name of the ECS frontend service. |
+| [frontend\_url](#output\_frontend\_url) | The URL to access the frontend. |
+| [nat\_gateway\_enabled](#output\_nat\_gateway\_enabled) | Whether a NAT Gateway is enabled. |
+| [private\_subnet\_ids](#output\_private\_subnet\_ids) | A list of private subnet IDs. |
+| [tasks\_cluster\_name](#output\_tasks\_cluster\_name) | The name of the ECS tasks cluster. |
+| [tasks\_security\_group\_id](#output\_tasks\_security\_group\_id) | The ID of the security group for ECS tasks. |
+| [tasks\_subnet\_ids](#output\_tasks\_subnet\_ids) | A list of public or private subnet IDs for ECS tasks. |
+
+
diff --git a/infrastructure/modules/alb/README.md b/infrastructure/modules/alb/README.md
new file mode 100644
index 0000000000..c0ffe4240b
--- /dev/null
+++ b/infrastructure/modules/alb/README.md
@@ -0,0 +1,72 @@
+# Alb
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+| [random](#requirement\_random) | ~> 3.8.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+| [random](#provider\_random) | 3.8.1 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_acm_certificate.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource |
+| [aws_acm_certificate_validation.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource |
+| [aws_lb.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb) | resource |
+| [aws_lb_listener.http_redirect](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource |
+| [aws_lb_listener.https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource |
+| [aws_lb_listener_rule.backend_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource |
+| [aws_lb_target_group.backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource |
+| [aws_lb_target_group.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource |
+| [aws_s3_bucket.alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket_lifecycle_configuration.alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
+| [aws_s3_bucket_policy.alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
+| [aws_s3_bucket_public_access_block.alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_s3_bucket_server_side_encryption_configuration.alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
+| [aws_s3_bucket_versioning.alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
+| [random_id.suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [alb\_sg\_id](#input\_alb\_sg\_id) | The security group ID for the ALB. | `string` | n/a | yes |
+| [backend\_health\_check\_path](#input\_backend\_health\_check\_path) | The health check path for the backend target group. | `string` | `"/status/"` | no |
+| [backend\_port](#input\_backend\_port) | The port for the backend target group. | `number` | `8000` | no |
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [domain\_name](#input\_domain\_name) | The domain name for ACM certificate (e.g., nest.owasp.dev). | `string` | n/a | yes |
+| [environment](#input\_environment) | The environment name (e.g., staging, production). | `string` | n/a | yes |
+| [frontend\_health\_check\_path](#input\_frontend\_health\_check\_path) | The health check path for the frontend target group. | `string` | `"/"` | no |
+| [frontend\_port](#input\_frontend\_port) | The port for the frontend target group. | `number` | `3000` | no |
+| [log\_retention\_days](#input\_log\_retention\_days) | The number of days to retain ALB access logs. | `number` | `90` | no |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [public\_subnet\_ids](#input\_public\_subnet\_ids) | A list of public subnet IDs for the ALB. | `list(string)` | n/a | yes |
+| [static\_s3\_bucket\_name](#input\_static\_s3\_bucket\_name) | S3 static assets bucket name. | `string` | n/a | yes |
+| [vpc\_id](#input\_vpc\_id) | The ID of the VPC. | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | The ARN of the ACM certificate. |
+| [acm\_certificate\_domain\_validation\_options](#output\_acm\_certificate\_domain\_validation\_options) | The domain validation options for ACM certificate DNS validation. |
+| [acm\_certificate\_status](#output\_acm\_certificate\_status) | The status of the ACM certificate. |
+| [alb\_arn](#output\_alb\_arn) | The ARN of the ALB. |
+| [alb\_dns\_name](#output\_alb\_dns\_name) | The DNS name of the ALB. |
+| [alb\_zone\_id](#output\_alb\_zone\_id) | The zone ID of the ALB. |
+| [backend\_target\_group\_arn](#output\_backend\_target\_group\_arn) | The ARN of the backend target group. |
+| [frontend\_target\_group\_arn](#output\_frontend\_target\_group\_arn) | The ARN of the frontend target group. |
+| [http\_listener\_arn](#output\_http\_listener\_arn) | The ARN of the HTTP listener. |
+| [https\_listener\_arn](#output\_https\_listener\_arn) | The ARN of the HTTPS listener. |
+
+
diff --git a/infrastructure/modules/cache/README.md b/infrastructure/modules/cache/README.md
new file mode 100644
index 0000000000..f13de5021f
--- /dev/null
+++ b/infrastructure/modules/cache/README.md
@@ -0,0 +1,57 @@
+# Cache
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+| [random](#requirement\_random) | ~> 3.8.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+| [random](#provider\_random) | 3.8.1 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_cloudwatch_log_group.engine_log](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
+| [aws_cloudwatch_log_group.slow_log](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
+| [aws_elasticache_replication_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_replication_group) | resource |
+| [aws_elasticache_subnet_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_subnet_group) | resource |
+| [aws_ssm_parameter.django_redis_password](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [random_password.redis_auth_token](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | Whether minor engine upgrades will be applied automatically. | `bool` | `true` | no |
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key. | `string` | n/a | yes |
+| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The number of days to retain log events. | `number` | `90` | no |
+| [maintenance\_window](#input\_maintenance\_window) | The weekly time range for when maintenance on the cache cluster is performed. | `string` | `"mon:05:00-mon:07:00"` | no |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [redis\_engine\_version](#input\_redis\_engine\_version) | The version of the Redis engine. | `string` | n/a | yes |
+| [redis\_node\_type](#input\_redis\_node\_type) | The node type for the Redis cache. | `string` | n/a | yes |
+| [redis\_num\_cache\_nodes](#input\_redis\_num\_cache\_nodes) | The number of cache nodes in the Redis cluster. | `number` | n/a | yes |
+| [redis\_port](#input\_redis\_port) | The port for the Redis cache. | `number` | n/a | yes |
+| [security\_group\_ids](#input\_security\_group\_ids) | A list of security group IDs to associate with the Redis cache. | `list(string)` | n/a | yes |
+| [snapshot\_retention\_limit](#input\_snapshot\_retention\_limit) | The number of days for which automatic snapshots are retained. | `number` | `5` | no |
+| [snapshot\_window](#input\_snapshot\_window) | The daily time range (in UTC) during which ElastiCache will begin taking a daily snapshot. | `string` | `"03:00-05:00"` | no |
+| [subnet\_ids](#input\_subnet\_ids) | A list of subnet IDs for the cache subnet group. | `list(string)` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [redis\_password\_arn](#output\_redis\_password\_arn) | The SSM Parameter ARN of password of Redis. |
+| [redis\_primary\_endpoint](#output\_redis\_primary\_endpoint) | The primary endpoint of the Redis replication group. |
+
+
diff --git a/infrastructure/modules/database/README.md b/infrastructure/modules/database/README.md
new file mode 100644
index 0000000000..f4b023f3e8
--- /dev/null
+++ b/infrastructure/modules/database/README.md
@@ -0,0 +1,69 @@
+# Database
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+| [random](#requirement\_random) | ~> 3.8.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+| [random](#provider\_random) | 3.8.1 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_db_instance.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance) | resource |
+| [aws_db_proxy.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy) | resource |
+| [aws_db_proxy_default_target_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_default_target_group) | resource |
+| [aws_db_proxy_target.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_target) | resource |
+| [aws_db_subnet_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_subnet_group) | resource |
+| [aws_iam_role.rds_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy.rds_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
+| [aws_secretsmanager_secret.db_credentials](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
+| [aws_secretsmanager_secret_version.db_credentials](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
+| [aws_ssm_parameter.django_db_password](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [random_password.db_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [db\_allocated\_storage](#input\_db\_allocated\_storage) | The allocated storage for the RDS database in GB. | `number` | n/a | yes |
+| [db\_backup\_retention\_period](#input\_db\_backup\_retention\_period) | The number of days to retain backups for. | `number` | `7` | no |
+| [db\_backup\_window](#input\_db\_backup\_window) | The daily time range (in UTC) during which automated backups are created. | `string` | `"03:00-04:00"` | no |
+| [db\_copy\_tags\_to\_snapshot](#input\_db\_copy\_tags\_to\_snapshot) | Whether to copy all instance tags to snapshots. | `bool` | `true` | no |
+| [db\_deletion\_protection](#input\_db\_deletion\_protection) | Specifies whether to prevent database deletion. | `bool` | `true` | no |
+| [db\_engine\_version](#input\_db\_engine\_version) | The version of the PostgreSQL engine. | `string` | n/a | yes |
+| [db\_instance\_class](#input\_db\_instance\_class) | The instance class for the RDS database. | `string` | n/a | yes |
+| [db\_maintenance\_window](#input\_db\_maintenance\_window) | The weekly time range (in UTC) during which system maintenance can occur. | `string` | `"mon:04:00-mon:05:00"` | no |
+| [db\_name](#input\_db\_name) | The name of the RDS database. | `string` | n/a | yes |
+| [db\_password](#input\_db\_password) | The password for the RDS database. | `string` | `null` | no |
+| [db\_skip\_final\_snapshot](#input\_db\_skip\_final\_snapshot) | Whether a final DB snapshot is created before the DB instance is deleted. | `bool` | `false` | no |
+| [db\_storage\_type](#input\_db\_storage\_type) | The storage type for the RDS database. | `string` | `"gp3"` | no |
+| [db\_subnet\_ids](#input\_db\_subnet\_ids) | A list of subnet IDs for the DB subnet group. | `list(string)` | n/a | yes |
+| [db\_user](#input\_db\_user) | The username for the RDS database. | `string` | n/a | yes |
+| [enable\_rds\_proxy](#input\_enable\_rds\_proxy) | Whether to create an RDS proxy. | `bool` | `false` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key. | `string` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [proxy\_security\_group\_ids](#input\_proxy\_security\_group\_ids) | A list of security group IDs to associate with the RDS proxy. | `list(string)` | `[]` | no |
+| [secret\_recovery\_window\_in\_days](#input\_secret\_recovery\_window\_in\_days) | The number of days that Secrets Manager waits before it can delete the secret. Set to 0 to delete immediately. | `number` | `7` | no |
+| [security\_group\_ids](#input\_security\_group\_ids) | A list of security group IDs to associate with the RDS database. | `list(string)` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [db\_password\_arn](#output\_db\_password\_arn) | The SSM Parameter ARN of password of the RDS database. |
+| [db\_proxy\_endpoint](#output\_db\_proxy\_endpoint) | The RDS proxy endpoint when proxying is enabled, otherwise the DB instance endpoint. |
+
+
diff --git a/infrastructure/modules/database/outputs.tf b/infrastructure/modules/database/outputs.tf
index 46d3b02934..45684899be 100644
--- a/infrastructure/modules/database/outputs.tf
+++ b/infrastructure/modules/database/outputs.tf
@@ -4,6 +4,6 @@ output "db_password_arn" {
}
output "db_proxy_endpoint" {
- description = "The endpoint of the RDS proxy."
+ description = "The RDS proxy endpoint when proxying is enabled, otherwise the DB instance endpoint."
value = var.enable_rds_proxy ? aws_db_proxy.main[0].endpoint : aws_db_instance.main.address
}
diff --git a/infrastructure/modules/kms/README.md b/infrastructure/modules/kms/README.md
new file mode 100644
index 0000000000..681eb90b21
--- /dev/null
+++ b/infrastructure/modules/kms/README.md
@@ -0,0 +1,42 @@
+# Kms
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_kms_alias.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
+| [aws_kms_key.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [alias\_name](#input\_alias\_name) | The name of the KMS alias. | `string` | n/a | yes |
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [deletion\_window\_in\_days](#input\_deletion\_window\_in\_days) | The number of days before the KMS key is deleted after destruction. | `number` | `30` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [rotation\_period\_in\_days](#input\_rotation\_period\_in\_days) | Rotation period in days. | `number` | `90` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [key\_alias](#output\_key\_alias) | The alias of the KMS key. |
+| [key\_arn](#output\_key\_arn) | The ARN of the KMS key. |
+
+
diff --git a/infrastructure/modules/networking/README.md b/infrastructure/modules/networking/README.md
new file mode 100644
index 0000000000..7e17d8b645
--- /dev/null
+++ b/infrastructure/modules/networking/README.md
@@ -0,0 +1,75 @@
+# Networking
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [nacl](#module\_nacl) | ./modules/nacl | n/a |
+| [vpc\_endpoint](#module\_vpc\_endpoint) | ./modules/vpc-endpoint | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_cloudwatch_log_group.flow_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
+| [aws_eip.nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource |
+| [aws_flow_log.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/flow_log) | resource |
+| [aws_iam_policy.flow_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_role.flow_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.flow_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_internet_gateway.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource |
+| [aws_nat_gateway.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway) | resource |
+| [aws_route_table.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource |
+| [aws_route_table.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource |
+| [aws_route_table_association.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource |
+| [aws_route_table_association.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource |
+| [aws_subnet.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource |
+| [aws_subnet.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource |
+| [aws_vpc.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [availability\_zones](#input\_availability\_zones) | A list of availability zones for the VPC. | `list(string)` | n/a | yes |
+| [aws\_region](#input\_aws\_region) | The AWS region. | `string` | n/a | yes |
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [enable\_nat\_gateway](#input\_enable\_nat\_gateway) | Whether to enable a NAT Gateway. | `bool` | `true` | no |
+| [enable\_vpc\_cloudwatch\_logs\_endpoint](#input\_enable\_vpc\_cloudwatch\_logs\_endpoint) | Whether to create CloudWatch Logs VPC endpoint. | `bool` | `false` | no |
+| [enable\_vpc\_ecr\_api\_endpoint](#input\_enable\_vpc\_ecr\_api\_endpoint) | Whether to create ECR API VPC endpoint. | `bool` | `false` | no |
+| [enable\_vpc\_ecr\_dkr\_endpoint](#input\_enable\_vpc\_ecr\_dkr\_endpoint) | Whether to create ECR DKR VPC endpoint. | `bool` | `false` | no |
+| [enable\_vpc\_s3\_endpoint](#input\_enable\_vpc\_s3\_endpoint) | Whether to create S3 VPC endpoint (Gateway, free). | `bool` | `false` | no |
+| [enable\_vpc\_secretsmanager\_endpoint](#input\_enable\_vpc\_secretsmanager\_endpoint) | Whether to create Secrets Manager VPC endpoint. | `bool` | `false` | no |
+| [enable\_vpc\_ssm\_endpoint](#input\_enable\_vpc\_ssm\_endpoint) | Whether to create SSM VPC endpoint. | `bool` | `false` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key. | `string` | n/a | yes |
+| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The number of days to retain log events. | `number` | `90` | no |
+| [private\_subnet\_cidrs](#input\_private\_subnet\_cidrs) | A list of CIDR blocks for the private subnets. | `list(string)` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [public\_subnet\_cidrs](#input\_public\_subnet\_cidrs) | A list of CIDR blocks for the public subnets. | `list(string)` | n/a | yes |
+| [vpc\_cidr](#input\_vpc\_cidr) | The CIDR block for the VPC. | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [private\_subnet\_ids](#output\_private\_subnet\_ids) | A list of private subnet IDs. |
+| [public\_subnet\_ids](#output\_public\_subnet\_ids) | A list of public subnet IDs. |
+| [vpc\_endpoint\_security\_group\_id](#output\_vpc\_endpoint\_security\_group\_id) | Security group ID for VPC endpoints (null if disabled). |
+| [vpc\_id](#output\_vpc\_id) | The ID of the VPC. |
+
+
diff --git a/infrastructure/modules/networking/modules/nacl/README.md b/infrastructure/modules/networking/modules/nacl/README.md
new file mode 100644
index 0000000000..e2227113ac
--- /dev/null
+++ b/infrastructure/modules/networking/modules/nacl/README.md
@@ -0,0 +1,54 @@
+# Nacl
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_network_acl.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl) | resource |
+| [aws_network_acl.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl) | resource |
+| [aws_network_acl_association.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_association) | resource |
+| [aws_network_acl_association.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_association) | resource |
+| [aws_network_acl_rule.private_inbound_ephemeral](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_inbound_ephemeral_udp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_inbound_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_inbound_postgres](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_inbound_redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_outbound_dns_tcp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_outbound_dns_udp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_outbound_ephemeral](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_outbound_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_outbound_postgres](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.private_outbound_redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.public_inbound_ephemeral](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.public_inbound_ephemeral_udp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.public_inbound_http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.public_inbound_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+| [aws_network_acl_rule.public_outbound_all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | n/a | yes |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [private\_subnet\_ids](#input\_private\_subnet\_ids) | A list of private subnet IDs. | `list(string)` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [public\_subnet\_ids](#input\_public\_subnet\_ids) | A list of public subnet IDs. | `list(string)` | n/a | yes |
+| [vpc\_cidr](#input\_vpc\_cidr) | The CIDR block for the VPC. | `string` | n/a | yes |
+| [vpc\_id](#input\_vpc\_id) | The ID of the VPC. | `string` | n/a | yes |
+
+
diff --git a/infrastructure/modules/networking/modules/vpc-endpoint/README.md b/infrastructure/modules/networking/modules/vpc-endpoint/README.md
new file mode 100644
index 0000000000..7153696543
--- /dev/null
+++ b/infrastructure/modules/networking/modules/vpc-endpoint/README.md
@@ -0,0 +1,58 @@
+# Vpc Endpoint
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_security_group.vpc_endpoints](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group_rule.vpc_endpoints_ingress_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_vpc_endpoint.cloudwatch_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
+| [aws_vpc_endpoint.ecr_api](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
+| [aws_vpc_endpoint.ecr_dkr](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
+| [aws_vpc_endpoint.s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
+| [aws_vpc_endpoint.secretsmanager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
+| [aws_vpc_endpoint.ssm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
+| [aws_vpc_endpoint_route_table_association.s3_private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint_route_table_association) | resource |
+| [aws_vpc_endpoint_route_table_association.s3_public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint_route_table_association) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [aws\_region](#input\_aws\_region) | The AWS region. | `string` | n/a | yes |
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | n/a | yes |
+| [enable\_cloudwatch\_logs](#input\_enable\_cloudwatch\_logs) | Whether to create CloudWatch Logs VPC endpoint. | `bool` | `false` | no |
+| [enable\_ecr\_api](#input\_enable\_ecr\_api) | Whether to create ECR API VPC endpoint. | `bool` | `false` | no |
+| [enable\_ecr\_dkr](#input\_enable\_ecr\_dkr) | Whether to create ECR DKR VPC endpoint. | `bool` | `false` | no |
+| [enable\_s3](#input\_enable\_s3) | Whether to create S3 VPC endpoint (Gateway, free). | `bool` | `true` | no |
+| [enable\_secretsmanager](#input\_enable\_secretsmanager) | Whether to create Secrets Manager VPC endpoint. | `bool` | `false` | no |
+| [enable\_ssm](#input\_enable\_ssm) | Whether to create SSM VPC endpoint. | `bool` | `false` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [private\_route\_table\_id](#input\_private\_route\_table\_id) | The ID of the private route table. | `string` | n/a | yes |
+| [private\_subnet\_ids](#input\_private\_subnet\_ids) | A list of private subnet IDs. | `list(string)` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [public\_route\_table\_id](#input\_public\_route\_table\_id) | The ID of the public route table. | `string` | n/a | yes |
+| [vpc\_cidr](#input\_vpc\_cidr) | The CIDR block for the VPC. | `string` | n/a | yes |
+| [vpc\_id](#input\_vpc\_id) | The ID of the VPC. | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [security\_group\_id](#output\_security\_group\_id) | Security group ID for VPC interface endpoints, or null when no interface endpoints are enabled. |
+
+
diff --git a/infrastructure/modules/networking/modules/vpc-endpoint/outputs.tf b/infrastructure/modules/networking/modules/vpc-endpoint/outputs.tf
index 134ed2cb1c..0a12d195e4 100644
--- a/infrastructure/modules/networking/modules/vpc-endpoint/outputs.tf
+++ b/infrastructure/modules/networking/modules/vpc-endpoint/outputs.tf
@@ -1,4 +1,4 @@
output "security_group_id" {
- description = "Security group ID for VPC endpoints."
+ description = "Security group ID for VPC interface endpoints, or null when no interface endpoints are enabled."
value = length(aws_security_group.vpc_endpoints) > 0 ? aws_security_group.vpc_endpoints[0].id : null
}
diff --git a/infrastructure/modules/parameters/README.md b/infrastructure/modules/parameters/README.md
new file mode 100644
index 0000000000..66cbbd001d
--- /dev/null
+++ b/infrastructure/modules/parameters/README.md
@@ -0,0 +1,91 @@
+# Parameters
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+| [random](#requirement\_random) | ~> 3.8.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+| [random](#provider\_random) | 3.8.1 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_ssm_parameter.django_algolia_application_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_algolia_write_api_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_allowed_hosts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_allowed_origins](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_aws_storage_bucket_name](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_db_host](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_db_name](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_db_port](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_db_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_github_app_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_github_app_installation_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_open_ai_secret_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_redis_host](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_redis_use_tls](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_release_version](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_secret_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_sentry_dsn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_settings_module](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_slack_bot_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.django_slack_signing_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.github_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.nest_github_app_private_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.next_server_csrf_url](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.next_server_disable_ssr](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.next_server_github_client_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.next_server_github_client_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.next_server_graphql_url](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.nextauth_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.nextauth_url](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.slack_bot_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [random_string.django_secret_key](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
+| [random_string.nextauth_secret](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [db\_password\_arn](#input\_db\_password\_arn) | The SSM Parameter ARN of password of the database. | `string` | n/a | yes |
+| [django\_allowed\_hosts](#input\_django\_allowed\_hosts) | Django allowed hosts - hostname only, no protocol (e.g., nest.owasp.dev). | `string` | n/a | yes |
+| [django\_allowed\_origins](#input\_django\_allowed\_origins) | The Django allowed CORS origins (comma-separated URLs with protocol). | `string` | n/a | yes |
+| [django\_aws\_static\_bucket\_name](#input\_django\_aws\_static\_bucket\_name) | The name of the S3 bucket for Django static files. | `string` | n/a | yes |
+| [django\_configuration](#input\_django\_configuration) | The name of the Django configuration to use (e.g., Staging, Production). | `string` | n/a | yes |
+| [django\_db\_host](#input\_django\_db\_host) | The hostname of the database. | `string` | n/a | yes |
+| [django\_db\_name](#input\_django\_db\_name) | The name of the database. | `string` | n/a | yes |
+| [django\_db\_port](#input\_django\_db\_port) | The port of the database. | `string` | n/a | yes |
+| [django\_db\_user](#input\_django\_db\_user) | The user for the database. | `string` | n/a | yes |
+| [django\_redis\_host](#input\_django\_redis\_host) | The hostname of the Redis cache. | `string` | n/a | yes |
+| [django\_redis\_use\_tls](#input\_django\_redis\_use\_tls) | Whether Redis connections should use TLS (required for ElastiCache with transit encryption). | `bool` | `true` | no |
+| [django\_release\_version](#input\_django\_release\_version) | The Django release version. | `string` | n/a | yes |
+| [django\_settings\_module](#input\_django\_settings\_module) | The location of the Django settings module to use (e.g., settings.staging, settings.production). | `string` | n/a | yes |
+| [enable\_additional\_parameters](#input\_enable\_additional\_parameters) | Whether to create additional parameters (e.g. for production). | `bool` | `false` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [next\_server\_csrf\_url](#input\_next\_server\_csrf\_url) | The server-side CSRF URL for Next.js SSR (e.g., https://nest.owasp.dev/csrf/). | `string` | n/a | yes |
+| [next\_server\_graphql\_url](#input\_next\_server\_graphql\_url) | The server-side GraphQL URL for Next.js SSR (e.g., https://nest.owasp.dev/graphql/). | `string` | n/a | yes |
+| [nextauth\_url](#input\_nextauth\_url) | The NextAuth base URL (frontend URL with protocol). | `string` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [redis\_password\_arn](#input\_redis\_password\_arn) | The SSM Parameter ARN of password of the Redis cache. | `string` | n/a | yes |
+| [slack\_bot\_token\_suffix](#input\_slack\_bot\_token\_suffix) | The Suffix for the Slack bot token. | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [django\_ssm\_parameter\_arns](#output\_django\_ssm\_parameter\_arns) | Map of environment variable names to the ARNs of all SSM parameters (Required by Django). |
+| [frontend\_ssm\_parameter\_arns](#output\_frontend\_ssm\_parameter\_arns) | Map of frontend environment variable names to the ARNs of all SSM parameters. |
+
+
diff --git a/infrastructure/modules/security/README.md b/infrastructure/modules/security/README.md
new file mode 100644
index 0000000000..8a231e241c
--- /dev/null
+++ b/infrastructure/modules/security/README.md
@@ -0,0 +1,81 @@
+# Security
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_security_group.alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group.backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group.rds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group.rds_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group_rule.alb_http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.alb_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.alb_to_backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.alb_to_frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.backend_egress_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.backend_from_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.backend_to_rds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.backend_to_rds_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.backend_to_redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.backend_to_vpc_endpoints](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.frontend_from_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.frontend_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.frontend_to_vpc_endpoints](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.rds_from_backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.rds_from_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.rds_from_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.rds_proxy_from_backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.rds_proxy_from_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.rds_proxy_to_rds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.redis_from_backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.redis_from_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.task_egress_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.task_to_rds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.task_to_rds_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.task_to_redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_security_group_rule.task_to_vpc_endpoints](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [db\_port](#input\_db\_port) | The port for the RDS database. | `number` | n/a | yes |
+| [enable\_rds\_proxy](#input\_enable\_rds\_proxy) | Whether to create an RDS proxy. | `bool` | `false` | no |
+| [enable\_vpc\_endpoint\_rules](#input\_enable\_vpc\_endpoint\_rules) | Whether to create security group rules for VPC endpoints. | `bool` | `false` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [redis\_port](#input\_redis\_port) | The port for the Redis cache. | `number` | n/a | yes |
+| [vpc\_endpoint\_sg\_id](#input\_vpc\_endpoint\_sg\_id) | Security group ID for VPC endpoints (null if VPC endpoints disabled). | `string` | `null` | no |
+| [vpc\_id](#input\_vpc\_id) | The ID of the VPC. | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [alb\_sg\_id](#output\_alb\_sg\_id) | The ID of the ALB security group. |
+| [backend\_sg\_id](#output\_backend\_sg\_id) | The ID of the backend ECS security group. |
+| [frontend\_sg\_id](#output\_frontend\_sg\_id) | The ID of the frontend ECS security group. |
+| [rds\_proxy\_sg\_id](#output\_rds\_proxy\_sg\_id) | The ID of the RDS proxy security group. |
+| [rds\_sg\_id](#output\_rds\_sg\_id) | The ID of the RDS security group. |
+| [redis\_sg\_id](#output\_redis\_sg\_id) | The ID of the Redis security group. |
+| [tasks\_sg\_id](#output\_tasks\_sg\_id) | The ID of the ECS tasks security group. |
+
+
diff --git a/infrastructure/modules/service/README.md b/infrastructure/modules/service/README.md
new file mode 100644
index 0000000000..6b70d107d1
--- /dev/null
+++ b/infrastructure/modules/service/README.md
@@ -0,0 +1,79 @@
+# Service
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_appautoscaling_policy.cpu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource |
+| [aws_appautoscaling_target.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource |
+| [aws_cloudwatch_log_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
+| [aws_ecr_lifecycle_policy.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_lifecycle_policy) | resource |
+| [aws_ecr_repository.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_repository) | resource |
+| [aws_ecs_cluster.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource |
+| [aws_ecs_cluster_capacity_providers.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource |
+| [aws_ecs_service.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource |
+| [aws_ecs_task_definition.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource |
+| [aws_iam_policy.ecs_task_execution_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.ecs_task_execution_ssm_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.ecs_task_role_kms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_role.ecs_task_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role.ecs_task_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.ecs_task_execution_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.ecs_task_execution_ssm_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.ecs_task_role_kms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.task_role_policies](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [assign\_public\_ip](#input\_assign\_public\_ip) | Whether to assign public IPs to ECS tasks (required for public subnets). | `bool` | `false` | no |
+| [aws\_region](#input\_aws\_region) | The AWS region. | `string` | n/a | yes |
+| [command](#input\_command) | The command to run in the container. If null, the container's default CMD is used. | `list(string)` | `null` | no |
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [container\_cpu](#input\_container\_cpu) | The CPU units for the container (1024 = 1 vCPU). | `number` | `512` | no |
+| [container\_memory](#input\_container\_memory) | The memory for the container in MiB. | `number` | `1024` | no |
+| [container\_port](#input\_container\_port) | The port the container listens on. | `number` | n/a | yes |
+| [desired\_count](#input\_desired\_count) | The desired number of tasks. | `number` | `2` | no |
+| [enable\_auto\_scaling](#input\_enable\_auto\_scaling) | Whether to enable auto scaling for the service. | `bool` | `false` | no |
+| [environment](#input\_environment) | The environment name (e.g., staging, production). | `string` | n/a | yes |
+| [force\_new\_deployment](#input\_force\_new\_deployment) | Whether to force a new deployment on each apply. | `bool` | `false` | no |
+| [image\_tag](#input\_image\_tag) | The Docker image tag. | `string` | n/a | yes |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key for log encryption. | `string` | n/a | yes |
+| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The CloudWatch log retention in days. | `number` | `30` | no |
+| [max\_count](#input\_max\_count) | The maximum number of tasks for auto scaling. | `number` | `6` | no |
+| [min\_count](#input\_min\_count) | The minimum number of tasks for auto scaling. | `number` | `2` | no |
+| [parameters\_arns](#input\_parameters\_arns) | Map of environment variable names to the ARNs of SSM parameters. | `map(string)` | `{}` | no |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [security\_group\_id](#input\_security\_group\_id) | The ID of the security group for the service. | `string` | n/a | yes |
+| [service\_name](#input\_service\_name) | The name of the service (e.g., backend, frontend). | `string` | n/a | yes |
+| [subnet\_ids](#input\_subnet\_ids) | Subnet IDs for ECS tasks (can be public or private). | `list(string)` | n/a | yes |
+| [target\_group\_arn](#input\_target\_group\_arn) | The ARN of the ALB target group. | `string` | n/a | yes |
+| [task\_role\_policy\_arns](#input\_task\_role\_policy\_arns) | A list of additional IAM policy ARNs to attach to the ECS task role. | `list(string)` | `[]` | no |
+| [use\_fargate\_spot](#input\_use\_fargate\_spot) | Whether to use Fargate Spot capacity provider. | `bool` | `false` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [ecr\_repository\_arn](#output\_ecr\_repository\_arn) | The ARN of the ECR repository. |
+| [ecr\_repository\_url](#output\_ecr\_repository\_url) | The URL of the ECR repository. |
+| [ecs\_cluster\_arn](#output\_ecs\_cluster\_arn) | The ARN of the ECS cluster. |
+| [ecs\_cluster\_name](#output\_ecs\_cluster\_name) | The name of the ECS cluster. |
+| [ecs\_service\_name](#output\_ecs\_service\_name) | The name of the ECS service. |
+
+
diff --git a/infrastructure/modules/storage/README.md b/infrastructure/modules/storage/README.md
new file mode 100644
index 0000000000..af93f41483
--- /dev/null
+++ b/infrastructure/modules/storage/README.md
@@ -0,0 +1,54 @@
+# Storage
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+| [random](#requirement\_random) | ~> 3.8.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+| [random](#provider\_random) | 3.8.1 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [fixtures\_bucket](#module\_fixtures\_bucket) | ./modules/s3-bucket | n/a |
+| [static\_bucket](#module\_static\_bucket) | ./modules/s3-bucket | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_iam_policy.fixtures_read_only](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.static_read_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [random_id.suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [fixtures\_bucket\_name](#input\_fixtures\_bucket\_name) | The name of the S3 bucket for fixtures. | `string` | n/a | yes |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key (used for fixtures bucket encryption). | `string` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [fixtures\_read\_only\_policy\_arn](#output\_fixtures\_read\_only\_policy\_arn) | The ARN of the fixtures read-only IAM policy. |
+| [fixtures\_s3\_bucket\_arn](#output\_fixtures\_s3\_bucket\_arn) | The ARN of the S3 bucket for fixtures. |
+| [fixtures\_s3\_bucket\_name](#output\_fixtures\_s3\_bucket\_name) | The name of the S3 bucket for fixtures. |
+| [static\_read\_write\_policy\_arn](#output\_static\_read\_write\_policy\_arn) | The ARN of the static files read/write IAM policy. |
+| [static\_s3\_bucket\_name](#output\_static\_s3\_bucket\_name) | The name of the S3 bucket for static files. |
+
+
diff --git a/infrastructure/modules/storage/modules/s3-bucket/README.md b/infrastructure/modules/storage/modules/s3-bucket/README.md
new file mode 100644
index 0000000000..c232b3325a
--- /dev/null
+++ b/infrastructure/modules/storage/modules/s3-bucket/README.md
@@ -0,0 +1,46 @@
+# S3 Bucket
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket_lifecycle_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
+| [aws_s3_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
+| [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
+| [aws_s3_bucket_versioning.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [abort\_incomplete\_multipart\_upload\_days](#input\_abort\_incomplete\_multipart\_upload\_days) | The number of days after which an incomplete multipart upload is aborted. | `number` | `7` | no |
+| [allow\_public\_read](#input\_allow\_public\_read) | Whether to allow public read access to objects in the bucket. | `bool` | `false` | no |
+| [bucket\_name](#input\_bucket\_name) | The name of the bucket. | `string` | n/a | yes |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | ARN of the KMS key for SSE-KMS encryption. If null, uses SSE-S3 (AES256) instead. | `string` | `null` | no |
+| [noncurrent\_version\_expiration\_days](#input\_noncurrent\_version\_expiration\_days) | The number of days an object is noncurrent before it is expired. | `number` | `30` | no |
+| [tags](#input\_tags) | A map of tags to apply to all resources. | `map(string)` | `{}` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [arn](#output\_arn) | The ARN of the S3 bucket. |
+| [bucket](#output\_bucket) | The S3 bucket resource. |
+
+
diff --git a/infrastructure/modules/tasks/README.md b/infrastructure/modules/tasks/README.md
new file mode 100644
index 0000000000..0162082842
--- /dev/null
+++ b/infrastructure/modules/tasks/README.md
@@ -0,0 +1,87 @@
+# Tasks
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [index\_data\_task](#module\_index\_data\_task) | ./modules/task | n/a |
+| [load\_data\_task](#module\_load\_data\_task) | ./modules/task | n/a |
+| [migrate\_task](#module\_migrate\_task) | ./modules/task | n/a |
+| [owasp\_update\_project\_health\_metrics\_task](#module\_owasp\_update\_project\_health\_metrics\_task) | ./modules/task | n/a |
+| [owasp\_update\_project\_health\_scores\_task](#module\_owasp\_update\_project\_health\_scores\_task) | ./modules/task | n/a |
+| [sync\_data\_task](#module\_sync\_data\_task) | ./modules/task | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_ecs_cluster.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource |
+| [aws_ecs_cluster_capacity_providers.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource |
+| [aws_iam_policy.ecs_task_role_kms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.ecs_tasks_execution_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.ecs_tasks_execution_role_ssm_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.event_bridge_ecs_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_role.ecs_task_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role.ecs_tasks_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role.event_bridge_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.ecs_task_role_fixtures_s3_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.ecs_task_role_kms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.ecs_tasks_execution_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.ecs_tasks_execution_role_ssm_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.event_bridge_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [assign\_public\_ip](#input\_assign\_public\_ip) | Whether to assign public IPs to ECS tasks (required for public subnets). | `bool` | `false` | no |
+| [aws\_region](#input\_aws\_region) | The AWS region. | `string` | n/a | yes |
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [container\_parameters\_arns](#input\_container\_parameters\_arns) | Map of environment variable names to the ARNs of all SSM parameters. | `map(string)` | `{}` | no |
+| [ecr\_repository\_arn](#input\_ecr\_repository\_arn) | The ARN of the ECR repository for the backend image. | `string` | n/a | yes |
+| [ecr\_repository\_url](#input\_ecr\_repository\_url) | The URL of the ECR repository for the backend image. | `string` | n/a | yes |
+| [ecs\_sg\_id](#input\_ecs\_sg\_id) | The ID of the security group for the ECS tasks. | `string` | n/a | yes |
+| [enable\_cron\_tasks](#input\_enable\_cron\_tasks) | Whether to enable scheduled cron tasks. | `bool` | n/a | yes |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [fixtures\_bucket\_name](#input\_fixtures\_bucket\_name) | The name of the S3 bucket for fixtures. | `string` | n/a | yes |
+| [fixtures\_read\_only\_policy\_arn](#input\_fixtures\_read\_only\_policy\_arn) | The ARN of the fixtures read-only IAM policy. | `string` | n/a | yes |
+| [image\_tag](#input\_image\_tag) | The Docker image tag to use for ECS tasks. | `string` | n/a | yes |
+| [index\_data\_task\_cpu](#input\_index\_data\_task\_cpu) | The CPU for the index-data task. | `string` | `"256"` | no |
+| [index\_data\_task\_memory](#input\_index\_data\_task\_memory) | The memory for the index-data task. | `string` | `"2048"` | no |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key. | `string` | n/a | yes |
+| [load\_data\_task\_cpu](#input\_load\_data\_task\_cpu) | The CPU for the load-data task. | `string` | `"512"` | no |
+| [load\_data\_task\_memory](#input\_load\_data\_task\_memory) | The memory for the load-data task. | `string` | `"4096"` | no |
+| [migrate\_task\_cpu](#input\_migrate\_task\_cpu) | The CPU for the migrate task. | `string` | `"256"` | no |
+| [migrate\_task\_memory](#input\_migrate\_task\_memory) | The memory for the migrate task. | `string` | `"1024"` | no |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [subnet\_ids](#input\_subnet\_ids) | Subnet IDs for ECS tasks (can be public or private). | `list(string)` | n/a | yes |
+| [sync\_data\_task\_cpu](#input\_sync\_data\_task\_cpu) | The CPU for the sync-data task. | `string` | `"256"` | no |
+| [sync\_data\_task\_memory](#input\_sync\_data\_task\_memory) | The memory for the sync-data task. | `string` | `"1024"` | no |
+| [update\_project\_health\_metrics\_task\_cpu](#input\_update\_project\_health\_metrics\_task\_cpu) | The CPU for the update-project-health-metrics task. | `string` | `"256"` | no |
+| [update\_project\_health\_metrics\_task\_memory](#input\_update\_project\_health\_metrics\_task\_memory) | The memory for the update-project-health-metrics task. | `string` | `"1024"` | no |
+| [update\_project\_health\_scores\_task\_cpu](#input\_update\_project\_health\_scores\_task\_cpu) | The CPU for the update-project-health-scores task. | `string` | `"256"` | no |
+| [update\_project\_health\_scores\_task\_memory](#input\_update\_project\_health\_scores\_task\_memory) | The memory for the update-project-health-scores task. | `string` | `"1024"` | no |
+| [use\_fargate\_spot](#input\_use\_fargate\_spot) | Whether to use Fargate Spot capacity provider for cost savings. | `bool` | `false` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [ecs\_cluster\_arn](#output\_ecs\_cluster\_arn) | The ARN of the ECS tasks cluster. |
+| [ecs\_cluster\_name](#output\_ecs\_cluster\_name) | The name of the ECS tasks cluster. |
+
+
diff --git a/infrastructure/modules/tasks/modules/task/README.md b/infrastructure/modules/tasks/modules/task/README.md
new file mode 100644
index 0000000000..3f02f84f6d
--- /dev/null
+++ b/infrastructure/modules/tasks/modules/task/README.md
@@ -0,0 +1,52 @@
+# Task
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_cloudwatch_event_rule.task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource |
+| [aws_cloudwatch_event_target.task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource |
+| [aws_cloudwatch_log_group.task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
+| [aws_ecs_task_definition.task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [assign\_public\_ip](#input\_assign\_public\_ip) | Whether to assign public IPs to ECS tasks. | `bool` | `false` | no |
+| [aws\_region](#input\_aws\_region) | The AWS region for the CloudWatch logs. | `string` | n/a | yes |
+| [command](#input\_command) | The command to run in the container. | `list(string)` | n/a | yes |
+| [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
+| [container\_parameters\_arns](#input\_container\_parameters\_arns) | A Map of environment variable names to the ARNs of all SSM parameters. | `map(string)` | `{}` | no |
+| [cpu](#input\_cpu) | The CPU units to allocate for the task. | `string` | n/a | yes |
+| [ecs\_cluster\_arn](#input\_ecs\_cluster\_arn) | The ARN of the ECS cluster. | `string` | n/a | yes |
+| [ecs\_tasks\_execution\_role\_arn](#input\_ecs\_tasks\_execution\_role\_arn) | The ARN of the ECS task execution role. | `string` | n/a | yes |
+| [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
+| [event\_bridge\_role\_arn](#input\_event\_bridge\_role\_arn) | The ARN of the EventBridge role to trigger the task. Only required for scheduled tasks. | `string` | `null` | no |
+| [image\_url](#input\_image\_url) | The URL of the ECR image to run. | `string` | n/a | yes |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key. | `string` | n/a | yes |
+| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The number of days to retain log events. | `number` | `90` | no |
+| [memory](#input\_memory) | The memory (in MiB) to allocate for the task. | `string` | n/a | yes |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
+| [schedule\_expression](#input\_schedule\_expression) | The cron expression for the schedule. If null, the task is not scheduled. | `string` | `null` | no |
+| [security\_group\_ids](#input\_security\_group\_ids) | A list of security group IDs to associate with the task. | `list(string)` | n/a | yes |
+| [subnet\_ids](#input\_subnet\_ids) | Subnet IDs for the task (can be public or private). | `list(string)` | n/a | yes |
+| [task\_name](#input\_task\_name) | The unique name of the task. | `string` | n/a | yes |
+| [task\_role\_arn](#input\_task\_role\_arn) | The ARN of the IAM role for the task. | `string` | `null` | no |
+| [use\_fargate\_spot](#input\_use\_fargate\_spot) | Whether to use Fargate Spot capacity provider. | `bool` | `false` | no |
+
+
diff --git a/infrastructure/scripts/terraform_docs.py b/infrastructure/scripts/terraform_docs.py
new file mode 100644
index 0000000000..fecddd560e
--- /dev/null
+++ b/infrastructure/scripts/terraform_docs.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""Generate terraform-docs output for all Terraform directories in infrastructure."""
+
+from __future__ import annotations
+
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+
+REPO_ROOT = Path(__file__).resolve().parents[2]
+INFRA_ROOT = REPO_ROOT / "infrastructure"
+CONFIG_PATH = INFRA_ROOT / ".terraform-docs.yml"
+
+
+def terraform_directories() -> list[Path]:
+ project_dirs = [
+ INFRA_ROOT / "bootstrap",
+ INFRA_ROOT / "live",
+ INFRA_ROOT / "state",
+ ]
+ module_dirs = {
+ path.parent
+ for path in (INFRA_ROOT / "modules").rglob("*.tf")
+ if ".terraform" not in path.parts
+ }
+ return project_dirs + sorted(module_dirs)
+
+
+def main() -> int:
+ terraform_docs = shutil.which("terraform-docs")
+ if terraform_docs is None:
+ print("terraform-docs is required but was not found on PATH.", file=sys.stderr)
+ return 1
+
+ for directory in terraform_directories():
+ print(f"Generating terraform-docs for {directory.relative_to(REPO_ROOT)}...")
+ result = subprocess.run(
+ [terraform_docs, "--config", str(CONFIG_PATH), str(directory)],
+ cwd=REPO_ROOT,
+ check=False,
+ )
+ if result.returncode != 0:
+ return result.returncode
+
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/infrastructure/state/README.md b/infrastructure/state/README.md
index a32363b934..a72b05ab9b 100644
--- a/infrastructure/state/README.md
+++ b/infrastructure/state/README.md
@@ -81,3 +81,68 @@ Use the following inline permissions for the `nest-state` IAM User
]
}
```
+
+## Terraform Reference
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.14.0 |
+| [aws](#requirement\_aws) | ~> 6.36.0 |
+| [random](#requirement\_random) | ~> 3.8.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 6.36.0 |
+| [random](#provider\_random) | 3.8.1 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [kms](#module\_kms) | ../modules/kms | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_s3_bucket.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket.state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket_lifecycle_configuration.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
+| [aws_s3_bucket_lifecycle_configuration.state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
+| [aws_s3_bucket_logging.state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
+| [aws_s3_bucket_object_lock_configuration.state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration) | resource |
+| [aws_s3_bucket_policy.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
+| [aws_s3_bucket_policy.state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
+| [aws_s3_bucket_public_access_block.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_s3_bucket_public_access_block.state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_s3_bucket_server_side_encryption_configuration.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
+| [aws_s3_bucket_server_side_encryption_configuration.state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
+| [aws_s3_bucket_versioning.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
+| [aws_s3_bucket_versioning.state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
+| [random_id.suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [abort\_incomplete\_multipart\_upload\_days](#input\_abort\_incomplete\_multipart\_upload\_days) | The number of days after which an incomplete multipart upload is aborted. | `number` | `7` | no |
+| [aws\_region](#input\_aws\_region) | The AWS region to deploy resources in. | `string` | `"us-east-2"` | no |
+| [expire\_log\_days](#input\_expire\_log\_days) | The number of days to expire logs after. | `number` | `90` | no |
+| [noncurrent\_version\_expiration\_days](#input\_noncurrent\_version\_expiration\_days) | The number of days an object is noncurrent before it is expired. | `number` | `30` | no |
+| [project\_name](#input\_project\_name) | The name of the project. | `string` | `"nest"` | no |
+| [state\_environments](#input\_state\_environments) | A list of environments to create separate state buckets for. | `list(string)` | ```[ "bootstrap", "staging", "production" ]``` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [kms\_key\_aliases](#output\_kms\_key\_aliases) | The aliases of the per-environment KMS keys for Terraform state encryption. |
+| [kms\_key\_arns](#output\_kms\_key\_arns) | The ARNs of the per-environment KMS keys for Terraform state encryption. |
+| [state\_bucket\_names](#output\_state\_bucket\_names) | The names of the per-environment S3 buckets for Terraform state. |
+
+