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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9.3] - 2026-03-13
## [0.9.3] - 2026-03-14

### Changed

Expand All @@ -15,7 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Security

- **Command trigger shell injection eliminated (SEC-3)** — Replaced `sh -c` with direct `exec.CommandContext` + `strings.Fields` argument splitting. No shell interpretation of pipes, redirects, or variable expansion.
- **Encryption at rest for DynamoDB and SQS** — All DynamoDB tables now explicitly enable server-side encryption. SQS queues (alert queue, alert DLQ, stream-router DLQs) now use KMS encryption. New optional `kms_key_arn` variable for custom CMK when full key control and rotation are needed; defaults to AWS-managed keys (free, no configuration required).
- **SSRF protection on trigger HTTP clients** — Custom `http.Transport` with dial-time IP validation rejects connections to private, loopback, link-local, and multicast addresses. Protects HTTP, Airflow, and Databricks triggers against targeting internal endpoints.
- **EventBridge PutEvents partial failure detection** — `publishEvent` now checks `FailedEntryCount` on the response. Previously, partial failures were silently discarded.
- **Command trigger shell injection eliminated** — Replaced `sh -c` with direct `exec.CommandContext` + `strings.Fields` argument splitting. No shell interpretation of pipes, redirects, or variable expansion.

## [0.9.2] - 2026-03-13

Expand Down
18 changes: 11 additions & 7 deletions deploy/terraform/alerting.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@
# -----------------------------------------------------------------------------

resource "aws_sqs_queue" "alert_dlq" {
name = "${var.environment}-interlock-alert-dlq"
message_retention_seconds = 1209600 # 14 days
tags = var.tags
name = "${var.environment}-interlock-alert-dlq"
message_retention_seconds = 1209600 # 14 days
kms_master_key_id = var.kms_key_arn != "" ? var.kms_key_arn : "alias/aws/sqs"
kms_data_key_reuse_period_seconds = 300
tags = var.tags
}

resource "aws_sqs_queue" "alert" {
name = "${var.environment}-interlock-alerts"
message_retention_seconds = 86400 # 1 day
visibility_timeout_seconds = 60
tags = var.tags
name = "${var.environment}-interlock-alerts"
message_retention_seconds = 86400 # 1 day
visibility_timeout_seconds = 60
kms_master_key_id = var.kms_key_arn != "" ? var.kms_key_arn : "alias/aws/sqs"
kms_data_key_reuse_period_seconds = 300
tags = var.tags

redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.alert_dlq.arn
Expand Down
20 changes: 20 additions & 0 deletions deploy/terraform/dynamodb.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ resource "aws_dynamodb_table" "control" {
type = "S"
}

server_side_encryption {
enabled = true
kms_key_arn = var.kms_key_arn != "" ? var.kms_key_arn : null
}

ttl {
attribute_name = "ttl"
enabled = true
Expand Down Expand Up @@ -49,6 +54,11 @@ resource "aws_dynamodb_table" "joblog" {
type = "S"
}

server_side_encryption {
enabled = true
kms_key_arn = var.kms_key_arn != "" ? var.kms_key_arn : null
}

ttl {
attribute_name = "ttl"
enabled = true
Expand Down Expand Up @@ -92,6 +102,11 @@ resource "aws_dynamodb_table" "events" {
type = "N"
}

server_side_encryption {
enabled = true
kms_key_arn = var.kms_key_arn != "" ? var.kms_key_arn : null
}

global_secondary_index {
name = "GSI1"
hash_key = "eventType"
Expand Down Expand Up @@ -129,6 +144,11 @@ resource "aws_dynamodb_table" "rerun" {
type = "S"
}

server_side_encryption {
enabled = true
kms_key_arn = var.kms_key_arn != "" ? var.kms_key_arn : null
}

ttl {
attribute_name = "ttl"
enabled = true
Expand Down
4 changes: 2 additions & 2 deletions deploy/terraform/eventbridge.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ resource "aws_cloudwatch_event_bus_policy" "interlock_bus" {
Version = "2012-10-17"
Statement = [
{
Sid = "AllowInterlockLambdas"
Effect = "Allow"
Sid = "AllowInterlockLambdas"
Effect = "Allow"
Principal = {
AWS = [for name in local.lambda_names : aws_iam_role.lambda[name].arn]
}
Expand Down
48 changes: 42 additions & 6 deletions deploy/terraform/lambda.tf
Original file line number Diff line number Diff line change
Expand Up @@ -489,20 +489,56 @@ resource "aws_iam_role_policy" "sqs_dlq" {
policy = data.aws_iam_policy_document.sqs_dlq.json
}

# -----------------------------------------------------------------------------
# KMS — grant Lambda roles decrypt/encrypt access when a custom CMK is provided
# -----------------------------------------------------------------------------

resource "aws_iam_role_policy" "kms_sqs_stream_router" {
count = var.kms_key_arn != "" ? 1 : 0
name = "kms-sqs"
role = aws_iam_role.lambda["stream-router"].id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["kms:Decrypt", "kms:GenerateDataKey"]
Resource = [var.kms_key_arn]
}]
})
}

resource "aws_iam_role_policy" "kms_sqs_alert_dispatcher" {
count = var.kms_key_arn != "" ? 1 : 0
name = "kms-sqs"
role = aws_iam_role.lambda["alert-dispatcher"].id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["kms:Decrypt", "kms:GenerateDataKey"]
Resource = [var.kms_key_arn]
}]
})
}

# =============================================================================
# SQS Dead-Letter Queues for stream event source mappings
# =============================================================================

resource "aws_sqs_queue" "stream_router_control_dlq" {
name = "${var.environment}-interlock-sr-control-dlq"
message_retention_seconds = 1209600 # 14 days
tags = var.tags
name = "${var.environment}-interlock-sr-control-dlq"
message_retention_seconds = 1209600 # 14 days
kms_master_key_id = var.kms_key_arn != "" ? var.kms_key_arn : "alias/aws/sqs"
kms_data_key_reuse_period_seconds = 300
tags = var.tags
}

resource "aws_sqs_queue" "stream_router_joblog_dlq" {
name = "${var.environment}-interlock-sr-joblog-dlq"
message_retention_seconds = 1209600 # 14 days
tags = var.tags
name = "${var.environment}-interlock-sr-joblog-dlq"
message_retention_seconds = 1209600 # 14 days
kms_master_key_id = var.kms_key_arn != "" ? var.kms_key_arn : "alias/aws/sqs"
kms_data_key_reuse_period_seconds = 300
tags = var.tags
}

# =============================================================================
Expand Down
6 changes: 6 additions & 0 deletions deploy/terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ variable "lambda_concurrency" {
}
}

variable "kms_key_arn" {
description = "ARN of a KMS key for encrypting DynamoDB tables and SQS queues at rest. Empty = AWS-managed encryption."
type = string
default = ""
}

variable "sns_alarm_topic_arn" {
description = "SNS topic ARN for CloudWatch alarm notifications (empty = alarms fire but no notifications)"
type = string
Expand Down
Loading