Skip to content

Commit dff2437

Browse files
Merge remote-tracking branch 'origin/main' into feature/CCM-12860
2 parents 19315df + 3288163 commit dff2437

29 files changed

Lines changed: 9879 additions & 3771 deletions

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/.github/ @NHSDigital/nhs-notify-supplier-api-admins
66
*.code-workspace @NHSDigital/nhs-notify-supplier-api-admins
77
/docs/ @NHSDigital/nhs-notify-supplier-api
8-
/infrastructure/terraform/ @NHSDigital/nhs-notify-platform @NHSDigital/nhs-notify-supplier-api-admins
8+
/infrastructure/terraform/ @NHSDigital/nhs-notify-platform
99

1010
# Root level AGENTS.md owned by platform.
1111
AGENTS.md @NHSDigital/nhs-notify-platform

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.1.2-${yyyy}${mm}${dd}.${HH}${MM}${SS}+${hash}
1+
1.1.5-${yyyy}${mm}${dd}.${HH}${MM}${SS}+${hash}

infrastructure/terraform/components/api/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ No requirements.
6969
| <a name="module_s3bucket_test_letters"></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 |
7070
| <a name="module_sqs_alarms"></a> [sqs\_alarms](#module\_sqs\_alarms) | ../../modules/alarms/alarms-sqs | n/a |
7171
| <a name="module_sqs_letter_updates"></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 |
72+
| <a name="module_sqs_supplier_allocator"></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 |
73+
| <a name="module_supplier_allocator"></a> [supplier\_allocator](#module\_supplier\_allocator) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
7274
| <a name="module_supplier_ssl"></a> [supplier\_ssl](#module\_supplier\_ssl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-ssl.zip | n/a |
7375
| <a name="module_upsert_letter"></a> [upsert\_letter](#module\_upsert\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
7476
## Outputs
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
resource "aws_lambda_event_source_mapping" "supplier_allocator" {
2+
event_source_arn = module.sqs_supplier_allocator.sqs_queue_arn
3+
function_name = module.supplier_allocator.function_name
4+
batch_size = 10
5+
maximum_batching_window_in_seconds = 5
6+
function_response_types = [
7+
"ReportBatchItemFailures"
8+
]
9+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
module "supplier_allocator" {
2+
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip"
3+
4+
function_name = "supplier-allocator"
5+
description = "Allocate a letter to a supplier"
6+
7+
aws_account_id = var.aws_account_id
8+
component = var.component
9+
environment = var.environment
10+
project = var.project
11+
region = var.region
12+
group = var.group
13+
14+
log_retention_in_days = var.log_retention_in_days
15+
kms_key_arn = module.kms.key_arn
16+
17+
iam_policy_document = {
18+
body = data.aws_iam_policy_document.supplier_allocator_lambda.json
19+
}
20+
21+
function_s3_bucket = local.acct.s3_buckets["lambda_function_artefacts"]["id"]
22+
function_code_base_path = local.aws_lambda_functions_dir_path
23+
function_code_dir = "supplier-allocator/dist"
24+
function_include_common = true
25+
handler_function_name = "supplierAllocatorHandler"
26+
runtime = "nodejs22.x"
27+
memory = 512
28+
timeout = 29
29+
log_level = var.log_level
30+
31+
force_lambda_code_deploy = var.force_lambda_code_deploy
32+
enable_lambda_insights = false
33+
34+
log_destination_arn = local.destination_arn
35+
log_subscription_role_arn = local.acct.log_subscription_role_arn
36+
37+
lambda_env_vars = merge(local.common_lambda_env_vars, {
38+
VARIANT_MAP = jsonencode(var.letter_variant_map)
39+
UPSERT_LETTERS_QUEUE_URL = module.sqs_letter_updates.sqs_queue_url
40+
})
41+
}
42+
43+
data "aws_iam_policy_document" "supplier_allocator_lambda" {
44+
statement {
45+
sid = "KMSPermissions"
46+
effect = "Allow"
47+
48+
actions = [
49+
"kms:Decrypt",
50+
"kms:GenerateDataKey",
51+
]
52+
53+
resources = [
54+
module.kms.key_arn,
55+
]
56+
}
57+
58+
statement {
59+
sid = "AllowSQSRead"
60+
effect = "Allow"
61+
62+
actions = [
63+
"sqs:ReceiveMessage",
64+
"sqs:DeleteMessage",
65+
"sqs:GetQueueAttributes"
66+
]
67+
68+
resources = [
69+
module.sqs_supplier_allocator.sqs_queue_arn
70+
]
71+
}
72+
73+
statement {
74+
sid = "AllowSQSWrite"
75+
effect = "Allow"
76+
77+
actions = [
78+
"sqs:SendMessage"
79+
]
80+
81+
resources = [
82+
module.sqs_letter_updates.sqs_queue_arn
83+
]
84+
}
85+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module "sqs_supplier_allocator" {
2+
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-sqs.zip"
3+
4+
aws_account_id = var.aws_account_id
5+
component = var.component
6+
environment = var.environment
7+
project = var.project
8+
region = var.region
9+
name = "supplier-allocator"
10+
11+
sqs_kms_key_arn = module.kms.key_arn
12+
13+
visibility_timeout_seconds = 60
14+
15+
create_dlq = true
16+
sqs_policy_overload = data.aws_iam_policy_document.supplier_allocator_queue_policy.json
17+
}
18+
19+
data "aws_iam_policy_document" "supplier_allocator_queue_policy" {
20+
version = "2012-10-17"
21+
22+
statement {
23+
sid = "AllowSNSPermissions"
24+
effect = "Allow"
25+
26+
principals {
27+
type = "Service"
28+
identifiers = ["sns.amazonaws.com"]
29+
}
30+
31+
actions = [
32+
"sqs:SendMessage",
33+
"sqs:ListQueueTags",
34+
"sqs:GetQueueUrl",
35+
"sqs:GetQueueAttributes",
36+
]
37+
38+
resources = [
39+
"arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-${var.component}-supplier-allocator-queue"
40+
]
41+
42+
condition {
43+
test = "ArnEquals"
44+
variable = "aws:SourceArn"
45+
values = [module.eventsub.sns_topic.arn]
46+
}
47+
}
48+
}
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
resource "aws_sns_topic_subscription" "eventsub_sqs_letter_updates" {
2-
topic_arn = module.eventsub.sns_topic.arn
3-
protocol = "sqs"
4-
endpoint = module.sqs_letter_updates.sqs_queue_arn
2+
topic_arn = module.eventsub.sns_topic.arn
3+
protocol = "sqs"
4+
endpoint = module.sqs_letter_updates.sqs_queue_arn
5+
raw_message_delivery = true
6+
7+
filter_policy_scope = "MessageBody"
8+
filter_policy = jsonencode({
9+
type = [{ prefix = "uk.nhs.notify.supplier-api.letter" }]
10+
})
511
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
resource "aws_sns_topic_subscription" "eventsub_sqs_supplier_allocator" {
2+
topic_arn = module.eventsub.sns_topic.arn
3+
protocol = "sqs"
4+
endpoint = module.sqs_supplier_allocator.sqs_queue_arn
5+
raw_message_delivery = true
6+
7+
filter_policy_scope = "MessageBody"
8+
filter_policy = jsonencode({
9+
type = [{ prefix = "uk.nhs.notify.letter-rendering.letter-request.prepared" }]
10+
})
11+
}

lambdas/authorizer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"@aws-sdk/lib-dynamodb": "^3.858.0",
55
"@internal/datastore": "*",
66
"@internal/helpers": "*",
7+
"aws-embedded-metrics": "^4.2.1",
78
"aws-lambda": "^1.0.7",
89
"esbuild": "0.27.2",
910
"pino": "^10.3.0",

lambdas/authorizer/src/__tests__/index.test.ts

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,29 @@ import {
44
Callback,
55
Context,
66
} from "aws-lambda";
7+
import { metricScope } from "aws-embedded-metrics";
78
import pino from "pino";
89
import { Deps } from "../deps";
910
import { EnvVars } from "../env";
1011
import createAuthorizerHandler from "../authorizer";
1112

13+
jest.mock("aws-embedded-metrics", () => {
14+
const metricsMock = {
15+
setNamespace: jest.fn(),
16+
putMetric: jest.fn(),
17+
};
18+
19+
return {
20+
metricScope: jest.fn((handler) => async () => {
21+
const wrapped = handler(metricsMock);
22+
if (typeof wrapped === "function") {
23+
await wrapped();
24+
}
25+
}),
26+
__metricsMock: metricsMock,
27+
};
28+
});
29+
1230
const mockedDeps: jest.Mocked<Deps> = {
1331
logger: {
1432
info: jest.fn(),
@@ -61,6 +79,13 @@ describe("Authorizer Lambda Function", () => {
6179
jest
6280
.useFakeTimers({ doNotFake: ["nextTick"] })
6381
.setSystemTime(currentDate);
82+
(metricScope as jest.Mock).mockClear();
83+
(mockedDeps.logger.warn as jest.Mock).mockClear();
84+
const metricsMock = jest.requireMock(
85+
"aws-embedded-metrics",
86+
).__metricsMock;
87+
metricsMock.setNamespace.mockClear();
88+
metricsMock.putMetric.mockClear();
6489
});
6590

6691
afterEach(() => {
@@ -74,10 +99,7 @@ describe("Authorizer Lambda Function", () => {
7499
handler(mockEvent, mockContext, mockCallback);
75100
await new Promise(process.nextTick);
76101

77-
const mockedInfo = mockedDeps.logger.info as jest.Mock;
78-
expect(mockedInfo.mock.calls).not.toContainEqual(
79-
expect.stringContaining("CloudWatchMetrics"),
80-
);
102+
expect(metricScope).not.toHaveBeenCalled();
81103
});
82104

83105
it("Should log CloudWatch metric when the certificate expiry threshold is reached", async () => {
@@ -89,29 +111,20 @@ describe("Authorizer Lambda Function", () => {
89111
handler(mockEvent, mockContext, mockCallback);
90112
await new Promise(process.nextTick);
91113

92-
const mockedInfo = mockedDeps.logger.info as jest.Mock;
93-
expect(mockedInfo.mock.calls.map((call) => call[0])).toContain(
94-
JSON.stringify({
95-
_aws: {
96-
Timestamp: currentDate.getTime(),
97-
CloudWatchMetrics: [
98-
{
99-
Namespace: "cloudwatch-namespace",
100-
Dimensions: ["SUBJECT_DN", "NOT_AFTER"],
101-
Metrics: [
102-
{
103-
Name: "apim-client-certificate-near-expiry",
104-
Unit: "Count",
105-
Value: 1,
106-
},
107-
],
108-
},
109-
],
110-
},
111-
SUBJECT_DN: "CN=test-subject",
112-
NOT_AFTER: "2025-11-31T14:19:00Z",
113-
"apim-client-certificate-near-expiry": 1,
114-
}),
114+
const metricsMock = jest.requireMock(
115+
"aws-embedded-metrics",
116+
).__metricsMock;
117+
118+
expect(metricScope).toHaveBeenCalledTimes(1);
119+
expect(mockedDeps.logger.warn).toHaveBeenCalledWith({
120+
description: "APIM Certificate expiry",
121+
days: 30,
122+
});
123+
expect(metricsMock.setNamespace).toHaveBeenCalledWith("authorizer");
124+
expect(metricsMock.putMetric).toHaveBeenCalledWith(
125+
"apim-client-certificate-near-expiry",
126+
30,
127+
"Count",
115128
);
116129
});
117130

@@ -124,10 +137,7 @@ describe("Authorizer Lambda Function", () => {
124137
handler(mockEvent, mockContext, mockCallback);
125138
await new Promise(process.nextTick);
126139

127-
const mockedInfo = mockedDeps.logger.info as jest.Mock;
128-
expect(mockedInfo.mock.calls).not.toContainEqual(
129-
expect.stringContaining("CloudWatchMetrics"),
130-
);
140+
expect(metricScope).not.toHaveBeenCalled();
131141
});
132142
});
133143

0 commit comments

Comments
 (0)