diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index dca9c15..9da1991 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -56,7 +56,7 @@ jobs: uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@7ac3528750b346f181347bb09f6af927a1c0aa14 # v1.0.6 with: node-version: "24" - stacks: "prod-baseline dynamodb-cmk-prod alarm-topic-prod deploy-substrate-prod terraform-substrate-prod" + stacks: "prod-baseline dynamodb-cmk-prod alarm-topic-prod deploy-substrate-prod terraform-substrate-prod app-web-acl-prod" stack-name: "seahaven-prod-baseline" secrets: deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN_PROD }} diff --git a/README.md b/README.md index adb9d50..57c4fdc 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ the TypeScript source — no separate compile step needed for `cdk synth` / | `deploy-substrate-dev` | `seahaven-deploy-substrate` | 710827005802 | us-east-1 | `lib/deploy-substrate-stack.ts` | | `dynamodb-cmk-prod` | `seahaven-dynamodb-cmk` | 011934824531 | us-east-1 | `lib/dynamodb-cmk-stack.ts` | | `alarm-topic-prod` | `seahaven-alarm-topic` | 011934824531 | us-east-1 | `lib/alarm-topic-stack.ts` | +| `app-web-acl-prod` | `seahaven-app-web-acl` | 011934824531 | us-east-1 | `lib/app-web-acl-stack.ts` | Member-account stacks deploy with per-account credentials — the CD workflow runs one job per account, each assuming that account's OIDC deploy role. Local diff --git a/bin/app.ts b/bin/app.ts index af49987..b42a902 100644 --- a/bin/app.ts +++ b/bin/app.ts @@ -9,6 +9,7 @@ import { RegionalBaselineStack } from "../lib/regional-baseline-stack"; import { DeploySubstrateStack } from "../lib/deploy-substrate-stack"; import { TerraformSubstrateStack } from "../lib/terraform-substrate-stack"; import { DynamoDbCmkStack } from "../lib/dynamodb-cmk-stack"; +import { AppWebAclStack } from "../lib/app-web-acl-stack"; import { MemberBaselineStack } from "../lib/member-baseline-stack"; import { OrgGovernanceStack } from "../lib/org-governance-stack"; @@ -207,6 +208,15 @@ const terraformSubstrateProd = new TerraformSubstrateStack( ); terraformSubstrateProd.addStackDependency(deploySubstrateProd); +// Shared CloudFront WAF for seahaven-prod (PLAT-92). Same AppWebAcl construct +// as mgmt account-baseline; thin stack so prod does not inherit the full +// mgmt baseline. Publishes /seahaven/waf/app-web-acl-arn for in-account +// CloudFront associations (same-account only). +new AppWebAclStack(app, "app-web-acl-prod", { + stackName: "seahaven-app-web-acl", + env: { account: PROD_ACCOUNT, region: "us-east-1" }, +}); + const terraformSubstrateDev = new TerraformSubstrateStack( app, "terraform-substrate-dev", diff --git a/lib/app-web-acl-stack.ts b/lib/app-web-acl-stack.ts new file mode 100644 index 0000000..30d7ae2 --- /dev/null +++ b/lib/app-web-acl-stack.ts @@ -0,0 +1,25 @@ +import * as cdk from "aws-cdk-lib"; +import { Construct } from "constructs"; +import { AppWebAcl } from "./web-acl"; + +/** + * Thin per-account stack that owns the shared CloudFront WAFv2 WebACL (M-17) + * and publishes its ARN to SSM `/seahaven/waf/app-web-acl-arn`. + * + * Mgmt already has this ACL inside `AccountBaselineStack`. Workload accounts + * (starting with seahaven-prod / PLAT-92) get a dedicated stack so we do not + * pull the full mgmt baseline (trail, budgets, flow logs, …) into prod just + * to share a CloudFront WAF. App stacks associate by reading the SSM param + * in-account — WAFv2 CloudFront associations are same-account only. + */ +export class AppWebAclStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + new AppWebAcl(this, "AppWebAcl"); + + cdk.Tags.of(this).add("Project", "account-baseline"); + cdk.Tags.of(this).add("Owner", "adam@seahavenind.com"); + cdk.Tags.of(this).add("ManagedBy", "cdk"); + } +} diff --git a/lib/terraform-substrate/terraform-substrate.template.yaml b/lib/terraform-substrate/terraform-substrate.template.yaml index dbe482d..ab93017 100644 --- a/lib/terraform-substrate/terraform-substrate.template.yaml +++ b/lib/terraform-substrate/terraform-substrate.template.yaml @@ -1482,6 +1482,19 @@ Resources: - acm:ListTagsForCertificate - acm:GetCertificate Resource: "*" + - Sid: RefreshAppWebAclSsm + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + Resource: + - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/seahaven/waf/app-web-acl-arn" + - Sid: RefreshWafWebAcl + Effect: Allow + Action: + - wafv2:GetWebACL + - wafv2:ListWebACLs + Resource: "*" HcptfSeahavenSiteApplyRole: Type: AWS::IAM::Role @@ -1554,3 +1567,21 @@ Resources: Condition: StringEquals: "aws:ResourceTag/Project": seahaven-site + # CloudFront web_acl_id is set via UpdateDistribution (cloudfront:* + # above). Read the shared ACL ARN from SSM (PLAT-92) and allow + # WAFv2 describe so plans/applies can validate the association. + - Sid: ReadAppWebAclSsm + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + Resource: + - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/seahaven/waf/app-web-acl-arn" + - Sid: ReadWafWebAcl + Effect: Allow + Action: + - wafv2:GetWebACL + - wafv2:GetWebACLForResource + - wafv2:ListWebACLs + - wafv2:ListResourcesForWebACL + Resource: "*"