From 86666b8d0ba85b2c66838bdc8f3a9bfc271a1bcc Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 2 Oct 2026 00:25:04 +0000 Subject: [PATCH] fix(baseline): remove the management account web acl from the baseline (#173) The live template already retains the WebACL. Dropping the construct forgets that missing ACL and deletes the management SSM parameter. Prod keeps seahaven-app-web-acl. --- bin/app.ts | 7 +++---- lib/account-baseline-stack.ts | 14 -------------- lib/app-web-acl-stack.ts | 9 ++++----- lib/web-acl.ts | 35 +++-------------------------------- 4 files changed, 10 insertions(+), 55 deletions(-) diff --git a/bin/app.ts b/bin/app.ts index 7bb1c07..ea9910b 100644 --- a/bin/app.ts +++ b/bin/app.ts @@ -222,10 +222,9 @@ new DeploySubstrateStack(app, "deploy-substrate-dev", { // PLAT-145). External-dev stays: SHOC IAM is not moving (PLAT-148). // deploy-substrate stays for remaining SAM (PLAT-150). -// 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). +// Shared CloudFront WAF for seahaven-prod (PLAT-92). The management account +// no longer publishes /seahaven/waf/app-web-acl-arn. This stack publishes that +// parameter 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" }, diff --git a/lib/account-baseline-stack.ts b/lib/account-baseline-stack.ts index 5127107..0e9a87f 100644 --- a/lib/account-baseline-stack.ts +++ b/lib/account-baseline-stack.ts @@ -12,7 +12,6 @@ import { BedrockLogging } from "./bedrock-logging"; import { CisMonitoring } from "./cis-monitoring"; import { FlowLogs } from "./flow-logs"; import { SesMonitoring } from "./ses-monitoring"; -import { AppWebAcl } from "./web-acl"; /** * Account-level security baseline for Sea Haven (account 328440206208). @@ -252,19 +251,6 @@ export class AccountBaselineStack extends cdk.Stack { vpcIds: props.flowLogVpcIds, }); new SesMonitoring(this, "SesMonitoring"); - // Shared CloudFront WAF WebACL (M-17); ARN published to SSM for app stacks. - // RETAIN on the WebACL only. The physical ACL is already gone - // (WAFNonexistentItemException) while this stack still owns it. The - // deletion policy has to be in the live template before the construct is - // removed, or CloudFormation Deletes, fails, and rollback tries to - // recreate it. The SSM parameter stays on Delete. - // existingWebAclId keeps the parameter and output off WebACL.Arn. GetAtt - // calls GetWebACL, which fails because the ACL is gone. The id is the - // physical id CloudFormation already recorded for AppWebAclFE38F02F. - new AppWebAcl(this, "AppWebAcl", { - webAclRemovalPolicy: cdk.RemovalPolicy.RETAIN, - existingWebAclId: "cd668fe8-5cff-4d6a-9432-5c32145a7db4", - }); // ── Day 5 AI governance ── // Bedrock model invocation logging destinations + delivery role (H-20). diff --git a/lib/app-web-acl-stack.ts b/lib/app-web-acl-stack.ts index 30d7ae2..d290bb3 100644 --- a/lib/app-web-acl-stack.ts +++ b/lib/app-web-acl-stack.ts @@ -6,11 +6,10 @@ 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. + * Prod owns this stack (PLAT-92). The management account's copy lived inside + * `AccountBaselineStack` and was removed after its deletion policy was Retain, + * because the physical ACL was already gone. 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) { diff --git a/lib/web-acl.ts b/lib/web-acl.ts index 76335c5..02979f3 100644 --- a/lib/web-acl.ts +++ b/lib/web-acl.ts @@ -13,25 +13,8 @@ import { Construct } from "constructs"; * 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 interface AppWebAclProps { - /** - * Deletion policy for the WebACL only. The SSM parameter keeps the default - * Delete policy so a later stack update can remove the parameter. Prod does - * not set this. Management sets RETAIN first because the live WebACL is - * already gone and a Delete call would fail and roll back into a recreate. - */ - readonly webAclRemovalPolicy?: cdk.RemovalPolicy; - /** - * Physical id of a WebACL CloudFormation already tracks. When set, the SSM - * parameter and output publish `global/webacl/seahaven-app-waf/` with - * account and region tokens, and do not reference WebACL.Arn. GetAtt on a - * missing ACL calls GetWebACL and fails the stack update. - */ - readonly existingWebAclId?: string; -} - export class AppWebAcl extends Construct { - constructor(scope: Construct, id: string, props?: AppWebAclProps) { + constructor(scope: Construct, id: string) { super(scope, id); const vis = (metric: string): wafv2.CfnWebACL.VisibilityConfigProperty => ({ @@ -81,25 +64,13 @@ export class AppWebAcl extends Construct { }, ], }); - if (props?.webAclRemovalPolicy) { - webAcl.applyRemovalPolicy(props.webAclRemovalPolicy); - } - - const publishedArn = props?.existingWebAclId - ? cdk.Stack.of(this).formatArn({ - service: "wafv2", - resource: "global/webacl", - resourceName: `seahaven-app-waf/${props.existingWebAclId}`, - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - }) - : webAcl.attrArn; new ssm.StringParameter(this, "AppWebAclArnParam", { parameterName: "/seahaven/waf/app-web-acl-arn", - stringValue: publishedArn, + stringValue: webAcl.attrArn, description: "ARN of the shared CloudFront WAF WebACL (audit M-17)", }); - new cdk.CfnOutput(this, "AppWebAclArn", { value: publishedArn }); + new cdk.CfnOutput(this, "AppWebAclArn", { value: webAcl.attrArn }); } }