From 8f23c23a669c9c8fba52c438ab26822a11816519 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Tue, 14 Jul 2026 13:27:38 -0400 Subject: [PATCH] Absorb external-dev member baseline stack Moves seahaven-external-dev-baseline's stack in as MemberBaselineStack, construct ids and physical names byte-identical to the deployed stack (logical IDs are path-derived; empty cdk diff verified via change set against 396287094661). Retires the forked repo so member-account baselines share one drift surface and one dependency pin. --- bin/app.ts | 43 ++++++++++++++++++++++++ lib/member-baseline-stack.ts | 64 ++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 lib/member-baseline-stack.ts diff --git a/bin/app.ts b/bin/app.ts index 06564a7..af5ec11 100644 --- a/bin/app.ts +++ b/bin/app.ts @@ -6,8 +6,20 @@ import { BackupOffsiteStack } from "../lib/backup-offsite-stack"; import { BackupStack } from "../lib/backup-stack"; import { RegionalBaselineStack } from "../lib/regional-baseline-stack"; import { DynamoDbCmkStack } from "../lib/dynamodb-cmk-stack"; +import { MemberBaselineStack } from "../lib/member-baseline-stack"; const ACCOUNT = "328440206208"; +const EXTERNAL_DEV_ACCOUNT = "396287094661"; + +// All 5 VPCs in 328440206208 / us-east-1 (4 custom + default), audit Agent 7. +// Index-derived logical IDs — append only, never reorder. +const PROD_VPC_IDS = [ + "vpc-061d66990b6a4d1fb", + "vpc-0542a9e934b417d23", + "vpc-062d200c68bd4ca0e", + "vpc-0d3d4b67bd0cf8a68", + "vpc-02c10a89d66f6f9b8", +]; const app = new cdk.App(); @@ -16,6 +28,37 @@ new AccountBaselineStack(app, "account-baseline", { env: { account: ACCOUNT, region: "us-east-1" }, monthlyBudgetUsd: 1200, budgetAlertEmail: "adam@seahavenind.com", + flowLogVpcIds: PROD_VPC_IDS, +}); + +// ── Member-account baseline: seahaven-external-dev ─────────────────────────── +// Absorbed from the retired seahaven-external-dev-baseline repo. Stack name and +// every construct id preserved byte-identically (logical IDs are path-derived — +// renaming anything here replaces live resources). Deploys to the isolated +// external-dev member account via its own OIDC deploy role, NOT the mgmt role. +// +// Flow-log VPC ids come from context, NOT hardcoded — that account's VPCs +// change as the external dev team provisions infrastructure. Pass via: +// cdk deploy external-dev-baseline -c flowLogVpcIds=vpc-aaaa,vpc-bbbb +// Empty (default) keeps the hardened flow-logs bucket with no flow logs yet. +const vpcCtx = app.node.tryGetContext("flowLogVpcIds"); +const flowLogVpcIds: string[] = vpcCtx + ? String(vpcCtx) + .split(",") + .map((s) => s.trim()) + .filter(Boolean) + : []; + +new MemberBaselineStack(app, "external-dev-baseline", { + stackName: "seahaven-external-dev-baseline", + env: { account: EXTERNAL_DEV_ACCOUNT, region: "us-east-1" }, + namePrefix: "seahaven-extdev", + monthlyBudgetUsd: 200, + budgetAlertEmail: "adam@seahaven.com", + flowLogVpcIds, + // Keeps the tag value the stack was deployed with (zero-diff merge). Update + // to the current repo name in a deliberate follow-up change if desired. + managedByTag: "seahaven-external-dev-baseline", }); // ── Shared DynamoDB CMK (INFRA-95 / M-3) ───────────────────────────────────── diff --git a/lib/member-baseline-stack.ts b/lib/member-baseline-stack.ts new file mode 100644 index 0000000..bec50ab --- /dev/null +++ b/lib/member-baseline-stack.ts @@ -0,0 +1,64 @@ +import * as cdk from "aws-cdk-lib"; +import { Construct } from "constructs"; +import { DetectiveControls } from "./detective-controls"; +import { FlowLogs } from "./flow-logs"; +import { GovernanceToggles } from "./governance-toggles"; + +export interface MemberBaselineStackProps extends cdk.StackProps { + /** + * Physical-name prefix for account-scoped resources (e.g. "seahaven-extdev"). + * Also names the monthly budget (`-monthly-cost`). Stable per + * account — changing it on a deployed stack replaces live resources. + */ + readonly namePrefix: string; + /** Monthly cost budget ceiling in USD. */ + readonly monthlyBudgetUsd: number; + /** Sea Haven ops address that receives budget alerts (not the account's tenants). */ + readonly budgetAlertEmail: string; + /** VPC ids to attach flow logs to (from cdk context; may be empty). */ + readonly flowLogVpcIds: string[]; + /** Value for the ManagedBy tag on every resource in the stack. */ + readonly managedByTag: string; +} + +/** + * Account-local security baseline for org MEMBER accounts (first tenant: + * seahaven-external-dev). Absorbed from the retired seahaven-external-dev-baseline + * repo — a stripped fork of the management-account baseline, now sharing its + * constructs (prefix-parameterized) instead of forking them. + * + * Deliberately excludes everything that is org-level or prod-specific: + * - No local CloudTrail — the management-account org trail (seahaven-org-trail) + * already captures every member account's events centrally. + * - No CIS Section-4 metric-filter alarms — the Security Hub CIS standard + * evaluates those controls against Config without a local trail log group. + * - No WAF, SES monitoring, Bedrock logging, DynamoDB CMK, or AWS Backup — + * all prod-only concerns. + * + * Contains: AWS Config, GuardDuty, Security Hub (FSBP + CIS v3.0), IAM Access + * Analyzer, Inspector2 (post-deploy CLI, see README), VPC flow logs, and a + * monthly cost Budget. + */ +export class MemberBaselineStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: MemberBaselineStackProps) { + super(scope, id, props); + + new DetectiveControls(this, "DetectiveControls", { + namePrefix: props.namePrefix, + }); + + new FlowLogs(this, "FlowLogs", { + namePrefix: props.namePrefix, + vpcIds: props.flowLogVpcIds, + }); + + new GovernanceToggles(this, "GovernanceToggles", { + budgetName: `${props.namePrefix}-monthly-cost`, + monthlyLimitUsd: props.monthlyBudgetUsd, + alertEmail: props.budgetAlertEmail, + }); + + cdk.Tags.of(this).add("Owner", props.budgetAlertEmail); + cdk.Tags.of(this).add("ManagedBy", props.managedByTag); + } +}