import * as cdk from "aws-cdk-lib"; import * as wafv2 from "aws-cdk-lib/aws-wafv2"; import * as ssm from "aws-cdk-lib/aws-ssm"; import { Construct } from "constructs"; /** * Shared CloudFront WAF WebACL for Sea Haven app distributions (audit M-17). * * AWS managed rule groups (Common + Known Bad Inputs) plus an IP rate limit. * CLOUDFRONT-scope WebACLs must live in us-east-1 — which is where this stack * is — so it can be referenced by any app CloudFront distribution by ARN. * * The ARN is published to SSM (`/seahaven/waf/app-web-acl-arn`) so app stacks in * other repos can consume it via `{{resolve:ssm:...}}` without a hard CFN export. */ export class AppWebAcl extends Construct { constructor(scope: Construct, id: string) { super(scope, id); const vis = (metric: string): wafv2.CfnWebACL.VisibilityConfigProperty => ({ cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: metric, }); const webAcl = new wafv2.CfnWebACL(this, "AppWebAcl", { name: "seahaven-app-waf", scope: "CLOUDFRONT", defaultAction: { allow: {} }, visibilityConfig: vis("seahaven-app-waf"), rules: [ { name: "AWSCommonRuleSet", priority: 1, overrideAction: { none: {} }, statement: { managedRuleGroupStatement: { vendorName: "AWS", name: "AWSManagedRulesCommonRuleSet", }, }, visibilityConfig: vis("AWSCommonRuleSet"), }, { name: "AWSKnownBadInputs", priority: 2, overrideAction: { none: {} }, statement: { managedRuleGroupStatement: { vendorName: "AWS", name: "AWSManagedRulesKnownBadInputsRuleSet", }, }, visibilityConfig: vis("AWSKnownBadInputs"), }, { name: "RateLimitPerIp", priority: 3, action: { block: {} }, statement: { rateBasedStatement: { limit: 2000, aggregateKeyType: "IP" }, }, visibilityConfig: vis("RateLimitPerIp"), }, ], }); new ssm.StringParameter(this, "AppWebAclArnParam", { parameterName: "/seahaven/waf/app-web-acl-arn", stringValue: webAcl.attrArn, description: "ARN of the shared CloudFront WAF WebACL (audit M-17)", }); new cdk.CfnOutput(this, "AppWebAclArn", { value: webAcl.attrArn }); } }