Skip to content

Commit

Permalink
IMN-639 - Creating JWKS clients only once at services startup (#1131)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecamellini authored and sandrotaje committed Nov 18, 2024
1 parent 5ca8442 commit ceb7f3f
Show file tree
Hide file tree
Showing 15 changed files with 63 additions and 29 deletions.
2 changes: 1 addition & 1 deletion collections/bff/authorization/get Session Token.bru
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ headers {

body:json {
{
"identity_token": {{JWT}}
"identity_token": "{{process.env.JWT}}"
}
}

Expand Down
4 changes: 2 additions & 2 deletions collections/environments/PagoPA local.bru
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
vars {
host-agreement: http://localhost:3100
JWT: {{process.env.JWT}}
JWT: Bearer {{process.env.JWT}}
host-catalog: http://localhost:3000
correlation-id: 79eb4963-3866-422f-8ef0-87871e5f9a71
JWT-Invalid:
Expand All @@ -10,5 +10,5 @@ vars {
host-purpose: http://localhost:3400
host-authorization: http://localhost:3300
host-api-gw: http://localhost:3700/api-gateway/0.0
JWT-M2M: {{process.env.JWT-M2M}}
JWT-M2M: Bearer {{process.env.JWT-M2M}}
}
5 changes: 4 additions & 1 deletion packages/agreement-process/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
authenticationMiddleware,
buildJwksClients,
contextMiddleware,
loggerMiddleware,
zodiosCtx,
Expand All @@ -12,13 +13,15 @@ const serviceName = "agreement-process";

const app = zodiosCtx.app();

const jwksClients = buildJwksClients(config);

// Disable the "X-Powered-By: Express" HTTP header for security reasons.
// See https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#recommendation_16
app.disable("x-powered-by");

app.use(healthRouter);
app.use(contextMiddleware(serviceName));
app.use(authenticationMiddleware(config));
app.use(authenticationMiddleware(config, jwksClients));
app.use(loggerMiddleware(serviceName));
app.use(agreementRouter(zodiosCtx));

Expand Down
5 changes: 4 additions & 1 deletion packages/api-gateway/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
authenticationMiddleware,
buildJwksClients,
contextMiddleware,
initRedisRateLimiter,
loggerMiddleware,
Expand All @@ -17,6 +18,8 @@ const clients = getInteropBeClients();

const app = zodiosCtx.app();

const jwksClients = buildJwksClients(config);

const redisRateLimiter = await initRedisRateLimiter({
limiterGroup: "API_GW",
maxRequests: config.rateLimiterMaxRequests,
Expand All @@ -37,7 +40,7 @@ app.use(
`/api-gateway/${config.apiGatewayInterfaceVersion}`,
healthRouter,
contextMiddleware(serviceName, false),
authenticationMiddleware(config),
authenticationMiddleware(config, jwksClients),
// Authenticated routes - rate limiter relies on auth data to work
rateLimiterMiddleware(redisRateLimiter),
apiGatewayRouter(zodiosCtx, clients)
Expand Down
5 changes: 4 additions & 1 deletion packages/attribute-registry-process/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
authenticationMiddleware,
buildJwksClients,
contextMiddleware,
loggerMiddleware,
zodiosCtx,
Expand All @@ -12,13 +13,15 @@ const serviceName = "attribute-registry-process";

const app = zodiosCtx.app();

const jwksClients = buildJwksClients(config);

// Disable the "X-Powered-By: Express" HTTP header for security reasons.
// See https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#recommendation_16
app.disable("x-powered-by");

app.use(healthRouter);
app.use(contextMiddleware(serviceName));
app.use(authenticationMiddleware(config));
app.use(authenticationMiddleware(config, jwksClients));
app.use(loggerMiddleware(serviceName));
app.use(attributeRouter(zodiosCtx));

Expand Down
5 changes: 4 additions & 1 deletion packages/authorization-process/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
authenticationMiddleware,
buildJwksClients,
contextMiddleware,
zodiosCtx,
} from "pagopa-interop-commons";
Expand All @@ -11,13 +12,15 @@ const serviceName = "authorization-process";

const app = zodiosCtx.app();

const jwksClients = buildJwksClients(config);

// Disable the "X-Powered-By: Express" HTTP header for security reasons.
// See https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#recommendation_16
app.disable("x-powered-by");

app.use(healthRouter);
app.use(contextMiddleware(serviceName));
app.use(authenticationMiddleware(config));
app.use(authenticationMiddleware(config, jwksClients));
app.use(authorizationRouter(zodiosCtx));

export default app;
15 changes: 12 additions & 3 deletions packages/backend-for-frontend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
zodiosCtx,
initRedisRateLimiter,
rateLimiterMiddleware,
buildJwksClients,
} from "pagopa-interop-commons";
import express from "express";
import { config } from "./config/config.js";
Expand Down Expand Up @@ -37,6 +38,8 @@ const clients = getInteropBeClients();

const app = zodiosCtx.app();

const jwksClients = buildJwksClients(config);

const redisRateLimiter = await initRedisRateLimiter({
limiterGroup: "BFF",
maxRequests: config.rateLimiterMaxRequests,
Expand Down Expand Up @@ -66,16 +69,22 @@ app.use(
`/backend-for-frontend/${config.backendForFrontendInterfaceVersion}`,
healthRouter,
contextMiddleware(serviceName, false),
authorizationRouter(zodiosCtx, clients, allowList, redisRateLimiter),
authenticationMiddleware(config),
authorizationRouter(
zodiosCtx,
clients,
allowList,
redisRateLimiter,
jwksClients
),
authenticationMiddleware(config, jwksClients),
// Authenticated routes - rate limiter relies on auth data to work
rateLimiterMiddleware(redisRateLimiter),
catalogRouter(zodiosCtx, clients, fileManager),
attributeRouter(zodiosCtx, clients),
purposeRouter(zodiosCtx, clients),
agreementRouter(zodiosCtx, clients, fileManager),
selfcareRouter(clients, zodiosCtx),
supportRouter(zodiosCtx, clients, redisRateLimiter),
supportRouter(zodiosCtx, clients, redisRateLimiter, jwksClients),
toolRouter(zodiosCtx, clients),
tenantRouter(zodiosCtx, clients),
clientRouter(zodiosCtx, clients),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
zodiosValidationErrorToApiProblem,
RateLimiter,
rateLimiterHeadersFromStatus,
buildJwksClients,
} from "pagopa-interop-commons";
import { tooManyRequestsError } from "pagopa-interop-models";
import { makeApiProblem } from "../model/errors.js";
Expand All @@ -21,7 +22,8 @@ const authorizationRouter = (
ctx: ZodiosContext,
{ tenantProcessClient }: PagoPAInteropBeClients,
allowList: string[],
rateLimiter: RateLimiter
rateLimiter: RateLimiter,
jwksClients: ReturnType<typeof buildJwksClients>
): ZodiosRouter<ZodiosEndpointDefinitions, ExpressContext> => {
const authorizationRouter = ctx.router(bffApi.authorizationApi.api, {
validationErrorHandler: zodiosValidationErrorToApiProblem,
Expand All @@ -32,7 +34,8 @@ const authorizationRouter = (
interopTokenGenerator,
tenantProcessClient,
allowList,
rateLimiter
rateLimiter,
jwksClients
);

authorizationRouter
Expand Down
7 changes: 5 additions & 2 deletions packages/backend-for-frontend/src/routers/supportRouter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ZodiosEndpointDefinitions } from "@zodios/core";
import { ZodiosRouter } from "@zodios/express";
import {
buildJwksClients,
ExpressContext,
InteropTokenGenerator,
RateLimiter,
Expand All @@ -19,7 +20,8 @@ import { fromBffAppContext } from "../utilities/context.js";
const supportRouter = (
ctx: ZodiosContext,
{ tenantProcessClient }: PagoPAInteropBeClients,
rateLimiter: RateLimiter
rateLimiter: RateLimiter,
jwksClients: ReturnType<typeof buildJwksClients>
): ZodiosRouter<ZodiosEndpointDefinitions, ExpressContext> => {
const supportRouter = ctx.router(bffApi.supportApi.api, {
validationErrorHandler: zodiosValidationErrorToApiProblem,
Expand All @@ -30,7 +32,8 @@ const supportRouter = (
interopTokenGenerator,
tenantProcessClient,
config.tenantAllowedOrigins,
rateLimiter
rateLimiter,
jwksClients
);

supportRouter.post("/session/saml2/tokens", async (req, res) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
USER_ROLES,
WithLogger,
decodeJwtToken,
getJwksClients,
buildJwksClients,
userRoles,
verifyJwtToken,
} from "pagopa-interop-commons";
Expand Down Expand Up @@ -52,10 +52,9 @@ export function authorizationServiceBuilder(
interopTokenGenerator: InteropTokenGenerator,
tenantProcessClient: PagoPAInteropBeClients["tenantProcessClient"],
allowList: string[],
rateLimiter: RateLimiter
rateLimiter: RateLimiter,
jwksClients: ReturnType<typeof buildJwksClients>
) {
const jwksClients = getJwksClients(config);

const readJwt = async (
identityToken: string,
logger: Logger
Expand Down
5 changes: 4 additions & 1 deletion packages/catalog-process/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
authenticationMiddleware,
buildJwksClients,
contextMiddleware,
loggerMiddleware,
zodiosCtx,
Expand All @@ -12,13 +13,15 @@ const serviceName = "catalog-process";

const app = zodiosCtx.app();

const jwksClients = buildJwksClients(config);

// Disable the "X-Powered-By: Express" HTTP header for security reasons.
// See https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#recommendation_16
app.disable("x-powered-by");

app.use(healthRouter);
app.use(contextMiddleware(serviceName));
app.use(authenticationMiddleware(config));
app.use(authenticationMiddleware(config, jwksClients));
app.use(loggerMiddleware(serviceName));
app.use(eservicesRouter(zodiosCtx));

Expand Down
9 changes: 4 additions & 5 deletions packages/commons/src/auth/authenticationMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
unauthorizedError,
} from "pagopa-interop-models";
import { match } from "ts-pattern";
import { JwksClient } from "jwks-rsa";
import {
ExpressContext,
fromAppContext,
getJwksClients,
JWTConfig,
jwtFromAuthHeader,
} from "../index.js";
Expand All @@ -18,17 +18,16 @@ import { readAuthDataFromJwtToken, verifyJwtToken } from "./jwt.js";
const makeApiProblem = makeApiProblemBuilder({});

export const authenticationMiddleware: (
config: JWTConfig
config: JWTConfig,
jwksClients: JwksClient[]
) => ZodiosRouterContextRequestHandler<ExpressContext> =
(config: JWTConfig) =>
(config: JWTConfig, jwksClients: JwksClient[]) =>
async (req, res, next): Promise<unknown> => {
// We assume that:
// - contextMiddleware already set ctx.serviceName and ctx.correlationId
const ctx = fromAppContext(req.ctx);

try {
const jwksClients = getJwksClients(config);

const jwtToken = jwtFromAuthHeader(req, ctx.logger);
const valid = await verifyJwtToken(
jwtToken,
Expand Down
6 changes: 3 additions & 3 deletions packages/commons/src/auth/jwk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ export function sortJWK(jwk: JsonWebKey): JsonWebKey {
);
}

export function getJwksClients(config: JWTConfig): JwksClient[] {
export function buildJwksClients(config: JWTConfig): JwksClient[] {
return config.wellKnownUrls.map((url) =>
jwksClient({
cache: true,
rateLimit: true,
jwksUri: url,
/* If JWKS_CACHE_MAX_AGE_MILLIS not provided using 10 minute like default value:
https://github.com/auth0/node-jwks-rsa/blob/master/EXAMPLES.md#configuration
/* If JWKS_CACHE_MAX_AGE_MILLIS not provided using 10 minutes as default value:
https://github.com/auth0/node-jwks-rsa/blob/master/EXAMPLES.md#configuration
*/
cacheMaxAge: config.jwksCacheMaxAge ?? 600000,
})
Expand Down
5 changes: 4 additions & 1 deletion packages/purpose-process/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
authenticationMiddleware,
buildJwksClients,
contextMiddleware,
loggerMiddleware,
zodiosCtx,
Expand All @@ -12,13 +13,15 @@ const serviceName = "purpose-process";

const app = zodiosCtx.app();

const jwksClients = buildJwksClients(config);

// Disable the "X-Powered-By: Express" HTTP header for security reasons.
// See https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#recommendation_16
app.disable("x-powered-by");

app.use(healthRouter);
app.use(contextMiddleware(serviceName));
app.use(authenticationMiddleware(config));
app.use(authenticationMiddleware(config, jwksClients));
app.use(loggerMiddleware(serviceName));
app.use(purposeRouter(zodiosCtx));

Expand Down
5 changes: 4 additions & 1 deletion packages/tenant-process/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
authenticationMiddleware,
buildJwksClients,
contextMiddleware,
loggerMiddleware,
zodiosCtx,
Expand All @@ -12,13 +13,15 @@ const serviceName = "tenant-process";

const app = zodiosCtx.app();

const jwksClients = buildJwksClients(config);

// Disable the "X-Powered-By: Express" HTTP header for security reasons.
// See https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#recommendation_16
app.disable("x-powered-by");

app.use(healthRouter);
app.use(contextMiddleware(serviceName));
app.use(authenticationMiddleware(config));
app.use(authenticationMiddleware(config, jwksClients));
app.use(loggerMiddleware(serviceName));
app.use(tenantRouter(zodiosCtx));

Expand Down

0 comments on commit ceb7f3f

Please sign in to comment.