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
10 changes: 9 additions & 1 deletion infrastructure/terraform/components/callbacks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
| <a name="input_clients"></a> [clients](#input\_clients) | n/a | <pre>list(object({<br/> connection_name = string<br/> destination_name = string<br/> invocation_endpoint = string<br/> invocation_rate_limit_per_second = optional(number, 10)<br/> http_method = optional(string, "POST")<br/> header_name = optional(string, "x-api-key")<br/> header_value = string<br/> client_detail = list(string)<br/> }))</pre> | `[]` | no |
| <a name="input_component"></a> [component](#input\_component) | The variable encapsulating the name of this component | `string` | `"callbacks"` | 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_deploy_mock_webhook"></a> [deploy\_mock\_webhook](#input\_deploy\_mock\_webhook) | Flag to deploy mock webhook lambda for integration testing (test/dev environments only) | `bool` | `false` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | The name of the tfscaffold environment | `string` | 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 |
| <a name="input_group"></a> [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | n/a | yes |
Expand All @@ -32,13 +33,20 @@

| 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) | git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda | v2.0.29 |
| <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) | git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda | v2.0.29 |
| <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 |
## Outputs

No outputs.
| Name | Description |
|------|-------------|
| <a name="output_mock_webhook_lambda_function_arn"></a> [mock\_webhook\_lambda\_function\_arn](#output\_mock\_webhook\_lambda\_function\_arn) | ARN of the mock webhook lambda function (only present when deploy\_mock\_webhook=true) |
| <a name="output_mock_webhook_lambda_function_name"></a> [mock\_webhook\_lambda\_function\_name](#output\_mock\_webhook\_lambda\_function\_name) | Name of the mock webhook lambda function (only present when deploy\_mock\_webhook=true) |
| <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) |
<!-- vale on -->
<!-- markdownlint-enable -->
<!-- END_TF_DOCS -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
module "mock_webhook_lambda" {
count = var.deploy_mock_webhook ? 1 : 0
source = "git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda?ref=v2.0.29"

function_name = "mock-webhook"
description = "Mock webhook endpoint for integration testing - logs received callbacks to CloudWatch"

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

log_retention_in_days = var.log_retention_in_days
kms_key_arn = module.kms.key_arn

iam_policy_document = {
body = data.aws_iam_policy_document.mock_webhook_lambda[0].json
}

function_s3_bucket = local.acct.s3_buckets["lambda_function_artefacts"]["id"]
function_code_base_path = local.aws_lambda_functions_dir_path
function_code_dir = "mock-webhook-lambda/dist"
function_include_common = true
handler_function_name = "handler"
runtime = "nodejs22.x"
memory = 256
timeout = 10
log_level = var.log_level

force_lambda_code_deploy = var.force_lambda_code_deploy
enable_lambda_insights = false

log_destination_arn = local.log_destination_arn
log_subscription_role_arn = local.acct.log_subscription_role_arn

lambda_env_vars = {
LOG_LEVEL = var.log_level
}
}

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

statement {
sid = "KMSPermissions"
effect = "Allow"

actions = [
"kms:Decrypt",
"kms:GenerateDataKey",
]

resources = [
module.kms.key_arn,
]
}

# Mock webhook only needs CloudWatch Logs permissions (already granted by shared lambda module)
# No additional permissions required beyond base Lambda execution role
}

# Lambda Function URL for mock webhook (test/dev only)
resource "aws_lambda_function_url" "mock_webhook" {
count = var.deploy_mock_webhook ? 1 : 0
function_name = module.mock_webhook_lambda[0].function_name
authorization_type = "NONE" # Public endpoint for testing

cors {
allow_origins = ["*"]
allow_methods = ["POST"]
allow_headers = ["*"]
max_age = 86400
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module "client_transform_filter_lambda" {
kms_key_arn = module.kms.key_arn ## Requires shared kms module

iam_policy_document = {
body = data.aws_iam_policy_document.example_lambda.json
body = data.aws_iam_policy_document.client_transform_filter_lambda.json
}

function_s3_bucket = local.acct.s3_buckets["lambda_function_artefacts"]["id"]
Expand All @@ -38,7 +38,7 @@ module "client_transform_filter_lambda" {
}
}

data "aws_iam_policy_document" "example_lambda" {
data "aws_iam_policy_document" "client_transform_filter_lambda" {
statement {
sid = "KMSPermissions"
effect = "Allow"
Expand All @@ -52,4 +52,17 @@ data "aws_iam_policy_document" "example_lambda" {
module.kms.key_arn, ## Requires shared kms module
]
}

statement {
sid = "S3ClientConfigReadAccess"
effect = "Allow"

actions = [
"s3:GetObject",
]

resources = [
"${module.client_config_bucket.arn}/*",
]
}
}
24 changes: 24 additions & 0 deletions infrastructure/terraform/components/callbacks/outputs.tf
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# Define the outputs for the component. The outputs may well be referenced by other component in the same or different environments using terraform_remote_state data sources...

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

output "mock_webhook_lambda_function_name" {
description = "Name of the mock webhook lambda function (only present when deploy_mock_webhook=true)"
value = var.deploy_mock_webhook ? module.mock_webhook_lambda[0].function_name : null
}

output "mock_webhook_lambda_function_arn" {
description = "ARN of the mock webhook lambda function (only present when deploy_mock_webhook=true)"
value = var.deploy_mock_webhook ? module.mock_webhook_lambda[0].function_arn : null
}

output "mock_webhook_lambda_log_group_name" {
description = "CloudWatch log group name for mock webhook lambda (for integration test queries)"
value = var.deploy_mock_webhook ? module.mock_webhook_lambda[0].cloudwatch_log_group_name : null
}

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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
##
# S3 Bucket for Client Subscription Configuration
#
# Storage location for client subscription configurations loaded by Transform & Filter Lambda.
# Files are named {clientId}.json and contain ClientSubscriptionConfiguration arrays.
##

module "client_config_bucket" {
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-s3bucket.zip"

name = "subscription-config"

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 = "Client subscription configuration storage"
}
)

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

policy_documents = [
data.aws_iam_policy_document.client_config_bucket.json
]
}

##
# S3 Bucket Policy
#
# Allows Transform & Filter Lambda to read configuration files
##

data "aws_iam_policy_document" "client_config_bucket" {
statement {
sid = "AllowLambdaReadAccess"
effect = "Allow"

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

actions = [
"s3:GetObject",
]

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

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

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

actions = [
"s3:*",
]

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

condition {
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
}
}
6 changes: 6 additions & 0 deletions infrastructure/terraform/components/callbacks/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,9 @@ variable "pipe_sqs_max_batch_window" {
type = number
default = 2
}

variable "deploy_mock_webhook" {
type = bool
description = "Flag to deploy mock webhook lambda for integration testing (test/dev environments only)"
default = false
}
3 changes: 3 additions & 0 deletions lambdas/client-transform-filter-lambda/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ const utilsJestConfig = {
...(baseJestConfig.coveragePathIgnorePatterns ?? []),
"zod-validators.ts",
],

// Mirror tsconfig's baseUrl: "src" - automatically resolves non-relative imports
modulePaths: ["<rootDir>/src"],
};

export default utilsJestConfig;
5 changes: 4 additions & 1 deletion lambdas/client-transform-filter-lambda/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"dependencies": {
"esbuild": "^0.25.0"
"@aws-sdk/client-cloudwatch": "^3.709.0",
"cloudevents": "^8.0.2",
"esbuild": "^0.25.0",
"pino": "^9.5.0"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
Expand Down
Loading
Loading