Skip to content

Commit 919facf

Browse files
committed
Revert "Update the code to fetch secrets at runtime"
This reverts commit e6e4abe.
1 parent e6e4abe commit 919facf

7 files changed

Lines changed: 34 additions & 481 deletions

File tree

SAMtemplates/functions/main.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,8 +444,8 @@ Resources:
444444
Variables:
445445
LOG_LEVEL: !Ref LogLevel
446446
TABLE_NAME: !Ref PrescriptionNotificationStatesTableName
447-
APP_NAME_SECRET_ARN: !ImportValue secrets:PSUNotifyCallbackAppName
448-
API_KEY_SECRET_ARN: !ImportValue secrets:PSUNotifyCallbackApiKey
447+
APP_NAME: !ImportValue secrets:PSUNotifyCallbackAppName
448+
API_KEY: !ImportValue secrets:PSUNotifyCallbackApiKey
449449
Metadata:
450450
BuildMethod: esbuild
451451
guard:

package-lock.json

Lines changed: 0 additions & 388 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/* eslint-disable no-undef */
22
process.env.TABLE_NAME = "dummy_table";
3-
process.env.APP_NAME_SECRET_ARN = "app name";
4-
process.env.API_KEY_SECRET_ARN = "api key";
3+
process.env.APP_NAME = "app name";
4+
process.env.API_KEY = "api key";

packages/nhsNotifyUpdateCallback/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
"@aws-lambda-powertools/commons": "^2.17.0",
1818
"@aws-lambda-powertools/logger": "^2.18.0",
1919
"@aws-lambda-powertools/parameters": "^2.18.0",
20-
"@aws-sdk/client-secrets-manager": "^3.812.0",
2120
"@middy/core": "^6.2.2",
2221
"@middy/input-output-logger": "^6.2.2",
2322
"@nhs/fhir-middy-error-handler": "^2.1.29",

packages/nhsNotifyUpdateCallback/src/helpers.ts

Lines changed: 12 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import {Logger} from "@aws-lambda-powertools/logger"
44
import {DynamoDBClient} from "@aws-sdk/client-dynamodb"
55
import {DynamoDBDocumentClient, UpdateCommand, QueryCommand} from "@aws-sdk/lib-dynamodb"
66

7-
import {SecretsManagerClient, GetSecretValueCommand} from "@aws-sdk/client-secrets-manager"
87
import {createHmac, timingSafeEqual} from "crypto"
98

109
import {MessageStatusResponse} from "./types"
1110

11+
const APP_NAME = process.env.APP_NAME
12+
const API_KEY = process.env.API_KEY
13+
1214
// TTL is one week in seconds
1315
const TTL_DELTA = 60 * 60 * 24 * 7
1416

@@ -17,66 +19,25 @@ const dynamoTable = process.env.TABLE_NAME
1719
const dynamo = new DynamoDBClient({region: process.env.AWS_REGION})
1820
const docClient = DynamoDBDocumentClient.from(dynamo)
1921

20-
// Do a bit of secret caching to help reduce the number of fetches.
21-
let cachedAppName: string | undefined
22-
let cachedApiKey: string | undefined
23-
24-
const secretsClient = new SecretsManagerClient({
25-
region: process.env.AWS_REGION
26-
})
27-
2822
export function response(statusCode: number, body: unknown = {}) {
2923
return {
3024
statusCode,
3125
body: JSON.stringify(body)
3226
}
3327
}
3428

35-
async function getSecretValue(secretArn: string): Promise<string> {
36-
const cmd = new GetSecretValueCommand({SecretId: secretArn})
37-
const resp = await secretsClient.send(cmd)
38-
39-
if (resp.SecretString) {
40-
return resp.SecretString
41-
}
42-
43-
throw new Error(`Secret ${secretArn} has no usable SecretString`)
44-
}
45-
46-
/**
47-
* Loads both APP_NAME and API_KEY from Secrets Manager, if not already cached.
48-
* I'm loading these at runtime so that we can update the secret and have that change
49-
* reflected without the need for a full redeployment.
50-
*/
51-
async function loadSecrets() {
52-
if (cachedAppName && cachedApiKey) return
53-
54-
const appNameArn = process.env.APP_NAME_SECRET_ARN
55-
const apiKeyArn = process.env.API_KEY_SECRET_ARN
56-
57-
if (!appNameArn) {
58-
throw new Error("APP_NAME_SECRET_ARN environment variable is not set.")
59-
}
60-
if (!apiKeyArn) {
61-
throw new Error("API_KEY_SECRET_ARN environment variable is not set.")
62-
}
63-
64-
const [nameValue, keyValue] = await Promise.all([
65-
getSecretValue(appNameArn),
66-
getSecretValue(apiKeyArn)
67-
])
68-
69-
cachedAppName = nameValue.trim()
70-
cachedApiKey = keyValue.trim()
71-
}
72-
7329
/**
7430
* Checks the incoming NHS Notify request signature.
7531
* If it's okay, returns undefined.
7632
* If it's not okay, it returns the error response object.
7733
*/
78-
export async function checkSignature(logger: Logger, event: APIGatewayProxyEvent) {
79-
await loadSecrets()
34+
export function checkSignature(logger: Logger, event: APIGatewayProxyEvent) {
35+
if (!APP_NAME) {
36+
throw new Error("APP_NAME environment variable is not set.")
37+
}
38+
if (!API_KEY) {
39+
throw new Error("API_KEY environment variable is not set.")
40+
}
8041

8142
const signature = event.headers["x-hmac-sha256-signature"]
8243
if (!signature) {
@@ -91,10 +52,10 @@ export async function checkSignature(logger: Logger, event: APIGatewayProxyEvent
9152
}
9253

9354
// FIXME: Delete this line before PR
94-
logger.info("Secret data", {cachedAppName, cachedApiKey})
55+
logger.info("Secret data", {APP_NAME, API_KEY})
9556

9657
// Compute the HMAC-SHA256 hash of the combination of the request body and the secret value
97-
const secretValue = `${cachedAppName!}.${cachedApiKey!}`
58+
const secretValue = `${APP_NAME}.${API_KEY}`
9859
const payload = event.body ?? ""
9960

10061
// compare hashes as Buffers, rather than hex

packages/nhsNotifyUpdateCallback/src/lambdaHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayPro
2626
if (!event.headers["x-request-id"]) return response(400, {message: "No x-request-id given"})
2727

2828
// Check the request signature
29-
const isErr = await checkSignature(logger, event)
29+
const isErr = checkSignature(logger, event)
3030
if (isErr) return isErr
3131
logger.info("Signature OK!")
3232

packages/nhsNotifyUpdateCallback/tests/testHelpers.test.ts

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
} from "@jest/globals"
99
import {createHmac} from "crypto"
1010
import {DynamoDBDocumentClient, QueryCommand, UpdateCommand} from "@aws-sdk/lib-dynamodb"
11-
import {SecretsManagerClient} from "@aws-sdk/client-secrets-manager"
1211

1312
import {response, checkSignature, updateNotificationsTable} from "../src/helpers"
1413
import {Logger} from "@aws-lambda-powertools/logger"
@@ -45,18 +44,8 @@ describe("helpers.ts", () => {
4544

4645
describe("checkSignature()", () => {
4746
let logger: Logger
48-
let validHeaders: Record<string, string>
49-
let smSendSpy: jest.SpiedFunction<typeof SecretsManagerClient.prototype.send>
50-
47+
let validHeaders: { "x-request-id": string; "x-api-key": string; "x-hmac-sha256-signature": string }
5148
beforeEach(() => {
52-
// Stub SecretsManagerClient.send so we never call AWS in tests
53-
smSendSpy = jest
54-
.spyOn(SecretsManagerClient.prototype, "send")
55-
// first call: APP_NAME
56-
.mockImplementationOnce(() => Promise.resolve({SecretString: process.env.APP_NAME_SECRET_ARN!}))
57-
// second call: API_KEY
58-
.mockImplementationOnce(() => Promise.resolve({SecretString: process.env.API_KEY_SECRET_ARN!}))
59-
6049
logger = new Logger({serviceName: "nhsNotifyUpdateCallback"})
6150
validHeaders = {
6251
"x-request-id": "requestid",
@@ -65,48 +54,40 @@ describe("helpers.ts", () => {
6554
}
6655
})
6756

68-
afterEach(() => {
69-
smSendSpy.mockRestore()
70-
})
71-
72-
it("401 when missing signature header", async () => {
73-
const ev = generateMockEvent("{}", {
74-
"x-api-key": "foobar",
75-
"x-request-id": "rid"
76-
})
77-
const resp = await checkSignature(logger, ev)
57+
it("401 when missing signature header", () => {
58+
const ev = generateMockEvent("{}", {"x-api-key": "foobar", "x-request-id": "rid"})
59+
const resp = checkSignature(logger, ev)
7860
expect(resp).toEqual({
7961
statusCode: 401,
8062
body: JSON.stringify({message: "No x-hmac-sha256-signature given"})
8163
})
8264
})
8365

84-
it("401 when missing API key header", async () => {
85-
const ev = generateMockEvent("{}", {
86-
"x-hmac-sha256-signature": "foobar",
87-
"x-request-id": "rid"
88-
})
89-
const resp = await checkSignature(logger, ev)
66+
it("401 when missing API key header", () => {
67+
const ev = generateMockEvent("{}", {"x-hmac-sha256-signature": "foobar", "x-request-id": "rid"})
68+
const resp = checkSignature(logger, ev)
69+
9070
expect(resp).toEqual({
9171
statusCode: 401,
9272
body: JSON.stringify({message: "No x-api-key header given"})
9373
})
9474
})
9575

96-
it("403 when signature hex is malformed", async () => {
76+
it("403 when signature hex is malformed", () => {
9777
const headers = {
9878
...validHeaders,
9979
"x-hmac-sha256-signature": "not a hex string!@!#zzz"
10080
}
10181
const ev = generateMockEvent(JSON.stringify({message: "blah blah blah"}), headers)
102-
const resp = await checkSignature(logger, ev)
82+
const resp = checkSignature(logger, ev)
83+
10384
expect(resp).toEqual({
10485
statusCode: 403,
10586
body: JSON.stringify({message: "Incorrect signature"})
10687
})
10788
})
10889

109-
it("403 when signature does not match HMAC", async () => {
90+
it("403 when signature does not match HMAC", () => {
11091
const payload = "payload"
11192
const wrongSig = createHmac(
11293
"sha256",
@@ -119,16 +100,17 @@ describe("helpers.ts", () => {
119100
...validHeaders,
120101
"x-hmac-sha256-signature": wrongSig
121102
})
122-
const resp = await checkSignature(logger, ev)
103+
const resp = checkSignature(logger, ev)
104+
123105
expect(resp).toEqual({
124106
statusCode: 403,
125107
body: JSON.stringify({message: "Incorrect signature"})
126108
})
127109
})
128110

129-
it("returns undefined when signature is valid", async () => {
111+
it("returns undefined when signature is valid", () => {
130112
const payload = "hi there"
131-
const secret = `${process.env.APP_NAME_SECRET_ARN}.${process.env.API_KEY_SECRET_ARN}`
113+
const secret = `${process.env.APP_NAME}.${process.env.API_KEY}`
132114
const goodSig = createHmac("sha256", secret)
133115
.update(payload, "utf8")
134116
.digest("hex")
@@ -137,14 +119,13 @@ describe("helpers.ts", () => {
137119
...validHeaders,
138120
"x-hmac-sha256-signature": goodSig
139121
})
140-
const resp = await checkSignature(logger, ev)
122+
const resp = checkSignature(logger, ev)
141123
expect(resp).toBeUndefined()
142124
})
143125
})
144126

145127
describe("updateNotificationsTable()", () => {
146128
let logger: Logger
147-
148129
beforeEach(() => {
149130
logger = new Logger({serviceName: "nhsNotifyUpdateCallback"})
150131
jest.spyOn(logger, "error")

0 commit comments

Comments
 (0)