Skip to content

Commit 540d807

Browse files
masl2stevebux
andauthored
CCM-14630: FIX APIM cert expiry metric (#409)
* console.log the metric to be picked up * lint * move to use metricScope * lint * metric dep * satisfy lint rule * use void function to invoke wrapped function with the mock * invoke the metric scope * lint * Update lambdas/authorizer/src/authorizer.ts Co-authored-by: stevebux <104152898+stevebux@users.noreply.github.com> * fix bad commit of log format and update test --------- Co-authored-by: stevebux <104152898+stevebux@users.noreply.github.com>
1 parent bbfa920 commit 540d807

4 files changed

Lines changed: 54 additions & 63 deletions

File tree

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(),
@@ -60,6 +78,13 @@ describe("Authorizer Lambda Function", () => {
6078
jest
6179
.useFakeTimers({ doNotFake: ["nextTick"] })
6280
.setSystemTime(new Date("2025-11-03T14:19:00Z"));
81+
(metricScope as jest.Mock).mockClear();
82+
(mockedDeps.logger.warn as jest.Mock).mockClear();
83+
const metricsMock = jest.requireMock(
84+
"aws-embedded-metrics",
85+
).__metricsMock;
86+
metricsMock.setNamespace.mockClear();
87+
metricsMock.putMetric.mockClear();
6388
});
6489

6590
afterEach(() => {
@@ -73,10 +98,7 @@ describe("Authorizer Lambda Function", () => {
7398
handler(mockEvent, mockContext, mockCallback);
7499
await new Promise(process.nextTick);
75100

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

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

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

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

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

lambdas/authorizer/src/authorizer.ts

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Callback,
88
Context,
99
} from "aws-lambda";
10+
import { MetricsLogger, metricScope } from "aws-embedded-metrics";
1011
import { Supplier } from "@internal/datastore";
1112
import { Deps } from "./deps";
1213

@@ -100,33 +101,6 @@ function getCertificateExpiryInDays(
100101
return (expiry - now) / (1000 * 60 * 60 * 24);
101102
}
102103

103-
function buildCloudWatchMetric(
104-
namespace: string,
105-
certificate: APIGatewayEventClientCertificate,
106-
) {
107-
return {
108-
_aws: {
109-
Timestamp: Date.now(),
110-
CloudWatchMetrics: [
111-
{
112-
Namespace: namespace,
113-
Dimensions: ["SUBJECT_DN", "NOT_AFTER"],
114-
Metrics: [
115-
{
116-
Name: "apim-client-certificate-near-expiry",
117-
Unit: "Count",
118-
Value: 1,
119-
},
120-
],
121-
},
122-
],
123-
},
124-
SUBJECT_DN: certificate.subjectDN,
125-
NOT_AFTER: certificate.validity.notAfter,
126-
"apim-client-certificate-near-expiry": 1,
127-
};
128-
}
129-
130104
async function checkCertificateExpiry(
131105
certificate: APIGatewayEventClientCertificate | null,
132106
deps: Deps,
@@ -146,10 +120,15 @@ async function checkCertificateExpiry(
146120
const expiry = getCertificateExpiryInDays(certificate);
147121

148122
if (expiry <= deps.env.CLIENT_CERTIFICATE_EXPIRATION_ALERT_DAYS) {
149-
deps.logger.info(
150-
JSON.stringify(
151-
buildCloudWatchMetric(deps.env.CLOUDWATCH_NAMESPACE, certificate),
152-
),
153-
);
123+
await metricScope((metrics: MetricsLogger) => async () => {
124+
deps.logger.warn({
125+
description: "APIM Certificate expiry",
126+
days: expiry,
127+
});
128+
metrics.setNamespace(
129+
process.env.AWS_LAMBDA_FUNCTION_NAME || "authorizer",
130+
);
131+
metrics.putMetric("apim-client-certificate-near-expiry", expiry, "Count");
132+
})();
154133
}
155134
}

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)