From 6e18bd5bde8a73ba63adbe0b0d7488e0cfdc3b25 Mon Sep 17 00:00:00 2001 From: Matthew Bell <33056264+matthew2564@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:30:05 +0100 Subject: [PATCH 1/4] chore: debug auth flow --- src/functions/authorizer.ts | 16 ++++++++++++++++ src/services/azure.ts | 3 +++ src/services/signature-check.ts | 2 ++ 3 files changed, 21 insertions(+) diff --git a/src/functions/authorizer.ts b/src/functions/authorizer.ts index 04ad6fd..80b7e3f 100644 --- a/src/functions/authorizer.ts +++ b/src/functions/authorizer.ts @@ -20,26 +20,42 @@ import { Jwt, JwtPayload } from "jsonwebtoken"; export const authorizer = async (event: APIGatewayTokenAuthorizerEvent, context: Context): Promise => { const logEvent: ILogEvent = {}; + console.log("Invoked authoriser"); + if (!process.env.AZURE_TENANT_ID || !process.env.AZURE_CLIENT_ID) { writeLogMessage(event, logEvent, JWT_MESSAGE.INVALID_ID_SETUP); + console.error("Missing AZURE_TENANT_ID or AZURE_CLIENT_ID"); return unauthorisedPolicy(); } + console.log("AZURE_TENANT_ID and AZURE_CLIENT_ID are set"); + try { + console.log("Init log event"); + initialiseLogEvent(event); + + console.log("Getting valid JWT"); const jwt = await getValidJwt(event.authorizationToken, logEvent, process.env.AZURE_TENANT_ID, process.env.AZURE_CLIENT_ID); + console.log("Generating role policy"); const policy = generateRolePolicy(jwt, logEvent) ?? generateFunctionalPolicy(jwt, logEvent); if (policy !== undefined) { + console.log("Role policy generated"); return policy; } + console.warn("Reporting no valid roles"); reportNoValidRoles(jwt, event, context, logEvent); writeLogMessage(event, logEvent, JWT_MESSAGE.INVALID_ROLES); + + console.warn("TRY - Returning unauth policy"); return unauthorisedPolicy(); } catch (error: any) { + console.error("Catch - Error occurred", error); writeLogMessage(event, logEvent, error); + console.error("Catch - Returning unauth policy"); return unauthorisedPolicy(); } }; diff --git a/src/services/azure.ts b/src/services/azure.ts index 600267c..46ea1ba 100644 --- a/src/services/azure.ts +++ b/src/services/azure.ts @@ -3,6 +3,8 @@ import { KeyResponse } from "../models/KeyResponse"; export const getCertificateChain = async (tenantId: string, keyId: string): Promise => { const keys: Map = await getKeys(tenantId); + console.log("Keys fetched"); + const certificateChain = keys.get(keyId); if (!certificateChain) { @@ -29,5 +31,6 @@ const getKeys = async (tenantId: string): Promise> => { }; export const fetchKeys = (tenantId: string) => { + console.log("Fetching keys"); return fetch(`https://login.microsoftonline.com/${tenantId}/discovery/keys`); }; diff --git a/src/services/signature-check.ts b/src/services/signature-check.ts index 4b59f62..4d2be28 100644 --- a/src/services/signature-check.ts +++ b/src/services/signature-check.ts @@ -3,8 +3,10 @@ import { getCertificateChain } from "./azure"; export const checkSignature = async (encodedToken: string, decodedToken: JWT.Jwt, tenantId: string, clientId: string): Promise => { // tid = tenant ID, kid = key ID + console.log("Getting cert chain"); const certificate = await getCertificateChain(tenantId, decodedToken.header.kid as string); + console.log("Verifying token"); JWT.verify(encodedToken, certificate, { audience: clientId.split(","), issuer: [`https://sts.windows.net/${tenantId}/`, `https://login.microsoftonline.com/${tenantId}/v2.0`], From 6e2bd6f1cbe66d7b254e3bbb9268161e28ddef57 Mon Sep 17 00:00:00 2001 From: Matthew Bell <33056264+matthew2564@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:25:26 +0100 Subject: [PATCH 2/4] chore: cache ms keys --- src/services/azure.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/services/azure.ts b/src/services/azure.ts index 46ea1ba..f9726d3 100644 --- a/src/services/azure.ts +++ b/src/services/azure.ts @@ -1,9 +1,17 @@ import { KeyResponse } from "../models/KeyResponse"; +const cache: Map> = new Map(); + export const getCertificateChain = async (tenantId: string, keyId: string): Promise => { - const keys: Map = await getKeys(tenantId); + const cacheKeys = cache.get(tenantId); + + console.log(`Cache ${cacheKeys ? 'hit' : 'not hit'}`); - console.log("Keys fetched"); + const keys: Map = cacheKeys ?? await getKeys(tenantId); + + if (!cache.has(tenantId)) { + cache.set(tenantId, keys); + } const certificateChain = keys.get(keyId); From 5fbdc82e2c4b9f601a9fc31f96c8599896e516cf Mon Sep 17 00:00:00 2001 From: Matthew Bell <33056264+matthew2564@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:26:46 +0100 Subject: [PATCH 3/4] chore: cache ms keys --- src/services/azure.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/azure.ts b/src/services/azure.ts index f9726d3..003d686 100644 --- a/src/services/azure.ts +++ b/src/services/azure.ts @@ -8,6 +8,7 @@ export const getCertificateChain = async (tenantId: string, keyId: string): Prom console.log(`Cache ${cacheKeys ? 'hit' : 'not hit'}`); const keys: Map = cacheKeys ?? await getKeys(tenantId); + console.log("Public Keys Read"); if (!cache.has(tenantId)) { cache.set(tenantId, keys); @@ -35,10 +36,12 @@ const getKeys = async (tenantId: string): Promise> => { map.set(keyId, certificateChain); } + + console.log("Key Map Created"); return map; }; export const fetchKeys = (tenantId: string) => { - console.log("Fetching keys"); + console.log("Fetching keys from https://login.microsoftonline.com/${tenantId}/discovery/keys"); return fetch(`https://login.microsoftonline.com/${tenantId}/discovery/keys`); }; From d4d3b0a14cca9a49b308eb26d1f34f269ef1106e Mon Sep 17 00:00:00 2001 From: Matthew Bell <33056264+matthew2564@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:33:23 +0100 Subject: [PATCH 4/4] chore: implement logs behind debug flag --- src/common/Logger.ts | 29 +++++++++++++++++++++++++++ src/functions/authorizer.ts | 22 +++++++++----------- src/services/azure.ts | 12 ++++++----- src/services/signature-check.ts | 5 +++-- tests/unit/services/azure.unitTest.ts | 2 +- 5 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/common/Logger.ts b/src/common/Logger.ts index ea74861..cb3afa6 100644 --- a/src/common/Logger.ts +++ b/src/common/Logger.ts @@ -42,3 +42,32 @@ export const writeLogMessage = (event: APIGatewayTokenAuthorizerEvent, log: ILog } return log; }; + +export enum LogLevel { + DEBUG = "DEBUG", + INFO = "INFO", + WARN = "WARN", + ERROR = "ERROR", +} + +export const envLogger = (level: LogLevel, ...messages: string[]) => { + if (process.env.DEBUG === "true") { + switch (level) { + case LogLevel.DEBUG: + console.debug(messages); + break; + case LogLevel.INFO: + console.info(messages); + break; + case LogLevel.WARN: + console.warn(messages); + break; + case LogLevel.ERROR: + console.error(messages); + break; + default: + console.log(messages); + return; + } + } +}; diff --git a/src/functions/authorizer.ts b/src/functions/authorizer.ts index 80b7e3f..5662066 100644 --- a/src/functions/authorizer.ts +++ b/src/functions/authorizer.ts @@ -6,7 +6,7 @@ import { generatePolicy as generateFunctionalPolicy } from "./functionalPolicyFa import { getValidJwt } from "../services/tokens"; import { JWT_MESSAGE } from "../models/enums"; import { ILogEvent } from "../models/ILogEvent"; -import { writeLogMessage } from "../common/Logger"; +import { envLogger, LogLevel, writeLogMessage } from "../common/Logger"; import newPolicyDocument from "./newPolicyDocument"; import { Jwt, JwtPayload } from "jsonwebtoken"; @@ -20,42 +20,36 @@ import { Jwt, JwtPayload } from "jsonwebtoken"; export const authorizer = async (event: APIGatewayTokenAuthorizerEvent, context: Context): Promise => { const logEvent: ILogEvent = {}; - console.log("Invoked authoriser"); + envLogger(LogLevel.DEBUG, "Invoked authoriser"); if (!process.env.AZURE_TENANT_ID || !process.env.AZURE_CLIENT_ID) { writeLogMessage(event, logEvent, JWT_MESSAGE.INVALID_ID_SETUP); - console.error("Missing AZURE_TENANT_ID or AZURE_CLIENT_ID"); return unauthorisedPolicy(); } - console.log("AZURE_TENANT_ID and AZURE_CLIENT_ID are set"); + envLogger(LogLevel.DEBUG, "AZURE_TENANT_ID and AZURE_CLIENT_ID are set"); try { - console.log("Init log event"); - initialiseLogEvent(event); - console.log("Getting valid JWT"); + envLogger(LogLevel.INFO, "Getting valid JWT"); const jwt = await getValidJwt(event.authorizationToken, logEvent, process.env.AZURE_TENANT_ID, process.env.AZURE_CLIENT_ID); - console.log("Generating role policy"); + envLogger(LogLevel.INFO, "Generating role policy"); const policy = generateRolePolicy(jwt, logEvent) ?? generateFunctionalPolicy(jwt, logEvent); if (policy !== undefined) { - console.log("Role policy generated"); + envLogger(LogLevel.INFO, "Role policy generated"); return policy; } - console.warn("Reporting no valid roles"); reportNoValidRoles(jwt, event, context, logEvent); writeLogMessage(event, logEvent, JWT_MESSAGE.INVALID_ROLES); - console.warn("TRY - Returning unauth policy"); return unauthorisedPolicy(); } catch (error: any) { - console.error("Catch - Error occurred", error); + envLogger(LogLevel.ERROR, "Catch - Error occurred", error); writeLogMessage(event, logEvent, error); - console.error("Catch - Returning unauth policy"); return unauthorisedPolicy(); } }; @@ -83,6 +77,8 @@ const reportNoValidRoles = (jwt: Jwt, event: APIGatewayTokenAuthorizerEvent, con * @param event */ const initialiseLogEvent = (event: APIGatewayTokenAuthorizerEvent): ILogEvent => { + envLogger(LogLevel.DEBUG, "Init log event"); + return { requestUrl: event.methodArn, timeOfRequest: new Date().toISOString(), diff --git a/src/services/azure.ts b/src/services/azure.ts index 003d686..9514fe9 100644 --- a/src/services/azure.ts +++ b/src/services/azure.ts @@ -1,14 +1,16 @@ import { KeyResponse } from "../models/KeyResponse"; +import { envLogger, LogLevel } from "../common/Logger"; const cache: Map> = new Map(); export const getCertificateChain = async (tenantId: string, keyId: string): Promise => { const cacheKeys = cache.get(tenantId); - console.log(`Cache ${cacheKeys ? 'hit' : 'not hit'}`); + envLogger(LogLevel.DEBUG, `Cache ${cacheKeys ? "hit" : "not hit"}`); - const keys: Map = cacheKeys ?? await getKeys(tenantId); - console.log("Public Keys Read"); + const keys: Map = cacheKeys ?? (await getKeys(tenantId)); + + envLogger(LogLevel.DEBUG, "Public keys read"); if (!cache.has(tenantId)) { cache.set(tenantId, keys); @@ -37,11 +39,11 @@ const getKeys = async (tenantId: string): Promise> => { map.set(keyId, certificateChain); } - console.log("Key Map Created"); + envLogger(LogLevel.DEBUG, "Key Map Created"); return map; }; export const fetchKeys = (tenantId: string) => { - console.log("Fetching keys from https://login.microsoftonline.com/${tenantId}/discovery/keys"); + envLogger(LogLevel.DEBUG, `Fetching keys from https://login.microsoftonline.com/${tenantId}/discovery/keys`); return fetch(`https://login.microsoftonline.com/${tenantId}/discovery/keys`); }; diff --git a/src/services/signature-check.ts b/src/services/signature-check.ts index 4d2be28..a66e517 100644 --- a/src/services/signature-check.ts +++ b/src/services/signature-check.ts @@ -1,12 +1,13 @@ import * as JWT from "jsonwebtoken"; import { getCertificateChain } from "./azure"; +import { envLogger, LogLevel } from "../common/Logger"; export const checkSignature = async (encodedToken: string, decodedToken: JWT.Jwt, tenantId: string, clientId: string): Promise => { // tid = tenant ID, kid = key ID - console.log("Getting cert chain"); + envLogger(LogLevel.DEBUG, "Getting cert chain"); const certificate = await getCertificateChain(tenantId, decodedToken.header.kid as string); - console.log("Verifying token"); + envLogger(LogLevel.INFO, "Verifying token"); JWT.verify(encodedToken, certificate, { audience: clientId.split(","), issuer: [`https://sts.windows.net/${tenantId}/`, `https://login.microsoftonline.com/${tenantId}/v2.0`], diff --git a/tests/unit/services/azure.unitTest.ts b/tests/unit/services/azure.unitTest.ts index a3af6ba..98a97fc 100644 --- a/tests/unit/services/azure.unitTest.ts +++ b/tests/unit/services/azure.unitTest.ts @@ -32,6 +32,6 @@ describe("getCertificateChain()", () => { it("should throw an error if no key matches the given key ID", async (): Promise => { fetchSpy("somethingElse", "mySuperSecurePublicKey"); - await expect(azure.getCertificateChain("tenantId", "keyToTheKingdom")).rejects.toThrow("no public key"); + await expect(azure.getCertificateChain("tenantId", "otherKeyToTheKingdom")).rejects.toThrow("no public key"); }); });