diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/README.md b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/README.md similarity index 100% rename from terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/README.md rename to terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/README.md diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/bad-payload.json similarity index 100% rename from terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/bad-payload.json rename to terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/bad-payload.json diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/bad.tf new file mode 100644 index 00000000..19fe98e3 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/bad.tf @@ -0,0 +1,40 @@ +module "ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + name = "example" + privileged = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + tags = { + Environment = "dev" + Terraform = "true" + } +} + +# Setting up the configuration for using Docker and AWS providers + +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>2.20.0" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +# Configuring docker and AWS as providers +provider "docker" {} + +provider "aws" { + region = "us-west-1" +} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/good-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/good-payload.json similarity index 100% rename from terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/good-payload.json rename to terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/good-payload.json diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/good.tf similarity index 100% rename from terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/good.tf rename to terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/test/good.tf diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/validate-ecs-containers-nonprivileged.yaml b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/validate-ecs-containers-nonprivileged.yaml similarity index 100% rename from terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/validate-ecs-containers-nonprivileged.yaml rename to terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-module/validate-ecs-containers-nonprivileged.yaml diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/README.md b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/README.md new file mode 100644 index 00000000..db709aa9 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/README.md @@ -0,0 +1,65 @@ +# ECS containers should run as non-privileged + +It is important to ensure that ECS containers run without elevated privileges to enhance security. The policy checks the privileged parameter in the container definition and raises a concern if it is set to `true`. When a container is granted elevated permissions (similar to the root user) by having privileged set to true, it poses potential security risks on the host container instance. + +It is advised against running containers with elevated privileges, as this may compromise the overall security of your ECS task definitions. It is recommended to avoid using privileged and, instead, specify precise privileges using specific parameters. This approach allows for a more controlled and secure execution environment, minimizing the risks associated with running containers with unnecessary elevated privileges. + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring if the privileged parameter in the container definition is set to `false` (is default) in the Terraform plan payload, follow the steps outlined below: + +For testing this policy you will need to: +- Make sure you have `kyverno-json` installed on the machine +- Properly authenticate with AWS + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ``` + kyverno-json scan --payload payload.json --policy policy.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ``` + kyverno-json scan --policy validate-ecs-containers-nonprivileged-in-resource.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-containers-nonprivileged-in-resource / validate-ecs-containers-nonprivileged-in-resource / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ``` + kyverno-json scan --policy validate-ecs-containers-nonprivileged-in-resource.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-containers-nonprivileged-in-resource / validate-ecs-containers-nonprivileged-in-resource / FAILED: Containers `privileged` must be set to `false`.: any[0].check.~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'].values)[0].~.(json_parse(container_definitions))[0].privileged: Invalid value: true: Expected value: false; Privileged feild is not present. This is a valid Payload.: any[1].check.~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'].values)[0].~.(json_parse(container_definitions))[0].(!privileged): Invalid value: false: Expected value: true + Done + ``` + +--- diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/bad-payload.json new file mode 100644 index 00000000..c42c3c01 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/bad-payload.json @@ -0,0 +1,146 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"privileged\":true,\"user\":\"xyz\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"privileged\":true,\"user\":\"xyz\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"privileged\": true,\n \"user\" : \"xyz\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-29T20:21:42Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/bad.tf new file mode 100644 index 00000000..470e08d0 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/bad.tf @@ -0,0 +1,44 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + container_definitions = < 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"privileged\": false,\n \"user\" : \"xyz\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-29T20:19:58Z", + "errored": false +} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/good.tf new file mode 100644 index 00000000..8a596bda --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/terraform-resource/test/good.tf @@ -0,0 +1,44 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + container_definitions = <- + When privileged is set to true, the container is given elevated permissions on the host container instance (similar to the root user). + This policy checks if the privileged parameter in the container definition is set to false. +spec: + rules: + - name: validate-ecs-containers-nonprivileged-in-resource + match: + any: + - (planned_values.root_module.resources[?type=='aws_ecs_task_definition'] | length(@) > `0` ): true + assert: + any: + - check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition']): + values: + ~.(json_parse(container_definitions)): + (!!privileged): false + message: The `privileged` field, if present, should be set to `false` diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/README.md b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/README.md new file mode 100644 index 00000000..9a5f4eda --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/README.md @@ -0,0 +1,80 @@ +# Validate ECS Task definition log configuration + +The LogConfiguration property specifies log configuration options to send to a custom log driver for the container. This policy checks if the ECS TaskDefiniteion does not have the logConfiguration resource defined or the value for logConfiguration is null in at least one container definition. + +## Policy Details: + +- **Policy Name:** validate-ecs-task-definition-log-configuration +- **Check Description:** logConfiguration is not set on active ECS Task Definitions +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +**Key** : `logConfiguration` and `service_connect_configuration` + +- **Condition:** `service_connect_configuration` and `logConfiguration` is present + - **Result:** PASS + +- **Condition:** `service_connect_configuration` is present, but `logConfiguration` is not present + - **Result:** FAIL + +- **Condition:** `service_connect_configuration` and `logConfiguration` is not present + - **Result:** FAIL + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet policy standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy validate-ecs-task-definition-pid-mode-check.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-log-configuration.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-log-configuration / validate-ecs-task-definition-log-configuration / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-log-configuration.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-log-configuration / validate-ecs-task-definition-log-configuration / FAILED: all[1].check.~.(planned_values.root_module.resources[?type=='aws_ecs_service'])[0].~.(values.service_connect_configuration || `[]`)[0].(!log_configuration): Invalid value: true: Expected value: false + Done + ``` + +--- diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/bad-payload.json new file mode 100644 index 00000000..e460b8cc --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/bad-payload.json @@ -0,0 +1,208 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": null, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_new_deployment": null, + "health_check_grace_period_seconds": null, + "load_balancer": [], + "name": "foo-service", + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "propagate_tags": null, + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [ + { + "enabled": true, + "log_configuration": [], + "namespace": null, + "service": [] + } + ], + "service_registries": [], + "tags": null, + "task_definition": null, + "timeouts": null, + "wait_for_steady_state": false + }, + "sensitive_values": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "load_balancer": [], + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "service_connect_configuration": [ + { + "log_configuration": [], + "service": [] + } + ], + "service_registries": [], + "tags_all": {}, + "triggers": {} + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": null, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_new_deployment": null, + "health_check_grace_period_seconds": null, + "load_balancer": [], + "name": "foo-service", + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "propagate_tags": null, + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [ + { + "enabled": true, + "log_configuration": [], + "namespace": null, + "service": [] + } + ], + "service_registries": [], + "tags": null, + "task_definition": null, + "timeouts": null, + "wait_for_steady_state": false + }, + "after_unknown": { + "alarms": [], + "capacity_provider_strategy": [], + "cluster": true, + "deployment_circuit_breaker": [], + "deployment_controller": [], + "iam_role": true, + "id": true, + "launch_type": true, + "load_balancer": [], + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "platform_version": true, + "service_connect_configuration": [ + { + "log_configuration": [], + "service": [] + } + ], + "service_registries": [], + "tags_all": true, + "triggers": true + }, + "before_sensitive": false, + "after_sensitive": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "load_balancer": [], + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "service_connect_configuration": [ + { + "log_configuration": [], + "service": [] + } + ], + "service_registries": [], + "tags_all": {}, + "triggers": {} + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "foo-service" + }, + "service_connect_configuration": [ + { + "enabled": { + "constant_value": true + } + } + ] + }, + "schema_version": 0 + } + ] + } + }, + "timestamp": "2024-01-30T15:32:28Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/bad.tf new file mode 100644 index 00000000..e5e531dd --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/bad.tf @@ -0,0 +1,31 @@ +resource "aws_ecs_service" "service" { + name = "foo-service" + service_connect_configuration { + enabled = true + } + lifecycle { + ignore_changes = [task_definition] + } +} + +# Setting up the configuration for using Docker and AWS providers + +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>2.20.0" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +# Configuring docker and AWS as providers +provider "docker" {} + +provider "aws" { + region = "us-west-1" +} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/good-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/good-payload.json new file mode 100644 index 00000000..a8901f96 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/good-payload.json @@ -0,0 +1,240 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": null, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_new_deployment": null, + "health_check_grace_period_seconds": null, + "load_balancer": [], + "name": "foo-service", + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "propagate_tags": null, + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [ + { + "enabled": true, + "log_configuration": [ + { + "log_driver": "awslogs", + "secret_option": [] + } + ], + "namespace": null, + "service": [] + } + ], + "service_registries": [], + "tags": null, + "task_definition": null, + "timeouts": null, + "wait_for_steady_state": false + }, + "sensitive_values": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "load_balancer": [], + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "service_connect_configuration": [ + { + "log_configuration": [ + { + "options": {}, + "secret_option": [] + } + ], + "service": [] + } + ], + "service_registries": [], + "tags_all": {}, + "triggers": {} + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": null, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_new_deployment": null, + "health_check_grace_period_seconds": null, + "load_balancer": [], + "name": "foo-service", + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "propagate_tags": null, + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [ + { + "enabled": true, + "log_configuration": [ + { + "log_driver": "awslogs", + "secret_option": [] + } + ], + "namespace": null, + "service": [] + } + ], + "service_registries": [], + "tags": null, + "task_definition": null, + "timeouts": null, + "wait_for_steady_state": false + }, + "after_unknown": { + "alarms": [], + "capacity_provider_strategy": [], + "cluster": true, + "deployment_circuit_breaker": [], + "deployment_controller": [], + "iam_role": true, + "id": true, + "launch_type": true, + "load_balancer": [], + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "platform_version": true, + "service_connect_configuration": [ + { + "log_configuration": [ + { + "options": true, + "secret_option": [] + } + ], + "service": [] + } + ], + "service_registries": [], + "tags_all": true, + "triggers": true + }, + "before_sensitive": false, + "after_sensitive": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "load_balancer": [], + "network_configuration": [], + "ordered_placement_strategy": [], + "placement_constraints": [], + "service_connect_configuration": [ + { + "log_configuration": [ + { + "options": {}, + "secret_option": [] + } + ], + "service": [] + } + ], + "service_registries": [], + "tags_all": {}, + "triggers": {} + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "foo-service" + }, + "service_connect_configuration": [ + { + "enabled": { + "constant_value": true + }, + "log_configuration": [ + { + "log_driver": { + "constant_value": "awslogs" + } + } + ] + } + ] + }, + "schema_version": 0 + } + ] + } + }, + "timestamp": "2024-01-30T15:22:33Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/good.tf new file mode 100644 index 00000000..165d5a20 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/test/good.tf @@ -0,0 +1,34 @@ +resource "aws_ecs_service" "service" { + name = "foo-service" + service_connect_configuration { + enabled = true + log_configuration { + log_driver = "awslogs" + } + } + lifecycle { + ignore_changes = [task_definition] + } +} + +# Setting up the configuration for using Docker and AWS providers + +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>2.20.0" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +# Configuring docker and AWS as providers +provider "docker" {} + +provider "aws" { + region = "us-west-1" +} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/validate-ecs-task-definition-log-configuration.yaml b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/validate-ecs-task-definition-log-configuration.yaml new file mode 100644 index 00000000..52553131 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-ecs-service-resource/validate-ecs-task-definition-log-configuration.yaml @@ -0,0 +1,32 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: validate-ecs-task-definition-log-configuration + labels: + ecs.aws.tags.kyverno.io: ecs-service + annotations: + policies.kyverno.io/title: Validate ECS Task definition log configuration + policies.kyverno.io/category: ECS Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/description: >- + This policy checks if the ECS TaskDefiniteion does not have the + logConfiguration resource defined or the value for logConfiguration + is null in at least one container definition. +spec: + rules: + - name: validate-ecs-task-definition-log-configuration + match: + any: + - (planned_values.root_module.resources[?type=='aws_ecs_service'] | length(@) > `0`): true + assert: + all: + - message: logConfiguration is not defined for active ECS Task Definitions + check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_service']): + values: + (!service_connect_configuration): false + - message: logConfiguration is not set on active ECS Task Definitions + check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_service']): + ~.(values.service_connect_configuration || `[]`): + (!log_configuration): false diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/README.md b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/README.md new file mode 100644 index 00000000..79af86b5 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/README.md @@ -0,0 +1,77 @@ +# Validate ECS Task definition log configuration + +The LogConfiguration property specifies log configuration options to send to a custom log driver for the container. This policy checks if the ECS TaskDefiniteion does not have the logConfiguration resource defined or the value for logConfiguration is null in at least one container definition. + +## Policy Details: + +- **Policy Name:** validate-ecs-task-definition-log-configuration +- **Check Description:** logConfiguration is not set on active ECS Task Definitions +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +**Key** : `logConfiguration` + +- **Condition:** `logConfiguration` is present + - **Result:** PASS + +- **Condition:** `logConfiguration` is not present + - **Result:** FAIL + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet policy standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy policy.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-log-configuration.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-log-configuration / validate-ecs-task-definition-log-configuration / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-log-configuration.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-log-configuration / validate-ecs-task-definition-log-configuration / FAILED: logConfiguration is not set for active ECS Task Definitions: all[0].check.~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'])[0].values.~.(json_parse(container_definitions))[0].(!logConfiguration): Invalid value: true: Expected value: false + Done + ``` + +--- diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/bad-payload.json new file mode 100644 index 00000000..2e1d1d8f --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/bad-payload.json @@ -0,0 +1,150 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.2-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"essential\":true,\"image\":\"nginx:1.23.1\",\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":5000,\"hostPort\":5000}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "network_mode": "host", + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"essential\":true,\"image\":\"nginx:1.23.1\",\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":5000,\"hostPort\":5000}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "network_mode": "host", + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\": \"foo-task\",\n \"image\": \"nginx:1.23.1\",\n \"essential\": true,\n \"portMappings\": [\n {\n \"containerPort\": 5000,\n \"hostPort\": 5000\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "network_mode": { + "constant_value": "host" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-02-04T11:44:26Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/bad.tf new file mode 100644 index 00000000..5b8779f4 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/bad.tf @@ -0,0 +1,41 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + network_mode = "host" + container_definitions = < 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\": \"foo-task\",\n \"image\": \"nginx:1.23.1\",\n \"essential\": true,\n \"portMappings\": [\n {\n \"containerPort\": 5000,\n \"hostPort\": 5000\n }\n ],\n \"logConfiguration\": {\n \"logDriver\": \"awslogs\"\n }\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "network_mode": { + "constant_value": "host" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-02-04T11:37:53Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/good.tf new file mode 100644 index 00000000..fb8b5231 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-log-configuration/terraform-task-definition-resource/test/good.tf @@ -0,0 +1,44 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + network_mode = "host" + container_definitions = <- + Checks if logConfiguration is set on active ECS Task Definitions. +spec: + rules: + - name: validate-ecs-task-definition-log-configuration + match: + any: + - (planned_values.root_module.resources[?type=='aws_ecs_task_definition'] | length(@) > `0`): true + assert: + all: + - message: logConfiguration is not set for active ECS Task Definitions + check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition']): + values: + ~.(json_parse(container_definitions)): + (!logConfiguration): false diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/README.md b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/README.md new file mode 100644 index 00000000..aa09b3b3 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/README.md @@ -0,0 +1,83 @@ +# Validate ECS Task Definition Memory Hard Limit + +This policy checks if Amazon Elastic Container Service (ECS) task definitions have a set memory limit for its container definitions. + +## Policy Details: + +- **Policy Name:** validate-ecs-task-definition-memory-hard-limit +- **Check Description:** This policy ensures that ECS task definitions has a memory limit set +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +**Key** : memory +**Type** : int +**Required** : No + +- **Condition:** `memory` is present + - **Result:** PASS + - **Reason:** Memory limit is set + +- **Condition:** `memory` is absent + - **Result:** FAIL + - **Reason:** Memory limit is not set + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet security standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy policy.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-memory-hard-limit.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-memory-hard-limit / validate-ecs-task-definition-memory-hard-limit / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-memory-hard-limit.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-memory-hard-limit / validate-ecs-task-definition-memory-hard-limit / FAILED: Please set memory limit for container definitions.: all[0].check.(configuration.root_module.module_calls.ecs_container_definition.expressions).(!memory): Invalid value: true: Expected value: false + Done + ``` + +--- + +This way you can ensure that your ECS task definitions adhere to security standards regarding memory limits. diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/bad-payload.json new file mode 100644 index 00000000..5fbb2e90 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/bad-payload.json @@ -0,0 +1,466 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "child_modules": [ + { + "resources": [ + { + "address": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "kms_key_id": null, + "name": "/aws/ecs//example", + "retention_in_days": 30, + "skip_destroy": false, + "tags": { + "Environment": "dev", + "Terraform": "true" + }, + "tags_all": { + "Environment": "dev", + "Terraform": "true" + } + }, + "sensitive_values": { + "tags": {}, + "tags_all": {} + } + } + ], + "address": "module.ecs_container_definition" + } + ] + } + }, + "resource_changes": [ + { + "address": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "module_address": "module.ecs_container_definition", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "kms_key_id": null, + "name": "/aws/ecs//example", + "retention_in_days": 30, + "skip_destroy": false, + "tags": { + "Environment": "dev", + "Terraform": "true" + }, + "tags_all": { + "Environment": "dev", + "Terraform": "true" + } + }, + "after_unknown": { + "arn": true, + "id": true, + "name_prefix": true, + "tags": {}, + "tags_all": {} + }, + "before_sensitive": false, + "after_sensitive": { + "tags": {}, + "tags_all": {} + } + } + } + ], + "prior_state": { + "format_version": "1.0", + "terraform_version": "1.7.1", + "values": { + "root_module": { + "child_modules": [ + { + "resources": [ + { + "address": "module.ecs_container_definition.data.aws_region.current", + "mode": "data", + "type": "aws_region", + "name": "current", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "US West (N. California)", + "endpoint": "ec2.us-west-1.amazonaws.com", + "id": "us-west-1", + "name": "us-west-1" + }, + "sensitive_values": {} + } + ], + "address": "module.ecs_container_definition" + } + ] + } + } + }, + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "module_calls": { + "ecs_container_definition": { + "source": "terraform-aws-modules/ecs/aws//modules/container-definition", + "expressions": { + "image": { + "constant_value": "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + }, + "name": { + "constant_value": "example" + }, + "port_mappings": { + "constant_value": [ + { + "containerPort": 80, + "name": "ecs-sample", + "protocol": "tcp" + } + ] + }, + "tags": { + "constant_value": { + "Environment": "dev", + "Terraform": "true" + } + } + }, + "module": { + "outputs": { + "cloudwatch_log_group_arn": { + "expression": { + "references": [ + "aws_cloudwatch_log_group.this[0].arn", + "aws_cloudwatch_log_group.this[0]", + "aws_cloudwatch_log_group.this" + ] + }, + "description": "Arn of cloudwatch log group created" + }, + "cloudwatch_log_group_name": { + "expression": { + "references": [ + "aws_cloudwatch_log_group.this[0].name", + "aws_cloudwatch_log_group.this[0]", + "aws_cloudwatch_log_group.this" + ] + }, + "description": "Name of cloudwatch log group created" + }, + "container_definition": { + "expression": { + "references": [ + "local.container_definition" + ] + }, + "description": "Container definition" + } + }, + "resources": [ + { + "address": "aws_cloudwatch_log_group.this", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "provider_config_key": "aws", + "expressions": { + "kms_key_id": { + "references": [ + "var.cloudwatch_log_group_kms_key_id" + ] + }, + "name": { + "references": [ + "var.cloudwatch_log_group_use_name_prefix", + "local.log_group_name" + ] + }, + "name_prefix": { + "references": [ + "var.cloudwatch_log_group_use_name_prefix", + "local.log_group_name" + ] + }, + "retention_in_days": { + "references": [ + "var.cloudwatch_log_group_retention_in_days" + ] + }, + "tags": { + "references": [ + "var.tags" + ] + } + }, + "schema_version": 0, + "count_expression": { + "references": [ + "var.create_cloudwatch_log_group", + "var.enable_cloudwatch_logging" + ] + } + }, + { + "address": "data.aws_region.current", + "mode": "data", + "type": "aws_region", + "name": "current", + "provider_config_key": "aws", + "schema_version": 0 + } + ], + "variables": { + "cloudwatch_log_group_kms_key_id": { + "default": null, + "description": "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + }, + "cloudwatch_log_group_retention_in_days": { + "default": 30, + "description": "Number of days to retain log events. Default is 30 days" + }, + "cloudwatch_log_group_use_name_prefix": { + "default": false, + "description": "Determines whether the log group name should be used as a prefix" + }, + "command": { + "default": [], + "description": "The command that's passed to the container" + }, + "cpu": { + "default": null, + "description": "The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value" + }, + "create_cloudwatch_log_group": { + "default": true, + "description": "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled" + }, + "dependencies": { + "default": [], + "description": "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" + }, + "disable_networking": { + "default": null, + "description": "When this parameter is true, networking is disabled within the container" + }, + "dns_search_domains": { + "default": [], + "description": "Container DNS search domains. A list of DNS search domains that are presented to the container" + }, + "dns_servers": { + "default": [], + "description": "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" + }, + "docker_labels": { + "default": {}, + "description": "A key/value map of labels to add to the container" + }, + "docker_security_options": { + "default": [], + "description": "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" + }, + "enable_cloudwatch_logging": { + "default": true, + "description": "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers" + }, + "enable_execute_command": { + "default": false, + "description": "Specifies whether to enable Amazon ECS Exec for the tasks within the service" + }, + "entrypoint": { + "default": [], + "description": "The entry point that is passed to the container" + }, + "environment": { + "default": [], + "description": "The environment variables to pass to the container" + }, + "environment_files": { + "default": [], + "description": "A list of files containing the environment variables to pass to a container" + }, + "essential": { + "default": null, + "description": "If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped" + }, + "extra_hosts": { + "default": [], + "description": "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" + }, + "firelens_configuration": { + "default": {}, + "description": "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" + }, + "health_check": { + "default": {}, + "description": "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" + }, + "hostname": { + "default": null, + "description": "The hostname to use for your container" + }, + "image": { + "default": null, + "description": "The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest`" + }, + "interactive": { + "default": false, + "description": "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated" + }, + "links": { + "default": [], + "description": "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" + }, + "linux_parameters": { + "default": {}, + "description": "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + }, + "log_configuration": { + "default": {}, + "description": "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + }, + "memory": { + "default": null, + "description": "The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified" + }, + "memory_reservation": { + "default": null, + "description": "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" + }, + "mount_points": { + "default": [], + "description": "The mount points for data volumes in your container" + }, + "name": { + "default": null, + "description": "The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed" + }, + "operating_system_family": { + "default": "LINUX", + "description": "The OS family for task" + }, + "port_mappings": { + "default": [], + "description": "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" + }, + "privileged": { + "default": false, + "description": "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)" + }, + "pseudo_terminal": { + "default": false, + "description": "When this parameter is true, a `TTY` is allocated" + }, + "readonly_root_filesystem": { + "default": true, + "description": "When this parameter is true, the container is given read-only access to its root file system" + }, + "repository_credentials": { + "default": {}, + "description": "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" + }, + "resource_requirements": { + "default": [], + "description": "The type and amount of a resource to assign to a container. The only supported resource is a GPU" + }, + "secrets": { + "default": [], + "description": "The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide" + }, + "service": { + "default": "", + "description": "The name of the service that the container definition is associated with" + }, + "start_timeout": { + "default": 30, + "description": "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" + }, + "stop_timeout": { + "default": 120, + "description": "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" + }, + "system_controls": { + "default": [], + "description": "A list of namespaced kernel parameters to set in the container" + }, + "tags": { + "default": {}, + "description": "A map of tags to add to all resources" + }, + "ulimits": { + "default": [], + "description": "A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker" + }, + "user": { + "default": null, + "description": "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set" + }, + "volumes_from": { + "default": [], + "description": "Data volumes to mount from another container" + }, + "working_directory": { + "default": null, + "description": "The working directory to run commands inside the container" + } + } + } + } + } + } + }, + "relevant_attributes": [ + { + "resource": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "attribute": [ + "arn" + ] + }, + { + "resource": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "attribute": [ + "name" + ] + }, + { + "resource": "module.ecs_container_definition.data.aws_region.current", + "attribute": [ + "name" + ] + } + ], + "timestamp": "2024-01-31T12:22:25Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/bad.tf new file mode 100644 index 00000000..40a3e392 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/bad.tf @@ -0,0 +1,39 @@ +module "ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + name = "example" + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + tags = { + Environment = "dev" + Terraform = "true" + } +} + +# Setting up the configuration for using Docker and AWS providers + +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>2.20.0" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +# Configuring docker and AWS as providers +provider "docker" {} + +provider "aws" { + region = "us-west-1" +} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/good-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/good-payload.json new file mode 100644 index 00000000..e04e11ed --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/good-payload.json @@ -0,0 +1,468 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "child_modules": [ + { + "resources": [ + { + "address": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "kms_key_id": null, + "name": "/aws/ecs//example", + "retention_in_days": 30, + "skip_destroy": false, + "tags": { + "Environment": "dev", + "Terraform": "true" + }, + "tags_all": { + "Environment": "dev", + "Terraform": "true" + } + }, + "sensitive_values": { + "tags": {}, + "tags_all": {} + } + } + ], + "address": "module.ecs_container_definition" + } + ] + } + }, + "resource_changes": [ + { + "address": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "module_address": "module.ecs_container_definition", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "kms_key_id": null, + "name": "/aws/ecs//example", + "retention_in_days": 30, + "skip_destroy": false, + "tags": { + "Environment": "dev", + "Terraform": "true" + }, + "tags_all": { + "Environment": "dev", + "Terraform": "true" + } + }, + "after_unknown": { + "arn": true, + "id": true, + "name_prefix": true, + "tags": {}, + "tags_all": {} + }, + "before_sensitive": false, + "after_sensitive": { + "tags": {}, + "tags_all": {} + } + } + } + ], + "prior_state": { + "format_version": "1.0", + "terraform_version": "1.7.1", + "values": { + "root_module": { + "child_modules": [ + { + "resources": [ + { + "address": "module.ecs_container_definition.data.aws_region.current", + "mode": "data", + "type": "aws_region", + "name": "current", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "US West (N. California)", + "endpoint": "ec2.us-west-1.amazonaws.com", + "id": "us-west-1", + "name": "us-west-1" + }, + "sensitive_values": {} + } + ], + "address": "module.ecs_container_definition" + } + ] + } + } + }, + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "module_calls": { + "ecs_container_definition": { + "source": "terraform-aws-modules/ecs/aws//modules/container-definition", + "expressions": { + "image": { + "constant_value": "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + }, + "memory": { + "constant_value": 1024 + }, + "name": { + "constant_value": "example" + }, + "port_mappings": { + "constant_value": [ + { + "containerPort": 80, + "name": "ecs-sample", + "protocol": "tcp" + } + ] + }, + "tags": { + "constant_value": { + "Environment": "dev", + "Terraform": "true" + } + } + }, + "module": { + "outputs": { + "cloudwatch_log_group_arn": { + "expression": { + "references": [ + "aws_cloudwatch_log_group.this[0].arn", + "aws_cloudwatch_log_group.this[0]", + "aws_cloudwatch_log_group.this" + ] + }, + "description": "Arn of cloudwatch log group created" + }, + "cloudwatch_log_group_name": { + "expression": { + "references": [ + "aws_cloudwatch_log_group.this[0].name", + "aws_cloudwatch_log_group.this[0]", + "aws_cloudwatch_log_group.this" + ] + }, + "description": "Name of cloudwatch log group created" + }, + "container_definition": { + "expression": { + "references": [ + "local.container_definition" + ] + }, + "description": "Container definition" + } + }, + "resources": [ + { + "address": "aws_cloudwatch_log_group.this", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "provider_config_key": "aws", + "expressions": { + "kms_key_id": { + "references": [ + "var.cloudwatch_log_group_kms_key_id" + ] + }, + "name": { + "references": [ + "var.cloudwatch_log_group_use_name_prefix", + "local.log_group_name" + ] + }, + "name_prefix": { + "references": [ + "var.cloudwatch_log_group_use_name_prefix", + "local.log_group_name" + ] + }, + "retention_in_days": { + "references": [ + "var.cloudwatch_log_group_retention_in_days" + ] + }, + "tags": { + "references": [ + "var.tags" + ] + } + }, + "schema_version": 0, + "count_expression": { + "references": [ + "var.create_cloudwatch_log_group", + "var.enable_cloudwatch_logging" + ] + } + }, + { + "address": "data.aws_region.current", + "mode": "data", + "type": "aws_region", + "name": "current", + "provider_config_key": "aws", + "schema_version": 0 + } + ], + "variables": { + "cloudwatch_log_group_kms_key_id": { + "default": null, + "description": "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + }, + "cloudwatch_log_group_retention_in_days": { + "default": 30, + "description": "Number of days to retain log events. Default is 30 days" + }, + "cloudwatch_log_group_use_name_prefix": { + "default": false, + "description": "Determines whether the log group name should be used as a prefix" + }, + "command": { + "default": [], + "description": "The command that's passed to the container" + }, + "cpu": { + "default": null, + "description": "The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value" + }, + "create_cloudwatch_log_group": { + "default": true, + "description": "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled" + }, + "dependencies": { + "default": [], + "description": "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" + }, + "disable_networking": { + "default": null, + "description": "When this parameter is true, networking is disabled within the container" + }, + "dns_search_domains": { + "default": [], + "description": "Container DNS search domains. A list of DNS search domains that are presented to the container" + }, + "dns_servers": { + "default": [], + "description": "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" + }, + "docker_labels": { + "default": {}, + "description": "A key/value map of labels to add to the container" + }, + "docker_security_options": { + "default": [], + "description": "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" + }, + "enable_cloudwatch_logging": { + "default": true, + "description": "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers" + }, + "enable_execute_command": { + "default": false, + "description": "Specifies whether to enable Amazon ECS Exec for the tasks within the service" + }, + "entrypoint": { + "default": [], + "description": "The entry point that is passed to the container" + }, + "environment": { + "default": [], + "description": "The environment variables to pass to the container" + }, + "environment_files": { + "default": [], + "description": "A list of files containing the environment variables to pass to a container" + }, + "essential": { + "default": null, + "description": "If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped" + }, + "extra_hosts": { + "default": [], + "description": "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" + }, + "firelens_configuration": { + "default": {}, + "description": "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" + }, + "health_check": { + "default": {}, + "description": "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" + }, + "hostname": { + "default": null, + "description": "The hostname to use for your container" + }, + "image": { + "default": null, + "description": "The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest`" + }, + "interactive": { + "default": false, + "description": "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated" + }, + "links": { + "default": [], + "description": "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" + }, + "linux_parameters": { + "default": {}, + "description": "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + }, + "log_configuration": { + "default": {}, + "description": "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + }, + "memory": { + "default": null, + "description": "The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified" + }, + "memory_reservation": { + "default": null, + "description": "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" + }, + "mount_points": { + "default": [], + "description": "The mount points for data volumes in your container" + }, + "name": { + "default": null, + "description": "The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed" + }, + "operating_system_family": { + "default": "LINUX", + "description": "The OS family for task" + }, + "port_mappings": { + "default": [], + "description": "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" + }, + "privileged": { + "default": false, + "description": "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)" + }, + "pseudo_terminal": { + "default": false, + "description": "When this parameter is true, a `TTY` is allocated" + }, + "readonly_root_filesystem": { + "default": true, + "description": "When this parameter is true, the container is given read-only access to its root file system" + }, + "repository_credentials": { + "default": {}, + "description": "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" + }, + "resource_requirements": { + "default": [], + "description": "The type and amount of a resource to assign to a container. The only supported resource is a GPU" + }, + "secrets": { + "default": [], + "description": "The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide" + }, + "service": { + "default": "", + "description": "The name of the service that the container definition is associated with" + }, + "start_timeout": { + "default": 30, + "description": "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" + }, + "stop_timeout": { + "default": 120, + "description": "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" + }, + "system_controls": { + "default": [], + "description": "A list of namespaced kernel parameters to set in the container" + }, + "tags": { + "default": {}, + "description": "A map of tags to add to all resources" + }, + "ulimits": { + "default": [], + "description": "A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker" + }, + "user": { + "default": null, + "description": "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set" + }, + "volumes_from": { + "default": [], + "description": "Data volumes to mount from another container" + }, + "working_directory": { + "default": null, + "description": "The working directory to run commands inside the container" + } + } + } + } + } + } + }, + "relevant_attributes": [ + { + "resource": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "attribute": [ + "name" + ] + }, + { + "resource": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "attribute": [ + "arn" + ] + }, + { + "resource": "module.ecs_container_definition.data.aws_region.current", + "attribute": [ + "name" + ] + } + ], + "timestamp": "2024-01-31T12:21:01Z", + "errored": false +} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/good.tf new file mode 100644 index 00000000..412e94aa --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/test/good.tf @@ -0,0 +1,40 @@ +module "ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + name = "example" + memory = 1024 + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + tags = { + Environment = "dev" + Terraform = "true" + } +} + +# Setting up the configuration for using Docker and AWS providers + +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>2.20.0" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +# Configuring docker and AWS as providers +provider "docker" {} + +provider "aws" { + region = "us-west-1" +} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/validate-ecs-task-definition-memory-hard-limit.yaml b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/validate-ecs-task-definition-memory-hard-limit.yaml new file mode 100644 index 00000000..fe00d3e1 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-module/validate-ecs-task-definition-memory-hard-limit.yaml @@ -0,0 +1,25 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: validate-ecs-task-definition-memory-hard-limit + labels: + ecs.aws.tags.kyverno.io: ecs-service + annotations: + policies.kyverno.io/title: Validate ECS Task Definition Memory Hard Limit + policies.kyverno.io/category: ECS Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/description: >- + This policy checks if Amazon Elastic Container Service + (ECS) task definitions have a set memory limit for its container definitions. +spec: + rules: + - name: validate-ecs-task-definition-memory-hard-limit + match: + any: + - (configuration.root_module.module_calls.ecs_container_definition.expressions | length(@) > `0`): true + assert: + all: + - message: Memory limit for container definitions should be set. + check: + (configuration.root_module.module_calls.ecs_container_definition.expressions): + (!memory): false diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/README.md b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/README.md new file mode 100644 index 00000000..aa09b3b3 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/README.md @@ -0,0 +1,83 @@ +# Validate ECS Task Definition Memory Hard Limit + +This policy checks if Amazon Elastic Container Service (ECS) task definitions have a set memory limit for its container definitions. + +## Policy Details: + +- **Policy Name:** validate-ecs-task-definition-memory-hard-limit +- **Check Description:** This policy ensures that ECS task definitions has a memory limit set +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +**Key** : memory +**Type** : int +**Required** : No + +- **Condition:** `memory` is present + - **Result:** PASS + - **Reason:** Memory limit is set + +- **Condition:** `memory` is absent + - **Result:** FAIL + - **Reason:** Memory limit is not set + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet security standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy policy.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-memory-hard-limit.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-memory-hard-limit / validate-ecs-task-definition-memory-hard-limit / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-memory-hard-limit.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-memory-hard-limit / validate-ecs-task-definition-memory-hard-limit / FAILED: Please set memory limit for container definitions.: all[0].check.(configuration.root_module.module_calls.ecs_container_definition.expressions).(!memory): Invalid value: true: Expected value: false + Done + ``` + +--- + +This way you can ensure that your ECS task definitions adhere to security standards regarding memory limits. diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/bad-payload.json new file mode 100644 index 00000000..ed85ed2e --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/bad-payload.json @@ -0,0 +1,150 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "network_mode": "host", + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "network_mode": "host", + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "network_mode": { + "constant_value": "host" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-31T12:05:01Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/bad.tf new file mode 100644 index 00000000..dc7f5dd2 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/bad.tf @@ -0,0 +1,42 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + network_mode = "host" + container_definitions = < 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "network_mode": { + "constant_value": "host" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-31T12:03:41Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/good.tf new file mode 100644 index 00000000..19b9076c --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-memory-hard-limit/terraform-resource/test/good.tf @@ -0,0 +1,43 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + network_mode = "host" + container_definitions = <- + This policy checks if Amazon Elastic Container Service + (ECS) task definitions have a set memory limit for its container definitions. +spec: + rules: + - name: validate-ecs-task-definition-memory-hard-limit + match: + any: + - (planned_values.root_module.resources[?type=='aws_ecs_task_definition'] != null): true + assert: + all: + - message: Memory limit for container definitions should be set + check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition']): + values: + ~.(json_parse(container_definitions)): + (!memory): false diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/README.md b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/README.md new file mode 100644 index 00000000..00c7bcef --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/README.md @@ -0,0 +1,77 @@ +# Validate ECS Task Definition Non Root User + +This policy checks if ECSTaskDefinitions specify a user for Amazon Elastic Container Service (Amazon ECS) EC2 launch type containers to run on. The rule fails if the ‘user’ parameter is not present or set to ‘root’. + +## Policy Details: + +- **Policy Name:** validate-ecs-task-definition-nonroot-user +- **Check Description:** For ECS EC2 containers, `user` parameter should not be set to `root` +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +This policy is effective when ECS Task definitions with EC2 launch type is used. `user` parameter should not be set to `root` + +**Key:** user +**Valid Values:** null | "0" | "xyz:0" | "0:xyz" | "xyz" | not set +**Type:** string +**Required:** No + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet security standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy policy.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-nonroot-user.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-nonroot-user / validate-ecs-task-definition-nonroot-user / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-nonroot-user.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-nonroot-user / validate-ecs-task-definition-nonroot-user / FAILED: Please set user to non-root user for host network mode or false privileges.: all[0].check.~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'])[0].values.~.(json_parse(container_definitions))[0].(starts_with(user || '', '0:') || ends_with(user || '', ':0')): Invalid value: true: Expected value: false + Done + ``` + +--- + diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload.json new file mode 100644 index 00000000..2b43902c --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload.json @@ -0,0 +1,164 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":\"xyx:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":\"xyx:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"user\" : \"xyx:0\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "requires_compatibilities": { + "constant_value": [ + "EC2" + ] + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-31T20:48:11Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload1.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload1.json new file mode 100644 index 00000000..06d09aae --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload1.json @@ -0,0 +1,164 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":\"0:xyz\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":\"xyx:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"user\" : \"xyx:0\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "requires_compatibilities": { + "constant_value": [ + "EC2" + ] + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-31T20:48:11Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload2.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload2.json new file mode 100644 index 00000000..ea8d3e54 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload2.json @@ -0,0 +1,164 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":\"0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":\"xyx:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"user\" : \"xyx:0\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "requires_compatibilities": { + "constant_value": [ + "EC2" + ] + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-31T20:48:11Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload3.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload3.json new file mode 100644 index 00000000..4042d375 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload3.json @@ -0,0 +1,164 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":null}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":\"xyx:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"user\" : \"xyx:0\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "requires_compatibilities": { + "constant_value": [ + "EC2" + ] + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-31T20:48:11Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload4.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload4.json new file mode 100644 index 00000000..b7d2c207 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad-payload4.json @@ -0,0 +1,164 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"_user\":\"xyx:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"user\":\"xyx:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "EC2" + ], + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + false + ], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"user\" : \"xyx:0\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "requires_compatibilities": { + "constant_value": [ + "EC2" + ] + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-31T20:48:11Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad.tf new file mode 100644 index 00000000..2691a16d --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/bad.tf @@ -0,0 +1,44 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + requires_compatibilities = ["EC2"] + container_definitions = < 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"user\" : \"xyz\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "requires_compatibilities": { + "constant_value": [ + "EC2" + ] + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-31T20:46:38Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/good.tf new file mode 100644 index 00000000..cae13972 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-nonroot-user/test/good.tf @@ -0,0 +1,44 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + requires_compatibilities = ["EC2"] + container_definitions = <- + This policy checks if ECSTaskDefinitions specify a user for + Amazon Elastic Container Service (Amazon ECS) EC2 launch type + containers to run on. The rule fails if the ‘user’ parameter is not present or set to ‘root’. +spec: + rules: + - name: validate-ecs-task-definition-nonroot-user + match: + all: + - (planned_values.root_module.resources[?type=='aws_ecs_task_definition'] | length(@) > `0`): true + - ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition']): + values: + requires_compatibilities: + (contains(@, 'EC2')): true + assert: + all: + - message: For ECS EC2 containers, `user` parameter should not be set to `root` + check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition']): + values: + ~.(json_parse(container_definitions)): + (!user): false + (starts_with(user || '', '0:') || ends_with(user || '', ':0')): false + (user != null): true + ((user != '0')): true diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/README.md b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/README.md new file mode 100644 index 00000000..b3891962 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/README.md @@ -0,0 +1,80 @@ +# Validate ECS Task Definition User For Host Mode Policy + +This policy aims to identify and address unauthorized permissions in your active Amazon Elastic Container Service (Amazon ECS) task definitions that utilize the host network mode. The rule categorizes task definitions with NetworkMode set to host as NON_COMPLIANT under the following conditions: container definitions with privileged set to false or empty and user set to root or empty. + +In scenarios where tasks employ the host network mode, it's crucial to avoid running containers with the root user (UID 0) for enhanced security. As a recommended security practice, it is recommended to opt for a non-root user. + +## Policy Details: + +- **Policy Name:** validate-ecs-task-definition-user-for-host-mode-check-in-module +- **Check Description:** This policy makes sure that ECS task definitions avoids using the root user for the host network mode or false privileges. +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +This policy is effective when privileged is set to `false` in a Terraform MODULE. The policy is deemed NON_COMPLIANT if `user` is configured as null, 0, 'xyz:0", "0:xyz", or not set. + +**Key:** user +**Valid Values:** null | "0" | "xyz:0" | "0:xyz" | "xyz" | not set +**Type:** string +**Required:** No + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet security standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy validate-ecs-task-definition-user-for-host-mode-check-in-module.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-user-for-host-mode-check-in-module.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-user-for-host-mode-check-in-module / validate-ecs-task-definition-user-for-host-mode-check-in-module / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-user-for-host-mode-check-in-module.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-user-for-host-mode-check-in-module / validate-ecs-task-definition-user-for-host-mode-check-in-module / FAILED: Please use a non-root user for false privileges.: all[0].check.configuration.root_module.module_calls.ecs_container_definition.expressions.user.(starts_with(constant_value || '', '0:') || ends_with(constant_value || '', ':0')): Invalid value: true: Expected value: false + Done + ``` + +--- + +This way you can ensure that your ECS task definitions adhere to security standards regarding user's root access. diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/bad-payload.json new file mode 100644 index 00000000..b4c98a2c --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/bad-payload.json @@ -0,0 +1,472 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "child_modules": [ + { + "resources": [ + { + "address": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "kms_key_id": null, + "name": "/aws/ecs//example", + "retention_in_days": 30, + "skip_destroy": false, + "tags": { + "Environment": "dev", + "Terraform": "true" + }, + "tags_all": { + "Environment": "dev", + "Terraform": "true" + } + }, + "sensitive_values": { + "tags": {}, + "tags_all": {} + } + } + ], + "address": "module.ecs_container_definition" + } + ] + } + }, + "resource_changes": [ + { + "address": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "module_address": "module.ecs_container_definition", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "kms_key_id": null, + "name": "/aws/ecs//example", + "retention_in_days": 30, + "skip_destroy": false, + "tags": { + "Environment": "dev", + "Terraform": "true" + }, + "tags_all": { + "Environment": "dev", + "Terraform": "true" + } + }, + "after_unknown": { + "arn": true, + "id": true, + "name_prefix": true, + "tags": {}, + "tags_all": {} + }, + "before_sensitive": false, + "after_sensitive": { + "tags": {}, + "tags_all": {} + } + } + } + ], + "prior_state": { + "format_version": "1.0", + "terraform_version": "1.7.1", + "values": { + "root_module": { + "child_modules": [ + { + "resources": [ + { + "address": "module.ecs_container_definition.data.aws_region.current", + "mode": "data", + "type": "aws_region", + "name": "current", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "US West (N. California)", + "endpoint": "ec2.us-west-1.amazonaws.com", + "id": "us-west-1", + "name": "us-west-1" + }, + "sensitive_values": {} + } + ], + "address": "module.ecs_container_definition" + } + ] + } + } + }, + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "module_calls": { + "ecs_container_definition": { + "source": "terraform-aws-modules/ecs/aws//modules/container-definition", + "expressions": { + "image": { + "constant_value": "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + }, + "name": { + "constant_value": "example" + }, + "port_mappings": { + "constant_value": [ + { + "containerPort": 80, + "name": "ecs-sample", + "protocol": "tcp" + } + ] + }, + "privileged": { + "constant_value": false + }, + "tags": { + "constant_value": { + "Environment": "dev", + "Terraform": "true" + } + }, + "user": { + "constant_value": "root:0" + } + }, + "module": { + "outputs": { + "cloudwatch_log_group_arn": { + "expression": { + "references": [ + "aws_cloudwatch_log_group.this[0].arn", + "aws_cloudwatch_log_group.this[0]", + "aws_cloudwatch_log_group.this" + ] + }, + "description": "Arn of cloudwatch log group created" + }, + "cloudwatch_log_group_name": { + "expression": { + "references": [ + "aws_cloudwatch_log_group.this[0].name", + "aws_cloudwatch_log_group.this[0]", + "aws_cloudwatch_log_group.this" + ] + }, + "description": "Name of cloudwatch log group created" + }, + "container_definition": { + "expression": { + "references": [ + "local.container_definition" + ] + }, + "description": "Container definition" + } + }, + "resources": [ + { + "address": "aws_cloudwatch_log_group.this", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "provider_config_key": "aws", + "expressions": { + "kms_key_id": { + "references": [ + "var.cloudwatch_log_group_kms_key_id" + ] + }, + "name": { + "references": [ + "var.cloudwatch_log_group_use_name_prefix", + "local.log_group_name" + ] + }, + "name_prefix": { + "references": [ + "var.cloudwatch_log_group_use_name_prefix", + "local.log_group_name" + ] + }, + "retention_in_days": { + "references": [ + "var.cloudwatch_log_group_retention_in_days" + ] + }, + "tags": { + "references": [ + "var.tags" + ] + } + }, + "schema_version": 0, + "count_expression": { + "references": [ + "var.create_cloudwatch_log_group", + "var.enable_cloudwatch_logging" + ] + } + }, + { + "address": "data.aws_region.current", + "mode": "data", + "type": "aws_region", + "name": "current", + "provider_config_key": "aws", + "schema_version": 0 + } + ], + "variables": { + "cloudwatch_log_group_kms_key_id": { + "default": null, + "description": "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + }, + "cloudwatch_log_group_retention_in_days": { + "default": 30, + "description": "Number of days to retain log events. Default is 30 days" + }, + "cloudwatch_log_group_use_name_prefix": { + "default": false, + "description": "Determines whether the log group name should be used as a prefix" + }, + "command": { + "default": [], + "description": "The command that's passed to the container" + }, + "cpu": { + "default": null, + "description": "The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value" + }, + "create_cloudwatch_log_group": { + "default": true, + "description": "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled" + }, + "dependencies": { + "default": [], + "description": "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" + }, + "disable_networking": { + "default": null, + "description": "When this parameter is true, networking is disabled within the container" + }, + "dns_search_domains": { + "default": [], + "description": "Container DNS search domains. A list of DNS search domains that are presented to the container" + }, + "dns_servers": { + "default": [], + "description": "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" + }, + "docker_labels": { + "default": {}, + "description": "A key/value map of labels to add to the container" + }, + "docker_security_options": { + "default": [], + "description": "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" + }, + "enable_cloudwatch_logging": { + "default": true, + "description": "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers" + }, + "enable_execute_command": { + "default": false, + "description": "Specifies whether to enable Amazon ECS Exec for the tasks within the service" + }, + "entrypoint": { + "default": [], + "description": "The entry point that is passed to the container" + }, + "environment": { + "default": [], + "description": "The environment variables to pass to the container" + }, + "environment_files": { + "default": [], + "description": "A list of files containing the environment variables to pass to a container" + }, + "essential": { + "default": null, + "description": "If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped" + }, + "extra_hosts": { + "default": [], + "description": "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" + }, + "firelens_configuration": { + "default": {}, + "description": "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" + }, + "health_check": { + "default": {}, + "description": "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" + }, + "hostname": { + "default": null, + "description": "The hostname to use for your container" + }, + "image": { + "default": null, + "description": "The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest`" + }, + "interactive": { + "default": false, + "description": "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated" + }, + "links": { + "default": [], + "description": "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" + }, + "linux_parameters": { + "default": {}, + "description": "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + }, + "log_configuration": { + "default": {}, + "description": "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + }, + "memory": { + "default": null, + "description": "The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified" + }, + "memory_reservation": { + "default": null, + "description": "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" + }, + "mount_points": { + "default": [], + "description": "The mount points for data volumes in your container" + }, + "name": { + "default": null, + "description": "The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed" + }, + "operating_system_family": { + "default": "LINUX", + "description": "The OS family for task" + }, + "port_mappings": { + "default": [], + "description": "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" + }, + "privileged": { + "default": false, + "description": "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)" + }, + "pseudo_terminal": { + "default": false, + "description": "When this parameter is true, a `TTY` is allocated" + }, + "readonly_root_filesystem": { + "default": true, + "description": "When this parameter is true, the container is given read-only access to its root file system" + }, + "repository_credentials": { + "default": {}, + "description": "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" + }, + "resource_requirements": { + "default": [], + "description": "The type and amount of a resource to assign to a container. The only supported resource is a GPU" + }, + "secrets": { + "default": [], + "description": "The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide" + }, + "service": { + "default": "", + "description": "The name of the service that the container definition is associated with" + }, + "start_timeout": { + "default": 30, + "description": "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" + }, + "stop_timeout": { + "default": 120, + "description": "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" + }, + "system_controls": { + "default": [], + "description": "A list of namespaced kernel parameters to set in the container" + }, + "tags": { + "default": {}, + "description": "A map of tags to add to all resources" + }, + "ulimits": { + "default": [], + "description": "A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker" + }, + "user": { + "default": null, + "description": "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set" + }, + "volumes_from": { + "default": [], + "description": "Data volumes to mount from another container" + }, + "working_directory": { + "default": null, + "description": "The working directory to run commands inside the container" + } + } + } + } + } + } + }, + "relevant_attributes": [ + { + "resource": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "attribute": [ + "name" + ] + }, + { + "resource": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "attribute": [ + "arn" + ] + }, + { + "resource": "module.ecs_container_definition.data.aws_region.current", + "attribute": [ + "name" + ] + } + ], + "timestamp": "2024-01-29T13:43:03Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/bad.tf new file mode 100644 index 00000000..51701872 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/bad.tf @@ -0,0 +1,39 @@ +module "ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + name = "example" + privileged = false + user = "root:0" + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + tags = { + Environment = "dev" + Terraform = "true" + } +} + +# Setting up the configuration for using Docker and AWS providers + +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>2.20.0" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} +# Configuring docker and AWS as providers +provider "docker" {} + +provider "aws" { + region = "us-west-1" +} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/good-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/good-payload.json new file mode 100644 index 00000000..61ffbc10 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/good-payload.json @@ -0,0 +1,472 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "child_modules": [ + { + "resources": [ + { + "address": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "kms_key_id": null, + "name": "/aws/ecs//example", + "retention_in_days": 30, + "skip_destroy": false, + "tags": { + "Environment": "dev", + "Terraform": "true" + }, + "tags_all": { + "Environment": "dev", + "Terraform": "true" + } + }, + "sensitive_values": { + "tags": {}, + "tags_all": {} + } + } + ], + "address": "module.ecs_container_definition" + } + ] + } + }, + "resource_changes": [ + { + "address": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "module_address": "module.ecs_container_definition", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "kms_key_id": null, + "name": "/aws/ecs//example", + "retention_in_days": 30, + "skip_destroy": false, + "tags": { + "Environment": "dev", + "Terraform": "true" + }, + "tags_all": { + "Environment": "dev", + "Terraform": "true" + } + }, + "after_unknown": { + "arn": true, + "id": true, + "name_prefix": true, + "tags": {}, + "tags_all": {} + }, + "before_sensitive": false, + "after_sensitive": { + "tags": {}, + "tags_all": {} + } + } + } + ], + "prior_state": { + "format_version": "1.0", + "terraform_version": "1.7.1", + "values": { + "root_module": { + "child_modules": [ + { + "resources": [ + { + "address": "module.ecs_container_definition.data.aws_region.current", + "mode": "data", + "type": "aws_region", + "name": "current", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "US West (N. California)", + "endpoint": "ec2.us-west-1.amazonaws.com", + "id": "us-west-1", + "name": "us-west-1" + }, + "sensitive_values": {} + } + ], + "address": "module.ecs_container_definition" + } + ] + } + } + }, + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "module_calls": { + "ecs_container_definition": { + "source": "terraform-aws-modules/ecs/aws//modules/container-definition", + "expressions": { + "image": { + "constant_value": "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + }, + "name": { + "constant_value": "example" + }, + "port_mappings": { + "constant_value": [ + { + "containerPort": 80, + "name": "ecs-sample", + "protocol": "tcp" + } + ] + }, + "privileged": { + "constant_value": false + }, + "tags": { + "constant_value": { + "Environment": "dev", + "Terraform": "true" + } + }, + "user": { + "constant_value": "root" + } + }, + "module": { + "outputs": { + "cloudwatch_log_group_arn": { + "expression": { + "references": [ + "aws_cloudwatch_log_group.this[0].arn", + "aws_cloudwatch_log_group.this[0]", + "aws_cloudwatch_log_group.this" + ] + }, + "description": "Arn of cloudwatch log group created" + }, + "cloudwatch_log_group_name": { + "expression": { + "references": [ + "aws_cloudwatch_log_group.this[0].name", + "aws_cloudwatch_log_group.this[0]", + "aws_cloudwatch_log_group.this" + ] + }, + "description": "Name of cloudwatch log group created" + }, + "container_definition": { + "expression": { + "references": [ + "local.container_definition" + ] + }, + "description": "Container definition" + } + }, + "resources": [ + { + "address": "aws_cloudwatch_log_group.this", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "provider_config_key": "aws", + "expressions": { + "kms_key_id": { + "references": [ + "var.cloudwatch_log_group_kms_key_id" + ] + }, + "name": { + "references": [ + "var.cloudwatch_log_group_use_name_prefix", + "local.log_group_name" + ] + }, + "name_prefix": { + "references": [ + "var.cloudwatch_log_group_use_name_prefix", + "local.log_group_name" + ] + }, + "retention_in_days": { + "references": [ + "var.cloudwatch_log_group_retention_in_days" + ] + }, + "tags": { + "references": [ + "var.tags" + ] + } + }, + "schema_version": 0, + "count_expression": { + "references": [ + "var.create_cloudwatch_log_group", + "var.enable_cloudwatch_logging" + ] + } + }, + { + "address": "data.aws_region.current", + "mode": "data", + "type": "aws_region", + "name": "current", + "provider_config_key": "aws", + "schema_version": 0 + } + ], + "variables": { + "cloudwatch_log_group_kms_key_id": { + "default": null, + "description": "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + }, + "cloudwatch_log_group_retention_in_days": { + "default": 30, + "description": "Number of days to retain log events. Default is 30 days" + }, + "cloudwatch_log_group_use_name_prefix": { + "default": false, + "description": "Determines whether the log group name should be used as a prefix" + }, + "command": { + "default": [], + "description": "The command that's passed to the container" + }, + "cpu": { + "default": null, + "description": "The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value" + }, + "create_cloudwatch_log_group": { + "default": true, + "description": "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled" + }, + "dependencies": { + "default": [], + "description": "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" + }, + "disable_networking": { + "default": null, + "description": "When this parameter is true, networking is disabled within the container" + }, + "dns_search_domains": { + "default": [], + "description": "Container DNS search domains. A list of DNS search domains that are presented to the container" + }, + "dns_servers": { + "default": [], + "description": "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" + }, + "docker_labels": { + "default": {}, + "description": "A key/value map of labels to add to the container" + }, + "docker_security_options": { + "default": [], + "description": "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" + }, + "enable_cloudwatch_logging": { + "default": true, + "description": "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers" + }, + "enable_execute_command": { + "default": false, + "description": "Specifies whether to enable Amazon ECS Exec for the tasks within the service" + }, + "entrypoint": { + "default": [], + "description": "The entry point that is passed to the container" + }, + "environment": { + "default": [], + "description": "The environment variables to pass to the container" + }, + "environment_files": { + "default": [], + "description": "A list of files containing the environment variables to pass to a container" + }, + "essential": { + "default": null, + "description": "If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped" + }, + "extra_hosts": { + "default": [], + "description": "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" + }, + "firelens_configuration": { + "default": {}, + "description": "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" + }, + "health_check": { + "default": {}, + "description": "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" + }, + "hostname": { + "default": null, + "description": "The hostname to use for your container" + }, + "image": { + "default": null, + "description": "The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest`" + }, + "interactive": { + "default": false, + "description": "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated" + }, + "links": { + "default": [], + "description": "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" + }, + "linux_parameters": { + "default": {}, + "description": "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + }, + "log_configuration": { + "default": {}, + "description": "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + }, + "memory": { + "default": null, + "description": "The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified" + }, + "memory_reservation": { + "default": null, + "description": "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" + }, + "mount_points": { + "default": [], + "description": "The mount points for data volumes in your container" + }, + "name": { + "default": null, + "description": "The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed" + }, + "operating_system_family": { + "default": "LINUX", + "description": "The OS family for task" + }, + "port_mappings": { + "default": [], + "description": "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" + }, + "privileged": { + "default": false, + "description": "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)" + }, + "pseudo_terminal": { + "default": false, + "description": "When this parameter is true, a `TTY` is allocated" + }, + "readonly_root_filesystem": { + "default": true, + "description": "When this parameter is true, the container is given read-only access to its root file system" + }, + "repository_credentials": { + "default": {}, + "description": "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" + }, + "resource_requirements": { + "default": [], + "description": "The type and amount of a resource to assign to a container. The only supported resource is a GPU" + }, + "secrets": { + "default": [], + "description": "The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide" + }, + "service": { + "default": "", + "description": "The name of the service that the container definition is associated with" + }, + "start_timeout": { + "default": 30, + "description": "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" + }, + "stop_timeout": { + "default": 120, + "description": "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" + }, + "system_controls": { + "default": [], + "description": "A list of namespaced kernel parameters to set in the container" + }, + "tags": { + "default": {}, + "description": "A map of tags to add to all resources" + }, + "ulimits": { + "default": [], + "description": "A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker" + }, + "user": { + "default": null, + "description": "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set" + }, + "volumes_from": { + "default": [], + "description": "Data volumes to mount from another container" + }, + "working_directory": { + "default": null, + "description": "The working directory to run commands inside the container" + } + } + } + } + } + } + }, + "relevant_attributes": [ + { + "resource": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "attribute": [ + "name" + ] + }, + { + "resource": "module.ecs_container_definition.aws_cloudwatch_log_group.this[0]", + "attribute": [ + "arn" + ] + }, + { + "resource": "module.ecs_container_definition.data.aws_region.current", + "attribute": [ + "name" + ] + } + ], + "timestamp": "2024-01-29T19:14:41Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/good.tf similarity index 97% rename from terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/bad.tf rename to terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/good.tf index 1152f8e5..f93936a5 100644 --- a/terraform-best-practices/aws/ecs/validate-ecs-containers-nonprivileged/test/bad.tf +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/test/good.tf @@ -2,6 +2,7 @@ module "ecs_container_definition" { source = "terraform-aws-modules/ecs/aws//modules/container-definition" name = "example" privileged = false + user = "root" image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" port_mappings = [ { @@ -10,7 +11,6 @@ module "ecs_container_definition" { protocol = "tcp" } ] - tags = { Environment = "dev" Terraform = "true" @@ -31,7 +31,6 @@ terraform { } } } - # Configuring docker and AWS as providers provider "docker" {} diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/validate-ecs-task-definition-user-for-host-mode-check-in-module.yaml b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/validate-ecs-task-definition-user-for-host-mode-check-in-module.yaml new file mode 100644 index 00000000..eb596e01 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-module/validate-ecs-task-definition-user-for-host-mode-check-in-module.yaml @@ -0,0 +1,30 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: validate-ecs-task-definition-user-for-host-mode-check-in-module + labels: + ecs.aws.tags.kyverno.io: ecs-service + annotations: + policies.kyverno.io/title: Validate ECS Task Definition User for Host mode + policies.kyverno.io/category: ECS Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/description: >- + This policy makes sure that ECS task definitions avoid using + the root user for the host network mode or false privileges. +spec: + rules: + - name: validate-ecs-task-definition-user-for-host-mode-check-in-module + match: + any: + - (configuration.root_module.module_calls.ecs_container_definition.expressions.privileged): + constant_value: false + assert: + all: + - message: Specify a non-root user or group to avoid privilege escalation. + check: + (configuration.root_module.module_calls.ecs_container_definition.expressions): + (!user): false + user: + (starts_with(constant_value || '', '0:') || ends_with(constant_value || '', ':0')): false + (constant_value != null): true + (constant_value != '0'): true diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/README.md b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/README.md new file mode 100644 index 00000000..5cd2c90d --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/README.md @@ -0,0 +1,81 @@ +# Validate ECS Task Definition User For Host Mode Policy + +This policy aims to identify and address unauthorized permissions in your active Amazon Elastic Container Service (Amazon ECS) task definitions that utilize the host network mode. The rule categorizes task definitions with NetworkMode set to host as NON_COMPLIANT under the following conditions: container definitions with privileged set to false or empty and user set to root or empty. + +In scenarios where tasks employ the host network mode, it's crucial to avoid running containers with the root user (UID 0) for enhanced security. As a recommended security practice, it is recommended to opt for a non-root user. + +## Policy Details: + +- **Policy Name:** validate-ecs-task-definition-user-for-host-mode-check +- **Check Description:** This policy makes sure that ECS task definitions avoids using the root user for the host network mode or false privileges. +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +This policy is effective when the network_mode is set to `host` OR privileged is set to `false`. The policy is deemed NON_COMPLIANT if `user` is configured as null, 0, 'xyz:0", "0:xyz", or not set. + +**Key:** user +**Valid Values:** null | "0" | "xyz:0" | "0:xyz" | "xyz" | not set +**Type:** string +**Required:** No + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet security standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy validate-ecs-task-definition-user-for-host-mode-check.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-user-for-host-mode-check.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-user-for-host-mode-check / validate-ecs-task-definition-user-for-host-mode-check / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-definition-user-for-host-mode-check.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-definition-user-for-host-mode-check / validate-ecs-task-definition-user-for-host-mode-check / FAILED: Please set user to non-root user for host network mode or false privileges.: all[0].check.~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'] )[0].values.~.(json_parse(container_definitions))[0].(starts_with(user || '', '0:') || ends_with(user || '', ':0')): Invalid value: true: Expected value: false + Done + ``` + +--- + +This way you can ensure that your ECS task definitions adhere to security standards regarding user's root access. + diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/bad-payload.json new file mode 100644 index 00000000..d05df48c --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/bad-payload.json @@ -0,0 +1,150 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.0-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"privileged\":false,\"user\":\"xyz:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "network_mode": "host", + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}],\"privileged\":false,\"user\":\"xyz:0\"}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "network_mode": "host", + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"privileged\": false,\n \"user\" : \"xyz:0\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "network_mode": { + "constant_value": "host" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-25T12:14:34Z", + "errored": false + } + diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/bad.tf new file mode 100644 index 00000000..133f5de1 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/bad.tf @@ -0,0 +1,46 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + network_mode = "host" + container_definitions = < 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.task", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "task", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"privileged\": false,\n \"user\" : \"xyz\",\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "network_mode": { + "constant_value": "host" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-25T12:07:19Z", + "errored": false + } + diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/good.tf new file mode 100644 index 00000000..79118700 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-definition-user-for-host-mode-check/terraform-resource/test/good.tf @@ -0,0 +1,45 @@ +resource "aws_ecs_task_definition" "task" { + family = "service" + network_mode = "host" + container_definitions = <- + This policy makes sure that ECS task definitions avoid using + the root user for the host network mode or false privileges. +spec: + rules: + - name: validate-ecs-task-definition-user-for-host-mode-check + match: + any: + - ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'].values): + network_mode: host + - ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'].values): + ~.(json_parse(container_definitions)): + privileged: false + assert: + all: + - message: User should be set to non-root user when using the host network mode or privileged set to false. + check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'] ): + values: + ~.(json_parse(container_definitions)): + (!user): false + (starts_with(user || '', '0:') || ends_with(user || '', ':0')): false + (user != null): true + (user != '0'): true + diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/README.md b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/README.md new file mode 100644 index 00000000..6c379a6d --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/README.md @@ -0,0 +1,91 @@ +# Validate ECS Task Public IP + +A public IP address is an IP address that is reachable from the internet. If you launch your Amazon ECS instances with a public IP address, then your Amazon ECS instances are reachable from the internet. Amazon ECS services should not be publicly accessible, as this may allow unintended access to your container application servers. + +## Policy Details: + +- **Policy Name:** validate-ecs-task-public-ip +- **Check Description:** ECS tasks with public IP address enabled, are easily reachable from the internet. This policy validates whether public IP address is enabled on the ECS task. +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +**Key** : assign_public_ip +**Valid Values** : true | false | not set +**Type** : string +**Required** : No + +- **Condition:** `assign_public_ip` is set to `false` + - **Result:** PASS + - **Reason:** + + +- **Condition:** `assign_public_ip` is not present + - **Result:** PASS + - **Reason:** + +- - **Condition:** `network_configuration` is not present + - **Result:** PASS + - **Reason:** + +- **Condition:** `assign_public_ip` is set to `true` + - **Result:** FAIL + - **Reason:** + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet security standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy policy.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-public-ip.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-public-ip / validate-ecs-task-public-ip / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-ecs-task-public-ip.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-ecs-task-public-ip / validate-ecs-task-public-ip / FAILED: Public IP address is enabled on the ECS task: any[0].check.~.(planned_values.root_module.resources[?type=='aws_ecs_service'])[0].values.~.(network_configuration[?assign_public_ip] || `[]`)[0].assign_public_ip: Invalid value: true: Expected value: false + Done + ``` + +--- diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/bad-payload.json new file mode 100644 index 00000000..b97ed083 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/bad-payload.json @@ -0,0 +1,555 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "variables": { + "vpc_cidr": { + "value": "10.0.0.0/16" + } + }, + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_cluster.cluster", + "mode": "managed", + "type": "aws_ecs_cluster", + "name": "cluster", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "configuration": [], + "name": "cluster", + "service_connect_defaults": [], + "setting": [ + { + "name": "containerInsights", + "value": "enabled" + } + ], + "tags": null + }, + "sensitive_values": { + "capacity_providers": [], + "configuration": [], + "default_capacity_provider_strategy": [], + "service_connect_defaults": [], + "setting": [ + {} + ], + "tags_all": {} + } + }, + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": 1, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_new_deployment": null, + "health_check_grace_period_seconds": null, + "launch_type": "EC2", + "load_balancer": [], + "name": "foo-service", + "network_configuration": [ + { + "assign_public_ip": true, + "security_groups": null + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "propagate_tags": null, + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [], + "service_registries": [], + "tags": null, + "task_definition": null, + "timeouts": null, + "wait_for_steady_state": false + }, + "sensitive_values": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "load_balancer": [], + "network_configuration": [ + { + "subnets": [] + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "service_connect_configuration": [], + "service_registries": [], + "tags_all": {}, + "triggers": {} + } + }, + { + "address": "aws_subnet.subnet", + "mode": "managed", + "type": "aws_subnet", + "name": "subnet", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_ipv6_address_on_creation": false, + "cidr_block": "10.0.1.0/24", + "customer_owned_ipv4_pool": null, + "enable_dns64": false, + "enable_lni_at_device_index": null, + "enable_resource_name_dns_a_record_on_launch": false, + "enable_resource_name_dns_aaaa_record_on_launch": false, + "ipv6_cidr_block": null, + "ipv6_native": false, + "map_customer_owned_ip_on_launch": null, + "map_public_ip_on_launch": true, + "outpost_arn": null, + "tags": null, + "timeouts": null + }, + "sensitive_values": { + "tags_all": {} + } + }, + { + "address": "aws_vpc.main", + "mode": "managed", + "type": "aws_vpc", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_generated_ipv6_cidr_block": null, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "ipv4_ipam_pool_id": null, + "ipv4_netmask_length": null, + "ipv6_ipam_pool_id": null, + "ipv6_netmask_length": null, + "tags": { + "name": "main" + }, + "tags_all": { + "name": "main" + } + }, + "sensitive_values": { + "tags": {}, + "tags_all": {} + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_cluster.cluster", + "mode": "managed", + "type": "aws_ecs_cluster", + "name": "cluster", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "configuration": [], + "name": "cluster", + "service_connect_defaults": [], + "setting": [ + { + "name": "containerInsights", + "value": "enabled" + } + ], + "tags": null + }, + "after_unknown": { + "arn": true, + "capacity_providers": true, + "configuration": [], + "default_capacity_provider_strategy": true, + "id": true, + "service_connect_defaults": [], + "setting": [ + {} + ], + "tags_all": true + }, + "before_sensitive": false, + "after_sensitive": { + "capacity_providers": [], + "configuration": [], + "default_capacity_provider_strategy": [], + "service_connect_defaults": [], + "setting": [ + {} + ], + "tags_all": {} + } + } + }, + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": 1, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_new_deployment": null, + "health_check_grace_period_seconds": null, + "launch_type": "EC2", + "load_balancer": [], + "name": "foo-service", + "network_configuration": [ + { + "assign_public_ip": true, + "security_groups": null + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "propagate_tags": null, + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [], + "service_registries": [], + "tags": null, + "task_definition": null, + "timeouts": null, + "wait_for_steady_state": false + }, + "after_unknown": { + "alarms": [], + "capacity_provider_strategy": [], + "cluster": true, + "deployment_circuit_breaker": [], + "deployment_controller": [], + "iam_role": true, + "id": true, + "load_balancer": [], + "network_configuration": [ + { + "subnets": true + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "platform_version": true, + "service_connect_configuration": [], + "service_registries": [], + "tags_all": true, + "triggers": true + }, + "before_sensitive": false, + "after_sensitive": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "load_balancer": [], + "network_configuration": [ + { + "subnets": [] + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "service_connect_configuration": [], + "service_registries": [], + "tags_all": {}, + "triggers": {} + } + } + }, + { + "address": "aws_subnet.subnet", + "mode": "managed", + "type": "aws_subnet", + "name": "subnet", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_ipv6_address_on_creation": false, + "cidr_block": "10.0.1.0/24", + "customer_owned_ipv4_pool": null, + "enable_dns64": false, + "enable_lni_at_device_index": null, + "enable_resource_name_dns_a_record_on_launch": false, + "enable_resource_name_dns_aaaa_record_on_launch": false, + "ipv6_cidr_block": null, + "ipv6_native": false, + "map_customer_owned_ip_on_launch": null, + "map_public_ip_on_launch": true, + "outpost_arn": null, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "availability_zone": true, + "availability_zone_id": true, + "id": true, + "ipv6_cidr_block_association_id": true, + "owner_id": true, + "private_dns_hostname_type_on_launch": true, + "tags_all": true, + "vpc_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "tags_all": {} + } + } + }, + { + "address": "aws_vpc.main", + "mode": "managed", + "type": "aws_vpc", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_generated_ipv6_cidr_block": null, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "ipv4_ipam_pool_id": null, + "ipv4_netmask_length": null, + "ipv6_ipam_pool_id": null, + "ipv6_netmask_length": null, + "tags": { + "name": "main" + }, + "tags_all": { + "name": "main" + } + }, + "after_unknown": { + "arn": true, + "default_network_acl_id": true, + "default_route_table_id": true, + "default_security_group_id": true, + "dhcp_options_id": true, + "enable_classiclink": true, + "enable_classiclink_dns_support": true, + "enable_dns_hostnames": true, + "enable_network_address_usage_metrics": true, + "id": true, + "ipv6_association_id": true, + "ipv6_cidr_block": true, + "ipv6_cidr_block_network_border_group": true, + "main_route_table_id": true, + "owner_id": true, + "tags": {}, + "tags_all": {} + }, + "before_sensitive": false, + "after_sensitive": { + "tags": {}, + "tags_all": {} + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_cluster.cluster", + "mode": "managed", + "type": "aws_ecs_cluster", + "name": "cluster", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "cluster" + }, + "setting": [ + { + "name": { + "constant_value": "containerInsights" + }, + "value": { + "constant_value": "enabled" + } + } + ] + }, + "schema_version": 0 + }, + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_config_key": "aws", + "expressions": { + "cluster": { + "references": [ + "aws_ecs_cluster.cluster.id", + "aws_ecs_cluster.cluster" + ] + }, + "desired_count": { + "constant_value": 1 + }, + "launch_type": { + "constant_value": "EC2" + }, + "name": { + "constant_value": "foo-service" + }, + "network_configuration": [ + { + "assign_public_ip": { + "constant_value": true + }, + "subnets": { + "references": [ + "aws_subnet.subnet.id", + "aws_subnet.subnet" + ] + } + } + ] + }, + "schema_version": 0 + }, + { + "address": "aws_subnet.subnet", + "mode": "managed", + "type": "aws_subnet", + "name": "subnet", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "references": [ + "aws_vpc.main.cidr_block", + "aws_vpc.main" + ] + }, + "map_public_ip_on_launch": { + "constant_value": true + }, + "vpc_id": { + "references": [ + "aws_vpc.main.id", + "aws_vpc.main" + ] + } + }, + "schema_version": 1 + }, + { + "address": "aws_vpc.main", + "mode": "managed", + "type": "aws_vpc", + "name": "main", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "references": [ + "var.vpc_cidr" + ] + }, + "tags": { + "constant_value": { + "name": "main" + } + } + }, + "schema_version": 1 + } + ], + "variables": { + "vpc_cidr": { + "default": "10.0.0.0/16", + "description": "CIDR block for main" + } + } + } + }, + "relevant_attributes": [ + { + "resource": "aws_vpc.main", + "attribute": [ + "id" + ] + }, + { + "resource": "aws_vpc.main", + "attribute": [ + "cidr_block" + ] + }, + { + "resource": "aws_ecs_cluster.cluster", + "attribute": [ + "id" + ] + }, + { + "resource": "aws_subnet.subnet", + "attribute": [ + "id" + ] + } + ], + "timestamp": "2024-01-30T10:42:01Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/bad.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/bad.tf new file mode 100644 index 00000000..6101b47d --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/bad.tf @@ -0,0 +1,62 @@ +resource "aws_ecs_service" "service" { + name = "foo-service" + cluster = aws_ecs_cluster.cluster.id + desired_count = 1 + launch_type = "EC2" + network_configuration { + assign_public_ip = true + subnets = [aws_subnet.subnet.id] + } + lifecycle { + ignore_changes = [task_definition] + } +} + +resource "aws_ecs_cluster" "cluster" { + name = "cluster" + setting { + name = "containerInsights" + value = "enabled" + } +} + +resource "aws_subnet" "subnet" { + vpc_id = aws_vpc.main.id + cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, 1) ## takes 10.0.0.0/16 --> 10.0.1.0/24 + map_public_ip_on_launch = true +} + +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + tags = { + name = "main" + } +} + +variable "vpc_cidr" { + description = "CIDR block for main" + type = string + default = "10.0.0.0/16" +} + +# Setting up the configuration for using Docker and AWS providers + +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>2.20.0" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +# Configuring docker and AWS as providers +provider "docker" {} + +provider "aws" { + region = "us-west-1" +} \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/good-payload.json b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/good-payload.json new file mode 100644 index 00000000..c953bdce --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/good-payload.json @@ -0,0 +1,555 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "variables": { + "vpc_cidr": { + "value": "10.0.0.0/16" + } + }, + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_cluster.cluster", + "mode": "managed", + "type": "aws_ecs_cluster", + "name": "cluster", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "configuration": [], + "name": "cluster", + "service_connect_defaults": [], + "setting": [ + { + "name": "containerInsights", + "value": "enabled" + } + ], + "tags": null + }, + "sensitive_values": { + "capacity_providers": [], + "configuration": [], + "default_capacity_provider_strategy": [], + "service_connect_defaults": [], + "setting": [ + {} + ], + "tags_all": {} + } + }, + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": 1, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_new_deployment": null, + "health_check_grace_period_seconds": null, + "launch_type": "EC2", + "load_balancer": [], + "name": "foo-service", + "network_configuration": [ + { + "assign_public_ip": false, + "security_groups": null + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "propagate_tags": null, + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [], + "service_registries": [], + "tags": null, + "task_definition": null, + "timeouts": null, + "wait_for_steady_state": false + }, + "sensitive_values": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "load_balancer": [], + "network_configuration": [ + { + "subnets": [] + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "service_connect_configuration": [], + "service_registries": [], + "tags_all": {}, + "triggers": {} + } + }, + { + "address": "aws_subnet.subnet", + "mode": "managed", + "type": "aws_subnet", + "name": "subnet", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_ipv6_address_on_creation": false, + "cidr_block": "10.0.1.0/24", + "customer_owned_ipv4_pool": null, + "enable_dns64": false, + "enable_lni_at_device_index": null, + "enable_resource_name_dns_a_record_on_launch": false, + "enable_resource_name_dns_aaaa_record_on_launch": false, + "ipv6_cidr_block": null, + "ipv6_native": false, + "map_customer_owned_ip_on_launch": null, + "map_public_ip_on_launch": true, + "outpost_arn": null, + "tags": null, + "timeouts": null + }, + "sensitive_values": { + "tags_all": {} + } + }, + { + "address": "aws_vpc.main", + "mode": "managed", + "type": "aws_vpc", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_generated_ipv6_cidr_block": null, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "ipv4_ipam_pool_id": null, + "ipv4_netmask_length": null, + "ipv6_ipam_pool_id": null, + "ipv6_netmask_length": null, + "tags": { + "name": "main" + }, + "tags_all": { + "name": "main" + } + }, + "sensitive_values": { + "tags": {}, + "tags_all": {} + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_cluster.cluster", + "mode": "managed", + "type": "aws_ecs_cluster", + "name": "cluster", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "configuration": [], + "name": "cluster", + "service_connect_defaults": [], + "setting": [ + { + "name": "containerInsights", + "value": "enabled" + } + ], + "tags": null + }, + "after_unknown": { + "arn": true, + "capacity_providers": true, + "configuration": [], + "default_capacity_provider_strategy": true, + "id": true, + "service_connect_defaults": [], + "setting": [ + {} + ], + "tags_all": true + }, + "before_sensitive": false, + "after_sensitive": { + "capacity_providers": [], + "configuration": [], + "default_capacity_provider_strategy": [], + "service_connect_defaults": [], + "setting": [ + {} + ], + "tags_all": {} + } + } + }, + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": 1, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_new_deployment": null, + "health_check_grace_period_seconds": null, + "launch_type": "EC2", + "load_balancer": [], + "name": "foo-service", + "network_configuration": [ + { + "assign_public_ip": false, + "security_groups": null + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "propagate_tags": null, + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [], + "service_registries": [], + "tags": null, + "task_definition": null, + "timeouts": null, + "wait_for_steady_state": false + }, + "after_unknown": { + "alarms": [], + "capacity_provider_strategy": [], + "cluster": true, + "deployment_circuit_breaker": [], + "deployment_controller": [], + "iam_role": true, + "id": true, + "load_balancer": [], + "network_configuration": [ + { + "subnets": true + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "platform_version": true, + "service_connect_configuration": [], + "service_registries": [], + "tags_all": true, + "triggers": true + }, + "before_sensitive": false, + "after_sensitive": { + "alarms": [], + "capacity_provider_strategy": [], + "deployment_circuit_breaker": [], + "deployment_controller": [], + "load_balancer": [], + "network_configuration": [ + { + "subnets": [] + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "service_connect_configuration": [], + "service_registries": [], + "tags_all": {}, + "triggers": {} + } + } + }, + { + "address": "aws_subnet.subnet", + "mode": "managed", + "type": "aws_subnet", + "name": "subnet", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_ipv6_address_on_creation": false, + "cidr_block": "10.0.1.0/24", + "customer_owned_ipv4_pool": null, + "enable_dns64": false, + "enable_lni_at_device_index": null, + "enable_resource_name_dns_a_record_on_launch": false, + "enable_resource_name_dns_aaaa_record_on_launch": false, + "ipv6_cidr_block": null, + "ipv6_native": false, + "map_customer_owned_ip_on_launch": null, + "map_public_ip_on_launch": true, + "outpost_arn": null, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "availability_zone": true, + "availability_zone_id": true, + "id": true, + "ipv6_cidr_block_association_id": true, + "owner_id": true, + "private_dns_hostname_type_on_launch": true, + "tags_all": true, + "vpc_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "tags_all": {} + } + } + }, + { + "address": "aws_vpc.main", + "mode": "managed", + "type": "aws_vpc", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_generated_ipv6_cidr_block": null, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "ipv4_ipam_pool_id": null, + "ipv4_netmask_length": null, + "ipv6_ipam_pool_id": null, + "ipv6_netmask_length": null, + "tags": { + "name": "main" + }, + "tags_all": { + "name": "main" + } + }, + "after_unknown": { + "arn": true, + "default_network_acl_id": true, + "default_route_table_id": true, + "default_security_group_id": true, + "dhcp_options_id": true, + "enable_classiclink": true, + "enable_classiclink_dns_support": true, + "enable_dns_hostnames": true, + "enable_network_address_usage_metrics": true, + "id": true, + "ipv6_association_id": true, + "ipv6_cidr_block": true, + "ipv6_cidr_block_network_border_group": true, + "main_route_table_id": true, + "owner_id": true, + "tags": {}, + "tags_all": {} + }, + "before_sensitive": false, + "after_sensitive": { + "tags": {}, + "tags_all": {} + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_cluster.cluster", + "mode": "managed", + "type": "aws_ecs_cluster", + "name": "cluster", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "cluster" + }, + "setting": [ + { + "name": { + "constant_value": "containerInsights" + }, + "value": { + "constant_value": "enabled" + } + } + ] + }, + "schema_version": 0 + }, + { + "address": "aws_ecs_service.service", + "mode": "managed", + "type": "aws_ecs_service", + "name": "service", + "provider_config_key": "aws", + "expressions": { + "cluster": { + "references": [ + "aws_ecs_cluster.cluster.id", + "aws_ecs_cluster.cluster" + ] + }, + "desired_count": { + "constant_value": 1 + }, + "launch_type": { + "constant_value": "EC2" + }, + "name": { + "constant_value": "foo-service" + }, + "network_configuration": [ + { + "assign_public_ip": { + "constant_value": false + }, + "subnets": { + "references": [ + "aws_subnet.subnet.id", + "aws_subnet.subnet" + ] + } + } + ] + }, + "schema_version": 0 + }, + { + "address": "aws_subnet.subnet", + "mode": "managed", + "type": "aws_subnet", + "name": "subnet", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "references": [ + "aws_vpc.main.cidr_block", + "aws_vpc.main" + ] + }, + "map_public_ip_on_launch": { + "constant_value": true + }, + "vpc_id": { + "references": [ + "aws_vpc.main.id", + "aws_vpc.main" + ] + } + }, + "schema_version": 1 + }, + { + "address": "aws_vpc.main", + "mode": "managed", + "type": "aws_vpc", + "name": "main", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "references": [ + "var.vpc_cidr" + ] + }, + "tags": { + "constant_value": { + "name": "main" + } + } + }, + "schema_version": 1 + } + ], + "variables": { + "vpc_cidr": { + "default": "10.0.0.0/16", + "description": "CIDR block for main" + } + } + } + }, + "relevant_attributes": [ + { + "resource": "aws_vpc.main", + "attribute": [ + "id" + ] + }, + { + "resource": "aws_vpc.main", + "attribute": [ + "cidr_block" + ] + }, + { + "resource": "aws_ecs_cluster.cluster", + "attribute": [ + "id" + ] + }, + { + "resource": "aws_subnet.subnet", + "attribute": [ + "id" + ] + } + ], + "timestamp": "2024-01-30T10:43:19Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/good.tf b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/good.tf new file mode 100644 index 00000000..76a9b1f9 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/test/good.tf @@ -0,0 +1,62 @@ +resource "aws_ecs_service" "service" { + name = "foo-service" + cluster = aws_ecs_cluster.cluster.id + desired_count = 1 + launch_type = "EC2" + network_configuration { + assign_public_ip = false + subnets = [aws_subnet.subnet.id] + } + lifecycle { + ignore_changes = [task_definition] + } +} + +resource "aws_ecs_cluster" "cluster" { + name = "cluster" + setting { + name = "containerInsights" + value = "enabled" + } +} + +resource "aws_subnet" "subnet" { + vpc_id = aws_vpc.main.id + cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, 1) ## takes 10.0.0.0/16 --> 10.0.1.0/24 + map_public_ip_on_launch = true +} + +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + tags = { + name = "main" + } +} + +variable "vpc_cidr" { + description = "CIDR block for main" + type = string + default = "10.0.0.0/16" +} + +# Setting up the configuration for using Docker and AWS providers + +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>2.20.0" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +# Configuring docker and AWS as providers +provider "docker" {} + +provider "aws" { + region = "us-west-1" +} \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/validate-ecs-task-public-ip.yaml b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/validate-ecs-task-public-ip.yaml new file mode 100644 index 00000000..5737309f --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-ecs-task-public-ip/validate-ecs-task-public-ip.yaml @@ -0,0 +1,27 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: validate-ecs-task-public-ip + labels: + ecs.aws.tags.kyverno.io: ecs-service + annotations: + policies.kyverno.io/title: Validate ECS Task Public IP + policies.kyverno.io/category: ECS Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/description: >- + ECS tasks with public IP address enabled, are easily reachable from the internet. + This policy validates whether public IP address is enabled on the ECS task +spec: + rules: + - name: validate-ecs-task-public-ip + match: + any: + - (planned_values.root_module.resources[?type=='aws_ecs_service'] | length(@) > `0`): true + assert: + any: + - message: Public IP address should not be enabled + check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_service']): + values: + ~.(network_configuration[?assign_public_ip] || `[]`): + assign_public_ip: false diff --git a/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/README.md b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/README.md new file mode 100644 index 00000000..cec9f1be --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/README.md @@ -0,0 +1,87 @@ +# Validate EFS Volume Encryption + +Amazon EFS file systems can be used with Amazon ECS to export file system data across your fleet of container instances. To ensure encryption is enabled in transit, this policy validates whether transit_encryption is set to ENABLED in the task definition. + +## Policy Details: + +- **Policy Name:** validate-efs-volume-encryption +- **Check Description:** Ensure Transit Encryption is enabled for EFS volumes in ECS Task definitions +- **Policy Category:** AWS ECS Best Practices + +## How It Works: + +### Validation Criteria: + +**Key** : transit_encryption +**Valid Values** : ENABLED | DISABLED +**Type** : string +**Required** : No + +- **Condition:** `transit_encryption` is set to `ENABLED` + - **Result:** PASS + - **Reason:** Transit Encryption is enabled for EFS volumes in ECS Task definitions + + +- **Condition:** `transit_encryption` is set to `DISABLED` + - **Result:** FAIL + - **Reason:** Transit Encryption is disabled for EFS volumes in ECS Task definitions + +- **Condition:** `transit_encryption` is omitted + - **Result:** FAIL + - **Reason:** If `transit_encryption` is omitted, the default value of DISABLED is used. + +### Policy Validation Testing Instructions + +To evaluate and test the policy ensuring ECS task definitions meet security standards: + +1. **Initialize Terraform:** + ```bash + terraform init + ``` + +2. **Create Binary Terraform Plan:** + ```bash + terraform plan -out tfplan.binary + ``` + +3. **Convert Binary to JSON Payload:** + ```bash + terraform show -json tfplan.binary | jq > payload.json + ``` + +4. **Test the Policy with Kyverno:** + ```bash + kyverno-json scan --payload payload.json --policy policy.yaml + ``` + + a. **Test Policy Against Valid Payload:** + ```bash + kyverno-json scan --policy validate-efs-volume-encryption.yaml --payload test/good-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-efs-volume-encryption / validate-efs-volume-encryption / PASSED + Done + ``` + + b. **Test Against Invalid Payload:** + ```bash + kyverno-json scan --policy validate-efs-volume-encryption.yaml --payload test/bad-payload.json + ``` + + This produces the output: + ``` + Loading policies ... + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - validate-efs-volume-encryption / validate-efs-volume-encryption / FAILED: Transit Encryption is not `ENABLED` for EFS volumes in ECS Task definitions: all[0].check.~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition'])[0].(values.volume[].efs_volume_configuration[?transit_encryption=='ENABLED'][] | length(@) > `0`): Invalid value: false: Expected value: true + Done + ``` + +--- \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload.json b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload.json new file mode 100644 index 00000000..439e12d4 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload.json @@ -0,0 +1,228 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [], + "file_system_id": "fs-0123456789abcdef0", + "root_directory": "/opt/data", + "transit_encryption": "", + "transit_encryption_port": 2999 + } + ], + "fsx_windows_file_server_volume_configuration": [], + "host_path": "", + "name": "service-storage" + } + ] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [] + } + ], + "fsx_windows_file_server_volume_configuration": [] + } + ] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [], + "file_system_id": "fs-0123456789abcdef0", + "root_directory": "/opt/data", + "transit_encryption": "", + "transit_encryption_port": 2999 + } + ], + "fsx_windows_file_server_volume_configuration": [], + "host_path": "", + "name": "service-storage" + } + ] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [] + } + ], + "fsx_windows_file_server_volume_configuration": [] + } + ] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [] + } + ], + "fsx_windows_file_server_volume_configuration": [] + } + ] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "volume": [ + { + "efs_volume_configuration": [ + { + "file_system_id": { + "constant_value": "fs-0123456789abcdef0" + }, + "root_directory": { + "constant_value": "/opt/data" + }, + "transit_encryption_port": { + "constant_value": 2999 + } + } + ], + "name": { + "constant_value": "service-storage" + } + } + ] + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-30T13:31:07Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload1.json b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload1.json new file mode 100644 index 00000000..78597db2 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload1.json @@ -0,0 +1,230 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.1-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [], + "file_system_id": "fs-0123456789abcdef0", + "root_directory": "/opt/data", + "transit_encryption": "DISABLED", + "transit_encryption_port": 2999 + } + ], + "fsx_windows_file_server_volume_configuration": [], + "host_path": "", + "name": "service-storage" + } + ] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [] + } + ], + "fsx_windows_file_server_volume_configuration": [] + } + ] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [], + "file_system_id": "fs-0123456789abcdef0", + "root_directory": "/opt/data", + "transit_encryption": "DISABLED", + "transit_encryption_port": 2999 + } + ], + "fsx_windows_file_server_volume_configuration": [], + "host_path": "", + "name": "service-storage" + } + ] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [] + } + ], + "fsx_windows_file_server_volume_configuration": [] + } + ] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [ + { + "docker_volume_configuration": [], + "efs_volume_configuration": [ + { + "authorization_config": [] + } + ], + "fsx_windows_file_server_volume_configuration": [] + } + ] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "volume": [ + { + "efs_volume_configuration": [ + { + "file_system_id": { + "constant_value": "fs-0123456789abcdef0" + }, + "root_directory": { + "constant_value": "/opt/data" + }, + "transit_encryption": { + "constant_value": "DISABLED" + }, + "transit_encryption_port": { + "constant_value": 2999 + } + } + ], + "name": { + "constant_value": "service-storage" + } + } + ] + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-30T13:27:46Z", + "errored": false +} diff --git a/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload2.json b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload2.json new file mode 100644 index 00000000..3ad6cd74 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad-payload2.json @@ -0,0 +1,146 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.2-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "sensitive_values": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_definitions": "[{\"cpu\":512,\"essential\":true,\"image\":\"nginx:1.23.1\",\"memory\":2048,\"name\":\"foo-task\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80}]}]", + "cpu": null, + "ephemeral_storage": [], + "execution_role_arn": null, + "family": "service", + "inference_accelerator": [], + "ipc_mode": null, + "memory": null, + "pid_mode": null, + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": null, + "runtime_platform": [], + "skip_destroy": false, + "tags": null, + "task_role_arn": null, + "volume": [] + }, + "after_unknown": { + "arn": true, + "arn_without_revision": true, + "ephemeral_storage": [], + "id": true, + "inference_accelerator": [], + "network_mode": true, + "placement_constraints": [], + "proxy_configuration": [], + "revision": true, + "runtime_platform": [], + "tags_all": true, + "volume": [] + }, + "before_sensitive": false, + "after_sensitive": { + "ephemeral_storage": [], + "inference_accelerator": [], + "placement_constraints": [], + "proxy_configuration": [], + "runtime_platform": [], + "tags_all": {}, + "volume": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-02-05T11:06:06Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad.tf b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad.tf new file mode 100644 index 00000000..98e441f1 --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/bad.tf @@ -0,0 +1,52 @@ +resource "aws_ecs_task_definition" "service" { + family = "service" + container_definitions = < 4.0", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + }, + "docker": { + "name": "docker", + "full_name": "registry.terraform.io/kreuzwerker/docker", + "version_constraint": "~> 2.20.0" + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ecs_task_definition.service", + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "service", + "provider_config_key": "aws", + "expressions": { + "container_definitions": { + "constant_value": " [\n {\n \"name\" : \"foo-task\",\n \"image\" : \"nginx:1.23.1\",\n \"cpu\" : 512,\n \"memory\" : 2048,\n \"essential\" : true,\n \"portMappings\" : [\n {\n \"containerPort\" : 80,\n \"hostPort\" : 80\n }\n ]\n }\n ]\n" + }, + "family": { + "constant_value": "service" + }, + "volume": [ + { + "efs_volume_configuration": [ + { + "file_system_id": { + "constant_value": "fs-0123456789abcdef0" + }, + "root_directory": { + "constant_value": "/opt/data" + }, + "transit_encryption": { + "constant_value": "ENABLED" + }, + "transit_encryption_port": { + "constant_value": 2999 + } + } + ], + "name": { + "constant_value": "service-storage" + } + } + ] + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2024-01-30T13:27:46Z", + "errored": false + } + \ No newline at end of file diff --git a/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/good.tf b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/good.tf new file mode 100644 index 00000000..d4d19a7d --- /dev/null +++ b/terraform-best-practices/aws/ecs/validate-efs-volume-encryption/test/good.tf @@ -0,0 +1,53 @@ +resource "aws_ecs_task_definition" "service" { + family = "service" + container_definitions = <- + This policy validates whether transit_encryption is set to ENABLED in the task definition. +spec: + rules: + - name: validate-efs-volume-encryption + match: + any: + - (planned_values.root_module.resources[?type=='aws_ecs_task_definition'] | length(@) > `0`): true + assert: + all: + - message: Transit Encryption is not `ENABLED` for EFS volumes in ECS Task definitions + check: + ~.(planned_values.root_module.resources[?type=='aws_ecs_task_definition']): + (values.volume[].efs_volume_configuration[?transit_encryption=='ENABLED'][] | length(@) > `0`): true