seahaven-org-baseline/lib/web-acl.ts
Adam Moussa 60e8b0e9ed
Add shared CloudFront WAF WebACL (audit Day 3: M-17) (#7)
seahaven-app-waf (CLOUDFRONT scope, us-east-1): AWS managed Common + Known Bad
Inputs rule groups + per-IP rate limit (2000/5min). ARN published to SSM
/seahaven/waf/app-web-acl-arn for app stacks (meal-order/orders) to consume.
seahaven.com already has its own WAF; ledgerflow is being decommissioned
(INFRA-26); proposal-system-web skipped (not live).
2026-06-02 16:42:24 -04:00

76 lines
2.4 KiB
TypeScript

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 });
}
}