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