Skip to content
Draft
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
8 changes: 8 additions & 0 deletions .github/actions/acceptance-tests/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ runs:
echo "PR_NUMBER=${{ inputs.targetEnvironment }}" >> $GITHUB_ENV
echo "ENVIRONMENT=${{ inputs.targetEnvironment }}" >> $GITHUB_ENV

- name: "Set TEST_MOCK_WEBHOOK_URL environment variable"
shell: bash
run: |
mock_webhook_url=$(jq -r '.mock_webhook_url.value // empty' terraform_output.json)
if [ -n "$mock_webhook_url" ]; then
echo "TEST_MOCK_WEBHOOK_URL=${mock_webhook_url}" >> $GITHUB_ENV
fi

- name: Run test - ${{ inputs.testType }}
shell: bash
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cicd-1-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ jobs:
--terraformAction "apply" \
--overrideProjectName "nhs" \
--overrideRoleName "nhs-main-acct-client-callbacks-github-deploy" \
--overrides "branch_name=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
--overrides "branch_name=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}},deploy_mock_webhook=true"
acceptance-stage: # Recommended maximum execution time is 10 minutes
name: "Acceptance stage"
needs: [metadata, build-stage, pr-create-dynamic-environment]
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/terraform/components/callbacks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,24 @@
| <a name="input_pipe_sqs_max_batch_window"></a> [pipe\_sqs\_max\_batch\_window](#input\_pipe\_sqs\_max\_batch\_window) | n/a | `number` | `2` | no |
| <a name="input_project"></a> [project](#input\_project) | The name of the tfscaffold project | `string` | n/a | yes |
| <a name="input_region"></a> [region](#input\_region) | The AWS Region | `string` | n/a | yes |
| <a name="input_sqs_inbound_event_max_receive_count"></a> [sqs\_inbound\_event\_max\_receive\_count](#input\_sqs\_inbound\_event\_max\_receive\_count) | n/a | `number` | `3` | no |
| <a name="input_sqs_inbound_event_visibility_timeout_seconds"></a> [sqs\_inbound\_event\_visibility\_timeout\_seconds](#input\_sqs\_inbound\_event\_visibility\_timeout\_seconds) | n/a | `number` | `60` | no |
## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_client_config_bucket"></a> [client\_config\_bucket](#module\_client\_config\_bucket) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-s3bucket.zip | n/a |
| <a name="module_client_destination"></a> [client\_destination](#module\_client\_destination) | ../../modules/client-destination | n/a |
| <a name="module_client_transform_filter_lambda"></a> [client\_transform\_filter\_lambda](#module\_client\_transform\_filter\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| <a name="module_debug_log_bucket"></a> [debug\_log\_bucket](#module\_debug\_log\_bucket) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-s3bucket.zip | n/a |
| <a name="module_kms"></a> [kms](#module\_kms) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-kms.zip | n/a |
| <a name="module_mock_webhook_lambda"></a> [mock\_webhook\_lambda](#module\_mock\_webhook\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| <a name="module_sqs_inbound_event"></a> [sqs\_inbound\_event](#module\_sqs\_inbound\_event) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-sqs.zip | n/a |
| <a name="module_sqs_inbound_event"></a> [sqs\_inbound\_event](#module\_sqs\_inbound\_event) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.32/terraform-sqs.zip | n/a |
## Outputs

| Name | Description |
|------|-------------|
| <a name="output_debug_log_bucket_name"></a> [debug\_log\_bucket\_name](#output\_debug\_log\_bucket\_name) | S3 bucket name for debug logs (integration testing, deploy\_mock\_webhook=true only) |
| <a name="output_deployment"></a> [deployment](#output\_deployment) | Deployment details used for post-deployment scripts |
| <a name="output_mock_webhook_lambda_log_group_name"></a> [mock\_webhook\_lambda\_log\_group\_name](#output\_mock\_webhook\_lambda\_log\_group\_name) | CloudWatch log group name for mock webhook lambda (for integration test queries) |
| <a name="output_mock_webhook_url"></a> [mock\_webhook\_url](#output\_mock\_webhook\_url) | URL endpoint for mock webhook (for TEST\_WEBHOOK\_URL environment variable) |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
locals {
dlq_overview_metrics = [
for client_key in sort(keys(local.all_clients)) : [
"AWS/SQS",
"ApproximateNumberOfMessagesVisible",
"QueueName",
"${local.csi}-${client_key}-dlq-queue",
{ "label" : "${client_key} DLQ" }
]
]

dlq_timeseries_widgets = [
for idx, client_key in sort(keys(local.all_clients)) : {
height = 6
width = 12
y = 5 + (floor(idx / 2) * 6)
x = (idx % 2) * 12
type = "metric"
properties = {
title = "${client_key} DLQ Depth"
view = "timeSeries"
stacked = false
region = var.region
stat = "Maximum"
period = 300
metrics = [
[
"AWS/SQS",
"ApproximateNumberOfMessagesVisible",
"QueueName",
"${local.csi}-${client_key}-dlq-queue",
{ "label" : "Messages awaiting redrive", "color" : "#d62728" }
],
[
"AWS/SQS",
"NumberOfMessagesMoved",
"QueueName",
"${local.csi}-${client_key}-dlq-queue",
{ "label" : "Messages being redriven", "color" : "#2ca02c" }
]
]
annotations = {
horizontal = [
{
label = "Alarm threshold"
value = 0
fill = "above"
}
]
}
yAxis = {
left = {
min = 0
showUnits = false
label = "Message count"
}
}
}
}
]
}

resource "aws_cloudwatch_dashboard" "dlq" {
dashboard_name = "${local.csi}-dlq"

dashboard_body = jsonencode({
widgets = concat(
[
{
height = 1
width = 24
y = 0
x = 0
type = "text"
properties = {
markdown = "## DLQ Monitoring\nMessages in a DLQ indicate failed callback deliveries."
}
},
{
height = 4
width = 24
y = 1
x = 0
type = "metric"
properties = {
title = "DLQ Depth Overview"
view = "singleValue"
sparkline = true
region = var.region
stat = "Maximum"
period = 300
singleValueFullPrecision = false
metrics = local.dlq_overview_metrics
}
}
],
local.dlq_timeseries_widgets,
)
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
resource "aws_cloudwatch_metric_alarm" "client_dlq_depth" {
for_each = toset(keys(local.all_clients))

alarm_name = "${local.csi}-${each.key}-dlq-depth"
alarm_description = join(" ", [
"RELIABILITY: Messages are in DLQ for ${each.key}.",
"Failed callback deliveries require operator attention.",
])

comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "ApproximateNumberOfMessagesVisible"
namespace = "AWS/SQS"
period = 300
statistic = "Sum"
threshold = 0
actions_enabled = true
treat_missing_data = "notBreaching"

dimensions = {
QueueName = "${local.csi}-${each.key}-dlq-queue"
}

tags = merge(
local.default_tags,
{
Name = "${local.csi}-${each.key}-dlq-depth"
Client = each.key
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ module "mock_webhook_lambda" {
log_subscription_role_arn = local.acct.log_subscription_role_arn

lambda_env_vars = {
LOG_LEVEL = var.log_level
API_KEY = random_password.mock_webhook_api_key[0].result
LOG_LEVEL = var.log_level
API_KEY = random_password.mock_webhook_api_key[0].result
DEBUG_BUCKET_NAME = module.debug_log_bucket[0].id
}
}

Expand All @@ -63,6 +64,19 @@ data "aws_iam_policy_document" "mock_webhook_lambda" {
module.kms.key_arn,
]
}

statement {
sid = "DebugLogBucketWrite"
effect = "Allow"

actions = [
"s3:PutObject",
]

resources = [
"${module.debug_log_bucket[0].arn}/*",
]
}
}

# Lambda Function URL for mock webhook (test/dev only)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module "sqs_inbound_event" {
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-sqs.zip"
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.32/terraform-sqs.zip"

aws_account_id = var.aws_account_id
component = var.component
Expand All @@ -10,7 +10,8 @@ module "sqs_inbound_event" {

sqs_kms_key_arn = module.kms.key_arn

visibility_timeout_seconds = 60
visibility_timeout_seconds = local.sqs_inbound_event_effective_visibility_timeout_seconds
max_receive_count = local.sqs_inbound_event_max_receive_count

create_dlq = true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ module "client_transform_filter_lambda" {

lambda_env_vars = {
ENVIRONMENT = var.environment
METRICS_NAMESPACE = "nhs-notify-client-callbacks-metrics"
METRICS_NAMESPACE = "nhs-notify-client-callbacks"
DEBUG_BUCKET_NAME = var.deploy_mock_webhook ? module.debug_log_bucket[0].id : ""
CLIENT_SUBSCRIPTION_CONFIG_BUCKET = module.client_config_bucket.id
CLIENT_SUBSCRIPTION_CONFIG_PREFIX = "client_subscriptions/"
CLIENT_SUBSCRIPTION_CACHE_TTL_SECONDS = "60"
Expand Down Expand Up @@ -83,4 +84,21 @@ data "aws_iam_policy_document" "client_transform_filter_lambda" {
"*",
]
}

dynamic "statement" {
for_each = var.deploy_mock_webhook ? toset(["enabled"]) : toset([])

content {
sid = "DebugLogBucketWrite"
effect = "Allow"

actions = [
"s3:PutObject",
]

resources = [
"${module.debug_log_bucket[0].arn}/*",
]
}
}
}
7 changes: 6 additions & 1 deletion infrastructure/terraform/components/callbacks/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ output "deployment" {
}

##
# Mock Webhook Lambda Outputs (test/dev environments only)
# Mock Webhook Lambda Outputs (test/dev environments only).
##

output "mock_webhook_lambda_log_group_name" {
Expand All @@ -27,3 +27,8 @@ output "mock_webhook_url" {
description = "URL endpoint for mock webhook (for TEST_WEBHOOK_URL environment variable)"
value = var.deploy_mock_webhook ? aws_lambda_function_url.mock_webhook[0].function_url : null
}

output "debug_log_bucket_name" {
description = "S3 bucket name for debug logs (integration testing, deploy_mock_webhook=true only)"
value = var.deploy_mock_webhook ? module.debug_log_bucket[0].id : null
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ resource "aws_pipes_pipe" "main" {
source_parameters {
sqs_queue_parameters {
batch_size = var.pipe_sqs_input_batch_size
maximum_batching_window_in_seconds = var.pipe_sqs_max_batch_window
maximum_batching_window_in_seconds = local.pipe_sqs_effective_max_batch_window
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
module "debug_log_bucket" {
count = var.deploy_mock_webhook ? 1 : 0
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-s3bucket.zip"

name = "debug-log"

aws_account_id = var.aws_account_id
component = var.component
environment = var.environment
project = var.project
region = var.region

default_tags = merge(
local.default_tags,
{
Description = "Debug log storage for integration testing"
}
)

kms_key_arn = module.kms.key_arn
force_destroy = true
versioning = false
object_ownership = "BucketOwnerPreferred"
bucket_key_enabled = true

policy_documents = [
data.aws_iam_policy_document.debug_log_bucket[0].json,
]
}

data "aws_iam_policy_document" "debug_log_bucket" {
count = var.deploy_mock_webhook ? 1 : 0

statement {
sid = "AllowLambdaWriteAccess"
effect = "Allow"

principals {
type = "AWS"
identifiers = [
module.mock_webhook_lambda[0].iam_role_arn,
module.client_transform_filter_lambda.iam_role_arn,
]
}

actions = [
"s3:PutObject",
]

resources = [
"${module.debug_log_bucket[0].arn}/*",
]
}

statement {
sid = "DenyInsecureTransport"
effect = "Deny"

principals {
type = "*"
identifiers = ["*"]
}

actions = [
"s3:*",
]

resources = [
module.debug_log_bucket[0].arn,
"${module.debug_log_bucket[0].arn}/*",
]

condition {
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
}
}

resource "aws_s3_bucket_lifecycle_configuration" "debug_log" {
count = var.deploy_mock_webhook ? 1 : 0
bucket = module.debug_log_bucket[0].id

rule {
id = "expire-debug-logs"
status = "Enabled"

expiration {
days = 1
}
}
}
Loading
Loading