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
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ gitleaks 8.24.0
jq 1.6
nodejs 22.16.0
pre-commit 3.6.0
terraform 1.12.0
terraform 1.14.3
terraform-docs 0.19.0
trivy 0.61.0
trivy 0.69.2
vale 3.6.0
python 3.13.2

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
terraform 1.12.0
terraform 1.14.3
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

This bumps the asdf Terraform version to 1.14.3, but the module’s Terraform constraint still allows older versions (required_version = ">= 1.12.0" in infrastructure/terraform/components/events/versions.tf, also reflected in the generated README). If 1.14.3 is now the minimum supported version, consider updating required_version (and regenerating docs) to avoid contributors/CI running with an older Terraform than intended.

Suggested change
terraform 1.14.3
terraform 1.12.0

Copilot uses AI. Check for mistakes.
8 changes: 8 additions & 0 deletions infrastructure/terraform/components/events/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
| <a name="input_aws_account_id"></a> [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes |
| <a name="input_component"></a> [component](#input\_component) | The variable encapsulating the name of this component | `string` | `"events"` | no |
| <a name="input_default_tags"></a> [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no |
| <a name="input_enable_event_anomaly_detection"></a> [enable\_event\_anomaly\_detection](#input\_enable\_event\_anomaly\_detection) | Enable CloudWatch anomaly detection alarms for event bus traffic. Applies to both data and control plane ingestion and invocations. | `bool` | `true` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | The name of the tfscaffold environment | `string` | n/a | yes |
| <a name="input_event_anomaly_band_width"></a> [event\_anomaly\_band\_width](#input\_event\_anomaly\_band\_width) | The width of the anomaly detection band. Higher values (e.g. 4-6) reduce sensitivity and noise, lower values (e.g. 2-3) increase sensitivity. Recommended: 2-4. | `number` | `3` | no |
| <a name="input_event_anomaly_evaluation_periods"></a> [event\_anomaly\_evaluation\_periods](#input\_event\_anomaly\_evaluation\_periods) | Number of evaluation periods for the anomaly alarm. Each period is defined by event\_anomaly\_period. | `number` | `2` | no |
| <a name="input_event_anomaly_period"></a> [event\_anomaly\_period](#input\_event\_anomaly\_period) | The period in seconds over which the specified statistic is applied for anomaly detection. Minimum 300 seconds (5 minutes). Recommended: 300-600. | `number` | `300` | no |
| <a name="input_event_publisher_account_ids"></a> [event\_publisher\_account\_ids](#input\_event\_publisher\_account\_ids) | An object representing account id's of event publishers | `list(any)` | `[]` | no |
| <a name="input_event_target_arns"></a> [event\_target\_arns](#input\_event\_target\_arns) | A map of event target ARNs keyed by name | <pre>object({<br/> sms_nudge = string<br/> notify_core_sns_topic = optional(string, null)<br/> supplier_api_sns_topic = optional(string, null)<br/> app_response = optional(string, null)<br/> client_callbacks = optional(string, null)<br/> })</pre> | n/a | yes |
| <a name="input_force_lambda_code_deploy"></a> [force\_lambda\_code\_deploy](#input\_force\_lambda\_code\_deploy) | If the lambda package in s3 has the same commit id tag as the terraform build branch, the lambda will not update automatically. Set to True if making changes to Lambda code from on the same commit for example during development | `bool` | `false` | no |
Expand Down Expand Up @@ -43,7 +47,11 @@
| Name | Description |
|------|-------------|
| <a name="output_control_plane_event_bus"></a> [control\_plane\_event\_bus](#output\_control\_plane\_event\_bus) | n/a |
| <a name="output_control_plane_ingestion_anomaly_alarm"></a> [control\_plane\_ingestion\_anomaly\_alarm](#output\_control\_plane\_ingestion\_anomaly\_alarm) | Control plane ingestion anomaly detection alarm details |
| <a name="output_control_plane_invocations_anomaly_alarm"></a> [control\_plane\_invocations\_anomaly\_alarm](#output\_control\_plane\_invocations\_anomaly\_alarm) | Control plane invocations anomaly detection alarm details |
| <a name="output_data_plane_event_bus"></a> [data\_plane\_event\_bus](#output\_data\_plane\_event\_bus) | n/a |
| <a name="output_data_plane_ingestion_anomaly_alarm"></a> [data\_plane\_ingestion\_anomaly\_alarm](#output\_data\_plane\_ingestion\_anomaly\_alarm) | Data plane ingestion anomaly detection alarm details |
| <a name="output_data_plane_invocations_anomaly_alarm"></a> [data\_plane\_invocations\_anomaly\_alarm](#output\_data\_plane\_invocations\_anomaly\_alarm) | Data plane invocations anomaly detection alarm details |
<!-- vale on -->
<!-- markdownlint-enable -->
<!-- END_TF_DOCS -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
resource "aws_cloudwatch_metric_alarm" "control_plane_ingestion_anomaly" {
count = var.enable_event_anomaly_detection ? 1 : 0

alarm_name = "${local.csi}-control-plane-ingestion-anomaly"
alarm_description = "RELIABILITY: Detects anomalous patterns in events ingested to the control plane event bus"
comparison_operator = "LessThanLowerOrGreaterThanUpperThreshold"
evaluation_periods = var.event_anomaly_evaluation_periods
threshold_metric_id = "ad1"
treat_missing_data = "notBreaching"

metric_query {
id = "m1"
return_data = true

metric {
metric_name = "Ingestion"
namespace = "AWS/Events"
period = var.event_anomaly_period
stat = "Sum"

dimensions = {
EventBusName = aws_cloudwatch_event_bus.control_plane.name
}
}
}

metric_query {
id = "ad1"
expression = "ANOMALY_DETECTION_BAND(m1, ${var.event_anomaly_band_width})"
label = "Ingestion (expected)"
return_data = true
}

tags = merge(
local.default_tags,
{
Name = "${local.csi}-control-plane-ingestion-anomaly"
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
resource "aws_cloudwatch_metric_alarm" "control_plane_invocations_anomaly" {
count = var.enable_event_anomaly_detection ? 1 : 0

alarm_name = "${local.csi}-control-plane-invocations-anomaly"
alarm_description = "RELIABILITY: Detects anomalous patterns in events delivered from the control plane event bus to targets"
comparison_operator = "LessThanLowerOrGreaterThanUpperThreshold"
evaluation_periods = var.event_anomaly_evaluation_periods
threshold_metric_id = "ad1"
treat_missing_data = "notBreaching"

metric_query {
id = "m1"
return_data = true

metric {
metric_name = "Invocations"
namespace = "AWS/Events"
period = var.event_anomaly_period
stat = "Sum"

dimensions = {
EventBusName = aws_cloudwatch_event_bus.control_plane.name
}
}
}

metric_query {
id = "ad1"
expression = "ANOMALY_DETECTION_BAND(m1, ${var.event_anomaly_band_width})"
label = "Invocations (expected)"
return_data = true
}

tags = merge(
local.default_tags,
{
Name = "${local.csi}-control-plane-invocations-anomaly"
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
resource "aws_cloudwatch_metric_alarm" "data_plane_ingestion_anomaly" {
count = var.enable_event_anomaly_detection ? 1 : 0

alarm_name = "${local.csi}-data-plane-ingestion-anomaly"
alarm_description = "RELIABILITY: Detects anomalous patterns in events ingested to the data plane event bus"
comparison_operator = "LessThanLowerOrGreaterThanUpperThreshold"
evaluation_periods = var.event_anomaly_evaluation_periods
threshold_metric_id = "ad1"
treat_missing_data = "notBreaching"

metric_query {
id = "m1"
return_data = true

metric {
metric_name = "Ingestion"
namespace = "AWS/Events"
period = var.event_anomaly_period
stat = "Sum"

dimensions = {
EventBusName = aws_cloudwatch_event_bus.data_plane.name
}
}
}

metric_query {
id = "ad1"
expression = "ANOMALY_DETECTION_BAND(m1, ${var.event_anomaly_band_width})"
label = "Ingestion (expected)"
return_data = true
}

tags = merge(
local.default_tags,
{
Name = "${local.csi}-data-plane-ingestion-anomaly"
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
resource "aws_cloudwatch_metric_alarm" "data_plane_invocations_anomaly" {
count = var.enable_event_anomaly_detection ? 1 : 0

alarm_name = "${local.csi}-data-plane-invocations-anomaly"
alarm_description = "RELIABILITY: Detects anomalous patterns in events delivered from the data plane event bus to targets"
comparison_operator = "LessThanLowerOrGreaterThanUpperThreshold"
evaluation_periods = var.event_anomaly_evaluation_periods
threshold_metric_id = "ad1"
treat_missing_data = "notBreaching"

metric_query {
id = "m1"
return_data = true

metric {
metric_name = "Invocations"
namespace = "AWS/Events"
period = var.event_anomaly_period
stat = "Sum"

dimensions = {
EventBusName = aws_cloudwatch_event_bus.data_plane.name
}
}
}

metric_query {
id = "ad1"
expression = "ANOMALY_DETECTION_BAND(m1, ${var.event_anomaly_band_width})"
label = "Invocations (expected)"
return_data = true
}

tags = merge(
local.default_tags,
{
Name = "${local.csi}-data-plane-invocations-anomaly"
}
)
}
32 changes: 32 additions & 0 deletions infrastructure/terraform/components/events/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,35 @@ output "data_plane_event_bus" {
arn = aws_cloudwatch_event_bus.data_plane.arn
}
}

output "data_plane_ingestion_anomaly_alarm" {
description = "Data plane ingestion anomaly detection alarm details"
value = var.enable_event_anomaly_detection ? {
arn = aws_cloudwatch_metric_alarm.data_plane_ingestion_anomaly[0].arn
name = aws_cloudwatch_metric_alarm.data_plane_ingestion_anomaly[0].alarm_name
} : null
}

output "data_plane_invocations_anomaly_alarm" {
description = "Data plane invocations anomaly detection alarm details"
value = var.enable_event_anomaly_detection ? {
arn = aws_cloudwatch_metric_alarm.data_plane_invocations_anomaly[0].arn
name = aws_cloudwatch_metric_alarm.data_plane_invocations_anomaly[0].alarm_name
} : null
}

output "control_plane_ingestion_anomaly_alarm" {
description = "Control plane ingestion anomaly detection alarm details"
value = var.enable_event_anomaly_detection ? {
arn = aws_cloudwatch_metric_alarm.control_plane_ingestion_anomaly[0].arn
name = aws_cloudwatch_metric_alarm.control_plane_ingestion_anomaly[0].alarm_name
} : null
}

output "control_plane_invocations_anomaly_alarm" {
description = "Control plane invocations anomaly detection alarm details"
value = var.enable_event_anomaly_detection ? {
arn = aws_cloudwatch_metric_alarm.control_plane_invocations_anomaly[0].arn
name = aws_cloudwatch_metric_alarm.control_plane_invocations_anomaly[0].alarm_name
} : null
}
29 changes: 29 additions & 0 deletions infrastructure/terraform/components/events/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,32 @@ variable "notify_core_sns_kms_arn" {
type = string
default = null
}

variable "enable_event_anomaly_detection" {
type = bool
description = "Enable CloudWatch anomaly detection alarms for event bus traffic. Applies to both data and control plane ingestion and invocations."
default = true
}

variable "event_anomaly_evaluation_periods" {
type = number
description = "Number of evaluation periods for the anomaly alarm. Each period is defined by event_anomaly_period."
default = 2
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

event_anomaly_evaluation_periods is declared as a number, but CloudWatch expects an integer >= 1. Without a validation block, non-integer/invalid values (e.g., 1.5 or 0) will only fail at apply time. Consider adding validation to enforce a positive whole number.

Suggested change
default = 2
default = 2
validation {
condition = var.event_anomaly_evaluation_periods >= 1 && floor(var.event_anomaly_evaluation_periods) == var.event_anomaly_evaluation_periods
error_message = "event_anomaly_evaluation_periods must be a positive whole number (integer >= 1)."
}

Copilot uses AI. Check for mistakes.
}

variable "event_anomaly_period" {
type = number
description = "The period in seconds over which the specified statistic is applied for anomaly detection. Minimum 300 seconds (5 minutes). Recommended: 300-600."
default = 300
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

The description for event_anomaly_period states a minimum of 300 seconds, but there’s no validation enforcing this (or that the value is an integer). Adding a validation block (e.g., >= 300 and whole number) would prevent configuration values that will be rejected by CloudWatch at apply time.

Suggested change
default = 300
default = 300
validation {
condition = var.event_anomaly_period >= 300 && floor(var.event_anomaly_period) == var.event_anomaly_period
error_message = "Event anomaly period must be an integer number of seconds and at least 300 seconds."
}

Copilot uses AI. Check for mistakes.
}

variable "event_anomaly_band_width" {
type = number
description = "The width of the anomaly detection band. Higher values (e.g. 4-6) reduce sensitivity and noise, lower values (e.g. 2-3) increase sensitivity. Recommended: 2-4."
default = 3

validation {
condition = var.event_anomaly_band_width >= 2 && var.event_anomaly_band_width <= 10
error_message = "Band width must be between 2 and 10"
}
}
Loading