Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .trivyignore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ 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
paths:
Expand Down
10 changes: 6 additions & 4 deletions docker-compose/local/compose.o11y.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -50,6 +52,9 @@ services:

o11y-grafana:
container_name: nest-o11y-grafana
build:
context: ../../

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The o11y-grafana build context is the repository root, but the repo-root .dockerignore only excludes e2e/ caches and the e2e node_modules/playwright output (.dockerignore in-tree). The Dockerfile only needs docker-compose/local/grafana/..., so every docker compose build from make run-o11y ships the entire repo — including frontend/node_modules, backend/.venv, and .git — to the Docker daemon. In a normal dev checkout this makes the grafana build noticeably slow on every stack start. Consider scoping the context down to docker-compose/local (and adjusting the COPY paths in the Dockerfile to grafana/...) or adding a dedicated .dockerignore for this build so the context only contains the provisioning/dashboards subtree.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker-compose/local/compose.o11y.yaml, line 56:

<comment>The o11y-grafana build context is the repository root, but the repo-root `.dockerignore` only excludes `e2e/` caches and the e2e node_modules/playwright output (`.dockerignore` in-tree). The Dockerfile only needs `docker-compose/local/grafana/...`, so every `docker compose build` from `make run-o11y` ships the entire repo — including `frontend/node_modules`, `backend/.venv`, and `.git` — to the Docker daemon. In a normal dev checkout this makes the grafana build noticeably slow on every stack start. Consider scoping the context down to `docker-compose/local` (and adjusting the COPY paths in the Dockerfile to `grafana/...`) or adding a dedicated `.dockerignore` for this build so the context only contains the provisioning/dashboards subtree.</comment>

<file context>
@@ -50,6 +52,9 @@ services:
   o11y-grafana:
     container_name: nest-o11y-grafana
+    build:
+      context: ../../
+      dockerfile: docker/grafana/Dockerfile
     depends_on:
</file context>

dockerfile: docker/grafana/Dockerfile
depends_on:
o11y-metrics:
condition: service_healthy
Expand All @@ -60,7 +65,6 @@ services:
- GF_SECURITY_ADMIN_USER=admin
- GF_USERS_DEFAULT_THEME=light
- O11Y_METRICS_URL=http://o11y-metrics:8428
image: grafana/grafana-oss:13.0.2@sha256:5dad0df181cb644a14e13617b913b261a54f7d4fd4510721dba420929f35bea2
healthcheck:
interval: 5s
retries: 5
Expand All @@ -71,8 +75,6 @@ services:
ports:
- 127.0.0.1:3001:3000
volumes:
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- o11y-grafana-data:/var/lib/grafana

volumes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ providers:
allowUiUpdates: false
updateIntervalSeconds: 10
options:
path: /var/lib/grafana/dashboards
path: /etc/grafana/dashboards
foldersFromFilesStructure: false
6 changes: 6 additions & 0 deletions docker/grafana/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM grafana/grafana-oss:13.0.2@sha256:5dad0df181cb644a14e13617b913b261a54f7d4fd4510721dba420929f35bea2

COPY docker-compose/local/grafana/provisioning /etc/grafana/provisioning
COPY docker-compose/local/grafana/dashboards /etc/grafana/dashboards

USER grafana

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The USER grafana line is a redundant no-op: the grafana-oss base image already ends with USER 472 (the numeric UID of the grafana user), so the process already drops to the non-root grafana user before entrypoint. The added directive adds no behavior. Removing it (or replacing the COPYs by chowning to grafana if write access is ever needed) keeps the image definition aligned with the base image's user model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker/grafana/Dockerfile, line 6:

<comment>The `USER grafana` line is a redundant no-op: the grafana-oss base image already ends with `USER 472` (the numeric UID of the `grafana` user), so the process already drops to the non-root grafana user before entrypoint. The added directive adds no behavior. Removing it (or replacing the COPYs by chowning to `grafana` if write access is ever needed) keeps the image definition aligned with the base image's user model.</comment>

<file context>
@@ -0,0 +1,6 @@
+COPY docker-compose/local/grafana/provisioning /etc/grafana/provisioning
+COPY docker-compose/local/grafana/dashboards /etc/grafana/dashboards
+
+USER grafana
</file context>

1 change: 1 addition & 0 deletions docker/victoriametrics/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70

Check warning on line 1 in docker/victoriametrics/Dockerfile

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This image might run with "root" as the default user. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=OWASP_Nest&issues=AZ_vrQ88BTR_KTfKAJEr&open=AZ_vrQ88BTR_KTfKAJEr&pullRequest=5405

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 3 '^(FROM|USER)' docker/victoriametrics/Dockerfile || true
rg -n -C 4 'o11y-metrics|user:' docker-compose/local/compose.o11y.yaml || true
rg -n -C 4 '65532|user\s*=|efs|access_point' infrastructure/modules/observability --glob '*.tf' || true
rg -n -C 3 'DS-0002|victoriametrics' .trivyignore.yaml || true

Repository: OWASP/Nest

Length of output: 10614


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- Dockerfile and local service ---'
cat -n docker/victoriametrics/Dockerfile
sed -n '30,55p' docker-compose/local/compose.o11y.yaml

printf '%s\n' '--- VictoriaMetrics image config ---'
repo='victoriametrics/victoria-metrics'
digest='sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70'
token="$(curl -fsSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" | jq -r .token)"
manifest="$(curl -fsSL \
  -H "Authorization: Bearer ${token}" \
  -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json' \
  "https://registry-1.docker.io/v2/${repo}/manifests/${digest}")"
printf '%s\n' "$manifest" | jq '{mediaType, config, manifests: (.manifests // []) | map({platform, digest})}'

config_digest="$(printf '%s\n' "$manifest" | jq -r '.config.digest // empty')"
if [ -n "$config_digest" ]; then
  curl -fsSL -H "Authorization: Bearer ${token}" \
    "https://registry-1.docker.io/v2/${repo}/blobs/${config_digest}" |
    jq '{config: {User, WorkingDir, Entrypoint, Cmd}, rootfs}'
else
  printf '%s\n' 'The pinned digest is a multi-platform index; inspect each platform manifest config.'
  printf '%s\n' "$manifest" | jq -r '.manifests[] | [.platform.os, .platform.architecture, .digest] | `@tsv`' |
  while IFS=$'\t' read -r os arch platform_digest; do
    platform_manifest="$(curl -fsSL \
      -H "Authorization: Bearer ${token}" \
      -H 'Accept: application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json' \
      "https://registry-1.docker.io/v2/${repo}/manifests/${platform_digest}")"
    platform_config="$(printf '%s\n' "$platform_manifest" | jq -r '.config.digest')"
    printf '%s/%s %s\n' "$os" "$arch" "$platform_digest"
    curl -fsSL -H "Authorization: Bearer ${token}" \
      "https://registry-1.docker.io/v2/${repo}/blobs/${platform_config}" |
      jq --arg platform "$os/$arch" '{platform: $platform, config: {User, WorkingDir, Entrypoint, Cmd}, rootfs}'
  done
fi

Repository: OWASP/Nest

Length of output: 7516


Run the local VictoriaMetrics service as non-root.

The pinned image has no default USER, so o11y-metrics runs as root because Compose does not override it. Set USER 65532 in the Dockerfile or set user: "65532" in Compose. ECS already uses 65532, and the EFS access point grants that UID/GID ownership of /victoriametrics.

🧰 Tools
🪛 Checkov (3.3.9)

[low] 1-1: Ensure that HEALTHCHECK instructions have been added to container images

(CKV_DOCKER_2)


[low] 1-1: Ensure that a user for the container has been created

(CKV_DOCKER_3)

🪛 GitHub Check: SonarCloud Code Analysis

[warning] 1-1: This image might run with "root" as the default user. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=OWASP_Nest&issues=AZ_vrQ88BTR_KTfKAJEr&open=AZ_vrQ88BTR_KTfKAJEr&pullRequest=5405

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker/victoriametrics/Dockerfile` at line 1, Configure the local
VictoriaMetrics container to run as UID 65532 by adding a USER directive to the
Dockerfile or the equivalent user setting in Compose. Preserve the existing
image pin and ensure the setting applies to the o11y-metrics service, matching
ECS and EFS ownership requirements.

Source: Linters/SAST tools

2 changes: 2 additions & 0 deletions infrastructure/live/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ No providers.
| <a name="module_frontend_build_cache"></a> [frontend\_build\_cache](#module\_frontend\_build\_cache) | ../modules/ecr-cache | n/a |
| <a name="module_kms"></a> [kms](#module\_kms) | ../modules/kms | n/a |
| <a name="module_networking"></a> [networking](#module\_networking) | ../modules/networking | n/a |
| <a name="module_observability"></a> [observability](#module\_observability) | ../modules/observability | n/a |
| <a name="module_parameters"></a> [parameters](#module\_parameters) | ../modules/parameters | n/a |
| <a name="module_security"></a> [security](#module\_security) | ../modules/security | n/a |
| <a name="module_storage"></a> [storage](#module\_storage) | ../modules/storage | n/a |
Expand Down Expand Up @@ -101,6 +102,7 @@ No resources.
| <a name="input_enable_additional_parameters"></a> [enable\_additional\_parameters](#input\_enable\_additional\_parameters) | Whether to enable additional parameters (e.g. for production). | `bool` | `false` | no |
| <a name="input_enable_cron_tasks"></a> [enable\_cron\_tasks](#input\_enable\_cron\_tasks) | Whether to enable scheduled cron tasks. | `bool` | n/a | yes |
| <a name="input_enable_nat_gateway"></a> [enable\_nat\_gateway](#input\_enable\_nat\_gateway) | Whether to enable a NAT Gateway. | `bool` | `true` | no |
| <a name="input_enable_observability"></a> [enable\_observability](#input\_enable\_observability) | Whether to create the observability stack. | `bool` | `false` | no |
| <a name="input_enable_rds_proxy"></a> [enable\_rds\_proxy](#input\_enable\_rds\_proxy) | Whether to create an RDS proxy. | `bool` | `false` | no |
| <a name="input_enable_vpc_cloudwatch_logs_endpoint"></a> [enable\_vpc\_cloudwatch\_logs\_endpoint](#input\_enable\_vpc\_cloudwatch\_logs\_endpoint) | Whether to create CloudWatch Logs VPC endpoint. | `bool` | `false` | no |
| <a name="input_enable_vpc_ecr_api_endpoint"></a> [enable\_vpc\_ecr\_api\_endpoint](#input\_enable\_vpc\_ecr\_api\_endpoint) | Whether to create ECR API VPC endpoint. | `bool` | `false` | no |
Expand Down
24 changes: 24 additions & 0 deletions infrastructure/live/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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"

Expand Down
6 changes: 6 additions & 0 deletions infrastructure/live/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ variable "enable_nat_gateway" {
default = true
}

variable "enable_observability" {
description = "Whether to create the observability stack."
type = bool
default = false
}

variable "enable_rds_proxy" {
description = "Whether to create an RDS proxy."
type = bool
Expand Down
28 changes: 28 additions & 0 deletions infrastructure/modules/observability/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 72 additions & 0 deletions infrastructure/modules/observability/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
| ---- | ------- |
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.15.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 6.53.0 |

## Providers

| Name | Version |
| ---- | ------- |
| <a name="provider_aws"></a> [aws](#provider\_aws) | 6.53.0 |

## 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_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 |
| [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_service_discovery_private_dns_namespace.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_private_dns_namespace) | resource |
| [aws_service_discovery_service.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_service) | resource |

## Inputs

| Name | Description | Type | Default | Required |
| ---- | ----------- | ---- | ------- | :------: |
| <a name="input_app_security_group_ids"></a> [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 |
| <a name="input_assign_public_ip"></a> [assign\_public\_ip](#input\_assign\_public\_ip) | Whether to assign a public IP to the VictoriaMetrics task. | `bool` | `false` | no |
| <a name="input_aws_region"></a> [aws\_region](#input\_aws\_region) | The AWS region where the module is deployed. | `string` | n/a | yes |
| <a name="input_common_tags"></a> [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
| <a name="input_kms_key_arn"></a> [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key used to encrypt the EFS file system. | `string` | n/a | yes |
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The number of days to retain VictoriaMetrics container logs. | `number` | `90` | no |
| <a name="input_project_name"></a> [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | The private subnet IDs for the EFS mount targets and the VictoriaMetrics task. | `list(string)` | n/a | yes |
| <a name="input_vm_cpu"></a> [vm\_cpu](#input\_vm\_cpu) | The CPU units for the VictoriaMetrics Fargate task. | `number` | `512` | no |
| <a name="input_vm_desired_count"></a> [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 |
| <a name="input_vm_image"></a> [vm\_image](#input\_vm\_image) | The VictoriaMetrics container image (including digest). | `string` | n/a | yes |
| <a name="input_vm_memory"></a> [vm\_memory](#input\_vm\_memory) | The memory (in MiB) for the VictoriaMetrics Fargate task. | `number` | `1024` | no |
| <a name="input_vm_port"></a> [vm\_port](#input\_vm\_port) | The port VictoriaMetrics listens on for ingest and queries. | `number` | `8428` | no |
| <a name="input_vm_retention_period"></a> [vm\_retention\_period](#input\_vm\_retention\_period) | The VictoriaMetrics data retention period (e.g., 12, 5y). | `string` | `"12"` | no |
| <a name="input_vpc_id"></a> [vpc\_id](#input\_vpc\_id) | The VPC ID where the VictoriaMetrics security group is created. | `string` | n/a | yes |

## Outputs

| Name | Description |
| ---- | ----------- |
| <a name="output_efs_file_system_id"></a> [efs\_file\_system\_id](#output\_efs\_file\_system\_id) | The ID of the EFS file system backing VictoriaMetrics storage. |
| <a name="output_vm_cluster_name"></a> [vm\_cluster\_name](#output\_vm\_cluster\_name) | The name of the ECS cluster running VictoriaMetrics. |
| <a name="output_vm_endpoint"></a> [vm\_endpoint](#output\_vm\_endpoint) | The private host:port endpoint for reaching VictoriaMetrics. |
| <a name="output_vm_security_group_id"></a> [vm\_security\_group\_id](#output\_vm\_security\_group\_id) | The ID of the VictoriaMetrics security group. |
<!-- END_TF_DOCS -->
Loading
Loading