Skip to content

fix: enable API Gateway cache encryption at rest#70

Merged
kaleko merged 1 commit intomainfrom
fix/api-gateway-cache-encryption
Mar 25, 2026
Merged

fix: enable API Gateway cache encryption at rest#70
kaleko merged 1 commit intomainfrom
fix/api-gateway-cache-encryption

Conversation

@kaleko
Copy link
Copy Markdown
Contributor

@kaleko kaleko commented Mar 25, 2026

Adds cache_data_encrypted/cacheDataEncrypted to both Terraform and CDK API Gateway method settings. Resolves KICS critical finding for unencrypted API Gateway cache.

Issue #, if available:

Description of changes:

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Adds cache_data_encrypted/cacheDataEncrypted to both Terraform and CDK
API Gateway method settings. Resolves KICS critical finding for
unencrypted API Gateway cache.
@kaleko kaleko requested a review from a team March 25, 2026 18:07
@github-actions
Copy link
Copy Markdown

Latest scan for commit: 6448eaa | Updated: 2026-03-25 18:12:00 UTC

Security Scan Results

Scan Metadata

  • Project: ASH
  • Scan executed: 2026-03-25T18:11:39+00:00
  • ASH version: 3.2.2

Summary

Scanner Results

The table below shows findings by scanner, with status based on severity thresholds and dependencies:

Column Explanations:

Severity Levels (S/C/H/M/L/I):

  • Suppressed (S): Security findings that have been explicitly suppressed/ignored and don't affect the scanner's pass/fail status
  • Critical (C): The most severe security vulnerabilities requiring immediate remediation (e.g., SQL injection, remote code execution)
  • High (H): Serious security vulnerabilities that should be addressed promptly (e.g., authentication bypasses, privilege escalation)
  • Medium (M): Moderate security risks that should be addressed in normal development cycles (e.g., weak encryption, input validation issues)
  • Low (L): Minor security concerns with limited impact (e.g., information disclosure, weak recommendations)
  • Info (I): Informational findings for awareness with minimal security risk (e.g., code quality suggestions, best practice recommendations)

Other Columns:

  • Time: Duration taken by each scanner to complete its analysis
  • Action: Total number of actionable findings at or above the configured severity threshold that require attention

Scanner Results:

  • PASSED: Scanner found no security issues at or above the configured severity threshold - code is clean for this scanner
  • FAILED: Scanner found security vulnerabilities at or above the threshold that require attention and remediation
  • MISSING: Scanner could not run because required dependencies/tools are not installed or available
  • SKIPPED: Scanner was intentionally disabled or excluded from this scan
  • ERROR: Scanner encountered an execution error and could not complete successfully

Severity Thresholds (Thresh Column):

  • CRITICAL: Only Critical severity findings cause scanner to fail
  • HIGH: High and Critical severity findings cause scanner to fail
  • MEDIUM (MED): Medium, High, and Critical severity findings cause scanner to fail
  • LOW: Low, Medium, High, and Critical severity findings cause scanner to fail
  • ALL: Any finding of any severity level causes scanner to fail

Threshold Source: Values in parentheses indicate where the threshold is configured:

  • (g) = global: Set in the global_settings section of ASH configuration
  • (c) = config: Set in the individual scanner configuration section
  • (s) = scanner: Default threshold built into the scanner itself

Statistics calculation:

  • All statistics are calculated from the final aggregated SARIF report
  • Suppressed findings are counted separately and do not contribute to actionable findings
  • Scanner status is determined by comparing actionable findings to the threshold
Scanner S C H M L I Time Action Result Thresh
bandit 0 0 0 0 0 0 763ms 0 PASSED MED (g)
cdk-nag 0 0 0 0 0 0 40.5s 0 PASSED MED (g)
cfn-nag 0 0 0 0 0 0 5ms 0 PASSED MED (g)
checkov 0 16 0 0 0 0 5.6s 16 FAILED MED (g)
detect-secrets 0 0 0 0 0 0 737ms 0 PASSED MED (g)
grype 0 0 0 0 0 0 43.0s 0 PASSED MED (g)
npm-audit 0 0 0 0 0 0 177ms 0 PASSED MED (g)
opengrep 8 5 0 0 0 0 26.5s 5 FAILED MED (g)
semgrep 8 5 0 0 0 0 19.7s 5 FAILED MED (g)
syft 0 0 0 0 0 0 2.0s 0 PASSED MED (g)

Detailed Findings

Show 26 actionable findings

Finding 1: CKV_AWS_119

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_119
  • Location: infra-terraform/modules/backend/feedback.tf:13-54

Description:
Ensure DynamoDB Tables are encrypted using a KMS Customer Managed CMK

Code Snippet:

resource "aws_dynamodb_table" "feedback" {
  name         = "${var.stack_name_base}-feedback"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "feedbackId"

  attribute {
    name = "feedbackId"
    type = "S"
  }

  attribute {
    name = "feedbackType"
    type = "S"
  }

  attribute {
    name = "timestamp"
    type = "N"
  }

  # GSI for querying by feedbackType with timestamp sorting
  global_secondary_index {
    name            = "feedbackType-timestamp-index"
    hash_key        = "feedbackType"
    range_key       = "timestamp"
    projection_type = "ALL"
  }

  # Deletion protection disabled (allows terraform destroy)
  deletion_protection_enabled = false

  # Point-in-time recovery
  point_in_time_recovery {
    enabled = true
  }

  # Server-side encryption (AWS managed)
  server_side_encryption {
    enabled = true
  }

}

Finding 2: CKV_AWS_158

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_158
  • Location: infra-terraform/modules/backend/feedback.tf:60-64

Description:
Ensure that CloudWatch Log Group is encrypted by KMS

Code Snippet:

resource "aws_cloudwatch_log_group" "feedback_lambda" {
  name              = "/aws/lambda/${var.stack_name_base}-feedback"
  retention_in_days = local.log_retention_days

}

Finding 3: CKV_AWS_117

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_117
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Ensure that AWS Lambda function is configured inside a VPC

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 4: CKV_AWS_173

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_173
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Check encryption settings for Lambda environmental variable

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 5: CKV_AWS_50

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_50
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
X-Ray tracing is enabled for Lambda

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 6: CKV_AWS_116

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_116
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ)

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 7: CKV_AWS_115

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_115
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Ensure that AWS Lambda function is configured for function-level concurrent execution limit

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 8: CKV_AWS_272

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_272
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Ensure AWS Lambda function is configured to validate code-signing

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 9: CKV_AWS_237

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_237
  • Location: infra-terraform/modules/backend/feedback.tf:188-196

Description:
Ensure Create before destroy for API Gateway

Code Snippet:

resource "aws_api_gateway_rest_api" "feedback" {
  name        = "${var.stack_name_base}-feedback-api"
  description = "API Gateway for feedback collection"

  endpoint_configuration {
    types = ["REGIONAL"]
  }

}

Finding 10: CKV_AWS_120

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_120
  • Location: infra-terraform/modules/backend/feedback.tf:340-365

Description:
Ensure API Gateway caching is enabled

Code Snippet:

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  deployment_id = aws_api_gateway_deployment.feedback.id

  # Access logs
  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway_access.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      ip               = "$context.identity.sourceIp"
      caller           = "$context.identity.caller"
      user             = "$context.identity.user"
      requestTime      = "$context.requestTime"
      httpMethod       = "$context.httpMethod"
      resourcePath     = "$context.resourcePath"
      status           = "$context.status"
      protocol         = "$context.protocol"
      responseLength   = "$context.responseLength"
      integrationError = "$context.integrationErrorMessage"
    })
  }


  depends_on = [aws_cloudwatch_log_group.api_gateway_access]
}

Finding 11: CKV_AWS_73

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_73
  • Location: infra-terraform/modules/backend/feedback.tf:340-365

Description:
Ensure API Gateway has X-Ray Tracing enabled

Code Snippet:

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  deployment_id = aws_api_gateway_deployment.feedback.id

  # Access logs
  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway_access.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      ip               = "$context.identity.sourceIp"
      caller           = "$context.identity.caller"
      user             = "$context.identity.user"
      requestTime      = "$context.requestTime"
      httpMethod       = "$context.httpMethod"
      resourcePath     = "$context.resourcePath"
      status           = "$context.status"
      protocol         = "$context.protocol"
      responseLength   = "$context.responseLength"
      integrationError = "$context.integrationErrorMessage"
    })
  }


  depends_on = [aws_cloudwatch_log_group.api_gateway_access]
}

Finding 12: CKV_AWS_158

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_158
  • Location: infra-terraform/modules/backend/feedback.tf:368-372

Description:
Ensure that CloudWatch Log Group is encrypted by KMS

Code Snippet:

resource "aws_cloudwatch_log_group" "api_gateway_access" {
  name              = "/aws/apigateway/${var.stack_name_base}-feedback-api/access-logs"
  retention_in_days = local.log_retention_days

}

Finding 13: CKV2_AWS_53

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_53
  • Location: infra-terraform/modules/backend/feedback.tf:227-233

Description:
Ensure AWS API gateway request is validated

Code Snippet:

resource "aws_api_gateway_method" "post_feedback" {
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  resource_id   = aws_api_gateway_resource.feedback.id
  http_method   = "POST"
  authorization = "COGNITO_USER_POOLS"
  authorizer_id = aws_api_gateway_authorizer.cognito.id
}

Finding 14: CKV2_AWS_53

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_53
  • Location: infra-terraform/modules/backend/feedback.tf:236-241

Description:
Ensure AWS API gateway request is validated

Code Snippet:

resource "aws_api_gateway_method" "options_feedback" {
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  resource_id   = aws_api_gateway_resource.feedback.id
  http_method   = "OPTIONS"
  authorization = "NONE"
}

Finding 15: CKV2_AWS_51

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_51
  • Location: infra-terraform/modules/backend/feedback.tf:340-365

Description:
Ensure AWS API Gateway endpoints uses client certificate authentication

Code Snippet:

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  deployment_id = aws_api_gateway_deployment.feedback.id

  # Access logs
  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway_access.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      ip               = "$context.identity.sourceIp"
      caller           = "$context.identity.caller"
      user             = "$context.identity.user"
      requestTime      = "$context.requestTime"
      httpMethod       = "$context.httpMethod"
      resourcePath     = "$context.resourcePath"
      status           = "$context.status"
      protocol         = "$context.protocol"
      responseLength   = "$context.responseLength"
      integrationError = "$context.integrationErrorMessage"
    })
  }


  depends_on = [aws_cloudwatch_log_group.api_gateway_access]
}

Finding 16: CKV2_AWS_29

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_29
  • Location: infra-terraform/modules/backend/feedback.tf:340-365

Description:
Ensure public API gateway are protected by WAF

Code Snippet:

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  deployment_id = aws_api_gateway_deployment.feedback.id

  # Access logs
  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway_access.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      ip               = "$context.identity.sourceIp"
      caller           = "$context.identity.caller"
      user             = "$context.identity.user"
      requestTime      = "$context.requestTime"
      httpMethod       = "$context.httpMethod"
      resourcePath     = "$context.resourcePath"
      status           = "$context.status"
      protocol         = "$context.protocol"
      responseLength   = "$context.responseLength"
      integrationError = "$context.integrationErrorMessage"
    })
  }


  depends_on = [aws_cloudwatch_log_group.api_gateway_access]
}

Finding 17: terraform.aws.security.aws-dynamodb-table-unencrypted.aws-dynamodb-table-unencrypted

  • Severity: HIGH
  • Scanner: semgrep
  • Rule ID: terraform.aws.security.aws-dynamodb-table-unencrypted.aws-dynamodb-table-unencrypted
  • Location: infra-terraform/modules/backend/feedback.tf:13-54

Description:
By default, AWS DynamoDB Table is encrypted using AWS-managed keys. However, for added security, it's recommended to configure your own AWS KMS encryption key to protect your data in the DynamoDB table. You can either create a new aws_kms_key resource or use the ARN of an existing key in your AWS account to do so.

Code Snippet:

resource "aws_dynamodb_table" "feedback" {
  name         = "${var.stack_name_base}-feedback"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "feedbackId"

  attribute {
    name = "feedbackId"
    type = "S"
  }

  attribute {
    name = "feedbackType"
    type = "S"
  }

  attribute {
    name = "timestamp"
    type = "N"
  }

  # GSI for querying by feedbackType with timestamp sorting
  global_secondary_index {
    name            = "feedbackType-timestamp-index"
    hash_key        = "feedbackType"
    range_key       = "timestamp"
    projection_type = "ALL"
  }

  # Deletion protection disabled (allows terraform destroy)
  deletion_protection_enabled = false

  # Point-in-time recovery
  point_in_time_recovery {
    enabled = true
  }

  # Server-side encryption (AWS managed)
  server_side_encryption {
    enabled = true
  }

}

Finding 18: terraform.aws.security.aws-cloudwatch-log-group-unencrypted.aws-cloudwatch-log-group-unencrypted

  • Severity: HIGH
  • Scanner: semgrep
  • Rule ID: terraform.aws.security.aws-cloudwatch-log-group-unencrypted.aws-cloudwatch-log-group-unencrypted
  • Location: infra-terraform/modules/backend/feedback.tf:60-64

Description:
By default, AWS CloudWatch Log Group is encrypted using AWS-managed keys. However, for added security, it's recommended to configure your own AWS KMS encryption key to protect your log group in CloudWatch. You can either create a new aws_kms_key resource or use the ARN of an existing key in your AWS account to do so.

Code Snippet:

resource "aws_cloudwatch_log_group" "feedback_lambda" {
  name              = "/aws/lambda/${var.stack_name_base}-feedback"
  retention_in_days = local.log_retention_days

}

Finding 19: terraform.aws.security.aws-lambda-x-ray-tracing-not-active.aws-lambda-x-ray-tracing-not-active

  • Severity: HIGH
  • Scanner: semgrep
  • Rule ID: terraform.aws.security.aws-lambda-x-ray-tracing-not-active.aws-lambda-x-ray-tracing-not-active
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
The AWS Lambda function does not have active X-Ray tracing enabled. X-Ray tracing enables end-to-end debugging and analysis of all function activity. This makes it easier to trace the flow of logs and identify bottlenecks, slow downs and timeouts.

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 20: terraform.aws.security.aws-lambda-environment-unencrypted.aws-lambda-environment-unencrypted

  • Severity: HIGH
  • Scanner: semgrep
  • Rule ID: terraform.aws.security.aws-lambda-environment-unencrypted.aws-lambda-environment-unencrypted
  • Location: infra-terraform/modules/backend/feedback.tf:173-178

Description:
By default, the AWS Lambda Environment is encrypted using AWS-managed keys. However, for added security, it's recommended to configure your own AWS KMS encryption key to protect your environment variables in Lambda. You can either create a new aws_kms_key resource or use the ARN of an existing key in your AWS account to do so.

Code Snippet:

environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

Finding 21: terraform.aws.security.aws-cloudwatch-log-group-unencrypted.aws-cloudwatch-log-group-unencrypted

  • Severity: HIGH
  • Scanner: semgrep
  • Rule ID: terraform.aws.security.aws-cloudwatch-log-group-unencrypted.aws-cloudwatch-log-group-unencrypted
  • Location: infra-terraform/modules/backend/feedback.tf:368-372

Description:
By default, AWS CloudWatch Log Group is encrypted using AWS-managed keys. However, for added security, it's recommended to configure your own AWS KMS encryption key to protect your log group in CloudWatch. You can either create a new aws_kms_key resource or use the ARN of an existing key in your AWS account to do so.

Code Snippet:

resource "aws_cloudwatch_log_group" "api_gateway_access" {
  name              = "/aws/apigateway/${var.stack_name_base}-feedback-api/access-logs"
  retention_in_days = local.log_retention_days

}

Finding 22: terraform.aws.security.aws-dynamodb-table-unencrypted.aws-dynamodb-table-unencrypted

  • Severity: HIGH
  • Scanner: opengrep
  • Rule ID: terraform.aws.security.aws-dynamodb-table-unencrypted.aws-dynamodb-table-unencrypted
  • Location: infra-terraform/modules/backend/feedback.tf:13-54

Description:
By default, AWS DynamoDB Table is encrypted using AWS-managed keys. However, for added security, it's recommended to configure your own AWS KMS encryption key to protect your data in the DynamoDB table. You can either create a new aws_kms_key resource or use the ARN of an existing key in your AWS account to do so.

Code Snippet:

resource "aws_dynamodb_table" "feedback" {
  name         = "${var.stack_name_base}-feedback"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "feedbackId"

  attribute {
    name = "feedbackId"
    type = "S"
  }

  attribute {
    name = "feedbackType"
    type = "S"
  }

  attribute {
    name = "timestamp"
    type = "N"
  }

  # GSI for querying by feedbackType with timestamp sorting
  global_secondary_index {
    name            = "feedbackType-timestamp-index"
    hash_key        = "feedbackType"
    range_key       = "timestamp"
    projection_type = "ALL"
  }

  # Deletion protection disabled (allows terraform destroy)
  deletion_protection_enabled = false

  # Point-in-time recovery
  point_in_time_recovery {
    enabled = true
  }

  # Server-side encryption (AWS managed)
  server_side_encryption {
    enabled = true
  }

}

Finding 23: terraform.aws.security.aws-cloudwatch-log-group-unencrypted.aws-cloudwatch-log-group-unencrypted

  • Severity: HIGH
  • Scanner: opengrep
  • Rule ID: terraform.aws.security.aws-cloudwatch-log-group-unencrypted.aws-cloudwatch-log-group-unencrypted
  • Location: infra-terraform/modules/backend/feedback.tf:60-64

Description:
By default, AWS CloudWatch Log Group is encrypted using AWS-managed keys. However, for added security, it's recommended to configure your own AWS KMS encryption key to protect your log group in CloudWatch. You can either create a new aws_kms_key resource or use the ARN of an existing key in your AWS account to do so.

Code Snippet:

resource "aws_cloudwatch_log_group" "feedback_lambda" {
  name              = "/aws/lambda/${var.stack_name_base}-feedback"
  retention_in_days = local.log_retention_days

}

Finding 24: terraform.aws.security.aws-lambda-x-ray-tracing-not-active.aws-lambda-x-ray-tracing-not-active

  • Severity: HIGH
  • Scanner: opengrep
  • Rule ID: terraform.aws.security.aws-lambda-x-ray-tracing-not-active.aws-lambda-x-ray-tracing-not-active
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
The AWS Lambda function does not have active X-Ray tracing enabled. X-Ray tracing enables end-to-end debugging and analysis of all function activity. This makes it easier to trace the flow of logs and identify bottlenecks, slow downs and timeouts.

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 25: terraform.aws.security.aws-lambda-environment-unencrypted.aws-lambda-environment-unencrypted

  • Severity: HIGH
  • Scanner: opengrep
  • Rule ID: terraform.aws.security.aws-lambda-environment-unencrypted.aws-lambda-environment-unencrypted
  • Location: infra-terraform/modules/backend/feedback.tf:173-178

Description:
By default, the AWS Lambda Environment is encrypted using AWS-managed keys. However, for added security, it's recommended to configure your own AWS KMS encryption key to protect your environment variables in Lambda. You can either create a new aws_kms_key resource or use the ARN of an existing key in your AWS account to do so.

Code Snippet:

environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

Finding 26: terraform.aws.security.aws-cloudwatch-log-group-unencrypted.aws-cloudwatch-log-group-unencrypted

  • Severity: HIGH
  • Scanner: opengrep
  • Rule ID: terraform.aws.security.aws-cloudwatch-log-group-unencrypted.aws-cloudwatch-log-group-unencrypted
  • Location: infra-terraform/modules/backend/feedback.tf:368-372

Description:
By default, AWS CloudWatch Log Group is encrypted using AWS-managed keys. However, for added security, it's recommended to configure your own AWS KMS encryption key to protect your log group in CloudWatch. You can either create a new aws_kms_key resource or use the ARN of an existing key in your AWS account to do so.

Code Snippet:

resource "aws_cloudwatch_log_group" "api_gateway_access" {
  name              = "/aws/apigateway/${var.stack_name_base}-feedback-api/access-logs"
  retention_in_days = local.log_retention_days

}

Report generated by Automated Security Helper (ASH) at 2026-03-25T18:11:32+00:00

@kaleko kaleko merged commit e7a781e into main Mar 25, 2026
9 checks passed
@kaleko kaleko deleted the fix/api-gateway-cache-encryption branch March 25, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant