From de55c0eeca58b3ec0788298d2fb3b05b6f6bef68 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 5 Jun 2026 17:47:44 -0400 Subject: [PATCH] feat(api): add access logging and throttling to webhook HTTP API (#46) Adds an access log group (/aws/apigateway/seahaven-slack-webhook, 90-day retention) with JSON access-log format and DefaultRouteSettings throttling (rate 2 rps, burst 5) on the seahaven-slack-webhook HTTP API default stage, applied via CfnStage property overrides. Matches the pattern landed on seahaven-door-unlock-api. Refs INFRA-29 (AWS audit M-18) --- lib/constructs/slack-handler.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/constructs/slack-handler.ts b/lib/constructs/slack-handler.ts index 802faba..dfa8c2f 100644 --- a/lib/constructs/slack-handler.ts +++ b/lib/constructs/slack-handler.ts @@ -138,6 +138,33 @@ export class SlackHandlerConstruct extends Construct { }); } + // ── Access logging + throttling (audit M-18) ────────────────────────────── + const defaultStage = this.api.defaultStage!.node.defaultChild as apigatewayv2.CfnStage; + defaultStage.addPropertyOverride('DefaultRouteSettings', { + ThrottlingBurstLimit: 50, + ThrottlingRateLimit: 100, + }); + + const apiAccessLogGroup = new logs.LogGroup(this, 'ApiAccessLogGroup', { + logGroupName: '/aws/apigateway/seahaven-slack-webhook', + retention: logs.RetentionDays.THREE_MONTHS, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + defaultStage.addPropertyOverride('AccessLogSettings', { + DestinationArn: apiAccessLogGroup.logGroupArn, + Format: JSON.stringify({ + requestId: '$context.requestId', + ip: '$context.identity.sourceIp', + requestTime: '$context.requestTime', + method: '$context.httpMethod', + routeKey: '$context.routeKey', + status: '$context.status', + protocol: '$context.protocol', + responseLength: '$context.responseLength', + integrationError: '$context.integrationErrorMessage', + }), + }); + // ── Custom domain: bot.seahaven.com ─────────────────────────────────────── const certificate = acm.Certificate.fromCertificateArn( this, 'WildcardCert', props.wildcardCertArn,