diff --git a/infrastructure/terraform/components/api/README.md b/infrastructure/terraform/components/api/README.md
index 2d0a72d9a..f1f278c5e 100644
--- a/infrastructure/terraform/components/api/README.md
+++ b/infrastructure/terraform/components/api/README.md
@@ -20,9 +20,13 @@ No requirements.
| [enable\_alarms](#input\_enable\_alarms) | Enable CloudWatch alarms for this deployed environment | `bool` | `true` | no |
| [enable\_api\_data\_trace](#input\_enable\_api\_data\_trace) | Enable API Gateway data trace logging | `bool` | `false` | no |
| [enable\_backups](#input\_enable\_backups) | Enable backups | `bool` | `false` | no |
+| [enable\_event\_anomaly\_detection](#input\_enable\_event\_anomaly\_detection) | Enable CloudWatch anomaly detection alarm for SNS message Detects abnormal drops or spikes in event publishing volume. | `bool` | `true` | no |
| [enable\_event\_cache](#input\_enable\_event\_cache) | Enable caching of events to an S3 bucket | `bool` | `true` | no |
| [enable\_sns\_delivery\_logging](#input\_enable\_sns\_delivery\_logging) | Enable SNS Delivery Failure Notifications | `bool` | `true` | no |
| [environment](#input\_environment) | The name of the tfscaffold environment | `string` | n/a | yes |
+| [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` | `4` | no |
+| [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` | `3` | no |
+| [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 |
| [eventpub\_control\_plane\_bus\_arn](#input\_eventpub\_control\_plane\_bus\_arn) | ARN of the EventBridge control plane bus for eventpub | `string` | `""` | no |
| [eventpub\_data\_plane\_bus\_arn](#input\_eventpub\_data\_plane\_bus\_arn) | ARN of the EventBridge data plane bus for eventpub | `string` | `""` | no |
| [force\_destroy](#input\_force\_destroy) | Flag to force deletion of S3 buckets | `bool` | `false` | no |
@@ -45,14 +49,14 @@ No requirements.
| Name | Source | Version |
|------|--------|---------|
| [amendment\_event\_transformer](#module\_amendment\_event\_transformer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
-| [amendments\_queue](#module\_amendments\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-sqs.zip | n/a |
+| [amendments\_queue](#module\_amendments\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| [authorizer\_lambda](#module\_authorizer\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| [ddb\_alarms\_letter\_queue](#module\_ddb\_alarms\_letter\_queue) | ../../modules/alarms-ddb | n/a |
| [ddb\_alarms\_letters](#module\_ddb\_alarms\_letters) | ../../modules/alarms-ddb | n/a |
| [ddb\_alarms\_mi](#module\_ddb\_alarms\_mi) | ../../modules/alarms-ddb | n/a |
| [ddb\_alarms\_suppliers](#module\_ddb\_alarms\_suppliers) | ../../modules/alarms-ddb | n/a |
-| [domain\_truststore](#module\_domain\_truststore) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-s3bucket.zip | n/a |
-| [eventpub](#module\_eventpub) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-eventpub.zip | n/a |
+| [domain\_truststore](#module\_domain\_truststore) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-s3bucket.zip | n/a |
+| [eventpub](#module\_eventpub) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-eventpub.zip | n/a |
| [eventsub](#module\_eventsub) | ../../modules/eventsub | n/a |
| [get\_letter](#module\_get\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| [get\_letter\_data](#module\_get\_letter\_data) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
@@ -60,7 +64,7 @@ No requirements.
| [get\_status](#module\_get\_status) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| [kms](#module\_kms) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-kms.zip | n/a |
| [lambda\_alarms](#module\_lambda\_alarms) | ../../modules/alarms-lambda | n/a |
-| [letter\_status\_updates\_queue](#module\_letter\_status\_updates\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-sqs.zip | n/a |
+| [letter\_status\_updates\_queue](#module\_letter\_status\_updates\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| [letter\_updates\_transformer](#module\_letter\_updates\_transformer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| [mi\_updates\_transformer](#module\_mi\_updates\_transformer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
| [patch\_letter](#module\_patch\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
@@ -68,8 +72,8 @@ No requirements.
| [post\_mi](#module\_post\_mi) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| [s3bucket\_test\_letters](#module\_s3bucket\_test\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-s3bucket.zip | n/a |
| [sqs\_alarms](#module\_sqs\_alarms) | ../../modules/alarms-sqs | n/a |
-| [sqs\_letter\_updates](#module\_sqs\_letter\_updates) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-sqs.zip | n/a |
-| [sqs\_supplier\_allocator](#module\_sqs\_supplier\_allocator) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-sqs.zip | n/a |
+| [sqs\_letter\_updates](#module\_sqs\_letter\_updates) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
+| [sqs\_supplier\_allocator](#module\_sqs\_supplier\_allocator) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| [supplier\_allocator](#module\_supplier\_allocator) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| [supplier\_ssl](#module\_supplier\_ssl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-ssl.zip | n/a |
| [update\_letter\_queue](#module\_update\_letter\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
diff --git a/infrastructure/terraform/components/api/module_domain_truststore.tf b/infrastructure/terraform/components/api/module_domain_truststore.tf
index 77a927101..a0f479cf4 100644
--- a/infrastructure/terraform/components/api/module_domain_truststore.tf
+++ b/infrastructure/terraform/components/api/module_domain_truststore.tf
@@ -1,5 +1,5 @@
module "domain_truststore" {
- source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-s3bucket.zip"
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-s3bucket.zip"
name = "truststore"
aws_account_id = var.aws_account_id
diff --git a/infrastructure/terraform/components/api/module_sqs_amendments.tf b/infrastructure/terraform/components/api/module_sqs_amendments.tf
index f5d733c61..8fca10e9d 100644
--- a/infrastructure/terraform/components/api/module_sqs_amendments.tf
+++ b/infrastructure/terraform/components/api/module_sqs_amendments.tf
@@ -1,6 +1,6 @@
# Queue to transport letter status amendment messages
module "amendments_queue" {
- source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.5/terraform-sqs.zip"
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip"
name = "amendments_queue"
diff --git a/infrastructure/terraform/components/api/module_sqs_letter_status_updates.tf b/infrastructure/terraform/components/api/module_sqs_letter_status_updates.tf
index 2ce3afe55..db7d27a85 100644
--- a/infrastructure/terraform/components/api/module_sqs_letter_status_updates.tf
+++ b/infrastructure/terraform/components/api/module_sqs_letter_status_updates.tf
@@ -1,7 +1,7 @@
# Queue to transport update letter status messages. Now replaced by module.amendments_queue.
# This queue will not be removed just yet, to allow it to be drained following the release in which module.amendments_queue replaces it.
module "letter_status_updates_queue" {
- source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.5/terraform-sqs.zip"
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip"
name = "letter_status_updates_queue"
diff --git a/infrastructure/terraform/components/api/module_sqs_letter_updates.tf b/infrastructure/terraform/components/api/module_sqs_letter_updates.tf
index 1355e08bc..1e886d61e 100644
--- a/infrastructure/terraform/components/api/module_sqs_letter_updates.tf
+++ b/infrastructure/terraform/components/api/module_sqs_letter_updates.tf
@@ -1,5 +1,5 @@
module "sqs_letter_updates" {
- source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.5/terraform-sqs.zip"
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip"
aws_account_id = var.aws_account_id
component = var.component
diff --git a/infrastructure/terraform/components/api/module_sqs_supplier_allocator.tf b/infrastructure/terraform/components/api/module_sqs_supplier_allocator.tf
index 7ee7d45f0..565038d7b 100644
--- a/infrastructure/terraform/components/api/module_sqs_supplier_allocator.tf
+++ b/infrastructure/terraform/components/api/module_sqs_supplier_allocator.tf
@@ -1,5 +1,5 @@
module "sqs_supplier_allocator" {
- source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.5/terraform-sqs.zip"
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip"
aws_account_id = var.aws_account_id
component = var.component
diff --git a/infrastructure/terraform/components/api/modules_eventpub.tf b/infrastructure/terraform/components/api/modules_eventpub.tf
index d9145fe3d..f202cbfdd 100644
--- a/infrastructure/terraform/components/api/modules_eventpub.tf
+++ b/infrastructure/terraform/components/api/modules_eventpub.tf
@@ -1,5 +1,5 @@
module "eventpub" {
- source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-eventpub.zip"
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-eventpub.zip"
name = "eventpub"
@@ -21,19 +21,23 @@ module "eventpub" {
event_cache_buffer_interval = 500
enable_sns_delivery_logging = var.enable_sns_delivery_logging
sns_success_logging_sample_percent = var.sns_success_logging_sample_percent
+ access_logging_bucket = local.acct.s3_buckets["access_logs"]["id"]
event_cache_expiry_days = 30
enable_event_cache = var.enable_event_cache
-
- data_plane_bus_arn = var.eventpub_data_plane_bus_arn
- control_plane_bus_arn = var.eventpub_control_plane_bus_arn
-
- access_logging_bucket = local.acct.s3_buckets["access_logs"]["id"]
+ data_plane_bus_arn = var.eventpub_data_plane_bus_arn
+ control_plane_bus_arn = var.eventpub_control_plane_bus_arn
additional_policies_for_event_cache_bucket = [
data.aws_iam_policy_document.eventcache[0].json
]
+
+ enable_event_anomaly_detection = var.enable_event_anomaly_detection
+ event_anomaly_band_width = var.event_anomaly_band_width
+ event_anomaly_evaluation_periods = var.event_anomaly_evaluation_periods
+ event_anomaly_period = var.event_anomaly_period
}
+
data "aws_iam_policy_document" "eventcache" {
count = local.event_cache_bucket_name != null ? 1 : 0
statement {
diff --git a/infrastructure/terraform/components/api/variables.tf b/infrastructure/terraform/components/api/variables.tf
index 4559f30b9..97169278e 100644
--- a/infrastructure/terraform/components/api/variables.tf
+++ b/infrastructure/terraform/components/api/variables.tf
@@ -199,3 +199,27 @@ variable "enable_alarms" {
description = "Enable CloudWatch alarms for this deployed environment"
default = true
}
+
+variable "enable_event_anomaly_detection" {
+ type = bool
+ description = "Enable CloudWatch anomaly detection alarm for SNS message Detects abnormal drops or spikes in event publishing volume."
+ 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 = 3
+}
+
+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
+}
+
+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 = 4
+}
diff --git a/infrastructure/terraform/modules/eventsub/README.md b/infrastructure/terraform/modules/eventsub/README.md
index 2b4a7369a..4ddc516e1 100644
--- a/infrastructure/terraform/modules/eventsub/README.md
+++ b/infrastructure/terraform/modules/eventsub/README.md
@@ -15,10 +15,14 @@
| [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes |
| [component](#input\_component) | The name of the terraformscaffold component calling this module | `string` | n/a | yes |
| [default\_tags](#input\_default\_tags) | Default tag map for application to all taggable resources in the module | `map(string)` | `{}` | no |
+| [enable\_event\_anomaly\_detection](#input\_enable\_event\_anomaly\_detection) | Enable CloudWatch anomaly detection alarm for SNS topic message publishing | `bool` | `true` | no |
| [enable\_event\_cache](#input\_enable\_event\_cache) | Enable caching of events to an S3 bucket | `bool` | `true` | no |
| [enable\_firehose\_raw\_message\_delivery](#input\_enable\_firehose\_raw\_message\_delivery) | Enables raw message delivery on firehose subscription | `bool` | `false` | no |
| [enable\_sns\_delivery\_logging](#input\_enable\_sns\_delivery\_logging) | Enable SNS Delivery Failure Notifications | `bool` | `true` | no |
| [environment](#input\_environment) | The name of the terraformscaffold environment the module is called for | `string` | n/a | yes |
+| [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 |
+| [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 |
+| [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 |
| [event\_cache\_buffer\_interval](#input\_event\_cache\_buffer\_interval) | The buffer interval for data firehose | `number` | `500` | no |
| [event\_cache\_expiry\_days](#input\_event\_cache\_expiry\_days) | s3 archiving expiry in days | `number` | `30` | no |
| [force\_destroy](#input\_force\_destroy) | When enabled will force destroy event-cache S3 bucket | `bool` | `false` | no |
@@ -36,7 +40,7 @@
| Name | Source | Version |
|------|--------|---------|
-| [s3bucket\_event\_cache](#module\_s3bucket\_event\_cache) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-s3bucket.zip | n/a |
+| [s3bucket\_event\_cache](#module\_s3bucket\_event\_cache) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-s3bucket.zip | n/a |
## Outputs
| Name | Description |
diff --git a/infrastructure/terraform/modules/eventsub/cloudwatch_metric_alarm_subscriber_anomaly.tf b/infrastructure/terraform/modules/eventsub/cloudwatch_metric_alarm_subscriber_anomaly.tf
new file mode 100644
index 000000000..012969b38
--- /dev/null
+++ b/infrastructure/terraform/modules/eventsub/cloudwatch_metric_alarm_subscriber_anomaly.tf
@@ -0,0 +1,40 @@
+resource "aws_cloudwatch_metric_alarm" "subscriber_anomaly" {
+ count = var.enable_event_anomaly_detection ? 1 : 0
+
+ alarm_name = "${local.csi}-subscriber-anomaly"
+ alarm_description = "ANOMALY: Detects anomalous patterns in messages published to the SNS fanout topic"
+ 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 = "NumberOfNotificationsDelivered"
+ namespace = "AWS/SNS"
+ period = var.event_anomaly_period
+ stat = "Sum"
+
+ dimensions = {
+ TopicName = aws_sns_topic.main.name
+ }
+ }
+ }
+
+ metric_query {
+ id = "ad1"
+ expression = "ANOMALY_DETECTION_BAND(m1, ${var.event_anomaly_band_width})"
+ label = "NumberOfNotificationsDelivered (expected)"
+ return_data = true
+ }
+
+ tags = merge(
+ var.default_tags,
+ {
+ Name = "${local.csi}-subscriber-anomaly"
+ }
+ )
+}
diff --git a/infrastructure/terraform/modules/eventsub/module_s3bucket_event_cache.tf b/infrastructure/terraform/modules/eventsub/module_s3bucket_event_cache.tf
index 9e610641f..10ba75639 100644
--- a/infrastructure/terraform/modules/eventsub/module_s3bucket_event_cache.tf
+++ b/infrastructure/terraform/modules/eventsub/module_s3bucket_event_cache.tf
@@ -1,5 +1,5 @@
module "s3bucket_event_cache" {
- source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-s3bucket.zip"
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-s3bucket.zip"
count = var.enable_event_cache ? 1 : 0
diff --git a/infrastructure/terraform/modules/eventsub/variables.tf b/infrastructure/terraform/modules/eventsub/variables.tf
index 02a8ae85e..964e90bd6 100644
--- a/infrastructure/terraform/modules/eventsub/variables.tf
+++ b/infrastructure/terraform/modules/eventsub/variables.tf
@@ -79,6 +79,39 @@ variable "sns_success_logging_sample_percent" {
default = 0
}
+##
+# CloudWatch Anomaly Detection Variables
+##
+
+variable "enable_event_anomaly_detection" {
+ type = bool
+ description = "Enable CloudWatch anomaly detection alarm for SNS topic message publishing"
+ 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
+}
+
+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
+}
+
+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"
+ }
+}
+
variable "log_level" {
type = string
description = "The log level to be used in lambda functions within the component. Any log with a lower severity than the configured value will not be logged: https://docs.python.org/3/library/logging.html#levels"
diff --git a/scripts/config/pre-commit.yaml b/scripts/config/pre-commit.yaml
index 85bce3090..c217b3366 100644
--- a/scripts/config/pre-commit.yaml
+++ b/scripts/config/pre-commit.yaml
@@ -23,10 +23,9 @@ repos:
sdk/.* |
someotherdir/.* |
src/server/host/Properties/launchSettings.json |
+ tests/contracts/provider/tsconfig.json |
internal/events/.*/.*\.schema\.json
)$
-
- # - id: ...
- repo: local
hooks:
- id: sort-dictionary