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.
This commit is contained in:
Adam Moussa 2026-10-02 00:25:04 +00:00 • committed by GitHub
parent cd37817e1f
commit 86666b8d0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 10 additions and 55 deletions

View file

@ -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" },

View file

@ -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).

View file

@ -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) {

View file

@ -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/<id>` 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 });
}
}