diff --git a/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/README.md b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/README.md new file mode 100644 index 00000000..e02c32be --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/README.md @@ -0,0 +1,73 @@ +# Check Dead Letter Queue Config + +Dead Letter Queues (DLQs) allow Lambda functions to be set up with an SQS queue or SNS topic to capture information about failed asynchronous requests. When a Lambda function's processing fails and the request has exhausted its retries, the Lambda service can store details of the failed request to the configured DLQ. These failed messages can then be examined to determine the cause of failures. + +## Policy Details: + +- **Policy Name:** check-dead-letter-queue-config +- **Check Description:** This policy ensures that AWS Lambda function is configured for a Dead Letter Queue(DLQ) +- **Policy Category:** AWS Lambda Best Practices + +### Policy Validation Testing Instructions + +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 --payload test/good-test/good-payload-01.json --policy check-dead-letter-queue-config.yaml --bindings test/binding.yaml + ``` + + This produces the output: + ``` + Loading policies ... + Loading bindings ... + - analyzer -> map[resource:map[type:terraform-plan]] + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - PASSED (POLICY=check-dead-letter-queue-config, RULE=check-dead-letter-queue-config) + Done + ``` + + b. **Test Against Invalid Payload:** + ``` + kyverno-json scan --payload test/bad-test/bad-payload-01.json --policy check-dead-letter-queue-config.yaml --bindings test/binding.yaml + ``` + + This produces the output: + ``` + Loading policies ... + Loading bindings ... + - analyzer -> map[resource:map[type:terraform-plan]] + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - FAILED (POLICY=check-dead-letter-queue-config, RULE=check-dead-letter-queue-config) + -> AWS Lambda function should be configured for a Dead Letter Queue(DLQ) (CHECK=spec.rules[0].assert.all[0]) + -> Invalid value: false: Expected value: true (PATH=~.(planned_values.root_module.resources[?type=='aws_lambda_function'])[0].values.(dead_letter_config != `[]`)) + Done + ``` + +--- \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/check-dead-letter-queue-config.yaml b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/check-dead-letter-queue-config.yaml new file mode 100644 index 00000000..c1a8d192 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/check-dead-letter-queue-config.yaml @@ -0,0 +1,25 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: check-dead-letter-queue-config + annotations: + policies.kyverno.io/title: check-dead-letter-queue-config + policies.kyverno.io/category: AWS Lambda Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/description: >- + Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) +spec: + rules: + - name: check-dead-letter-queue-config + match: + all: + - ($analyzer.resource.type): terraform-plan + - (planned_values.root_module.resources[?type=='aws_lambda_function'] | length(@) > `0`): true + assert: + all: + - message: AWS Lambda function should be configured for a Dead Letter Queue(DLQ) + check: + ~.(planned_values.root_module.resources[?type=='aws_lambda_function']): + values: + (dead_letter_config != `[]`): true + diff --git a/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/bad-test/bad-01.tf b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/bad-test/bad-01.tf new file mode 100644 index 00000000..90ef5ad6 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/bad-test/bad-01.tf @@ -0,0 +1,45 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.32" + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_lambda_function" "example" { + function_name = "example_lambda" + handler = "index.lambda_handler" + role = aws_iam_role.lambda_exec.arn + runtime = "python3.8" + filename = "" +} + +resource "aws_iam_role" "lambda_exec" { + name = "lambda_exec_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_exec_policy" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/bad-test/bad-payload-01.json b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/bad-test/bad-payload-01.json new file mode 100644 index 00000000..48e0ddd8 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/bad-test/bad-payload-01.json @@ -0,0 +1,340 @@ +{ + "format_version": "1.2", + "terraform_version": "1.8.4", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "sensitive_values": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "sensitive_values": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "name_prefix": true, + "tags_all": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "after_unknown": { + "architectures": true, + "arn": true, + "code_sha256": true, + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": true, + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "logging_config": true, + "qualified_arn": true, + "qualified_invoke_arn": true, + "role": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "snap_start": [], + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [] + }, + "before_sensitive": false, + "after_sensitive": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": ">= 5.32.0", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": {}, + "name": { + "constant_value": "lambda_exec_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "constant_value": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.name", + "aws_iam_role.lambda_exec" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "filename": { + "constant_value": "" + }, + "function_name": { + "constant_value": "example_lambda" + }, + "handler": { + "constant_value": "index.lambda_handler" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.arn", + "aws_iam_role.lambda_exec" + ] + }, + "runtime": { + "constant_value": "python3.8" + } + }, + "schema_version": 0 + } + ] + } + }, + "relevant_attributes": [ + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "name" + ] + }, + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "arn" + ] + } + ], + "timestamp": "2024-07-03T18:08:59Z", + "applyable": true, + "complete": true, + "errored": false +} diff --git a/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/binding.yaml b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/binding.yaml new file mode 100644 index 00000000..a3dd760d --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/binding.yaml @@ -0,0 +1,3 @@ +analyzer: + resource: + type: terraform-plan \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/chainsaw-test.yaml b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/chainsaw-test.yaml new file mode 100644 index 00000000..5b79c48f --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/chainsaw-test.yaml @@ -0,0 +1,61 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: good-test-01 +spec: + steps: + - name: kyverno-json + try: + - script: + content: | + set -e + kyverno-json scan --payload ./good-test/good-payload-01.json --policy ../check-dead-letter-queue-config.yaml --output json --bindings ./binding.yaml + check: + ($error): ~ + (json_parse($stdout)): + - results: + - policy: + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-dead-letter-queue-config + rules: + - rule: + name: check-dead-letter-queue-config + error: ~ + violations: ~ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: bad-test-01 +spec: + steps: + - name: kyverno-json + try: + - script: + content: | + set -e + kyverno-json scan --payload ./bad-test/bad-payload-01.json --policy ../check-dead-letter-queue-config.yaml --output json --bindings ./binding.yaml + check: + ($error): ~ + (json_parse($stdout)): + - results: + - policy: + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-dead-letter-queue-config + rules: + - rule: + name: check-dead-letter-queue-config + error: ~ + violations: + - (contains(message, 'AWS Lambda function should be configured for a Dead Letter Queue(DLQ)')): true + errors: + - type: FieldValueInvalid + value: false + detail: 'Expected value: true' + diff --git a/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/good-test/good-01.tf b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/good-test/good-01.tf new file mode 100644 index 00000000..e71b5080 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/good-test/good-01.tf @@ -0,0 +1,48 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.32" + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_lambda_function" "example" { + function_name = "example_lambda" + handler = "index.lambda_handler" + role = aws_iam_role.lambda_exec.arn + runtime = "python3.8" + filename = "" + dead_letter_config { + target_arn = "arn:aws:sqs:us-west-2:123456789012:example_queue" + } +} + +resource "aws_iam_role" "lambda_exec" { + name = "lambda_exec_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_exec_policy" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/good-test/good-payload-01.json b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/good-test/good-payload-01.json new file mode 100644 index 00000000..ac24f302 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-dead-letter-queue-config/test/good-test/good-payload-01.json @@ -0,0 +1,361 @@ +{ + "format_version": "1.2", + "terraform_version": "1.8.4", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "sensitive_values": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [ + { + "target_arn": "arn:aws:sqs:us-west-2:123456789012:example_queue" + } + ], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "sensitive_values": { + "architectures": [], + "dead_letter_config": [ + {} + ], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "name_prefix": true, + "tags_all": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [ + { + "target_arn": "arn:aws:sqs:us-west-2:123456789012:example_queue" + } + ], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "after_unknown": { + "architectures": true, + "arn": true, + "code_sha256": true, + "dead_letter_config": [ + {} + ], + "environment": [], + "ephemeral_storage": true, + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "logging_config": true, + "qualified_arn": true, + "qualified_invoke_arn": true, + "role": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "snap_start": [], + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [] + }, + "before_sensitive": false, + "after_sensitive": { + "architectures": [], + "dead_letter_config": [ + {} + ], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": ">= 5.32.0", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": {}, + "name": { + "constant_value": "lambda_exec_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "constant_value": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.name", + "aws_iam_role.lambda_exec" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "dead_letter_config": [ + { + "target_arn": { + "constant_value": "arn:aws:sqs:us-west-2:123456789012:example_queue" + } + } + ], + "filename": { + "constant_value": "" + }, + "function_name": { + "constant_value": "example_lambda" + }, + "handler": { + "constant_value": "index.lambda_handler" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.arn", + "aws_iam_role.lambda_exec" + ] + }, + "runtime": { + "constant_value": "python3.8" + } + }, + "schema_version": 0 + } + ] + } + }, + "relevant_attributes": [ + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "name" + ] + }, + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "arn" + ] + } + ], + "timestamp": "2024-07-03T18:11:12Z", + "applyable": true, + "complete": true, + "errored": false +} diff --git a/terraform/plan/lambda-best-practices/check-lambda-public-access/README.md b/terraform/plan/lambda-best-practices/check-lambda-public-access/README.md new file mode 100644 index 00000000..1f3c29ea --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-public-access/README.md @@ -0,0 +1,75 @@ +# Check Lambda Public Access + +Granting public access to AWS Lambda functions is not recommended for security reasons. Restricting public access can help you prevent unauthorized invocations, which could compromise your data or incur unwanted costs. + +To restrict access to your Lambda functions, specify the AWS account IDs or the Amazon Resource Names (ARNs) of the IAM users, roles, or services explicitly in the `principal` attribute instead of using `*`. This ensures that only trusted entities can invoke your Lambda functions, enhancing your security posture. + +## Policy Details: + +- **Policy Name:** check-lambda-public-access +- **Check Description:** This policy ensures that AWS Lambda function is not publicly accessible +- **Policy Category:** AWS Lambda Best Practices + +### Policy Validation Testing Instructions + +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 --payload test/good-test/good-payload-01.json --policy check-lambda-public-access.yaml --bindings test/binding.yaml + ``` + + This produces the output: + ``` + Loading policies ... + Loading bindings ... + - analyzer -> map[resource:map[type:terraform-plan]] + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - PASSED (POLICY=check-lambda-public-access, RULE=check-lambda-public-access) + Done + ``` + + b. **Test Against Invalid Payload:** + ``` + kyverno-json scan --payload test/bad-test/bad-payload-01.json --policy check-lambda-public-access.yaml --bindings test/binding.yaml + ``` + + This produces the output: + ``` + Loading policies ... + Loading bindings ... + - analyzer -> map[resource:map[type:terraform-plan]] + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - FAILED (POLICY=check-lambda-public-access, RULE=check-lambda-public-access) + -> Principal must be set to a specific resource or user account and not '*' (CHECK=spec.rules[0].assert.all[0]) + -> Invalid value: false: Expected value: true (PATH=~.(planned_values.root_module.resources[?type=='aws_lambda_permission'])[0].values.(principal != '*')) + Done + ``` + +--- \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-lambda-public-access/check-lambda-public-access.yaml b/terraform/plan/lambda-best-practices/check-lambda-public-access/check-lambda-public-access.yaml new file mode 100644 index 00000000..339f86f6 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-public-access/check-lambda-public-access.yaml @@ -0,0 +1,25 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: check-lambda-public-access + annotations: + policies.kyverno.io/title: check-lambda-public-access + policies.kyverno.io/category: AWS Lambda Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/description: >- + This policy ensures that AWS Lambda function is not publicly accessible +spec: + rules: + - name: check-lambda-public-access + match: + all: + - ($analyzer.resource.type): terraform-plan + - (planned_values.root_module.resources[?type=='aws_lambda_function'] | length(@) > `0`): true + assert: + all: + - message: Principal must be set to a specific resource or user account and not '*' + check: + ~.(planned_values.root_module.resources[?type=='aws_lambda_permission']): + values: + (principal != '*'): true + diff --git a/terraform/plan/lambda-best-practices/check-lambda-public-access/test/bad-test/bad-01.tf b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/bad-test/bad-01.tf new file mode 100644 index 00000000..cde0647c --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/bad-test/bad-01.tf @@ -0,0 +1,51 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.32" + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_lambda_function" "example" { + function_name = "example_lambda" + handler = "index.lambda_handler" + role = aws_iam_role.lambda_exec.arn + runtime = "python3.8" + filename = "" +} + +resource "aws_lambda_permission" "example_permission" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.example.function_name + principal = "*" +} + +resource "aws_iam_role" "lambda_exec" { + name = "lambda_exec_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_exec_policy" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/plan/lambda-best-practices/check-lambda-public-access/test/bad-test/bad-payload-01.json b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/bad-test/bad-payload-01.json new file mode 100644 index 00000000..d44f93bc --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/bad-test/bad-payload-01.json @@ -0,0 +1,419 @@ +{ + "format_version": "1.2", + "terraform_version": "1.8.4", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "sensitive_values": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "sensitive_values": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "*", + "principal_org_id": null, + "qualifier": null, + "source_account": null, + "source_arn": null + }, + "sensitive_values": {} + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "name_prefix": true, + "tags_all": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "after_unknown": { + "architectures": true, + "arn": true, + "code_sha256": true, + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": true, + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "logging_config": true, + "qualified_arn": true, + "qualified_invoke_arn": true, + "role": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "snap_start": [], + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [] + }, + "before_sensitive": false, + "after_sensitive": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "*", + "principal_org_id": null, + "qualifier": null, + "source_account": null, + "source_arn": null + }, + "after_unknown": { + "id": true, + "statement_id": true, + "statement_id_prefix": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "\u003e= 5.32.0", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": {}, + "name": { + "constant_value": "lambda_exec_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "constant_value": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.name", + "aws_iam_role.lambda_exec" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "filename": { + "constant_value": "" + }, + "function_name": { + "constant_value": "example_lambda" + }, + "handler": { + "constant_value": "index.lambda_handler" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.arn", + "aws_iam_role.lambda_exec" + ] + }, + "runtime": { + "constant_value": "python3.8" + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_config_key": "aws", + "expressions": { + "action": { + "constant_value": "lambda:InvokeFunction" + }, + "function_name": { + "references": [ + "aws_lambda_function.example.function_name", + "aws_lambda_function.example" + ] + }, + "principal": { + "constant_value": "*" + } + }, + "schema_version": 0 + } + ] + } + }, + "relevant_attributes": [ + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "name" + ] + }, + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "arn" + ] + }, + { + "resource": "aws_lambda_function.example", + "attribute": [ + "function_name" + ] + } + ], + "timestamp": "2024-06-23T15:30:55Z", + "applyable": true, + "complete": true, + "errored": false +} \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-lambda-public-access/test/binding.yaml b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/binding.yaml new file mode 100644 index 00000000..a3dd760d --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/binding.yaml @@ -0,0 +1,3 @@ +analyzer: + resource: + type: terraform-plan \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-lambda-public-access/test/chainsaw-test.yaml b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/chainsaw-test.yaml new file mode 100644 index 00000000..03f46edf --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/chainsaw-test.yaml @@ -0,0 +1,61 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: good-test-01 +spec: + steps: + - name: kyverno-json + try: + - script: + content: | + set -e + kyverno-json scan --payload ./good-test/good-payload-01.json --policy ../check-lambda-public-access.yaml --output json --bindings ./binding.yaml + check: + ($error): ~ + (json_parse($stdout)): + - results: + - policy: + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-lambda-public-access + rules: + - rule: + name: check-lambda-public-access + error: ~ + violations: ~ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: bad-test-01 +spec: + steps: + - name: kyverno-json + try: + - script: + content: | + set -e + kyverno-json scan --payload ./bad-test/bad-payload-01.json --policy ../check-lambda-public-access.yaml --output json --bindings ./binding.yaml + check: + ($error): ~ + (json_parse($stdout)): + - results: + - policy: + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-lambda-public-access + rules: + - rule: + name: check-lambda-public-access + error: ~ + violations: + - (contains(message, 'Principal must be set to a specific resource or user account and not \'*\'')): true + errors: + - type: FieldValueInvalid + value: false + detail: 'Expected value: true' + diff --git a/terraform/plan/lambda-best-practices/check-lambda-public-access/test/good-test/good-01.tf b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/good-test/good-01.tf new file mode 100644 index 00000000..18586e8c --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/good-test/good-01.tf @@ -0,0 +1,51 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.32" + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_lambda_function" "example" { + function_name = "example_lambda" + handler = "index.lambda_handler" + role = aws_iam_role.lambda_exec.arn + runtime = "python3.8" + filename = "" +} + +resource "aws_lambda_permission" "example_permission" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.example.function_name + principal = "events.amazonaws.com" +} + +resource "aws_iam_role" "lambda_exec" { + name = "lambda_exec_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_exec_policy" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/plan/lambda-best-practices/check-lambda-public-access/test/good-test/good-payload-01.json b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/good-test/good-payload-01.json new file mode 100644 index 00000000..96beb2fd --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-public-access/test/good-test/good-payload-01.json @@ -0,0 +1,419 @@ +{ + "format_version": "1.2", + "terraform_version": "1.8.4", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "sensitive_values": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "sensitive_values": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "events.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": null, + "source_arn": null + }, + "sensitive_values": {} + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "name_prefix": true, + "tags_all": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "after_unknown": { + "architectures": true, + "arn": true, + "code_sha256": true, + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": true, + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "logging_config": true, + "qualified_arn": true, + "qualified_invoke_arn": true, + "role": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "snap_start": [], + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [] + }, + "before_sensitive": false, + "after_sensitive": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "events.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": null, + "source_arn": null + }, + "after_unknown": { + "id": true, + "statement_id": true, + "statement_id_prefix": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "\u003e= 5.32.0", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": {}, + "name": { + "constant_value": "lambda_exec_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "constant_value": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.name", + "aws_iam_role.lambda_exec" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "filename": { + "constant_value": "" + }, + "function_name": { + "constant_value": "example_lambda" + }, + "handler": { + "constant_value": "index.lambda_handler" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.arn", + "aws_iam_role.lambda_exec" + ] + }, + "runtime": { + "constant_value": "python3.8" + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_config_key": "aws", + "expressions": { + "action": { + "constant_value": "lambda:InvokeFunction" + }, + "function_name": { + "references": [ + "aws_lambda_function.example.function_name", + "aws_lambda_function.example" + ] + }, + "principal": { + "constant_value": "events.amazonaws.com" + } + }, + "schema_version": 0 + } + ] + } + }, + "relevant_attributes": [ + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "name" + ] + }, + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "arn" + ] + }, + { + "resource": "aws_lambda_function.example", + "attribute": [ + "function_name" + ] + } + ], + "timestamp": "2024-06-23T15:31:50Z", + "applyable": true, + "complete": true, + "errored": false +} \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-lambda-vpc/README.md b/terraform/plan/lambda-best-practices/check-lambda-vpc/README.md new file mode 100644 index 00000000..fe5964d6 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-vpc/README.md @@ -0,0 +1,73 @@ +# Check Lambda VPC + +By default, Lambda functions run in a Lambda-managed VPC that has internet access. To access resources in a VPC in your account, you can add a VPC configuration to a function. This restricts the function to resources within that VPC, unless the VPC has internet access. Running Lambda functions within a VPC provides better isolation and control over network access. + +## Policy Details: + +- **Policy Name:** check-lambda-vpc +- **Check Description:** This policy validates whether vpc_config is specified for the Lambda function. +- **Policy Category:** AWS Lambda Best Practices + +### Policy Validation Testing Instructions + +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 --payload test/good-test/good-payload-01.json --policy check-lambda-vpc.yaml --bindings test/binding.yaml + ``` + + This produces the output: + ``` + Loading policies ... + Loading bindings ... + - analyzer -> map[resource:map[type:terraform-plan]] + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - PASSED (POLICY=check-lambda-vpc, RULE=check-lambda-vpc) + Done + ``` + + b. **Test Against Invalid Payload:** + ``` + kyverno-json scan --payload test/bad-test/bad-payload-01.json --policy check-lambda-vpc.yaml --bindings test/binding.yaml + ``` + + This produces the output: + ``` + Loading policies ... + Loading bindings ... + - analyzer -> map[resource:map[type:terraform-plan]] + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - FAILED (POLICY=check-lambda-vpc, RULE=check-lambda-vpc) + -> 'vpc_config' must be present for the Lambda function (CHECK=spec.rules[0].assert.all[0]) + -> Invalid value: false: Expected value: true (PATH=~.(planned_values.root_module.resources[?type=='aws_lambda_function'])[0].values.(vpc_config != `[]`)) + Done + ``` + +--- \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-lambda-vpc/check-lambda-vpc.yaml b/terraform/plan/lambda-best-practices/check-lambda-vpc/check-lambda-vpc.yaml new file mode 100644 index 00000000..5d25ce44 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-vpc/check-lambda-vpc.yaml @@ -0,0 +1,26 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: check-lambda-vpc + annotations: + policies.kyverno.io/title: check-lambda-vpc + policies.kyverno.io/category: AWS Lambda Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/description: >- + VPC provides isolation and enhanced security to Lambda functions. + This policy validates whether vpc_config is specified for the Lambda function. +spec: + rules: + - name: check-lambda-vpc + match: + all: + - ($analyzer.resource.type): terraform-plan + - (planned_values.root_module.resources[?type=='aws_lambda_function'] | length(@) > `0`): true + assert: + all: + - message: "'vpc_config' must be present for the Lambda function" + check: + ~.(planned_values.root_module.resources[?type=='aws_lambda_function']): + values: + (vpc_config != `[]`): true + diff --git a/terraform/plan/lambda-best-practices/check-lambda-vpc/test/bad-test/bad-01.tf b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/bad-test/bad-01.tf new file mode 100644 index 00000000..e0a91671 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/bad-test/bad-01.tf @@ -0,0 +1,46 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.32" + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_lambda_function" "example" { + function_name = "example_lambda" + handler = "index.lambda_handler" + role = aws_iam_role.lambda_exec.arn + runtime = "python3.8" + filename = "" +} + + +resource "aws_iam_role" "lambda_exec" { + name = "lambda_exec_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_exec_policy" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/plan/lambda-best-practices/check-lambda-vpc/test/bad-test/bad-payload-01.json b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/bad-test/bad-payload-01.json new file mode 100644 index 00000000..95452faa --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/bad-test/bad-payload-01.json @@ -0,0 +1,340 @@ +{ + "format_version": "1.2", + "terraform_version": "1.8.4", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "sensitive_values": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "sensitive_values": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "name_prefix": true, + "tags_all": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "after_unknown": { + "architectures": true, + "arn": true, + "code_sha256": true, + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": true, + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "logging_config": true, + "qualified_arn": true, + "qualified_invoke_arn": true, + "role": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "snap_start": [], + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [] + }, + "before_sensitive": false, + "after_sensitive": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": ">= 5.32.0", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": {}, + "name": { + "constant_value": "lambda_exec_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "constant_value": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.name", + "aws_iam_role.lambda_exec" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "filename": { + "constant_value": "" + }, + "function_name": { + "constant_value": "example_lambda" + }, + "handler": { + "constant_value": "index.lambda_handler" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.arn", + "aws_iam_role.lambda_exec" + ] + }, + "runtime": { + "constant_value": "python3.8" + } + }, + "schema_version": 0 + } + ] + } + }, + "relevant_attributes": [ + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "name" + ] + }, + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "arn" + ] + } + ], + "timestamp": "2024-06-26T17:14:11Z", + "applyable": true, + "complete": true, + "errored": false +} diff --git a/terraform/plan/lambda-best-practices/check-lambda-vpc/test/binding.yaml b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/binding.yaml new file mode 100644 index 00000000..a3dd760d --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/binding.yaml @@ -0,0 +1,3 @@ +analyzer: + resource: + type: terraform-plan \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-lambda-vpc/test/chainsaw-test.yaml b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/chainsaw-test.yaml new file mode 100644 index 00000000..780925f4 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/chainsaw-test.yaml @@ -0,0 +1,60 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: good-test-01 +spec: + steps: + - name: kyverno-json + try: + - script: + content: | + set -e + kyverno-json scan --payload ./good-test/good-payload-01.json --policy ../check-lambda-vpc.yaml --output json --bindings ./binding.yaml + check: + ($error): ~ + (json_parse($stdout)): + - results: + - policy: + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-lambda-vpc + rules: + - rule: + name: check-lambda-vpc + error: ~ + violations: ~ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: bad-test-01 +spec: + steps: + - name: kyverno-json + try: + - script: + content: | + set -e + kyverno-json scan --payload ./bad-test/bad-payload-01.json --policy ../check-lambda-vpc.yaml --output json --bindings ./binding.yaml + check: + ($error): ~ + (json_parse($stdout)): + - results: + - policy: + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-lambda-vpc + rules: + - rule: + name: check-lambda-vpc + error: ~ + violations: + - (contains(message, '\'vpc_config\' must be present for the Lambda function')): true + errors: + - type: FieldValueInvalid + value: false + detail: 'Expected value: true' diff --git a/terraform/plan/lambda-best-practices/check-lambda-vpc/test/good-test/good-01.tf b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/good-test/good-01.tf new file mode 100644 index 00000000..0b4c998a --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/good-test/good-01.tf @@ -0,0 +1,50 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.32" + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_lambda_function" "example" { + function_name = "example_lambda" + handler = "index.lambda_handler" + role = aws_iam_role.lambda_exec.arn + runtime = "python3.8" + filename = "" + + vpc_config { + subnet_ids = ["subnet-12345678", "subnet-87654321"] + security_group_ids = ["sg-abcdef12"] + } +} + +resource "aws_iam_role" "lambda_exec" { + name = "lambda_exec_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_exec_policy" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/plan/lambda-best-practices/check-lambda-vpc/test/good-test/good-payload-01.json b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/good-test/good-payload-01.json new file mode 100644 index 00000000..2770dcc6 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-lambda-vpc/test/good-test/good-payload-01.json @@ -0,0 +1,408 @@ +{ + "format_version": "1.2", + "terraform_version": "1.8.4", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "sensitive_values": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [ + { + "ipv6_allowed_for_dual_stack": false, + "security_group_ids": [ + "sg-abcdef12" + ], + "subnet_ids": [ + "subnet-12345678", + "subnet-87654321" + ] + } + ] + }, + "sensitive_values": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [ + { + "security_group_ids": [ + false + ], + "subnet_ids": [ + false, + false + ] + } + ] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "name_prefix": true, + "tags_all": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [ + { + "ipv6_allowed_for_dual_stack": false, + "security_group_ids": [ + "sg-abcdef12" + ], + "subnet_ids": [ + "subnet-12345678", + "subnet-87654321" + ] + } + ] + }, + "after_unknown": { + "architectures": true, + "arn": true, + "code_sha256": true, + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": true, + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "logging_config": true, + "qualified_arn": true, + "qualified_invoke_arn": true, + "role": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "snap_start": [], + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [ + { + "security_group_ids": [ + false + ], + "subnet_ids": [ + false, + false + ], + "vpc_id": true + } + ] + }, + "before_sensitive": false, + "after_sensitive": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [ + { + "security_group_ids": [ + false + ], + "subnet_ids": [ + false, + false + ] + } + ] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": ">= 5.32.0", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": {}, + "name": { + "constant_value": "lambda_exec_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "constant_value": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.name", + "aws_iam_role.lambda_exec" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "filename": { + "constant_value": "" + }, + "function_name": { + "constant_value": "example_lambda" + }, + "handler": { + "constant_value": "index.lambda_handler" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.arn", + "aws_iam_role.lambda_exec" + ] + }, + "runtime": { + "constant_value": "python3.8" + }, + "vpc_config": [ + { + "security_group_ids": { + "constant_value": [ + "sg-abcdef12" + ] + }, + "subnet_ids": { + "constant_value": [ + "subnet-12345678", + "subnet-87654321" + ] + } + } + ] + }, + "schema_version": 0 + } + ] + } + }, + "relevant_attributes": [ + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "arn" + ] + }, + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "name" + ] + } + ], + "timestamp": "2024-06-26T17:17:12Z", + "applyable": true, + "complete": true, + "errored": false +} diff --git a/terraform/plan/lambda-best-practices/check-source-arn-account/README.md b/terraform/plan/lambda-best-practices/check-source-arn-account/README.md new file mode 100644 index 00000000..4fcd41b2 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-source-arn-account/README.md @@ -0,0 +1,73 @@ +# Check Source Arn Account + +Granting unrestricted access to Lambda functions from other AWS services can compromise the security of your system. To mitigate this risk, it is essential to restrict access by using the `source_arn` or `source_account` parameters. By doing so, you ensure that access is only allowed from specific resources and accounts, respectively, thereby enhancing the security of your applications and data within AWS. + +## Policy Details: + +- **Policy Name:** check-source-arn-account +- **Check Description:** This policy ensures that AWS Lambda function permissions delegated to AWS services are limited by SourceArn or SourceAccount +- **Policy Category:** AWS Lambda Best Practices + +### Policy Validation Testing Instructions + +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 --payload test/good-test/good-payload-01.json --policy check-source-arn-account.yaml --bindings test/binding.yaml + ``` + + This produces the output: + ``` + Loading policies ... + Loading bindings ... + - analyzer -> map[resource:map[type:terraform-plan]] + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - PASSED (POLICY=check-source-arn-account, RULE=check-source-arn-account) + Done + ``` + + b. **Test Against Invalid Payload:** + ``` + kyverno-json scan --payload test/bad-test/bad-payload-01.json --policy check-source-arn-account.yaml --bindings test/binding.yaml + ``` + + This produces the output: + ``` + Loading policies ... + Loading bindings ... + - analyzer -> map[resource:map[type:terraform-plan]] + Loading payload ... + Pre processing ... + Running ( evaluating 1 resource against 1 policy ) ... + - FAILED (POLICY=check-source-arn-account, RULE=check-source-arn-account) + -> AWS Lambda function permissions delegated to AWS services should be limited by SourceArn or SourceAccount (CHECK=spec.rules[0].assert.all[0]) + -> Invalid value: true: Expected value: false (PATH=~.(planned_values.root_module.resources[?type=='aws_lambda_permission'].values[?ends_with(principal, '.amazonaws.com')])[0].(!source_arn == `true` && !source_account == `true`)) + Done + ``` + +--- \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-source-arn-account/check-source-arn-account.yaml b/terraform/plan/lambda-best-practices/check-source-arn-account/check-source-arn-account.yaml new file mode 100644 index 00000000..0213179f --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-source-arn-account/check-source-arn-account.yaml @@ -0,0 +1,24 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: check-source-arn-account + annotations: + policies.kyverno.io/title: check-source-arn-account + policies.kyverno.io/category: AWS Lambda Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/description: >- + This policy ensures that AWS Lambda function permissions delegated to AWS services are limited by SourceArn or SourceAccount +spec: + rules: + - name: check-source-arn-account + match: + all: + - ($analyzer.resource.type): terraform-plan + - (planned_values.root_module.resources[?type=='aws_lambda_permission'].values[?ends_with(principal, '.amazonaws.com')] | length(@) > `0`): true + assert: + all: + - message: AWS Lambda function permissions delegated to AWS services should be limited by SourceArn or SourceAccount + check: + ~.(planned_values.root_module.resources[?type=='aws_lambda_permission'].values[?ends_with(principal, '.amazonaws.com')]): + (!source_arn == `true` && !source_account == `true`): false + diff --git a/terraform/plan/lambda-best-practices/check-source-arn-account/test/bad-test/bad-01.tf b/terraform/plan/lambda-best-practices/check-source-arn-account/test/bad-test/bad-01.tf new file mode 100644 index 00000000..4e2c4868 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-source-arn-account/test/bad-test/bad-01.tf @@ -0,0 +1,51 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.32" + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_lambda_function" "example" { + function_name = "example_lambda" + handler = "index.lambda_handler" + role = aws_iam_role.lambda_exec.arn + runtime = "python3.8" + filename = "" +} + +resource "aws_lambda_permission" "example_permission" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.example.function_name + principal = "apigateway.amazonaws.com" +} + +resource "aws_iam_role" "lambda_exec" { + name = "lambda_exec_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_exec_policy" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/plan/lambda-best-practices/check-source-arn-account/test/bad-test/bad-payload-01.json b/terraform/plan/lambda-best-practices/check-source-arn-account/test/bad-test/bad-payload-01.json new file mode 100644 index 00000000..be7a575e --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-source-arn-account/test/bad-test/bad-payload-01.json @@ -0,0 +1,419 @@ +{ + "format_version": "1.2", + "terraform_version": "1.8.4", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "sensitive_values": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "sensitive_values": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "apigateway.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": null, + "source_arn": null + }, + "sensitive_values": {} + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "name_prefix": true, + "tags_all": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "after_unknown": { + "architectures": true, + "arn": true, + "code_sha256": true, + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": true, + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "logging_config": true, + "qualified_arn": true, + "qualified_invoke_arn": true, + "role": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "snap_start": [], + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [] + }, + "before_sensitive": false, + "after_sensitive": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "apigateway.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": null, + "source_arn": null + }, + "after_unknown": { + "id": true, + "statement_id": true, + "statement_id_prefix": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": ">= 5.32.0", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": {}, + "name": { + "constant_value": "lambda_exec_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "constant_value": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.name", + "aws_iam_role.lambda_exec" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "filename": { + "constant_value": "" + }, + "function_name": { + "constant_value": "example_lambda" + }, + "handler": { + "constant_value": "index.lambda_handler" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.arn", + "aws_iam_role.lambda_exec" + ] + }, + "runtime": { + "constant_value": "python3.8" + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_config_key": "aws", + "expressions": { + "action": { + "constant_value": "lambda:InvokeFunction" + }, + "function_name": { + "references": [ + "aws_lambda_function.example.function_name", + "aws_lambda_function.example" + ] + }, + "principal": { + "constant_value": "apigateway.amazonaws.com" + } + }, + "schema_version": 0 + } + ] + } + }, + "relevant_attributes": [ + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "name" + ] + }, + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "arn" + ] + }, + { + "resource": "aws_lambda_function.example", + "attribute": [ + "function_name" + ] + } + ], + "timestamp": "2024-07-04T18:30:59Z", + "applyable": true, + "complete": true, + "errored": false +} diff --git a/terraform/plan/lambda-best-practices/check-source-arn-account/test/binding.yaml b/terraform/plan/lambda-best-practices/check-source-arn-account/test/binding.yaml new file mode 100644 index 00000000..a3dd760d --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-source-arn-account/test/binding.yaml @@ -0,0 +1,3 @@ +analyzer: + resource: + type: terraform-plan \ No newline at end of file diff --git a/terraform/plan/lambda-best-practices/check-source-arn-account/test/chainsaw-test.yaml b/terraform/plan/lambda-best-practices/check-source-arn-account/test/chainsaw-test.yaml new file mode 100644 index 00000000..ad86581f --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-source-arn-account/test/chainsaw-test.yaml @@ -0,0 +1,61 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: good-test-01 +spec: + steps: + - name: kyverno-json + try: + - script: + content: | + set -e + kyverno-json scan --payload ./good-test/good-payload-01.json --policy ../check-source-arn-account.yaml --output json --bindings ./binding.yaml + check: + ($error): ~ + (json_parse($stdout)): + - results: + - policy: + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-source-arn-account + rules: + - rule: + name: check-source-arn-account + error: ~ + violations: ~ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: bad-test-01 +spec: + steps: + - name: kyverno-json + try: + - script: + content: | + set -e + kyverno-json scan --payload ./bad-test/bad-payload-01.json --policy ../check-source-arn-account.yaml --output json --bindings ./binding.yaml + check: + ($error): ~ + (json_parse($stdout)): + - results: + - policy: + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-source-arn-account + rules: + - rule: + name: check-source-arn-account + error: ~ + violations: + - (contains(message, 'AWS Lambda function permissions delegated to AWS services should be limited by SourceArn or SourceAccount')): true + errors: + - type: FieldValueInvalid + value: true + detail: 'Expected value: false' + diff --git a/terraform/plan/lambda-best-practices/check-source-arn-account/test/good-test/good-01.tf b/terraform/plan/lambda-best-practices/check-source-arn-account/test/good-test/good-01.tf new file mode 100644 index 00000000..b192ad6a --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-source-arn-account/test/good-test/good-01.tf @@ -0,0 +1,67 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.32" + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_lambda_function" "example" { + function_name = "example_lambda" + handler = "index.lambda_handler" + role = aws_iam_role.lambda_exec.arn + runtime = "python3.8" + filename = "" +} + +resource "aws_lambda_permission" "example_permission" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.example.function_name + principal = "s3.amazonaws.com" + source_arn = "arn:aws:s3:::example-bucket" +} + +resource "aws_lambda_permission" "example_permission_2" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.example.function_name + principal = "s3.amazonaws.com" + source_account = "123456789012" +} + +resource "aws_lambda_permission" "example_permission_3" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.example.function_name + principal = "s3.amazonaws.com" + source_arn = "arn:aws:s3:::example-bucket-2" + source_account = "123456789012" +} + +resource "aws_iam_role" "lambda_exec" { + name = "lambda_exec_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_exec_policy" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/plan/lambda-best-practices/check-source-arn-account/test/good-test/good-payload-01.json b/terraform/plan/lambda-best-practices/check-source-arn-account/test/good-test/good-payload-01.json new file mode 100644 index 00000000..4d123f14 --- /dev/null +++ b/terraform/plan/lambda-best-practices/check-source-arn-account/test/good-test/good-payload-01.json @@ -0,0 +1,577 @@ +{ + "format_version": "1.2", + "terraform_version": "1.8.4", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "sensitive_values": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "sensitive_values": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "s3.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": null, + "source_arn": "arn:aws:s3:::example-bucket" + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_permission.example_permission_2", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "s3.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": "123456789012", + "source_arn": null + }, + "sensitive_values": {} + }, + { + "address": "aws_lambda_permission.example_permission_3", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission_3", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "s3.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": "123456789012", + "source_arn": "arn:aws:s3:::example-bucket-2" + }, + "sensitive_values": {} + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "lambda_exec_role", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "name_prefix": true, + "tags_all": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "inline_policy": [], + "managed_policy_arns": [], + "tags_all": {} + } + } + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "lambda_exec_role" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": "", + "function_name": "example_lambda", + "handler": "index.lambda_handler", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 128, + "package_type": "Zip", + "publish": false, + "replace_security_groups_on_destroy": null, + "replacement_security_group_ids": null, + "reserved_concurrent_executions": -1, + "runtime": "python3.8", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "skip_destroy": false, + "snap_start": [], + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "after_unknown": { + "architectures": true, + "arn": true, + "code_sha256": true, + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": true, + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "logging_config": true, + "qualified_arn": true, + "qualified_invoke_arn": true, + "role": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "snap_start": [], + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [] + }, + "before_sensitive": false, + "after_sensitive": { + "architectures": [], + "dead_letter_config": [], + "environment": [], + "ephemeral_storage": [], + "file_system_config": [], + "image_config": [], + "logging_config": [], + "snap_start": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "s3.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": null, + "source_arn": "arn:aws:s3:::example-bucket" + }, + "after_unknown": { + "id": true, + "statement_id": true, + "statement_id_prefix": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_permission.example_permission_2", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "s3.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": "123456789012", + "source_arn": null + }, + "after_unknown": { + "id": true, + "statement_id": true, + "statement_id_prefix": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "aws_lambda_permission.example_permission_3", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission_3", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "action": "lambda:InvokeFunction", + "event_source_token": null, + "function_name": "example_lambda", + "function_url_auth_type": null, + "principal": "s3.amazonaws.com", + "principal_org_id": null, + "qualifier": null, + "source_account": "123456789012", + "source_arn": "arn:aws:s3:::example-bucket-2" + }, + "after_unknown": { + "id": true, + "statement_id": true, + "statement_id_prefix": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": ">= 5.32.0", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_role.lambda_exec", + "mode": "managed", + "type": "aws_iam_role", + "name": "lambda_exec", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": {}, + "name": { + "constant_value": "lambda_exec_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy_attachment.lambda_exec_policy", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "lambda_exec_policy", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "constant_value": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.name", + "aws_iam_role.lambda_exec" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_function.example", + "mode": "managed", + "type": "aws_lambda_function", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "filename": { + "constant_value": "" + }, + "function_name": { + "constant_value": "example_lambda" + }, + "handler": { + "constant_value": "index.lambda_handler" + }, + "role": { + "references": [ + "aws_iam_role.lambda_exec.arn", + "aws_iam_role.lambda_exec" + ] + }, + "runtime": { + "constant_value": "python3.8" + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_permission.example_permission", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission", + "provider_config_key": "aws", + "expressions": { + "action": { + "constant_value": "lambda:InvokeFunction" + }, + "function_name": { + "references": [ + "aws_lambda_function.example.function_name", + "aws_lambda_function.example" + ] + }, + "principal": { + "constant_value": "s3.amazonaws.com" + }, + "source_arn": { + "constant_value": "arn:aws:s3:::example-bucket" + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_permission.example_permission_2", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission_2", + "provider_config_key": "aws", + "expressions": { + "action": { + "constant_value": "lambda:InvokeFunction" + }, + "function_name": { + "references": [ + "aws_lambda_function.example.function_name", + "aws_lambda_function.example" + ] + }, + "principal": { + "constant_value": "s3.amazonaws.com" + }, + "source_account": { + "constant_value": "123456789012" + } + }, + "schema_version": 0 + }, + { + "address": "aws_lambda_permission.example_permission_3", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "example_permission_3", + "provider_config_key": "aws", + "expressions": { + "action": { + "constant_value": "lambda:InvokeFunction" + }, + "function_name": { + "references": [ + "aws_lambda_function.example.function_name", + "aws_lambda_function.example" + ] + }, + "principal": { + "constant_value": "s3.amazonaws.com" + }, + "source_account": { + "constant_value": "123456789012" + }, + "source_arn": { + "constant_value": "arn:aws:s3:::example-bucket-2" + } + }, + "schema_version": 0 + } + ] + } + }, + "relevant_attributes": [ + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "name" + ] + }, + { + "resource": "aws_iam_role.lambda_exec", + "attribute": [ + "arn" + ] + }, + { + "resource": "aws_lambda_function.example", + "attribute": [ + "function_name" + ] + } + ], + "timestamp": "2024-07-04T18:39:27Z", + "applyable": true, + "complete": true, + "errored": false +}