From 02bddc8e2cbb1efef1b692918788d5cb3b398adc Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Fri, 31 Jul 2026 18:52:36 +0500 Subject: [PATCH 01/13] add o11y terraform module Signed-off-by: hassaansaleem28 --- infrastructure/live/main.tf | 20 ++ infrastructure/live/variables.tf | 12 + infrastructure/modules/observability/main.tf | 270 ++++++++++++++++++ .../modules/observability/outputs.tf | 14 + .../modules/observability/variables.tf | 81 ++++++ 5 files changed, 397 insertions(+) create mode 100644 infrastructure/modules/observability/main.tf create mode 100644 infrastructure/modules/observability/outputs.tf create mode 100644 infrastructure/modules/observability/variables.tf diff --git a/infrastructure/live/main.tf b/infrastructure/live/main.tf index 6e8b2d66a3..c00c32355f 100644 --- a/infrastructure/live/main.tf +++ b/infrastructure/live/main.tf @@ -249,3 +249,23 @@ module "tasks" { subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids use_fargate_spot = var.tasks_use_fargate_spot } + +module "observability" { + count = var.enable_observability ? 1 : 0 + source = "../modules/observability" + + app_security_group_ids = [ + module.security.backend_sg_id, + module.security.frontend_sg_id, + module.security.tasks_sg_id, + ] + assign_public_ip = local.assign_public_ip + aws_region = var.aws_region + common_tags = local.common_tags + environment = var.environment + kms_key_arn = module.kms.key_arn + project_name = var.project_name + subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids + vm_image = var.observability_vm_image + vpc_id = module.networking.vpc_id +} diff --git a/infrastructure/live/variables.tf b/infrastructure/live/variables.tf index 7bedd26ed4..7edff0f74a 100644 --- a/infrastructure/live/variables.tf +++ b/infrastructure/live/variables.tf @@ -193,6 +193,12 @@ variable "enable_nat_gateway" { default = true } +variable "enable_observability" { + description = "Whether to create the observability stack (VictoriaMetrics)." + type = bool + default = false +} + variable "enable_rds_proxy" { description = "Whether to create an RDS proxy." type = bool @@ -285,6 +291,12 @@ variable "frontend_use_fargate_spot" { default = true } +variable "observability_vm_image" { + description = "The VictoriaMetrics container image (including digest)." + type = string + default = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70" +} + variable "private_subnet_cidrs" { description = "A list of CIDR blocks for the private subnets." type = list(string) diff --git a/infrastructure/modules/observability/main.tf b/infrastructure/modules/observability/main.tf new file mode 100644 index 0000000000..62f7f5ea32 --- /dev/null +++ b/infrastructure/modules/observability/main.tf @@ -0,0 +1,270 @@ +terraform { + required_version = "~> 1.15.0" + + required_providers { + aws = { + source = "hashicorp/aws" + } + } +} + +locals { + name_prefix = "${var.project_name}-${var.environment}-observability" + + vm_container_definition = { + command = [ + "-storageDataPath=/data", + "-retentionPeriod=${var.vm_retention_period}", + "-httpListenAddr=:${var.vm_port}", + ] + essential = true + healthCheck = { + command = ["CMD-SHELL", "wget --spider -q http://localhost:${var.vm_port}/health || exit 1"] + interval = 30 + retries = 3 + startPeriod = 30 + timeout = 5 + } + image = var.vm_image + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.vm.name + "awslogs-region" = var.aws_region + "awslogs-stream-prefix" = "ecs" + } + } + mountPoints = [ + { + containerPath = "/data" + readOnly = false + sourceVolume = "vm-data" + } + ] + name = "victoriametrics" + portMappings = [ + { + containerPort = var.vm_port + hostPort = var.vm_port + protocol = "tcp" + } + ] + } +} + +data "aws_caller_identity" "current" {} + +resource "aws_security_group" "vm" { + description = "Security group for the VictoriaMetrics task" + name = "${local.name_prefix}-vm-sg" + tags = merge(var.common_tags, { + Name = "${local.name_prefix}-vm-sg" + }) + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "vm_ingest_from_apps" { + for_each = toset(var.app_security_group_ids) + + description = "Allow metrics ingest and queries from application tasks" + from_port = var.vm_port + protocol = "tcp" + security_group_id = aws_security_group.vm.id + source_security_group_id = each.value + to_port = var.vm_port + type = "ingress" +} + +resource "aws_security_group_rule" "vm_egress_https" { + cidr_blocks = ["0.0.0.0/0"] + description = "Allow HTTPS egress for container image pulls" + from_port = 443 + protocol = "tcp" + security_group_id = aws_security_group.vm.id + to_port = 443 + type = "egress" +} + +resource "aws_security_group_rule" "vm_to_efs" { + description = "Allow NFS to the observability EFS" + from_port = 2049 + protocol = "tcp" + security_group_id = aws_security_group.vm.id + source_security_group_id = aws_security_group.efs.id + to_port = 2049 + type = "egress" +} + +resource "aws_security_group" "efs" { + description = "Security group for the observability EFS file system" + name = "${local.name_prefix}-efs-sg" + tags = merge(var.common_tags, { + Name = "${local.name_prefix}-efs-sg" + }) + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "efs_from_vm" { + description = "Allow NFS from the VictoriaMetrics task" + from_port = 2049 + protocol = "tcp" + security_group_id = aws_security_group.efs.id + source_security_group_id = aws_security_group.vm.id + to_port = 2049 + type = "ingress" +} + +resource "aws_efs_file_system" "vm" { + encrypted = true + kms_key_id = var.kms_key_arn + tags = merge(var.common_tags, { + Name = "${local.name_prefix}-vm" + }) +} + +resource "aws_efs_mount_target" "vm" { + for_each = toset(var.subnet_ids) + + file_system_id = aws_efs_file_system.vm.id + security_groups = [aws_security_group.efs.id] + subnet_id = each.value +} + +resource "aws_cloudwatch_log_group" "vm" { + kms_key_id = var.kms_key_arn + name = "/aws/ecs/${local.name_prefix}" + retention_in_days = var.log_retention_in_days + tags = merge(var.common_tags, { + Name = "${local.name_prefix}-logs" + }) +} + +resource "aws_ecs_cluster" "vm" { + name = "${local.name_prefix}-cluster" + tags = merge(var.common_tags, { + Name = "${local.name_prefix}-cluster" + }) + + setting { + name = "containerInsights" + value = "enabled" + } +} + +resource "aws_ecs_cluster_capacity_providers" "vm" { + capacity_providers = ["FARGATE"] + cluster_name = aws_ecs_cluster.vm.name + + default_capacity_provider_strategy { + base = 0 + capacity_provider = "FARGATE" + weight = 1 + } +} + +resource "aws_iam_role" "ecs_task_execution_role" { + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) + name = "${local.name_prefix}-execution-role" + tags = var.common_tags +} + +resource "aws_iam_policy" "ecs_task_execution_policy" { + description = "Policy for the VictoriaMetrics ECS task execution - ECR and CloudWatch Logs access." + name = "${local.name_prefix}-execution-policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + # https://docs.aws.amazon.com/AmazonECR/latest/public/public-repository-policies.html#repository-policy-vs-iam-policy + # NOSEMGREP: terraform.lang.security.iam.no-iam-creds-exposure.no-iam-creds-exposure + Action = "ecr:GetAuthorizationToken" + Effect = "Allow" + Resource = "*" # NOSONAR + }, + { + Action = [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:GetDownloadUrlForLayer" + ] + Effect = "Allow" + Resource = "arn:aws:ecr:${var.aws_region}:${data.aws_caller_identity.current.account_id}:repository/*" + }, + { + Action = [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Effect = "Allow" + Resource = "${aws_cloudwatch_log_group.vm.arn}:*" + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "ecs_task_execution_policy_attachment" { + policy_arn = aws_iam_policy.ecs_task_execution_policy.arn + role = aws_iam_role.ecs_task_execution_role.name +} + +resource "aws_ecs_task_definition" "vm" { + container_definitions = jsonencode([local.vm_container_definition]) + cpu = var.vm_cpu + execution_role_arn = aws_iam_role.ecs_task_execution_role.arn + family = local.name_prefix + memory = var.vm_memory + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + runtime_platform { + cpu_architecture = "ARM64" + operating_system_family = "LINUX" + } + tags = merge(var.common_tags, { + Name = "${local.name_prefix}-task-def" + }) + + volume { + name = "vm-data" + + efs_volume_configuration { + file_system_id = aws_efs_file_system.vm.id + transit_encryption = "ENABLED" + } + } +} + +resource "aws_ecs_service" "vm" { + cluster = aws_ecs_cluster.vm.id + deployment_maximum_percent = 100 + deployment_minimum_healthy_percent = 0 + desired_count = 1 + name = "${local.name_prefix}-service" + tags = merge(var.common_tags, { + Name = "${local.name_prefix}-service" + }) + task_definition = aws_ecs_task_definition.vm.arn + + capacity_provider_strategy { + base = 0 + capacity_provider = "FARGATE" + weight = 1 + } + + network_configuration { + assign_public_ip = var.assign_public_ip + security_groups = [aws_security_group.vm.id] + subnets = var.subnet_ids + } +} diff --git a/infrastructure/modules/observability/outputs.tf b/infrastructure/modules/observability/outputs.tf new file mode 100644 index 0000000000..df41aebd2c --- /dev/null +++ b/infrastructure/modules/observability/outputs.tf @@ -0,0 +1,14 @@ +output "efs_file_system_id" { + description = "The ID of the EFS file system backing VictoriaMetrics storage." + value = aws_efs_file_system.vm.id +} + +output "vm_cluster_name" { + description = "The name of the ECS cluster running VictoriaMetrics." + value = aws_ecs_cluster.vm.name +} + +output "vm_security_group_id" { + description = "The ID of the VictoriaMetrics security group." + value = aws_security_group.vm.id +} diff --git a/infrastructure/modules/observability/variables.tf b/infrastructure/modules/observability/variables.tf new file mode 100644 index 0000000000..d6037096e4 --- /dev/null +++ b/infrastructure/modules/observability/variables.tf @@ -0,0 +1,81 @@ +variable "app_security_group_ids" { + description = "Security group IDs of the application tasks allowed to send metrics to VictoriaMetrics." + type = list(string) +} + +variable "assign_public_ip" { + description = "Whether to assign a public IP to the VictoriaMetrics task." + type = bool + default = false +} + +variable "aws_region" { + description = "The AWS region where the module is deployed." + type = string +} + +variable "common_tags" { + description = "A map of common tags to apply to all resources." + type = map(string) + default = {} +} + +variable "environment" { + description = "The environment (e.g., staging, production)." + type = string +} + +variable "kms_key_arn" { + description = "The ARN of the KMS key used to encrypt the EFS file system." + type = string +} + +variable "log_retention_in_days" { + description = "The number of days to retain VictoriaMetrics container logs." + type = number + default = 90 +} + +variable "project_name" { + description = "The name of the project." + type = string +} + +variable "subnet_ids" { + description = "The private subnet IDs for the EFS mount targets and the VictoriaMetrics task." + type = list(string) +} + +variable "vm_cpu" { + description = "The CPU units for the VictoriaMetrics Fargate task." + type = number + default = 512 +} + +variable "vm_image" { + description = "The VictoriaMetrics container image (including digest)." + type = string +} + +variable "vm_memory" { + description = "The memory (in MiB) for the VictoriaMetrics Fargate task." + type = number + default = 1024 +} + +variable "vm_port" { + description = "The port VictoriaMetrics listens on for ingest and queries." + type = number + default = 8428 +} + +variable "vm_retention_period" { + description = "The VictoriaMetrics data retention period (e.g., 12, 5y)." + type = string + default = "12" +} + +variable "vpc_id" { + description = "The VPC ID where the VictoriaMetrics security group is created." + type = string +} From 71602cbde6e2aedf7d358ce24681c9e013dd5a7e Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Fri, 31 Jul 2026 19:33:38 +0500 Subject: [PATCH 02/13] make check Signed-off-by: hassaansaleem28 --- infrastructure/live/README.md | 3 + .../modules/observability/README.md | 67 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 infrastructure/modules/observability/README.md diff --git a/infrastructure/live/README.md b/infrastructure/live/README.md index ad556f5171..d177051044 100644 --- a/infrastructure/live/README.md +++ b/infrastructure/live/README.md @@ -59,6 +59,7 @@ No providers. | [frontend\_build\_cache](#module\_frontend\_build\_cache) | ../modules/ecr-cache | n/a | | [kms](#module\_kms) | ../modules/kms | n/a | | [networking](#module\_networking) | ../modules/networking | n/a | +| [observability](#module\_observability) | ../modules/observability | n/a | | [parameters](#module\_parameters) | ../modules/parameters | n/a | | [security](#module\_security) | ../modules/security | n/a | | [storage](#module\_storage) | ../modules/storage | n/a | @@ -101,6 +102,7 @@ No resources. | [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\_observability](#input\_enable\_observability) | Whether to create the observability stack (VictoriaMetrics). | `bool` | `false` | 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 | @@ -116,6 +118,7 @@ No resources. | [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 | +| [observability\_vm\_image](#input\_observability\_vm\_image) | The VictoriaMetrics container image (including digest). | `string` | `"victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70"` | 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 | diff --git a/infrastructure/modules/observability/README.md b/infrastructure/modules/observability/README.md new file mode 100644 index 0000000000..689bc83f88 --- /dev/null +++ b/infrastructure/modules/observability/README.md @@ -0,0 +1,67 @@ + +## Requirements + +| Name | Version | +| ---- | ------- | +| [terraform](#requirement\_terraform) | ~> 1.15.0 | + +## Providers + +| Name | Version | +| ---- | ------- | +| [aws](#provider\_aws) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| ---- | ---- | +| [aws_cloudwatch_log_group.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ecs_cluster.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | +| [aws_ecs_cluster_capacity_providers.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | +| [aws_ecs_service.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_efs_file_system.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system) | resource | +| [aws_efs_mount_target.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_mount_target) | resource | +| [aws_iam_policy.ecs_task_execution_policy](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_policy_attachment.ecs_task_execution_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_security_group.efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.efs_from_vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.vm_egress_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.vm_ingest_from_apps](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.vm_to_efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [app\_security\_group\_ids](#input\_app\_security\_group\_ids) | Security group IDs of the application tasks allowed to send metrics to VictoriaMetrics. | `list(string)` | n/a | yes | +| [assign\_public\_ip](#input\_assign\_public\_ip) | Whether to assign a public IP to the VictoriaMetrics task. | `bool` | `false` | no | +| [aws\_region](#input\_aws\_region) | The AWS region where the module is deployed. | `string` | n/a | yes | +| [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 used to encrypt the EFS file system. | `string` | n/a | yes | +| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The number of days to retain VictoriaMetrics container logs. | `number` | `90` | no | +| [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes | +| [subnet\_ids](#input\_subnet\_ids) | The private subnet IDs for the EFS mount targets and the VictoriaMetrics task. | `list(string)` | n/a | yes | +| [vm\_cpu](#input\_vm\_cpu) | The CPU units for the VictoriaMetrics Fargate task. | `number` | `512` | no | +| [vm\_image](#input\_vm\_image) | The VictoriaMetrics container image (including digest). | `string` | n/a | yes | +| [vm\_memory](#input\_vm\_memory) | The memory (in MiB) for the VictoriaMetrics Fargate task. | `number` | `1024` | no | +| [vm\_port](#input\_vm\_port) | The port VictoriaMetrics listens on for ingest and queries. | `number` | `8428` | no | +| [vm\_retention\_period](#input\_vm\_retention\_period) | The VictoriaMetrics data retention period (e.g., 12, 5y). | `string` | `"12"` | no | +| [vpc\_id](#input\_vpc\_id) | The VPC ID where the VictoriaMetrics security group is created. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| [efs\_file\_system\_id](#output\_efs\_file\_system\_id) | The ID of the EFS file system backing VictoriaMetrics storage. | +| [vm\_cluster\_name](#output\_vm\_cluster\_name) | The name of the ECS cluster running VictoriaMetrics. | +| [vm\_security\_group\_id](#output\_vm\_security\_group\_id) | The ID of the VictoriaMetrics security group. | + \ No newline at end of file From ff31b6e98bcf0b3e98f6c234a25d23b20c0e7024 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Sat, 1 Aug 2026 16:36:27 +0500 Subject: [PATCH 03/13] add test Signed-off-by: hassaansaleem28 --- .../observability/tests/unit.tftest.hcl | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 infrastructure/modules/observability/tests/unit.tftest.hcl diff --git a/infrastructure/modules/observability/tests/unit.tftest.hcl b/infrastructure/modules/observability/tests/unit.tftest.hcl new file mode 100644 index 0000000000..ba1d44c475 --- /dev/null +++ b/infrastructure/modules/observability/tests/unit.tftest.hcl @@ -0,0 +1,141 @@ +mock_provider "aws" {} + +variables { + app_security_group_ids = ["sg-backend", "sg-frontend", "sg-tasks"] + aws_region = "us-east-2" + common_tags = { Environment = "test", Project = "nest" } + environment = "test" + kms_key_arn = "arn:aws:kms:us-east-2:123456789012:key/12345678-1234-1234-1234-123456789012" + project_name = "nest" + subnet_ids = ["subnet-1", "subnet-2"] + vm_image = "victoriametrics/victoria-metrics:test" + vm_port = 8428 + vpc_id = "vpc-12345" +} + +run "test_efs_encryption_enabled" { + command = plan + + assert { + condition = aws_efs_file_system.vm.encrypted == true + error_message = "EFS must be encrypted at rest." + } +} + +run "test_efs_uses_kms_key" { + command = plan + + assert { + condition = aws_efs_file_system.vm.kms_key_id == var.kms_key_arn + error_message = "EFS must be encrypted with the provided KMS key." + } +} + +run "test_efs_mount_target_per_subnet" { + command = plan + + assert { + condition = length(aws_efs_mount_target.vm) == length(var.subnet_ids) + error_message = "There must be one EFS mount target per subnet." + } +} + +run "test_vm_ingest_rule_per_app_security_group" { + command = plan + + assert { + condition = length(aws_security_group_rule.vm_ingest_from_apps) == length(var.app_security_group_ids) + error_message = "There must be one VM ingest rule per application security group." + } +} + +run "test_vm_ingest_from_source_security_group_only" { + command = plan + + assert { + condition = aws_security_group_rule.vm_ingest_from_apps["sg-backend"].source_security_group_id == "sg-backend" + error_message = "VM ingest must be restricted to application security groups, not public CIDRs." + } + + assert { + condition = aws_security_group_rule.vm_ingest_from_apps["sg-backend"].from_port == var.vm_port + error_message = "VM ingest must be allowed on the configured VictoriaMetrics port." + } +} + +run "test_efs_ingress_from_vm_only" { + command = plan + + assert { + condition = aws_security_group_rule.efs_from_vm.from_port == 2049 && aws_security_group_rule.efs_from_vm.type == "ingress" + error_message = "EFS must only allow NFS ingress from the VictoriaMetrics security group." + } +} + +run "test_vm_service_is_single_task" { + command = plan + + assert { + condition = aws_ecs_service.vm.desired_count == 1 + error_message = "VictoriaMetrics must run as a single task." + } +} + +run "test_vm_service_stops_before_starting" { + command = plan + + assert { + condition = aws_ecs_service.vm.deployment_minimum_healthy_percent == 0 && aws_ecs_service.vm.deployment_maximum_percent == 100 + error_message = "Deployments must stop the old task before starting the new one to avoid two writers on EFS." + } +} + +run "test_vm_uses_on_demand_fargate_only" { + command = plan + + assert { + condition = length(aws_ecs_cluster_capacity_providers.vm.capacity_providers) == 1 && contains(aws_ecs_cluster_capacity_providers.vm.capacity_providers, "FARGATE") + error_message = "VictoriaMetrics must use on-demand FARGATE only, never FARGATE_SPOT." + } +} + +run "test_task_uses_arm64" { + command = plan + + assert { + condition = aws_ecs_task_definition.vm.runtime_platform[0].cpu_architecture == "ARM64" + error_message = "The VictoriaMetrics task must run on ARM64." + } +} + +run "test_task_mounts_encrypted_efs_volume" { + command = plan + + assert { + condition = [for v in aws_ecs_task_definition.vm.volume : v.efs_volume_configuration[0].transit_encryption][0] == "ENABLED" + error_message = "The task EFS volume must enable transit encryption." + } +} + +run "test_log_group_name_and_retention" { + command = plan + + assert { + condition = aws_cloudwatch_log_group.vm.name == "/aws/ecs/${var.project_name}-${var.environment}-observability" + error_message = "CloudWatch log group name must follow the /aws/ecs/{project}-{environment}-observability format." + } + + assert { + condition = aws_cloudwatch_log_group.vm.retention_in_days == var.log_retention_in_days + error_message = "CloudWatch log group must use the configured retention." + } +} + +run "test_cluster_name_format" { + command = plan + + assert { + condition = aws_ecs_cluster.vm.name == "${var.project_name}-${var.environment}-observability-cluster" + error_message = "ECS cluster name must follow the {project}-{environment}-observability-cluster format." + } +} From dd9df90709579865ba83cf32c8358ee8b671f4d0 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Mon, 3 Aug 2026 15:05:08 +0500 Subject: [PATCH 04/13] Update code with bot suggestions Signed-off-by: hassaansaleem28 --- .../modules/observability/README.md | 3 +- infrastructure/modules/observability/main.tf | 26 +++------ .../observability/tests/unit.tftest.hcl | 56 ++++++++++++++++--- .../modules/observability/variables.tf | 20 +++++++ 4 files changed, 77 insertions(+), 28 deletions(-) diff --git a/infrastructure/modules/observability/README.md b/infrastructure/modules/observability/README.md index 689bc83f88..761b33f41b 100644 --- a/infrastructure/modules/observability/README.md +++ b/infrastructure/modules/observability/README.md @@ -35,7 +35,6 @@ No modules. | [aws_security_group_rule.vm_egress_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | | [aws_security_group_rule.vm_ingest_from_apps](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | | [aws_security_group_rule.vm_to_efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | -| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | ## Inputs @@ -64,4 +63,4 @@ No modules. | [efs\_file\_system\_id](#output\_efs\_file\_system\_id) | The ID of the EFS file system backing VictoriaMetrics storage. | | [vm\_cluster\_name](#output\_vm\_cluster\_name) | The name of the ECS cluster running VictoriaMetrics. | | [vm\_security\_group\_id](#output\_vm\_security\_group\_id) | The ID of the VictoriaMetrics security group. | - \ No newline at end of file + diff --git a/infrastructure/modules/observability/main.tf b/infrastructure/modules/observability/main.tf index 62f7f5ea32..83465457b2 100644 --- a/infrastructure/modules/observability/main.tf +++ b/infrastructure/modules/observability/main.tf @@ -52,8 +52,6 @@ locals { } } -data "aws_caller_identity" "current" {} - resource "aws_security_group" "vm" { description = "Security group for the VictoriaMetrics task" name = "${local.name_prefix}-vm-sg" @@ -120,6 +118,10 @@ resource "aws_efs_file_system" "vm" { tags = merge(var.common_tags, { Name = "${local.name_prefix}-vm" }) + + lifecycle { + prevent_destroy = true + } } resource "aws_efs_mount_target" "vm" { @@ -180,28 +182,12 @@ resource "aws_iam_role" "ecs_task_execution_role" { } resource "aws_iam_policy" "ecs_task_execution_policy" { - description = "Policy for the VictoriaMetrics ECS task execution - ECR and CloudWatch Logs access." + description = "Policy for the VictoriaMetrics ECS task execution - CloudWatch Logs access." name = "${local.name_prefix}-execution-policy" policy = jsonencode({ Version = "2012-10-17" Statement = [ - { - # https://docs.aws.amazon.com/AmazonECR/latest/public/public-repository-policies.html#repository-policy-vs-iam-policy - # NOSEMGREP: terraform.lang.security.iam.no-iam-creds-exposure.no-iam-creds-exposure - Action = "ecr:GetAuthorizationToken" - Effect = "Allow" - Resource = "*" # NOSONAR - }, - { - Action = [ - "ecr:BatchCheckLayerAvailability", - "ecr:BatchGetImage", - "ecr:GetDownloadUrlForLayer" - ] - Effect = "Allow" - Resource = "arn:aws:ecr:${var.aws_region}:${data.aws_caller_identity.current.account_id}:repository/*" - }, { Action = [ "logs:CreateLogStream", @@ -267,4 +253,6 @@ resource "aws_ecs_service" "vm" { security_groups = [aws_security_group.vm.id] subnets = var.subnet_ids } + + depends_on = [aws_efs_mount_target.vm] } diff --git a/infrastructure/modules/observability/tests/unit.tftest.hcl b/infrastructure/modules/observability/tests/unit.tftest.hcl index ba1d44c475..0da368bf1b 100644 --- a/infrastructure/modules/observability/tests/unit.tftest.hcl +++ b/infrastructure/modules/observability/tests/unit.tftest.hcl @@ -1,4 +1,22 @@ -mock_provider "aws" {} +mock_provider "aws" { + mock_resource "aws_iam_role" { + defaults = { + arn = "arn:aws:iam::123456789012:role/mock-role" + } + } + + mock_resource "aws_ecs_task_definition" { + defaults = { + arn = "arn:aws:ecs:us-east-2:123456789012:task-definition/mock:1" + } + } + + mock_resource "aws_iam_policy" { + defaults = { + arn = "arn:aws:iam::123456789012:policy/mock-policy" + } + } +} variables { app_security_group_ids = ["sg-backend", "sg-frontend", "sg-tasks"] @@ -8,7 +26,7 @@ variables { kms_key_arn = "arn:aws:kms:us-east-2:123456789012:key/12345678-1234-1234-1234-123456789012" project_name = "nest" subnet_ids = ["subnet-1", "subnet-2"] - vm_image = "victoriametrics/victoria-metrics:test" + vm_image = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70" vm_port = 8428 vpc_id = "vpc-12345" } @@ -64,11 +82,16 @@ run "test_vm_ingest_from_source_security_group_only" { } run "test_efs_ingress_from_vm_only" { - command = plan + command = apply assert { condition = aws_security_group_rule.efs_from_vm.from_port == 2049 && aws_security_group_rule.efs_from_vm.type == "ingress" - error_message = "EFS must only allow NFS ingress from the VictoriaMetrics security group." + error_message = "EFS must only allow NFS ingress on port 2049." + } + + assert { + condition = aws_security_group_rule.efs_from_vm.source_security_group_id == aws_security_group.vm.id + error_message = "EFS ingress must come only from the VictoriaMetrics security group." } } @@ -109,11 +132,16 @@ run "test_task_uses_arm64" { } run "test_task_mounts_encrypted_efs_volume" { - command = plan + command = apply assert { - condition = [for v in aws_ecs_task_definition.vm.volume : v.efs_volume_configuration[0].transit_encryption][0] == "ENABLED" - error_message = "The task EFS volume must enable transit encryption." + condition = one([for v in aws_ecs_task_definition.vm.volume : v if v.name == "vm-data"]).efs_volume_configuration[0].transit_encryption == "ENABLED" + error_message = "The vm-data volume must enable transit encryption." + } + + assert { + condition = one([for v in aws_ecs_task_definition.vm.volume : v if v.name == "vm-data"]).efs_volume_configuration[0].file_system_id == aws_efs_file_system.vm.id + error_message = "The vm-data volume must reference the module's EFS file system." } } @@ -139,3 +167,17 @@ run "test_cluster_name_format" { error_message = "ECS cluster name must follow the {project}-{environment}-observability-cluster format." } } + +run "test_common_tags_applied" { + command = plan + + assert { + condition = alltrue([for k, v in var.common_tags : lookup(aws_efs_file_system.vm.tags, k, null) == v]) + error_message = "common_tags must be applied to the EFS file system." + } + + assert { + condition = alltrue([for k, v in var.common_tags : lookup(aws_ecs_cluster.vm.tags, k, null) == v]) + error_message = "common_tags must be applied to the ECS cluster." + } +} diff --git a/infrastructure/modules/observability/variables.tf b/infrastructure/modules/observability/variables.tf index d6037096e4..31ce3fedb6 100644 --- a/infrastructure/modules/observability/variables.tf +++ b/infrastructure/modules/observability/variables.tf @@ -1,6 +1,11 @@ variable "app_security_group_ids" { description = "Security group IDs of the application tasks allowed to send metrics to VictoriaMetrics." type = list(string) + + validation { + condition = length(var.app_security_group_ids) > 0 + error_message = "app_security_group_ids must contain at least one security group." + } } variable "assign_public_ip" { @@ -44,6 +49,11 @@ variable "project_name" { variable "subnet_ids" { description = "The private subnet IDs for the EFS mount targets and the VictoriaMetrics task." type = list(string) + + validation { + condition = length(var.subnet_ids) > 0 + error_message = "subnet_ids must contain at least one subnet." + } } variable "vm_cpu" { @@ -55,6 +65,11 @@ variable "vm_cpu" { variable "vm_image" { description = "The VictoriaMetrics container image (including digest)." type = string + + validation { + condition = can(regex("@sha256:[0-9a-f]{64}$", var.vm_image)) + error_message = "vm_image must be pinned to an immutable digest (e.g., repo:tag@sha256:...)." + } } variable "vm_memory" { @@ -67,6 +82,11 @@ variable "vm_port" { description = "The port VictoriaMetrics listens on for ingest and queries." type = number default = 8428 + + validation { + condition = var.vm_port > 0 && var.vm_port < 65536 + error_message = "vm_port must be between 1 and 65535." + } } variable "vm_retention_period" { From bbab304921ca52a2b0041607e1f851a7f2c7a357 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Mon, 3 Aug 2026 15:40:12 +0500 Subject: [PATCH 05/13] Update code Signed-off-by: hassaansaleem28 --- infrastructure/modules/observability/variables.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/modules/observability/variables.tf b/infrastructure/modules/observability/variables.tf index 31ce3fedb6..d09dd8ea7e 100644 --- a/infrastructure/modules/observability/variables.tf +++ b/infrastructure/modules/observability/variables.tf @@ -67,8 +67,8 @@ variable "vm_image" { type = string validation { - condition = can(regex("@sha256:[0-9a-f]{64}$", var.vm_image)) - error_message = "vm_image must be pinned to an immutable digest (e.g., repo:tag@sha256:...)." + condition = can(regex("^[^@]+@sha256:[0-9a-f]{64}$", var.vm_image)) + error_message = "vm_image must be an image reference pinned to an immutable digest (e.g., repo:tag@sha256:...)." } } @@ -84,8 +84,8 @@ variable "vm_port" { default = 8428 validation { - condition = var.vm_port > 0 && var.vm_port < 65536 - error_message = "vm_port must be between 1 and 65535." + condition = var.vm_port > 0 && var.vm_port < 65536 && floor(var.vm_port) == var.vm_port + error_message = "vm_port must be a whole number between 1 and 65535." } } From 0f3ec7d050c04bcc3253ecb605f1fde889ea728a Mon Sep 17 00:00:00 2001 From: Muhammad Hassaan Saleem Date: Wed, 5 Aug 2026 08:40:33 +0500 Subject: [PATCH 06/13] Update infrastructure/live/variables.tf Co-authored-by: Rudransh Shrivastava --- infrastructure/live/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/live/variables.tf b/infrastructure/live/variables.tf index 7edff0f74a..545cc8d0ed 100644 --- a/infrastructure/live/variables.tf +++ b/infrastructure/live/variables.tf @@ -194,7 +194,7 @@ variable "enable_nat_gateway" { } variable "enable_observability" { - description = "Whether to create the observability stack (VictoriaMetrics)." + description = "Whether to create the observability stack." type = bool default = false } From 894e967ff23b9b6b821923b7e0035ad4a918bfe1 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 6 Aug 2026 16:08:52 +0500 Subject: [PATCH 07/13] address review + add integration tests Signed-off-by: hassaansaleem28 --- docker-compose/local/compose.o11y.yaml | 4 +- docker/victoriametrics/Dockerfile | 1 + infrastructure/live/README.md | 3 +- infrastructure/live/main.tf | 44 ++++++++------- infrastructure/live/variables.tf | 6 -- .../modules/observability/README.md | 3 +- infrastructure/modules/observability/main.tf | 3 +- .../tests/integration.tftest.hcl | 55 +++++++++++++++++++ .../observability/tests/unit.tftest.hcl | 34 +----------- infrastructure/scripts/localstack.py | 4 ++ 10 files changed, 95 insertions(+), 62 deletions(-) create mode 100644 docker/victoriametrics/Dockerfile create mode 100644 infrastructure/modules/observability/tests/integration.tftest.hcl diff --git a/docker-compose/local/compose.o11y.yaml b/docker-compose/local/compose.o11y.yaml index 3873f69c7f..ccd2acc025 100644 --- a/docker-compose/local/compose.o11y.yaml +++ b/docker-compose/local/compose.o11y.yaml @@ -35,7 +35,9 @@ services: command: - -retentionPeriod=5y - -storageDataPath=/data - image: victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70 + build: + context: ../../docker/victoriametrics + dockerfile: Dockerfile healthcheck: interval: 5s retries: 5 diff --git a/docker/victoriametrics/Dockerfile b/docker/victoriametrics/Dockerfile new file mode 100644 index 0000000000..6fbce0da07 --- /dev/null +++ b/docker/victoriametrics/Dockerfile @@ -0,0 +1 @@ +FROM victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70 diff --git a/infrastructure/live/README.md b/infrastructure/live/README.md index d177051044..f6ead732ff 100644 --- a/infrastructure/live/README.md +++ b/infrastructure/live/README.md @@ -102,7 +102,7 @@ No resources. | [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\_observability](#input\_enable\_observability) | Whether to create the observability stack (VictoriaMetrics). | `bool` | `false` | no | +| [enable\_observability](#input\_enable\_observability) | Whether to create the observability stack. | `bool` | `false` | 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 | @@ -118,7 +118,6 @@ No resources. | [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 | -| [observability\_vm\_image](#input\_observability\_vm\_image) | The VictoriaMetrics container image (including digest). | `string` | `"victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70"` | 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 | diff --git a/infrastructure/live/main.tf b/infrastructure/live/main.tf index c00c32355f..9f0ba9395d 100644 --- a/infrastructure/live/main.tf +++ b/infrastructure/live/main.tf @@ -22,6 +22,10 @@ locals { Project = var.project_name } fixtures_bucket_name = coalesce(var.fixtures_bucket_name, "${var.project_name}-${var.environment}-fixtures") + observability_vm_image = trimspace(trimprefix( + one([for line in split("\n", file("${path.root}/../../docker/victoriametrics/Dockerfile")) : line if startswith(line, "FROM ")]), + "FROM " + )) } module "alb" { @@ -179,6 +183,26 @@ module "networking" { vpc_cidr = var.vpc_cidr } +module "observability" { + count = var.enable_observability ? 1 : 0 + source = "../modules/observability" + + app_security_group_ids = [ + module.security.backend_sg_id, + module.security.frontend_sg_id, + module.security.tasks_sg_id, + ] + assign_public_ip = local.assign_public_ip + aws_region = var.aws_region + common_tags = local.common_tags + environment = var.environment + kms_key_arn = module.kms.key_arn + project_name = var.project_name + subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids + vm_image = local.observability_vm_image + vpc_id = module.networking.vpc_id +} + module "parameters" { source = "../modules/parameters" @@ -249,23 +273,3 @@ module "tasks" { subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids use_fargate_spot = var.tasks_use_fargate_spot } - -module "observability" { - count = var.enable_observability ? 1 : 0 - source = "../modules/observability" - - app_security_group_ids = [ - module.security.backend_sg_id, - module.security.frontend_sg_id, - module.security.tasks_sg_id, - ] - assign_public_ip = local.assign_public_ip - aws_region = var.aws_region - common_tags = local.common_tags - environment = var.environment - kms_key_arn = module.kms.key_arn - project_name = var.project_name - subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids - vm_image = var.observability_vm_image - vpc_id = module.networking.vpc_id -} diff --git a/infrastructure/live/variables.tf b/infrastructure/live/variables.tf index 545cc8d0ed..3fe0115074 100644 --- a/infrastructure/live/variables.tf +++ b/infrastructure/live/variables.tf @@ -291,12 +291,6 @@ variable "frontend_use_fargate_spot" { default = true } -variable "observability_vm_image" { - description = "The VictoriaMetrics container image (including digest)." - type = string - default = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70" -} - variable "private_subnet_cidrs" { description = "A list of CIDR blocks for the private subnets." type = list(string) diff --git a/infrastructure/modules/observability/README.md b/infrastructure/modules/observability/README.md index 761b33f41b..6f743db79d 100644 --- a/infrastructure/modules/observability/README.md +++ b/infrastructure/modules/observability/README.md @@ -4,12 +4,13 @@ | Name | Version | | ---- | ------- | | [terraform](#requirement\_terraform) | ~> 1.15.0 | +| [aws](#requirement\_aws) | ~> 6.53.0 | ## Providers | Name | Version | | ---- | ------- | -| [aws](#provider\_aws) | n/a | +| [aws](#provider\_aws) | ~> 6.53.0 | ## Modules diff --git a/infrastructure/modules/observability/main.tf b/infrastructure/modules/observability/main.tf index 83465457b2..a3dfd922ff 100644 --- a/infrastructure/modules/observability/main.tf +++ b/infrastructure/modules/observability/main.tf @@ -3,7 +3,8 @@ terraform { required_providers { aws = { - source = "hashicorp/aws" + source = "hashicorp/aws" + version = "~> 6.53.0" } } } diff --git a/infrastructure/modules/observability/tests/integration.tftest.hcl b/infrastructure/modules/observability/tests/integration.tftest.hcl new file mode 100644 index 0000000000..b116064551 --- /dev/null +++ b/infrastructure/modules/observability/tests/integration.tftest.hcl @@ -0,0 +1,55 @@ +provider "aws" { + access_key = "test" + region = "us-east-1" + s3_use_path_style = true + secret_key = "test" + skip_credentials_validation = true + skip_metadata_api_check = true + skip_requesting_account_id = true +} + +variables { + app_security_group_ids = ["sg-11111111", "sg-22222222", "sg-33333333"] + aws_region = "us-east-1" + common_tags = { Environment = "test", Project = "nest" } + environment = "test" + kms_key_arn = "arn:aws:kms:us-east-1:000000000000:key/1234abcd-12ab-34cd-56ef-1234567890ab" + project_name = "nest" + subnet_ids = ["subnet-11111111", "subnet-22222222"] + vm_image = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70" + vpc_id = "vpc-11111111" +} + +run "observability_integration_apply" { + command = apply + + assert { + condition = can(aws_efs_file_system.vm.id) + error_message = "EFS file system was not created." + } + + assert { + condition = aws_efs_file_system.vm.encrypted == true + error_message = "EFS must be encrypted at rest." + } + + assert { + condition = length(aws_efs_mount_target.vm) == length(var.subnet_ids) + error_message = "There must be one EFS mount target per subnet." + } + + assert { + condition = aws_security_group_rule.efs_from_vm.source_security_group_id == aws_security_group.vm.id + error_message = "EFS ingress must come only from the VictoriaMetrics security group." + } + + assert { + condition = one([for v in aws_ecs_task_definition.vm.volume : v if v.name == "vm-data"]).efs_volume_configuration[0].file_system_id == aws_efs_file_system.vm.id + error_message = "The vm-data volume must reference the module's EFS file system." + } + + assert { + condition = can(aws_ecs_service.vm.id) + error_message = "ECS service was not created." + } +} diff --git a/infrastructure/modules/observability/tests/unit.tftest.hcl b/infrastructure/modules/observability/tests/unit.tftest.hcl index 0da368bf1b..3060d34e0d 100644 --- a/infrastructure/modules/observability/tests/unit.tftest.hcl +++ b/infrastructure/modules/observability/tests/unit.tftest.hcl @@ -1,22 +1,4 @@ -mock_provider "aws" { - mock_resource "aws_iam_role" { - defaults = { - arn = "arn:aws:iam::123456789012:role/mock-role" - } - } - - mock_resource "aws_ecs_task_definition" { - defaults = { - arn = "arn:aws:ecs:us-east-2:123456789012:task-definition/mock:1" - } - } - - mock_resource "aws_iam_policy" { - defaults = { - arn = "arn:aws:iam::123456789012:policy/mock-policy" - } - } -} +mock_provider "aws" {} variables { app_security_group_ids = ["sg-backend", "sg-frontend", "sg-tasks"] @@ -82,17 +64,12 @@ run "test_vm_ingest_from_source_security_group_only" { } run "test_efs_ingress_from_vm_only" { - command = apply + command = plan assert { condition = aws_security_group_rule.efs_from_vm.from_port == 2049 && aws_security_group_rule.efs_from_vm.type == "ingress" error_message = "EFS must only allow NFS ingress on port 2049." } - - assert { - condition = aws_security_group_rule.efs_from_vm.source_security_group_id == aws_security_group.vm.id - error_message = "EFS ingress must come only from the VictoriaMetrics security group." - } } run "test_vm_service_is_single_task" { @@ -132,17 +109,12 @@ run "test_task_uses_arm64" { } run "test_task_mounts_encrypted_efs_volume" { - command = apply + command = plan assert { condition = one([for v in aws_ecs_task_definition.vm.volume : v if v.name == "vm-data"]).efs_volume_configuration[0].transit_encryption == "ENABLED" error_message = "The vm-data volume must enable transit encryption." } - - assert { - condition = one([for v in aws_ecs_task_definition.vm.volume : v if v.name == "vm-data"]).efs_volume_configuration[0].file_system_id == aws_efs_file_system.vm.id - error_message = "The vm-data volume must reference the module's EFS file system." - } } run "test_log_group_name_and_retention" { diff --git a/infrastructure/scripts/localstack.py b/infrastructure/scripts/localstack.py index a793b0f1da..ee6f82feb9 100644 --- a/infrastructure/scripts/localstack.py +++ b/infrastructure/scripts/localstack.py @@ -256,6 +256,10 @@ class OverrideManager: # Temporary Terraform overrides that disable prevent_destroy during integration tests. OVERRIDES: list[tuple[str, str]] = [ + ( + "infrastructure/modules/observability/test_override.tf", + "aws_efs_file_system.vm", + ), ( "infrastructure/modules/storage/modules/s3-bucket/test_override.tf", "aws_s3_bucket.this", From a2ea8b72c370ec742d37fb51bf647ffcce68b0eb Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 6 Aug 2026 16:42:09 +0500 Subject: [PATCH 08/13] Update code Signed-off-by: hassaansaleem28 --- .trivyignore.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.trivyignore.yaml b/.trivyignore.yaml index 5079ce5db1..918bf163ec 100644 --- a/.trivyignore.yaml +++ b/.trivyignore.yaml @@ -14,6 +14,7 @@ misconfigurations: - docker/osv-scanner/Dockerfile - docker/semgrep/Dockerfile - docker/trivy/Dockerfile + - docker/victoriametrics/Dockerfile - docker/zap/Dockerfile - id: AVD-AWS-0053 # Public ALB intentional for nest.owasp.dev paths: From 3d5fbdde7ef1ee27900941d719dda8b38ce884c8 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 6 Aug 2026 17:57:16 +0500 Subject: [PATCH 09/13] Update Code Signed-off-by: hassaansaleem28 --- .trivyignore.yaml | 1 + .../modules/observability/README.md | 1 + infrastructure/modules/observability/main.tf | 28 +++++++++++++++++++ .../tests/integration.tftest.hcl | 5 ++++ .../observability/tests/unit.tftest.hcl | 18 ++++++++++++ 5 files changed, 53 insertions(+) diff --git a/.trivyignore.yaml b/.trivyignore.yaml index 918bf163ec..fdcf518131 100644 --- a/.trivyignore.yaml +++ b/.trivyignore.yaml @@ -14,6 +14,7 @@ misconfigurations: - docker/osv-scanner/Dockerfile - docker/semgrep/Dockerfile - docker/trivy/Dockerfile + # Container runs as non-root (user 65532) via the ECS task definition; the image is a thin version-pin for dependabot. - docker/victoriametrics/Dockerfile - docker/zap/Dockerfile - id: AVD-AWS-0053 # Public ALB intentional for nest.owasp.dev diff --git a/infrastructure/modules/observability/README.md b/infrastructure/modules/observability/README.md index 6f743db79d..f69d4a131e 100644 --- a/infrastructure/modules/observability/README.md +++ b/infrastructure/modules/observability/README.md @@ -25,6 +25,7 @@ No modules. | [aws_ecs_cluster_capacity_providers.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | | [aws_ecs_service.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | | [aws_ecs_task_definition.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_efs_access_point.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_access_point) | resource | | [aws_efs_file_system.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system) | resource | | [aws_efs_mount_target.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_mount_target) | resource | | [aws_iam_policy.ecs_task_execution_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | diff --git a/infrastructure/modules/observability/main.tf b/infrastructure/modules/observability/main.tf index a3dfd922ff..6226212783 100644 --- a/infrastructure/modules/observability/main.tf +++ b/infrastructure/modules/observability/main.tf @@ -50,6 +50,7 @@ locals { protocol = "tcp" } ] + user = "65532" } } @@ -133,6 +134,29 @@ resource "aws_efs_mount_target" "vm" { subnet_id = each.value } +resource "aws_efs_access_point" "vm" { + file_system_id = aws_efs_file_system.vm.id + + posix_user { + gid = 65532 + uid = 65532 + } + + root_directory { + path = "/victoriametrics" + + creation_info { + owner_gid = 65532 + owner_uid = 65532 + permissions = "0755" + } + } + + tags = merge(var.common_tags, { + Name = "${local.name_prefix}-vm" + }) +} + resource "aws_cloudwatch_log_group" "vm" { kms_key_id = var.kms_key_arn name = "/aws/ecs/${local.name_prefix}" @@ -228,6 +252,10 @@ resource "aws_ecs_task_definition" "vm" { efs_volume_configuration { file_system_id = aws_efs_file_system.vm.id transit_encryption = "ENABLED" + + authorization_config { + access_point_id = aws_efs_access_point.vm.id + } } } } diff --git a/infrastructure/modules/observability/tests/integration.tftest.hcl b/infrastructure/modules/observability/tests/integration.tftest.hcl index b116064551..4fa9f7540d 100644 --- a/infrastructure/modules/observability/tests/integration.tftest.hcl +++ b/infrastructure/modules/observability/tests/integration.tftest.hcl @@ -48,6 +48,11 @@ run "observability_integration_apply" { error_message = "The vm-data volume must reference the module's EFS file system." } + assert { + condition = can(aws_efs_access_point.vm.id) + error_message = "EFS access point was not created." + } + assert { condition = can(aws_ecs_service.vm.id) error_message = "ECS service was not created." diff --git a/infrastructure/modules/observability/tests/unit.tftest.hcl b/infrastructure/modules/observability/tests/unit.tftest.hcl index 3060d34e0d..efc5378971 100644 --- a/infrastructure/modules/observability/tests/unit.tftest.hcl +++ b/infrastructure/modules/observability/tests/unit.tftest.hcl @@ -108,6 +108,24 @@ run "test_task_uses_arm64" { } } +run "test_container_runs_as_non_root" { + command = plan + + assert { + condition = jsondecode(aws_ecs_task_definition.vm.container_definitions)[0].user == "65532" + error_message = "The VictoriaMetrics container must run as the non-root user 65532." + } +} + +run "test_access_point_enforces_non_root_owner" { + command = plan + + assert { + condition = aws_efs_access_point.vm.posix_user[0].uid == 65532 && aws_efs_access_point.vm.posix_user[0].gid == 65532 + error_message = "The EFS access point must enforce the non-root POSIX user 65532." + } +} + run "test_task_mounts_encrypted_efs_volume" { command = plan From c70fdb0393aeabe0b3a129e5b45101dbead6ffa2 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Fri, 7 Aug 2026 08:18:29 +0500 Subject: [PATCH 10/13] Update after verification Signed-off-by: hassaansaleem28 --- .../modules/observability/.terraform.lock.hcl | 26 +++++++++ .../modules/observability/README.md | 3 +- infrastructure/modules/observability/main.tf | 2 +- .../tests/integration.tftest.hcl | 33 ++++++++---- .../observability/tests/setup/.gitignore | 1 + .../modules/observability/tests/setup/main.tf | 53 +++++++++++++++++++ .../modules/observability/variables.tf | 11 ++++ 7 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 infrastructure/modules/observability/.terraform.lock.hcl create mode 100644 infrastructure/modules/observability/tests/setup/.gitignore create mode 100644 infrastructure/modules/observability/tests/setup/main.tf diff --git a/infrastructure/modules/observability/.terraform.lock.hcl b/infrastructure/modules/observability/.terraform.lock.hcl new file mode 100644 index 0000000000..1b10d8dc5b --- /dev/null +++ b/infrastructure/modules/observability/.terraform.lock.hcl @@ -0,0 +1,26 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.53.0" + constraints = "~> 6.53.0" + hashes = [ + "h1:bvlWCSuVJQshwuA/vJONdjEUIzGdsW3uSfLxoP9RnIw=", + "zh:0757ce9d5a30e8225521857924f5d6c49e5885fd9e309e56193c9c9920b3f8f0", + "zh:10ddb3a20e0779788e8002bc67d184eb166dec70f9d07220bfc55e15c3e5e206", + "zh:1e943f59a8c3f3b04f09fd5d967fef2518e0ce3ec4959e31f5d7f820660e8524", + "zh:21d55ad3b28f48c6dd34faceeb1fd1fe2ec3b1aaecfc5eb0e0841884f6520d69", + "zh:2e551bc0ff29ea608a99f63d899afbdf2f23c14035f27dc0c0bdca60209d9575", + "zh:581db963564364200426f2a4be866470a975cde6a4bd6fd09362ec2bab119cbb", + "zh:71a7abc88e16ccbe4410fc4966bd35fae9e0161c1385a9b3c19800e3db626b01", + "zh:8767767776c45590d4cc74b4c37f6377e05ce75039a9c2e834de0bbcc29f7a1a", + "zh:99c8e37beb9b1017f67c41e402031559b745fa797848f1a4ef8b40421dee3f05", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ba1477eda2f3ee51846492449338146f80806088d2952c08c2f8af874550a75d", + "zh:c389f68ba4d39fa2bf8a54d4af7f7b2132da2a964c260892c1dd1a89b67e61f8", + "zh:d7381490a637a1fa45769d0ee6b3e0578cce488d078077ec63d9090113784f2a", + "zh:df3b82fb1a675fc6548fac2fff10a8741efa5bcb5a53d01d8f0659179a2f717f", + "zh:e5d78b2ac3dc5477cf21d7cd4c8b9e3d944ff091e90b62c1b2ea9cc7c7eef57c", + "zh:fd224078287c6d82de2ec30ea03d3d550c2c554e372b234f7a6ac2b07c23dba0", + ] +} diff --git a/infrastructure/modules/observability/README.md b/infrastructure/modules/observability/README.md index f69d4a131e..11596653e8 100644 --- a/infrastructure/modules/observability/README.md +++ b/infrastructure/modules/observability/README.md @@ -10,7 +10,7 @@ | Name | Version | | ---- | ------- | -| [aws](#provider\_aws) | ~> 6.53.0 | +| [aws](#provider\_aws) | 6.53.0 | ## Modules @@ -52,6 +52,7 @@ No modules. | [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes | | [subnet\_ids](#input\_subnet\_ids) | The private subnet IDs for the EFS mount targets and the VictoriaMetrics task. | `list(string)` | n/a | yes | | [vm\_cpu](#input\_vm\_cpu) | The CPU units for the VictoriaMetrics Fargate task. | `number` | `512` | no | +| [vm\_desired\_count](#input\_vm\_desired\_count) | The number of VictoriaMetrics tasks to run (0 or 1; it is a single-node store). | `number` | `1` | no | | [vm\_image](#input\_vm\_image) | The VictoriaMetrics container image (including digest). | `string` | n/a | yes | | [vm\_memory](#input\_vm\_memory) | The memory (in MiB) for the VictoriaMetrics Fargate task. | `number` | `1024` | no | | [vm\_port](#input\_vm\_port) | The port VictoriaMetrics listens on for ingest and queries. | `number` | `8428` | no | diff --git a/infrastructure/modules/observability/main.tf b/infrastructure/modules/observability/main.tf index 6226212783..16cbb10842 100644 --- a/infrastructure/modules/observability/main.tf +++ b/infrastructure/modules/observability/main.tf @@ -264,7 +264,7 @@ resource "aws_ecs_service" "vm" { cluster = aws_ecs_cluster.vm.id deployment_maximum_percent = 100 deployment_minimum_healthy_percent = 0 - desired_count = 1 + desired_count = var.vm_desired_count name = "${local.name_prefix}-service" tags = merge(var.common_tags, { Name = "${local.name_prefix}-service" diff --git a/infrastructure/modules/observability/tests/integration.tftest.hcl b/infrastructure/modules/observability/tests/integration.tftest.hcl index 4fa9f7540d..0282a88198 100644 --- a/infrastructure/modules/observability/tests/integration.tftest.hcl +++ b/infrastructure/modules/observability/tests/integration.tftest.hcl @@ -9,20 +9,30 @@ provider "aws" { } variables { - app_security_group_ids = ["sg-11111111", "sg-22222222", "sg-33333333"] - aws_region = "us-east-1" - common_tags = { Environment = "test", Project = "nest" } - environment = "test" - kms_key_arn = "arn:aws:kms:us-east-1:000000000000:key/1234abcd-12ab-34cd-56ef-1234567890ab" - project_name = "nest" - subnet_ids = ["subnet-11111111", "subnet-22222222"] - vm_image = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70" - vpc_id = "vpc-11111111" + aws_region = "us-east-1" + common_tags = { Environment = "test", Project = "nest" } + environment = "test" + project_name = "nest" + vm_image = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70" +} + +run "setup" { + module { + source = "./tests/setup" + } } run "observability_integration_apply" { command = apply + variables { + app_security_group_ids = run.setup.app_security_group_ids + kms_key_arn = run.setup.kms_key_arn + subnet_ids = run.setup.subnet_ids + vm_desired_count = 0 + vpc_id = run.setup.vpc_id + } + assert { condition = can(aws_efs_file_system.vm.id) error_message = "EFS file system was not created." @@ -53,6 +63,11 @@ run "observability_integration_apply" { error_message = "EFS access point was not created." } + assert { + condition = one([for v in aws_ecs_task_definition.vm.volume : v if v.name == "vm-data"]).efs_volume_configuration[0].authorization_config[0].access_point_id == aws_efs_access_point.vm.id + error_message = "The vm-data volume must mount through the EFS access point (for non-root UID enforcement)." + } + assert { condition = can(aws_ecs_service.vm.id) error_message = "ECS service was not created." diff --git a/infrastructure/modules/observability/tests/setup/.gitignore b/infrastructure/modules/observability/tests/setup/.gitignore new file mode 100644 index 0000000000..3f0336e2ba --- /dev/null +++ b/infrastructure/modules/observability/tests/setup/.gitignore @@ -0,0 +1 @@ +.terraform.lock.hcl diff --git a/infrastructure/modules/observability/tests/setup/main.tf b/infrastructure/modules/observability/tests/setup/main.tf new file mode 100644 index 0000000000..3357c49e84 --- /dev/null +++ b/infrastructure/modules/observability/tests/setup/main.tf @@ -0,0 +1,53 @@ +terraform { + required_version = "~> 1.15.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.53.0" + } + } +} + +data "aws_availability_zones" "available" { + state = "available" +} + +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "main" { + count = 2 + + availability_zone = data.aws_availability_zones.available.names[count.index] + cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index) + vpc_id = aws_vpc.main.id +} + +resource "aws_security_group" "app" { + count = 3 + + name = "nest-test-app-${count.index}" + vpc_id = aws_vpc.main.id +} + +resource "aws_kms_key" "main" { + description = "Test key for the observability integration tests." +} + +output "app_security_group_ids" { + value = aws_security_group.app[*].id +} + +output "kms_key_arn" { + value = aws_kms_key.main.arn +} + +output "subnet_ids" { + value = aws_subnet.main[*].id +} + +output "vpc_id" { + value = aws_vpc.main.id +} diff --git a/infrastructure/modules/observability/variables.tf b/infrastructure/modules/observability/variables.tf index d09dd8ea7e..95f6bfc7db 100644 --- a/infrastructure/modules/observability/variables.tf +++ b/infrastructure/modules/observability/variables.tf @@ -62,6 +62,17 @@ variable "vm_cpu" { default = 512 } +variable "vm_desired_count" { + description = "The number of VictoriaMetrics tasks to run (0 or 1; it is a single-node store)." + type = number + default = 1 + + validation { + condition = contains([0, 1], var.vm_desired_count) + error_message = "vm_desired_count must be 0 or 1 (VictoriaMetrics is a single-node store)." + } +} + variable "vm_image" { description = "The VictoriaMetrics container image (including digest)." type = string From d639e743769798136c692cec176d217b9dfc7dd5 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Fri, 7 Aug 2026 09:13:56 +0500 Subject: [PATCH 11/13] make check Signed-off-by: hassaansaleem28 --- .../modules/observability/.terraform.lock.hcl | 2 ++ .../modules/observability/tests/setup/main.tf | 16 ---------------- .../observability/tests/setup/outputs.tf | 19 +++++++++++++++++++ .../observability/tests/setup/variables.tf | 0 4 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 infrastructure/modules/observability/tests/setup/outputs.tf create mode 100644 infrastructure/modules/observability/tests/setup/variables.tf diff --git a/infrastructure/modules/observability/.terraform.lock.hcl b/infrastructure/modules/observability/.terraform.lock.hcl index 1b10d8dc5b..6656dd31ca 100644 --- a/infrastructure/modules/observability/.terraform.lock.hcl +++ b/infrastructure/modules/observability/.terraform.lock.hcl @@ -5,7 +5,9 @@ provider "registry.terraform.io/hashicorp/aws" { version = "6.53.0" constraints = "~> 6.53.0" hashes = [ + "h1:UFEhEEFJcR/pAOZcwdR11gN9W3X8VwvSl1IS0vbj2G0=", "h1:bvlWCSuVJQshwuA/vJONdjEUIzGdsW3uSfLxoP9RnIw=", + "h1:nZ85OLLO0sNw/76mgQQQmnOodiQGaaGVxq1NaV3ol4g=", "zh:0757ce9d5a30e8225521857924f5d6c49e5885fd9e309e56193c9c9920b3f8f0", "zh:10ddb3a20e0779788e8002bc67d184eb166dec70f9d07220bfc55e15c3e5e206", "zh:1e943f59a8c3f3b04f09fd5d967fef2518e0ce3ec4959e31f5d7f820660e8524", diff --git a/infrastructure/modules/observability/tests/setup/main.tf b/infrastructure/modules/observability/tests/setup/main.tf index 3357c49e84..7bf5e3a170 100644 --- a/infrastructure/modules/observability/tests/setup/main.tf +++ b/infrastructure/modules/observability/tests/setup/main.tf @@ -35,19 +35,3 @@ resource "aws_security_group" "app" { resource "aws_kms_key" "main" { description = "Test key for the observability integration tests." } - -output "app_security_group_ids" { - value = aws_security_group.app[*].id -} - -output "kms_key_arn" { - value = aws_kms_key.main.arn -} - -output "subnet_ids" { - value = aws_subnet.main[*].id -} - -output "vpc_id" { - value = aws_vpc.main.id -} diff --git a/infrastructure/modules/observability/tests/setup/outputs.tf b/infrastructure/modules/observability/tests/setup/outputs.tf new file mode 100644 index 0000000000..50efb66319 --- /dev/null +++ b/infrastructure/modules/observability/tests/setup/outputs.tf @@ -0,0 +1,19 @@ +output "app_security_group_ids" { + description = "The IDs of the stand-in application security groups." + value = aws_security_group.app[*].id +} + +output "kms_key_arn" { + description = "The ARN of the stand-in KMS key." + value = aws_kms_key.main.arn +} + +output "subnet_ids" { + description = "The IDs of the stand-in subnets." + value = aws_subnet.main[*].id +} + +output "vpc_id" { + description = "The ID of the stand-in VPC." + value = aws_vpc.main.id +} diff --git a/infrastructure/modules/observability/tests/setup/variables.tf b/infrastructure/modules/observability/tests/setup/variables.tf new file mode 100644 index 0000000000..e69de29bb2 From aa1b8ac81f44e961ca676595ffe4fa0af7684829 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Fri, 7 Aug 2026 09:28:07 +0500 Subject: [PATCH 12/13] address bot suggestion Signed-off-by: hassaansaleem28 --- .../modules/observability/tests/integration.tftest.hcl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/modules/observability/tests/integration.tftest.hcl b/infrastructure/modules/observability/tests/integration.tftest.hcl index 0282a88198..844e4b7d2a 100644 --- a/infrastructure/modules/observability/tests/integration.tftest.hcl +++ b/infrastructure/modules/observability/tests/integration.tftest.hcl @@ -17,6 +17,8 @@ variables { } run "setup" { + command = apply + module { source = "./tests/setup" } From 8953fc5434255bf2c14508ac4382222caf6c3de3 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Sat, 15 Aug 2026 22:33:36 +0500 Subject: [PATCH 13/13] address reviews Signed-off-by: hassaansaleem28 --- infrastructure/live/main.tf | 14 ++++----- .../modules/observability/README.md | 2 +- infrastructure/modules/observability/main.tf | 8 ++--- .../tests/integration.tftest.hcl | 2 +- .../observability/tests/setup/.gitignore | 1 - .../tests/setup/.terraform.lock.hcl | 29 +++++++++++++++++++ .../observability/tests/unit.tftest.hcl | 6 ++-- .../modules/observability/variables.tf | 4 +-- 8 files changed, 46 insertions(+), 20 deletions(-) delete mode 100644 infrastructure/modules/observability/tests/setup/.gitignore create mode 100644 infrastructure/modules/observability/tests/setup/.terraform.lock.hcl diff --git a/infrastructure/live/main.tf b/infrastructure/live/main.tf index 9f0ba9395d..9da7c287ed 100644 --- a/infrastructure/live/main.tf +++ b/infrastructure/live/main.tf @@ -21,11 +21,8 @@ locals { ManagedBy = "Terraform" Project = var.project_name } - fixtures_bucket_name = coalesce(var.fixtures_bucket_name, "${var.project_name}-${var.environment}-fixtures") - observability_vm_image = trimspace(trimprefix( - one([for line in split("\n", file("${path.root}/../../docker/victoriametrics/Dockerfile")) : line if startswith(line, "FROM ")]), - "FROM " - )) + fixtures_bucket_name = coalesce(var.fixtures_bucket_name, "${var.project_name}-${var.environment}-fixtures") + observability_vm_image = regex("(?m)^FROM (victoriametrics/victoria-metrics:\\S+)", file("${path.root}/../../docker/victoriametrics/Dockerfile"))[0] } module "alb" { @@ -198,9 +195,10 @@ module "observability" { environment = var.environment kms_key_arn = module.kms.key_arn project_name = var.project_name - subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids - vm_image = local.observability_vm_image - vpc_id = module.networking.vpc_id + # TODO(#5429): Use private_subnet_ids unconditionally once NAT is enabled in all environments. + subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids + vm_image = local.observability_vm_image + vpc_id = module.networking.vpc_id } module "parameters" { diff --git a/infrastructure/modules/observability/README.md b/infrastructure/modules/observability/README.md index 11596653e8..9ebb3e1578 100644 --- a/infrastructure/modules/observability/README.md +++ b/infrastructure/modules/observability/README.md @@ -56,7 +56,7 @@ No modules. | [vm\_image](#input\_vm\_image) | The VictoriaMetrics container image (including digest). | `string` | n/a | yes | | [vm\_memory](#input\_vm\_memory) | The memory (in MiB) for the VictoriaMetrics Fargate task. | `number` | `1024` | no | | [vm\_port](#input\_vm\_port) | The port VictoriaMetrics listens on for ingest and queries. | `number` | `8428` | no | -| [vm\_retention\_period](#input\_vm\_retention\_period) | The VictoriaMetrics data retention period (e.g., 12, 5y). | `string` | `"12"` | no | +| [vm\_retention\_period](#input\_vm\_retention\_period) | The VictoriaMetrics data retention period. A value without a suffix is in months, so the default "12" means 12 months (duration suffixes like 1y, 30d, 1w are also supported). | `string` | `"12"` | no | | [vpc\_id](#input\_vpc\_id) | The VPC ID where the VictoriaMetrics security group is created. | `string` | n/a | yes | ## Outputs diff --git a/infrastructure/modules/observability/main.tf b/infrastructure/modules/observability/main.tf index 16cbb10842..e1e3c3a93e 100644 --- a/infrastructure/modules/observability/main.tf +++ b/infrastructure/modules/observability/main.tf @@ -64,13 +64,13 @@ resource "aws_security_group" "vm" { } resource "aws_security_group_rule" "vm_ingest_from_apps" { - for_each = toset(var.app_security_group_ids) + count = length(var.app_security_group_ids) description = "Allow metrics ingest and queries from application tasks" from_port = var.vm_port protocol = "tcp" security_group_id = aws_security_group.vm.id - source_security_group_id = each.value + source_security_group_id = var.app_security_group_ids[count.index] to_port = var.vm_port type = "ingress" } @@ -127,11 +127,11 @@ resource "aws_efs_file_system" "vm" { } resource "aws_efs_mount_target" "vm" { - for_each = toset(var.subnet_ids) + count = length(var.subnet_ids) file_system_id = aws_efs_file_system.vm.id security_groups = [aws_security_group.efs.id] - subnet_id = each.value + subnet_id = var.subnet_ids[count.index] } resource "aws_efs_access_point" "vm" { diff --git a/infrastructure/modules/observability/tests/integration.tftest.hcl b/infrastructure/modules/observability/tests/integration.tftest.hcl index 844e4b7d2a..2602d48641 100644 --- a/infrastructure/modules/observability/tests/integration.tftest.hcl +++ b/infrastructure/modules/observability/tests/integration.tftest.hcl @@ -13,7 +13,7 @@ variables { common_tags = { Environment = "test", Project = "nest" } environment = "test" project_name = "nest" - vm_image = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70" + vm_image = regex("(?m)^FROM (victoriametrics/victoria-metrics:\\S+)", file("../../../docker/victoriametrics/Dockerfile"))[0] } run "setup" { diff --git a/infrastructure/modules/observability/tests/setup/.gitignore b/infrastructure/modules/observability/tests/setup/.gitignore deleted file mode 100644 index 3f0336e2ba..0000000000 --- a/infrastructure/modules/observability/tests/setup/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.terraform.lock.hcl diff --git a/infrastructure/modules/observability/tests/setup/.terraform.lock.hcl b/infrastructure/modules/observability/tests/setup/.terraform.lock.hcl new file mode 100644 index 0000000000..af840c017f --- /dev/null +++ b/infrastructure/modules/observability/tests/setup/.terraform.lock.hcl @@ -0,0 +1,29 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.53.0" + constraints = "~> 6.53.0" + hashes = [ + "h1:UFEhEEFJcR/pAOZcwdR11gN9W3X8VwvSl1IS0vbj2G0=", + "h1:bvlWCSuVJQshwuA/vJONdjEUIzGdsW3uSfLxoP9RnIw=", + "h1:nZ85OLLO0sNw/76mgQQQmnOodiQGaaGVxq1NaV3ol4g=", + "h1:sSfqLt0XIbqfTvOSZr2gkyiv1Ysg6uuiVXFd/btmsoY=", + "zh:0757ce9d5a30e8225521857924f5d6c49e5885fd9e309e56193c9c9920b3f8f0", + "zh:10ddb3a20e0779788e8002bc67d184eb166dec70f9d07220bfc55e15c3e5e206", + "zh:1e943f59a8c3f3b04f09fd5d967fef2518e0ce3ec4959e31f5d7f820660e8524", + "zh:21d55ad3b28f48c6dd34faceeb1fd1fe2ec3b1aaecfc5eb0e0841884f6520d69", + "zh:2e551bc0ff29ea608a99f63d899afbdf2f23c14035f27dc0c0bdca60209d9575", + "zh:581db963564364200426f2a4be866470a975cde6a4bd6fd09362ec2bab119cbb", + "zh:71a7abc88e16ccbe4410fc4966bd35fae9e0161c1385a9b3c19800e3db626b01", + "zh:8767767776c45590d4cc74b4c37f6377e05ce75039a9c2e834de0bbcc29f7a1a", + "zh:99c8e37beb9b1017f67c41e402031559b745fa797848f1a4ef8b40421dee3f05", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ba1477eda2f3ee51846492449338146f80806088d2952c08c2f8af874550a75d", + "zh:c389f68ba4d39fa2bf8a54d4af7f7b2132da2a964c260892c1dd1a89b67e61f8", + "zh:d7381490a637a1fa45769d0ee6b3e0578cce488d078077ec63d9090113784f2a", + "zh:df3b82fb1a675fc6548fac2fff10a8741efa5bcb5a53d01d8f0659179a2f717f", + "zh:e5d78b2ac3dc5477cf21d7cd4c8b9e3d944ff091e90b62c1b2ea9cc7c7eef57c", + "zh:fd224078287c6d82de2ec30ea03d3d550c2c554e372b234f7a6ac2b07c23dba0", + ] +} diff --git a/infrastructure/modules/observability/tests/unit.tftest.hcl b/infrastructure/modules/observability/tests/unit.tftest.hcl index efc5378971..6e15bbcad8 100644 --- a/infrastructure/modules/observability/tests/unit.tftest.hcl +++ b/infrastructure/modules/observability/tests/unit.tftest.hcl @@ -8,7 +8,7 @@ variables { kms_key_arn = "arn:aws:kms:us-east-2:123456789012:key/12345678-1234-1234-1234-123456789012" project_name = "nest" subnet_ids = ["subnet-1", "subnet-2"] - vm_image = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70" + vm_image = regex("(?m)^FROM (victoriametrics/victoria-metrics:\\S+)", file("../../../docker/victoriametrics/Dockerfile"))[0] vm_port = 8428 vpc_id = "vpc-12345" } @@ -53,12 +53,12 @@ run "test_vm_ingest_from_source_security_group_only" { command = plan assert { - condition = aws_security_group_rule.vm_ingest_from_apps["sg-backend"].source_security_group_id == "sg-backend" + condition = aws_security_group_rule.vm_ingest_from_apps[0].source_security_group_id == var.app_security_group_ids[0] error_message = "VM ingest must be restricted to application security groups, not public CIDRs." } assert { - condition = aws_security_group_rule.vm_ingest_from_apps["sg-backend"].from_port == var.vm_port + condition = aws_security_group_rule.vm_ingest_from_apps[0].from_port == var.vm_port error_message = "VM ingest must be allowed on the configured VictoriaMetrics port." } } diff --git a/infrastructure/modules/observability/variables.tf b/infrastructure/modules/observability/variables.tf index 95f6bfc7db..9d4e405f19 100644 --- a/infrastructure/modules/observability/variables.tf +++ b/infrastructure/modules/observability/variables.tf @@ -101,9 +101,9 @@ variable "vm_port" { } variable "vm_retention_period" { - description = "The VictoriaMetrics data retention period (e.g., 12, 5y)." + description = "The VictoriaMetrics data retention period. A value without a suffix is in months, so the default \"12\" means 12 months (duration suffixes like 1y, 30d, 1w are also supported)." type = string - default = "12" + default = "12" # 12 months } variable "vpc_id" {