seahaven-dev account baseline with org-managed detection (Phase 4) (#49)

* Add seahaven-dev member baseline with org-managed detection

Account 710827005802 (internal dev/staging) is the first account born
after delegation: GuardDuty/Security Hub enroll it via the org admin,
so DetectiveControls gains a localDetectiveServices flag (default true
— zero diff on the three deployed consumers, verified) and the dev
instance sets orgManagedDetection to skip the colliding local
detector/hub/analyzer. Default VPC kept and flow-logged (dev runs real
workloads). Enrollment verified Enabled in both services before this
commit.

* Fix Phase-4 review findings: standards + analyzer stay CFN-owned

SH-DEV-001: org AutoEnableStandards DEFAULT gave dev legacy CIS v1.2.0
and nothing owned CIS v3.0 — org config set to NONE, standards are now
unconditional in DetectiveControls (attach fine to an org-enabled hub),
legacy ruleset disabled in dev. SH-DEVBASE-002: the ORGANIZATION
analyzer treats the whole org as trusted so it cannot flag intra-org
exposure — account analyzer restored unconditionally (coexistence
verified live). Enrollment comments corrected: manual create-members,
the automatic sweep is still unexercised. Zero diff re-verified on all
three deployed baseline stacks.
This commit is contained in:
Adam Moussa 2026-07-14 16:41:36 -04:00 • committed by GitHub
parent 2d3ba94140
commit 0a7c1bc450
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 109 additions and 20 deletions

View file

@ -41,3 +41,12 @@ jobs:
stack-name: "seahaven-security-baseline"
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN_SECURITY }}
deploy-dev:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@3cde673b9d05c0e68aac4d997d582f2543853d20 # main
with:
node-version: "24"
stacks: "dev-baseline"
stack-name: "seahaven-dev-baseline"
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN_DEV }}

View file

@ -30,6 +30,7 @@ Stacks (deployed by the CD workflow — one job per target account):
| `seahaven-backup-offsite` | 328440206208 | us-west-2 | Governance-locked offsite copy vault (C-7) |
| `seahaven-external-dev-baseline` | 396287094661 | us-east-1 | Member-account baseline: Config, GuardDuty, Security Hub (FSBP + CIS v3.0), Access Analyzer, flow logs, budget |
| `seahaven-security-baseline` | 001520130573 | us-east-1 | Member-account baseline for the delegated security-admin account (same construct set) |
| `seahaven-dev-baseline` | 710827005802 | us-east-1 | Member-account baseline for internal dev/staging (org-managed detection — no local GuardDuty/SecurityHub) |
## CDK app
@ -47,7 +48,7 @@ the TypeScript source — no separate compile step needed for `cdk synth` /
| `tsconfig.json` | TypeScript compiler options (`outDir: cdk.out`) |
| `package.json` | Pinned `aws-cdk-lib`, CDK CLI, and the `build` / `synth` / `diff` / `deploy` npm scripts |
`bin/app.ts` synthesizes seven stacks across three regions and two accounts:
`bin/app.ts` synthesizes nine stacks across three regions and four accounts:
| Construct id | Stack name | Account | Region | Source |
|---|---|---|---|---|
@ -59,15 +60,27 @@ the TypeScript source — no separate compile step needed for `cdk synth` /
| `backup` | `seahaven-backup` | 328440206208 | us-east-1 | `lib/backup-stack.ts` |
| `external-dev-baseline` | `seahaven-external-dev-baseline` | 396287094661 | us-east-1 | `lib/member-baseline-stack.ts` |
| `security-baseline` | `seahaven-security-baseline` | 001520130573 | us-east-1 | `lib/member-baseline-stack.ts` |
| `dev-baseline` | `seahaven-dev-baseline` | 710827005802 | us-east-1 | `lib/member-baseline-stack.ts` (orgManagedDetection) |
The member-account stack (`external-dev-baseline`) deploys with credentials for
**396287094661** — the CD workflow runs it as a separate job assuming that
account's OIDC deploy role (`githubdeploy-seahaven-external-dev-baseline`,
repo secret `AWS_DEPLOY_ROLE_ARN_EXTDEV`). Local deploys/diffs of that stack
assume `OrganizationAccountAccessRole` in 396287094661. Its shared constructs
(`DetectiveControls`, `FlowLogs`, `GovernanceToggles`) are prefix-parameterized
(`seahaven` vs `seahaven-extdev`) — construct ids and physical names must stay
byte-identical to the deployed stack (logical IDs are path-derived).
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
deploys/diffs assume `OrganizationAccountAccessRole` in the target account.
| Account | OIDC deploy role | Repo secret |
|---|---|---|
| 396287094661 (external-dev) | `githubdeploy-seahaven-external-dev-baseline` | `AWS_DEPLOY_ROLE_ARN_EXTDEV` |
| 001520130573 (security) | `githubdeploy-seahaven-org-baseline` | `AWS_DEPLOY_ROLE_ARN_SECURITY` |
| 710827005802 (dev) | `githubdeploy-seahaven-org-baseline` | `AWS_DEPLOY_ROLE_ARN_DEV` |
Shared constructs (`DetectiveControls`, `FlowLogs`, `GovernanceToggles`) are
prefix-parameterized — construct ids and physical names must stay
byte-identical to the deployed stacks (logical IDs are path-derived).
Accounts enrolled by the org delegated admin (post 2026-07-14) set
`orgManagedDetection: true`: the GuardDuty detector + Security Hub hub come
from the org, while STANDARDS (FSBP + CIS v3.0) and the account analyzer stay
CFN-owned (org `AutoEnableStandards` is `NONE` — the DEFAULT setting enrolls
legacy CIS v1.2.0). Enrollment (member `Enabled` in GuardDuty + Security Hub)
is a hard precondition for such a stack's first deploy.
`backup` declares an explicit dependency on `backup-offsite` so the offsite copy
vault exists before the primary plan that copies into it. Stack names are set

View file

@ -12,6 +12,7 @@ import { OrgGovernanceStack } from "../lib/org-governance-stack";
const ACCOUNT = "328440206208";
const EXTERNAL_DEV_ACCOUNT = "396287094661";
const SECURITY_ACCOUNT = "001520130573";
const DEV_ACCOUNT = "710827005802";
// All 5 VPCs in 328440206208 / us-east-1 (4 custom + default), audit Agent 7.
// Index-derived logical IDs — append only, never reorder.
@ -100,6 +101,33 @@ new MemberBaselineStack(app, "security-baseline", {
managedByTag: "seahaven-org-baseline",
});
// ── Member-account baseline: seahaven-dev (Phase 4) ─────────────────────────
// Internal dev/staging workloads (NOT the external-dev engagement account).
// Created 2026-07-14 AFTER org delegation went live: GuardDuty detector +
// Security Hub hub are org-managed — enrolled via delegated-admin
// create-members and verified Enabled (the AUTOMATIC new-account sweep
// remains unexercised; do not rely on it without verifying). Standards and
// the account analyzer stay CFN-owned (SH-DEV-001/SH-DEVBASE-002). Same
// lifecycle rule as the other new accounts: root-harden at org ROOT, then
// move-account into the nonprod OU (ou-nbuj-zpt5ka98) — NO WORKLOADS until
// the account is inside the OU (SH-DEV-002: until then no region lock, no
// baseline-tamper SCP, usable root).
new MemberBaselineStack(app, "dev-baseline", {
stackName: "seahaven-dev-baseline",
env: { account: DEV_ACCOUNT, region: "us-east-1" },
namePrefix: "seahaven-dev",
monthlyBudgetUsd: 150,
budgetAlertEmail: "aws@seahaven.com",
ownerEmail: "adam@seahaven.com",
// Default VPC kept (dev runs real workloads); flow-logged from this stack's
// first deploy. Index-derived logical IDs — append only, never reorder
// (replacing the default VPC later = append the new id, keep this entry
// until its flow log is deliberately retired).
flowLogVpcIds: ["vpc-08f07dc5edeea621f"],
managedByTag: "seahaven-org-baseline",
orgManagedDetection: true,
});
// ── Shared DynamoDB CMK (INFRA-95 / M-3) ─────────────────────────────────────
// Dedicated, standalone stack so the customer-managed key for sensitive
// finance/PII DynamoDB tables is an independent shared dependency for the owning

View file

@ -31,6 +31,21 @@ export interface DetectiveControlsProps {
* custom resources; keep it stable per account.
*/
readonly namePrefix: string;
/**
* Create the account-local GuardDuty detector + Security Hub hub. Default
* TRUE (pre-delegation accounts own these). Set FALSE for accounts enrolled
* by the org delegated admin (post 2026-07-14): the org creates the
* detector/hub itself and a CFN-owned duplicate fails (one per account).
* ALWAYS created regardless of this flag (security review SH-DEV-001 /
* SH-DEVBASE-002): Security Hub STANDARDS (org AutoEnableStandards is NONE —
* DEFAULT would enroll legacy CIS v1.2.0, so IaC owns FSBP + CIS v3.0;
* CfnStandard attaches fine to an org-enabled hub), the account Access
* Analyzer (the ORGANIZATION analyzer treats the whole org as trusted and
* cannot flag intra-org exposure — e.g. to the isolated contractor account;
* both analyzer types coexist, verified live), and the Config recorder
* (recorders stay per-account, delegation never makes one).
*/
readonly localDetectiveServices?: boolean;
}
export class DetectiveControls extends Construct {
@ -297,12 +312,17 @@ export class DetectiveControls extends Construct {
});
// ──────────────────────────────────────────────────────────────────────
// H-3 GuardDuty
// H-3 GuardDuty detector + H-4 Security Hub hub — skipped when the org
// delegated admin owns them (localDetectiveServices: false). Standards and
// the analyzer below are created UNCONDITIONALLY (see props doc).
// ──────────────────────────────────────────────────────────────────────
new guardduty.CfnDetector(this, "GuardDutyDetector", {
enable: true,
findingPublishingFrequency: "FIFTEEN_MINUTES",
});
const localDetection = props.localDetectiveServices ?? true;
let hub: securityhub.CfnHub | undefined;
if (localDetection) {
new guardduty.CfnDetector(this, "GuardDutyDetector", {
enable: true,
findingPublishingFrequency: "FIFTEEN_MINUTES",
});
// ──────────────────────────────────────────────────────────────────────
// H-4 Security Hub (FSBP + CIS v3.0)
@ -310,11 +330,12 @@ export class DetectiveControls extends Construct {
// CIS/FSBP controls evaluate against the Config recording managed by the
// custom resource above; no CFN dependency needed (findings populate once
// recording is active).
const hub = new securityhub.CfnHub(this, "SecurityHub", {
hub = new securityhub.CfnHub(this, "SecurityHub", {
enableDefaultStandards: false,
controlFindingGenerator: "SECURITY_CONTROL",
autoEnableControls: true,
});
}
const fsbpArn = cdk.Arn.format(
{
@ -337,18 +358,27 @@ export class DetectiveControls extends Construct {
stack
);
// When the hub is org-managed (localDetection false) it already exists
// before this stack deploys (enrollment is a deploy precondition), so the
// standards attach without a CFN dependency.
const fsbp = new securityhub.CfnStandard(this, "StandardFSBP", {
standardsArn: fsbpArn,
});
fsbp.node.addDependency(hub);
if (hub) {
fsbp.node.addDependency(hub);
}
const cis = new securityhub.CfnStandard(this, "StandardCIS", {
standardsArn: cisArn,
});
cis.node.addDependency(hub);
if (hub) {
cis.node.addDependency(hub);
}
// ──────────────────────────────────────────────────────────────────────
// M-5 IAM Access Analyzer (free, account-scoped external-access)
// M-5 IAM Access Analyzer (free, account-scoped external-access) —
// always created: the ORGANIZATION analyzer cannot flag intra-org
// exposure (SH-DEVBASE-002).
// ──────────────────────────────────────────────────────────────────────
new accessanalyzer.CfnAnalyzer(this, "AccountAnalyzer", {
analyzerName: `${prefix}-account-analyzer`,

View file

@ -21,6 +21,12 @@ export interface MemberBaselineStackProps extends cdk.StackProps {
readonly flowLogVpcIds: string[];
/** Value for the ManagedBy tag on every resource in the stack. */
readonly managedByTag: string;
/**
* TRUE for accounts created after org delegation (2026-07-14): GuardDuty /
* Security Hub / analyzer are org-managed (auto-enrolled), so the stack must
* not create local duplicates. Default FALSE (pre-delegation accounts).
*/
readonly orgManagedDetection?: boolean;
}
/**
@ -37,9 +43,11 @@ export interface MemberBaselineStackProps extends cdk.StackProps {
* - 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
* Contains: AWS Config, Security Hub standards (FSBP + CIS v3.0), IAM Access
* Analyzer, Inspector2 (post-deploy CLI, see README), VPC flow logs, and a
* monthly cost Budget.
* monthly cost Budget — plus the GuardDuty detector + Security Hub hub only
* when the account predates org delegation (orgManagedDetection false); newer
* accounts get those from the delegated admin.
*/
export class MemberBaselineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: MemberBaselineStackProps) {
@ -47,6 +55,7 @@ export class MemberBaselineStack extends cdk.Stack {
new DetectiveControls(this, "DetectiveControls", {
namePrefix: props.namePrefix,
localDetectiveServices: !(props.orgManagedDetection ?? false),
});
new FlowLogs(this, "FlowLogs", {