From b1981bfa4dda44164cc63711f2097a5836ce6389 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 14 Mar 2024 09:53:24 +0100 Subject: [PATCH] Protect lambda function URL with a verify header --- apps/aws-app/cdk/stack.ts | 11 ++++++++++- apps/aws-app/dev-server/auth-middleware.ts | 13 +++++++++++++ apps/aws-app/dev-server/run.ts | 2 ++ apps/aws-app/src/handler/auth-middleware.ts | 12 ++++++++++++ apps/aws-app/src/handler/index.tsx | 6 ++++-- .../src/handler/{logger.ts => logger-middleware.ts} | 2 +- 6 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 apps/aws-app/dev-server/auth-middleware.ts create mode 100644 apps/aws-app/src/handler/auth-middleware.ts rename apps/aws-app/src/handler/{logger.ts => logger-middleware.ts} (90%) diff --git a/apps/aws-app/cdk/stack.ts b/apps/aws-app/cdk/stack.ts index 8d6f6d5..380625f 100644 --- a/apps/aws-app/cdk/stack.ts +++ b/apps/aws-app/cdk/stack.ts @@ -2,6 +2,7 @@ import path from 'path'; import * as cdk from 'aws-cdk-lib'; import type {Construct} from 'constructs'; +const verifyHeader = process.env.AWS_HANDLER_VERIFY_HEADER; const distDirname = path.join(import.meta.dirname, `../dist/`); export class Stack extends cdk.Stack { @@ -15,7 +16,10 @@ export class Stack extends cdk.Stack { entry: path.join(distDirname, `handler/index.js`), runtime: cdk.aws_lambda.Runtime.NODEJS_20_X, bundling: {format: cdk.aws_lambda_nodejs.OutputFormat.ESM}, - timeout: cdk.Duration.seconds(28), + timeout: cdk.Duration.minutes(1), + environment: verifyHeader + ? {AWS_HANDLER_VERIFY_HEADER: verifyHeader} + : undefined, }, ); @@ -39,6 +43,11 @@ export class Stack extends cdk.Stack { defaultBehavior: { origin: new cdk.aws_cloudfront_origins.FunctionUrlOrigin( lambdaFunctionUrl, + { + customHeaders: verifyHeader + ? {'X-Origin-Verify': verifyHeader} + : undefined, + }, ), allowedMethods: cdk.aws_cloudfront.AllowedMethods.ALLOW_ALL, cachePolicy: new cdk.aws_cloudfront.CachePolicy(this, `cache-policy`, { diff --git a/apps/aws-app/dev-server/auth-middleware.ts b/apps/aws-app/dev-server/auth-middleware.ts new file mode 100644 index 0000000..2a6c6ed --- /dev/null +++ b/apps/aws-app/dev-server/auth-middleware.ts @@ -0,0 +1,13 @@ +import type {MiddlewareHandler} from 'hono'; + +const verifyHeader = process.env.AWS_HANDLER_VERIFY_HEADER; + +export const authMiddleware: MiddlewareHandler = async (context, next) => { + if (verifyHeader) { + context.req.raw.headers.set(`X-Origin-Verify`, verifyHeader); + } else { + console.warn(`process.env.AWS_HANDLER_VERIFY_HEADER is undefined`); + } + + return next(); +}; diff --git a/apps/aws-app/dev-server/run.ts b/apps/aws-app/dev-server/run.ts index f42db69..6f203a2 100644 --- a/apps/aws-app/dev-server/run.ts +++ b/apps/aws-app/dev-server/run.ts @@ -1,6 +1,7 @@ import {serve} from '@hono/node-server'; import {serveStatic} from '@hono/node-server/serve-static'; import {Hono} from 'hono'; +import {authMiddleware} from './auth-middleware.js'; import './stub-awslambda.js'; // @ts-ignore @@ -8,6 +9,7 @@ const handlerModule = await import(`../dist/handler/index.js`); const {app: handlerApp} = handlerModule as {app: Hono}; const app = new Hono(); +app.use(authMiddleware); app.use(`/client/*`, serveStatic({root: `dist/static`})); app.route(`/`, handlerApp); diff --git a/apps/aws-app/src/handler/auth-middleware.ts b/apps/aws-app/src/handler/auth-middleware.ts new file mode 100644 index 0000000..4038bcc --- /dev/null +++ b/apps/aws-app/src/handler/auth-middleware.ts @@ -0,0 +1,12 @@ +import type {MiddlewareHandler} from 'hono'; + +export const authMiddleware: MiddlewareHandler = async (context, next) => { + if ( + context.req.header(`X-Origin-Verify`) !== + process.env.AWS_HANDLER_VERIFY_HEADER + ) { + return context.text(`Unauthorized`, 401); + } + + return next(); +}; diff --git a/apps/aws-app/src/handler/index.tsx b/apps/aws-app/src/handler/index.tsx index 23817ee..d6e1582 100644 --- a/apps/aws-app/src/handler/index.tsx +++ b/apps/aws-app/src/handler/index.tsx @@ -11,7 +11,8 @@ import {streamHandle} from 'hono/aws-lambda'; import * as React from 'react'; import type {ReactFormState} from 'react-dom/server'; import {App} from './app.js'; -import {logger} from './logger.js'; +import {authMiddleware} from './auth-middleware.js'; +import {loggerMiddleware} from './logger-middleware.js'; import { cssManifest, jsManifest, @@ -22,7 +23,8 @@ import { export const app = new Hono(); -app.use(logger); +app.use(authMiddleware); +app.use(loggerMiddleware); app.get(`/*`, async (context) => handleGet(context.req.raw)); app.post(`/*`, async (context) => handlePost(context.req.raw)); diff --git a/apps/aws-app/src/handler/logger.ts b/apps/aws-app/src/handler/logger-middleware.ts similarity index 90% rename from apps/aws-app/src/handler/logger.ts rename to apps/aws-app/src/handler/logger-middleware.ts index b63c86d..eb6d5fc 100644 --- a/apps/aws-app/src/handler/logger.ts +++ b/apps/aws-app/src/handler/logger-middleware.ts @@ -1,7 +1,7 @@ import type {LambdaFunctionURLEvent} from 'aws-lambda'; import type {MiddlewareHandler} from 'hono'; -export const logger: MiddlewareHandler<{ +export const loggerMiddleware: MiddlewareHandler<{ Bindings: { // Not available in dev server. event?: LambdaFunctionURLEvent;