From e0b12cb93eb2b8492564ff37e1651620168ed66d Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Tue, 2 Jun 2026 17:01:19 -0400 Subject: [PATCH] API access logging + throttling (audit Day 3 M-18) (#18) * Add WAF + API access logging/throttling (audit Day 3: M-17, M-18) - M-17: associate the shared seahaven-app-waf CloudFront WebACL (ARN from SSM /seahaven/waf/app-web-acl-arn) with the orders.seahaven.com distribution. - M-18: enable HTTP API access logging to /aws/apigateway/meal-order-manager (90d) + default route throttling (100 rps, 50 burst) on OrderApi. * Defer M-17 WAF association (deploy role lacks wafv2) The github-cfn-execution-role (sticky CFN service role on this stack) has cloudfront:* + ssm:* but no wafv2:*, so associating the WebACL fails with 'Unable to verify read permissions on Web ACL'. Landing M-18 (access logging + throttling) now; WAF association re-added once the deploy role gets wafv2 perms (tracked separately). --- template.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/template.yaml b/template.yaml index fc7d5a3..965bc08 100644 --- a/template.yaml +++ b/template.yaml @@ -164,6 +164,9 @@ Resources: Comment: meal-order-manager form hosting PriceClass: PriceClass_100 HttpVersion: http2and3 + # WebACLId (M-17) deferred: the github-cfn-execution-role lacks wafv2 + # permissions, so the WAF association fails ("Unable to verify read + # permissions on Web ACL"). Re-add once the deploy role is granted wafv2. Aliases: !If - HasCustomDomain - [!Ref CustomDomain] @@ -198,10 +201,23 @@ Resources: # ─── API Gateway ─────────────────────────────────────────────── + ApiAccessLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: /aws/apigateway/meal-order-manager + RetentionInDays: 90 + OrderApi: Type: AWS::Serverless::HttpApi Properties: StageName: $default + # Access logging + default throttling (audit M-18). + AccessLogSettings: + DestinationArn: !GetAtt ApiAccessLogGroup.Arn + Format: '{"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"}' + DefaultRouteSettings: + ThrottlingBurstLimit: 50 + ThrottlingRateLimit: 100 # CORS only allows the production domain. For local development, use the # Flask dev server (app.py) which proxies API requests and doesn't enforce CORS. CorsConfiguration: