Skip to content

Commit f8546e3

Browse files
committed
CCM-12615: APIM Authentication
1 parent 14d87a9 commit f8546e3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3177
-35
lines changed

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export default defineConfig([
253253
'no-await-in-loop': 0,
254254
'no-plusplus': [2, { allowForLoopAfterthoughts: true }],
255255
'unicorn/prefer-top-level-await': 0, // top level await is not available in commonjs
256+
'import-x/prefer-default-export': "off"
256257
},
257258
},
258259
]);

infrastructure/terraform/components/dl/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ No requirements.
99

1010
| Name | Description | Type | Default | Required |
1111
|------|-------------|------|---------|:--------:|
12+
| <a name="input_apim_auth_token_schedule"></a> [apim\_auth\_token\_schedule](#input\_apim\_auth\_token\_schedule) | Schedule to renew the APIM auth token | `string` | `"rate(9 minutes)"` | no |
13+
| <a name="input_apim_auth_token_url"></a> [apim\_auth\_token\_url](#input\_apim\_auth\_token\_url) | URL to generate an APIM auth token | `string` | `"https://int.api.service.nhs.uk/oauth2/token"` | no |
14+
| <a name="input_apim_base_url"></a> [apim\_base\_url](#input\_apim\_base\_url) | The NHS Notify send message target for nudge communications. Defaults to sandbox | `string` | `"https://sandbox.api.service.nhs.uk"` | no |
15+
| <a name="input_apim_keygen_schedule"></a> [apim\_keygen\_schedule](#input\_apim\_keygen\_schedule) | Schedule to refresh key pairs if necessary | `string` | `"cron(0 14 * * ? *)"` | no |
1216
| <a name="input_aws_account_id"></a> [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes |
1317
| <a name="input_component"></a> [component](#input\_component) | The variable encapsulating the name of this component | `string` | `"dl"` | no |
1418
| <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 |
@@ -32,6 +36,8 @@ No requirements.
3236
| Name | Source | Version |
3337
|------|--------|---------|
3438
| <a name="module_kms"></a> [kms](#module\_kms) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-kms.zip | n/a |
39+
| <a name="module_lambda_apim_key_generation"></a> [lambda\_apim\_key\_generation](#module\_lambda\_apim\_key\_generation) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-lambda.zip | n/a |
40+
| <a name="module_lambda_lambda_apim_refresh_token"></a> [lambda\_lambda\_apim\_refresh\_token](#module\_lambda\_lambda\_apim\_refresh\_token) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-lambda.zip | n/a |
3541
| <a name="module_mesh_poll"></a> [mesh\_poll](#module\_mesh\_poll) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
3642
| <a name="module_s3bucket_letters"></a> [s3bucket\_letters](#module\_s3bucket\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-s3bucket.zip | n/a |
3743
| <a name="module_sqs_event_publisher_errors"></a> [sqs\_event\_publisher\_errors](#module\_sqs\_event\_publisher\_errors) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-sqs.zip | n/a |
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
locals {
22
aws_lambda_functions_dir_path = "../../../../lambdas"
33
log_destination_arn = "arn:aws:logs:${var.region}:${var.shared_infra_account_id}:destination:nhs-main-obs-firehose-logs"
4-
4+
apim_access_token_ssm_parameter_name = "/${var.component}/${var.environment}/apim/access_token"
5+
apim_api_key_ssm_parameter_name = "/${var.component}/${var.environment}/apim/api_key"
6+
apim_private_key_ssm_parameter_name = "/${var.component}/${var.environment}/apim/private_key"
7+
apim_keystore_s3_bucket = "nhs-${var.aws_account_id}-${var.region}-${var.environment}-${var.component}-static-assets"
58
ttl_shard_count = 3
69
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
module "lambda_apim_key_generation" {
2+
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-lambda.zip"
3+
4+
function_name = "apim-key-generation"
5+
description = "A function to generate APIM public and private keys"
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.lambda_apim_key_generation.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 = "key-generation/dist"
24+
function_include_common = true
25+
function_module_name = "lambda"
26+
handler_function_name = "handler"
27+
runtime = "nodejs22.x"
28+
memory = 512
29+
timeout = 300
30+
log_level = var.log_level
31+
schedule = var.apim_keygen_schedule
32+
33+
force_lambda_code_deploy = var.force_lambda_code_deploy
34+
enable_lambda_insights = false
35+
36+
send_to_firehose = true
37+
log_destination_arn = local.log_destination_arn
38+
log_subscription_role_arn = local.acct.log_subscription_role_arn
39+
40+
lambda_env_vars = {
41+
SSM_PRIVATE_KEY_PARAMETER_NAME = local.apim_private_key_ssm_parameter_name
42+
KEYSTORE_S3_BUCKET = local.apim_keystore_s3_bucket
43+
ENVIRONMENT = var.environment
44+
}
45+
}
46+
47+
data "aws_iam_policy_document" "lambda_apim_key_generation" {
48+
statement {
49+
sid = "AllowS3List"
50+
effect = "Allow"
51+
52+
actions = [
53+
"s3:ListBucket",
54+
"s3:PutObject"
55+
]
56+
57+
resources = [
58+
"arn:aws:s3:::${local.apim_keystore_s3_bucket}/*"
59+
]
60+
}
61+
62+
statement {
63+
sid = "AllowSSMParam"
64+
effect = "Allow"
65+
66+
actions = [
67+
"ssm:DeleteParameter",
68+
"ssm:GetParameter",
69+
"ssm:GetParameters",
70+
"ssm:GetParametersByPath",
71+
"ssm:PutParameter",
72+
]
73+
74+
resources = [
75+
"arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${var.component}/${var.environment}/apim/*"
76+
]
77+
}
78+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
module "lambda_lambda_apim_refresh_token" {
2+
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-lambda.zip"
3+
4+
function_name = "apim-refresh-token"
5+
description = "A function to generate APIM access tokens"
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.lambda_apim_refresh_token.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 = "refresh-apim-access-token/dist"
24+
function_include_common = true
25+
handler_function_name = "handler"
26+
runtime = "nodejs22.x"
27+
memory = 128
28+
timeout = 5
29+
log_level = var.log_level
30+
schedule = var.apim_auth_token_schedule
31+
32+
force_lambda_code_deploy = var.force_lambda_code_deploy
33+
enable_lambda_insights = false
34+
35+
send_to_firehose = true
36+
log_destination_arn = local.log_destination_arn
37+
log_subscription_role_arn = local.acct.log_subscription_role_arn
38+
39+
lambda_env_vars = {
40+
NHS_AUTH_SERVER_TOKEN_ENDPOINT = var.apim_auth_token_url
41+
SSM_ACCESS_TOKEN_PARAMETER_NAME = local.apim_access_token_ssm_parameter_name
42+
SSM_API_KEY_PARAMETER_NAME = local.apim_api_key_ssm_parameter_name
43+
SSM_PRIVATE_KEY_PARAMETER_NAME = local.apim_private_key_ssm_parameter_name
44+
ENVIRONMENT = var.environment
45+
}
46+
}
47+
48+
data "aws_iam_policy_document" "lambda_apim_refresh_token" {
49+
statement {
50+
sid = "AllowSSMParam"
51+
effect = "Allow"
52+
53+
actions = [
54+
"ssm:DeleteParameter",
55+
"ssm:GetParameter",
56+
"ssm:GetParameters",
57+
"ssm:GetParametersByPath",
58+
"ssm:PutParameter",
59+
]
60+
61+
resources = [
62+
"arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${var.component}/${var.environment}/apim/*"
63+
]
64+
}
65+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
resource "aws_ssm_parameter" "access_token" {
2+
name = local.apim_access_token_ssm_parameter_name
3+
description = "Access token for APIM"
4+
type = "SecureString"
5+
value = jsonencode({
6+
tokens = []
7+
})
8+
tags = merge(local.default_tags, { Backup = "true" })
9+
10+
lifecycle {
11+
ignore_changes = [
12+
value
13+
]
14+
}
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
resource "aws_ssm_parameter" "api_key" {
2+
name = local.apim_api_key_ssm_parameter_name
3+
description = "Access token for APIM"
4+
type = "SecureString"
5+
value = "unset"
6+
tags = merge(local.default_tags, { Backup = "true" })
7+
8+
lifecycle {
9+
ignore_changes = [
10+
value
11+
]
12+
}
13+
}

infrastructure/terraform/components/dl/variables.tf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,27 @@ variable "ttl_poll_schedule" {
115115
description = "Schedule to poll for any overdue TTL records"
116116
default = "rate(10 minutes)" # Every 10 minutes
117117
}
118+
119+
variable "apim_base_url" {
120+
type = string
121+
description = "The NHS Notify send message target for nudge communications. Defaults to sandbox"
122+
default = "https://sandbox.api.service.nhs.uk"
123+
}
124+
125+
variable "apim_auth_token_url" {
126+
type = string
127+
description = "URL to generate an APIM auth token"
128+
default = "https://int.api.service.nhs.uk/oauth2/token"
129+
}
130+
131+
variable "apim_keygen_schedule" {
132+
type = string
133+
description = "Schedule to refresh key pairs if necessary"
134+
default = "cron(0 14 * * ? *)"
135+
}
136+
137+
variable "apim_auth_token_schedule" {
138+
type = string
139+
description = "Schedule to renew the APIM auth token"
140+
default = "rate(9 minutes)"
141+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Config } from 'jest';
2+
3+
export const baseJestConfig: Config = {
4+
preset: 'ts-jest',
5+
6+
// Automatically clear mock calls, instances, contexts and results before every test
7+
clearMocks: true,
8+
9+
// Indicates whether the coverage information should be collected while executing the test
10+
collectCoverage: true,
11+
12+
// The directory where Jest should output its coverage files
13+
coverageDirectory: './.reports/unit/coverage',
14+
15+
// Indicates which provider should be used to instrument code for coverage
16+
coverageProvider: 'babel',
17+
18+
coverageThreshold: {
19+
global: {
20+
branches: 90,
21+
functions: 90,
22+
lines: 90,
23+
statements: -10,
24+
},
25+
},
26+
27+
coveragePathIgnorePatterns: ['/__tests__/', 'lambda.ts', '/config.ts'],
28+
transform: { '^.+\\.ts$': 'ts-jest' },
29+
testPathIgnorePatterns: ['.build'],
30+
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
31+
32+
// Use this configuration option to add custom reporters to Jest
33+
reporters: [
34+
'default',
35+
[
36+
'jest-html-reporter',
37+
{
38+
pageTitle: 'Test Report',
39+
outputPath: './.reports/unit/test-report.html',
40+
includeFailureMsg: true,
41+
},
42+
],
43+
],
44+
45+
// The test environment that will be used for testing
46+
testEnvironment: 'jsdom',
47+
};
48+
49+
const utilsJestConfig = {
50+
...baseJestConfig,
51+
52+
testEnvironment: 'node',
53+
54+
coveragePathIgnorePatterns: [
55+
...(baseJestConfig.coveragePathIgnorePatterns ?? []),
56+
],
57+
58+
moduleDirectories: ['node_modules', 'src'],
59+
};
60+
61+
export default utilsJestConfig;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"dependencies": {
3+
"aws-lambda": "^1.0.7",
4+
"date-fns": "^4.1.0",
5+
"esbuild": "^0.25.9",
6+
"utils": "*",
7+
"node-jose": "^2.2.0"
8+
},
9+
"devDependencies": {
10+
"@tsconfig/node22": "^22.0.2",
11+
"@types/aws-lambda": "^8.10.148",
12+
"@types/jest": "^29.5.14",
13+
"@types/node": "^24.0.10",
14+
"@types/node-jose": "^1.1.13",
15+
"jest": "^29.7.0",
16+
"jest-mock-extended": "^3.0.7",
17+
"typescript": "^5.8.2"
18+
},
19+
"exports": {
20+
".": "./src/index.ts"
21+
},
22+
"name": "key-generation",
23+
"private": true,
24+
"scripts": {
25+
"lambda-build": "rm -rf dist && npx esbuild --bundle --minify --sourcemap --target=es2020 --platform=node --loader:.node=file --entry-names=[name] --outdir=dist src/lambda.ts",
26+
"lint": "eslint .",
27+
"lint:fix": "eslint . --fix",
28+
"test:unit": "jest",
29+
"typecheck": "tsc --noEmit"
30+
},
31+
"version": "0.0.1"
32+
}

0 commit comments

Comments
 (0)