Merge external-dev member baseline; rename to seahaven-org-baseline (#43)

* Parameterize baseline constructs for multi-account reuse

DetectiveControls, FlowLogs, and GovernanceToggles were forked into
seahaven-external-dev-baseline with only physical-name and VPC-sourcing
differences. Prefix/name props let one implementation serve both
accounts; synthesized templates are unchanged (verified: empty cdk diff
against all deployed stacks).

* 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.

* Rename package to seahaven-org-baseline

Prepares the repo rename: the app now spans the management account and
org member accounts, so 'account-baseline' undersells the scope. README
documents the two-account deploy topology and logical-ID constraints.

* Commit extdev flow-log VPC ids in code, not -c context

Security review SH-ORG-004 (confirmed high): with the ids sourced from
ephemeral cdk context, any context-less deploy silently removes every
flow log in the isolated account. A committed list makes the attachment
set reviewable and immune to a forgotten -c flag. Empty list matches
the deployed stack (zero diff).

* Split CD into per-account deploy jobs

The app now spans two AWS accounts; cdk deploy --all under one role
fails on the other account's stacks (security review IAC-01). Each job
passes explicit stack selectors and its own account's OIDC role via the
new cd-cdk stacks input.
This commit is contained in:
Adam Moussa 2026-07-14 13:53:07 -04:00 • committed by GitHub
parent f3c37d5b20
commit 3ab3bc773a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 234 additions and 61 deletions

View file

@ -11,10 +11,24 @@ concurrency:
group: deploy
cancel-in-progress: false
# One job per target AWS account: cdk deploy with explicit stack selectors so
# each OIDC role only ever deploys its own account's stacks. A new stack added
# to bin/app.ts MUST be appended to exactly one job's `stacks` list — explicit
# selectors mean an unlisted stack is silently never deployed (security review
# SH-ORG-005).
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@fd60e4c9041784f666ac0fdefb9bec3c7fbf5143 # main
deploy-management:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@3cde673b9d05c0e68aac4d997d582f2543853d20 # main
with:
node-version: "24"
stacks: "account-baseline dynamodb-cmk regional-baseline-us-west-2 regional-baseline-us-east-2 backup-offsite backup"
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
deploy-external-dev:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@3cde673b9d05c0e68aac4d997d582f2543853d20 # main
with:
node-version: "24"
stacks: "external-dev-baseline"
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN_EXTDEV }}

View file

@ -1,26 +1,34 @@
# seahaven-account-baseline
# seahaven-org-baseline
![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)
![AWS CDK](https://img.shields.io/badge/AWS-CDK-FF9900?logo=amazonaws&logoColor=white)
![CI](https://github.com/Sea-Haven-Industries/seahaven-account-baseline/actions/workflows/ci.yaml/badge.svg)
![CI](https://github.com/Sea-Haven-Industries/seahaven-org-baseline/actions/workflows/ci.yaml/badge.svg)
Account-level security and governance baseline for Sea Haven Industries
(AWS account **328440206208**), managed as a single CDK TypeScript app. The
primary baseline is in **us-east-1**, with secondary-region baselines in
**us-east-2** and **us-west-2** and the offsite backup vault in **us-west-2**.
This is where account-wide detective and recovery controls live, so they are
versioned, reviewed, and drift-checked like any other stack.
Organization-wide security and governance baseline for Sea Haven Industries,
managed as a single CDK TypeScript app. Covers the management account
(**328440206208**: primary baseline in **us-east-1**, secondary-region
baselines in **us-east-2**/**us-west-2**, offsite backup vault in
**us-west-2**) and org **member accounts** (first tenant:
`seahaven-external-dev` **396287094661**, absorbed from the retired
`seahaven-external-dev-baseline` repo). This is where account-wide detective
and recovery controls live, so they are versioned, reviewed, and drift-checked
like any other stack.
Stacks (all deployed by `cdk deploy --all` / the CD workflow):
> **History:** this repo was `seahaven-account-baseline` (management account
> only) until 2026-07-14, when the external-dev member baseline was merged in
> and the repo renamed. Deployed CloudFormation stack names are unchanged.
| Stack | Region | Purpose |
|---|---|---|
| `seahaven-account-baseline` | us-east-1 | CloudTrail + future detective controls (C-1) |
| `seahaven-dynamodb-cmk` | us-east-1 | Shared customer-managed KMS key for finance/PII DynamoDB tables; ARN published to SSM `/seahaven/dynamodb/cmk-arn` (INFRA-95 / M-3) |
| `seahaven-regional-baseline-us-west-2` | us-west-2 | Bedrock invocation logging (INFRA-91) |
| `seahaven-regional-baseline-us-east-2` | us-east-2 | Bedrock invocation logging + AWS Config recorder + Security Hub (INFRA-91 / INFRA-16) |
| `seahaven-backup` | us-east-1 | Primary AWS Backup vault + plan + role (C-7) |
| `seahaven-backup-offsite` | us-west-2 | Governance-locked offsite copy vault (C-7) |
Stacks (deployed by the CD workflow — one job per target account):
| Stack | Account | Region | Purpose |
|---|---|---|---|
| `seahaven-account-baseline` | 328440206208 | us-east-1 | CloudTrail + detective controls (C-1) |
| `seahaven-dynamodb-cmk` | 328440206208 | us-east-1 | Shared customer-managed KMS key for finance/PII DynamoDB tables; ARN published to SSM `/seahaven/dynamodb/cmk-arn` (INFRA-95 / M-3) |
| `seahaven-regional-baseline-us-west-2` | 328440206208 | us-west-2 | Bedrock invocation logging (INFRA-91) |
| `seahaven-regional-baseline-us-east-2` | 328440206208 | us-east-2 | Bedrock invocation logging + AWS Config recorder + Security Hub (INFRA-91 / INFRA-16) |
| `seahaven-backup` | 328440206208 | us-east-1 | Primary AWS Backup vault + plan + role (C-7) |
| `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 |
## CDK app
@ -38,16 +46,26 @@ 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 six stacks across three regions:
`bin/app.ts` synthesizes seven stacks across three regions and two accounts:
| Construct id | Stack name | Region | Source |
|---|---|---|---|
| `account-baseline` | `seahaven-account-baseline` | us-east-1 | `lib/account-baseline-stack.ts` |
| `dynamodb-cmk` | `seahaven-dynamodb-cmk` | us-east-1 | `lib/dynamodb-cmk-stack.ts` |
| `regional-baseline-us-west-2` | `seahaven-regional-baseline-us-west-2` | us-west-2 | `lib/regional-baseline-stack.ts` |
| `regional-baseline-us-east-2` | `seahaven-regional-baseline-us-east-2` | us-east-2 | `lib/regional-baseline-stack.ts` |
| `backup-offsite` | `seahaven-backup-offsite` | us-west-2 | `lib/backup-offsite-stack.ts` |
| `backup` | `seahaven-backup` | us-east-1 | `lib/backup-stack.ts` |
| Construct id | Stack name | Account | Region | Source |
|---|---|---|---|---|
| `account-baseline` | `seahaven-account-baseline` | 328440206208 | us-east-1 | `lib/account-baseline-stack.ts` |
| `dynamodb-cmk` | `seahaven-dynamodb-cmk` | 328440206208 | us-east-1 | `lib/dynamodb-cmk-stack.ts` |
| `regional-baseline-us-west-2` | `seahaven-regional-baseline-us-west-2` | 328440206208 | us-west-2 | `lib/regional-baseline-stack.ts` |
| `regional-baseline-us-east-2` | `seahaven-regional-baseline-us-east-2` | 328440206208 | us-east-2 | `lib/regional-baseline-stack.ts` |
| `backup-offsite` | `seahaven-backup-offsite` | 328440206208 | us-west-2 | `lib/backup-offsite-stack.ts` |
| `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` |
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).
`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

@ -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,35 @@ 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 are COMMITTED here, not passed via -c context. The old
// repo's `-c flowLogVpcIds=...` pattern was a confirmed security-review trap
// (SH-ORG-004): once flow logs were attached via context, any context-less
// deploy (including CI) would silently REMOVE them all. Append ids via PR;
// never reorder (index-derived logical IDs). Empty = hardened bucket only,
// matching the currently deployed stack.
const EXTDEV_FLOW_LOG_VPC_IDS: string[] = [];
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,
// NOTE: seahaven.com (not seahavenind.com) is deliberate-as-deployed; flagged
// in the 2026-07-14 security review (SH-ORG-007) for mailbox verification.
budgetAlertEmail: "adam@seahaven.com",
flowLogVpcIds: EXTDEV_FLOW_LOG_VPC_IDS,
// 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) ─────────────────────────────────────

View file

@ -31,6 +31,11 @@ export interface AccountBaselineStackProps extends cdk.StackProps {
readonly monthlyBudgetUsd: number;
/** Email for budget threshold alerts (M-10). */
readonly budgetAlertEmail: string;
/**
* VPC ids to attach ALL-traffic flow logs to (H-14). Logical IDs are
* index-derived — only append, never reorder (see lib/flow-logs.ts).
*/
readonly flowLogVpcIds: string[];
}
export class AccountBaselineStack extends cdk.Stack {
@ -225,9 +230,12 @@ export class AccountBaselineStack extends cdk.Stack {
// ── Day 1 detective layer + governance toggles ──
// Config (H-2), GuardDuty (H-3), Security Hub (H-4), Access Analyzer (M-5).
new DetectiveControls(this, "DetectiveControls");
new DetectiveControls(this, "DetectiveControls", {
namePrefix: "seahaven",
});
// Monthly cost budget (M-10). Other governance toggles are CLI + documented.
new GovernanceToggles(this, "GovernanceToggles", {
budgetName: "seahaven-monthly-cost",
monthlyLimitUsd: props.monthlyBudgetUsd,
alertEmail: props.budgetAlertEmail,
});
@ -239,7 +247,10 @@ export class AccountBaselineStack extends cdk.Stack {
alarmEmail: props.budgetAlertEmail,
trailLogGroup,
});
new FlowLogs(this, "FlowLogs");
new FlowLogs(this, "FlowLogs", {
namePrefix: "seahaven",
vpcIds: props.flowLogVpcIds,
});
new SesMonitoring(this, "SesMonitoring");
// Shared CloudFront WAF WebACL (M-17); ARN published to SSM for app stacks.
new AppWebAcl(this, "AppWebAcl");

View file

@ -16,14 +16,29 @@ import { Construct } from "constructs";
* H-4 Security Hub with AWS FSBP + CIS v3.0 standards
* M-5 IAM Access Analyzer (account-scoped external-access analyzer)
*
* Serves both the management-account baseline (namePrefix "seahaven") and
* member-account baselines (e.g. "seahaven-extdev") — physical resource names
* are prefix-parameterized, structure is identical.
*
* Scope is us-east-1 only — all workloads live here (Adam's call, Day 1).
* Multi-region coverage is a documented follow-up.
*/
export interface DetectiveControlsProps {
/**
* Physical-name prefix for account-scoped resources (bucket, roles, recorder,
* delivery channel, analyzer). Also baked into the custom resources'
* PhysicalResourceId strings — changing it on a deployed stack REPLACES the
* custom resources; keep it stable per account.
*/
readonly namePrefix: string;
}
export class DetectiveControls extends Construct {
constructor(scope: Construct, id: string) {
constructor(scope: Construct, id: string, props: DetectiveControlsProps) {
super(scope, id);
const stack = cdk.Stack.of(this);
const prefix = props.namePrefix;
// ──────────────────────────────────────────────────────────────────────
// H-2 AWS Config
@ -33,7 +48,7 @@ export class DetectiveControls extends Construct {
// versioned, SSE-S3 (Config writes here; SSE-S3 avoids a second KMS-grant
// failure mode and is sufficient — CIS does not require a CMK here).
const configBucket = new s3.Bucket(this, "ConfigBucket", {
bucketName: `seahaven-config-${stack.account}`,
bucketName: `${prefix}-config-${stack.account}`,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
@ -85,7 +100,7 @@ export class DetectiveControls extends Construct {
// grants delivery to the bucket above. **This role is the Day 1 cross-review
// item (IAM change per CLAUDE.md).**
const recorderRole = new iam.Role(this, "ConfigRecorderRole", {
roleName: "seahaven-config-recorder-role",
roleName: `${prefix}-config-recorder-role`,
assumedBy: new iam.ServicePrincipal("config.amazonaws.com"),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWS_ConfigRole"),
@ -143,7 +158,7 @@ export class DetectiveControls extends Construct {
this,
"ConfigCustomResourceRole",
{
roleName: "seahaven-config-custom-resource-role",
roleName: `${prefix}-config-custom-resource-role`,
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName(
@ -191,7 +206,7 @@ export class DetectiveControls extends Construct {
action: "putConfigurationRecorder",
parameters: {
ConfigurationRecorder: {
name: "seahaven-config-recorder",
name: `${prefix}-config-recorder`,
roleARN: recorderRole.roleArn,
recordingGroup: {
allSupported: true,
@ -200,7 +215,7 @@ export class DetectiveControls extends Construct {
},
},
// No meaningful response data to extract.
physicalResourceId: cr.PhysicalResourceId.of("seahaven-config-recorder"),
physicalResourceId: cr.PhysicalResourceId.of(`${prefix}-config-recorder`),
};
const putChannelCall: cr.AwsSdkCall = {
@ -208,7 +223,7 @@ export class DetectiveControls extends Construct {
action: "putDeliveryChannel",
parameters: {
DeliveryChannel: {
name: "seahaven-config-delivery",
name: `${prefix}-config-delivery`,
s3BucketName: configBucket.bucketName,
configSnapshotDeliveryProperties: {
deliveryFrequency: "TwentyFour_Hours",
@ -216,7 +231,7 @@ export class DetectiveControls extends Construct {
},
},
physicalResourceId: cr.PhysicalResourceId.of(
"seahaven-config-delivery"
`${prefix}-config-delivery`
),
};
@ -224,10 +239,10 @@ export class DetectiveControls extends Construct {
service: "ConfigService",
action: "startConfigurationRecorder",
parameters: {
ConfigurationRecorderName: "seahaven-config-recorder",
ConfigurationRecorderName: `${prefix}-config-recorder`,
},
physicalResourceId: cr.PhysicalResourceId.of(
"seahaven-config-recorder-start"
`${prefix}-config-recorder-start`
),
};
@ -265,10 +280,10 @@ export class DetectiveControls extends Construct {
service: "ConfigService",
action: "stopConfigurationRecorder",
parameters: {
ConfigurationRecorderName: "seahaven-config-recorder",
ConfigurationRecorderName: `${prefix}-config-recorder`,
},
physicalResourceId: cr.PhysicalResourceId.of(
"seahaven-config-recorder-stop"
`${prefix}-config-recorder-stop`
),
},
role: configCustomResourceRole,
@ -336,7 +351,7 @@ export class DetectiveControls extends Construct {
// M-5 IAM Access Analyzer (free, account-scoped external-access)
// ──────────────────────────────────────────────────────────────────────
new accessanalyzer.CfnAnalyzer(this, "AccountAnalyzer", {
analyzerName: "seahaven-account-analyzer",
analyzerName: `${prefix}-account-analyzer`,
type: "ACCOUNT",
});

View file

@ -5,32 +5,39 @@ import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
/**
* VPC flow logs for every VPC, delivered to a hardened S3 bucket (audit H-14,
* CIS 3.9/5.6). S3 destination (not CloudWatch Logs) for cost — query
* forensically via Athena. ALL traffic (accept + reject).
* VPC flow logs delivered to a hardened S3 bucket (audit H-14, CIS 3.9/5.6).
* S3 destination (not CloudWatch Logs) for cost — query forensically via
* Athena. ALL traffic (accept + reject).
*
* Serves both the management-account baseline and member-account baselines:
* VPC ids are passed via props (the management account pins its 5 audited
* VPCs in bin/app.ts; member accounts source theirs from cdk context because
* their VPCs change over time). Pass an empty list to create the hardened
* destination bucket without any flow logs attached yet.
*
* S3 delivery needs no IAM role; instead the bucket policy grants the
* `delivery.logs.amazonaws.com` service principal write access, scoped to this
* account. That bucket policy is the Day 2 cross-review item.
*/
// All 5 VPCs in 328440206208 / us-east-1 (4 custom + default), audit Agent 7.
const VPC_IDS = [
"vpc-061d66990b6a4d1fb",
"vpc-0542a9e934b417d23",
"vpc-062d200c68bd4ca0e",
"vpc-0d3d4b67bd0cf8a68",
"vpc-02c10a89d66f6f9b8",
];
export interface FlowLogsProps {
/** Physical-name prefix for the destination bucket (e.g. "seahaven" or "seahaven-extdev"). */
readonly namePrefix: string;
/**
* VPC ids to attach ALL-traffic flow logs to. May be empty. Logical IDs are
* index-derived (FlowLog0, FlowLog1, ...) — REORDERING this list replaces
* deployed flow logs; only append.
*/
readonly vpcIds: string[];
}
export class FlowLogs extends Construct {
constructor(scope: Construct, id: string) {
constructor(scope: Construct, id: string, props: FlowLogsProps) {
super(scope, id);
const stack = cdk.Stack.of(this);
const bucket = new s3.Bucket(this, "FlowLogsBucket", {
bucketName: `seahaven-vpc-flow-logs-${stack.account}`,
bucketName: `${props.namePrefix}-vpc-flow-logs-${stack.account}`,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
@ -90,7 +97,7 @@ export class FlowLogs extends Construct {
})
);
VPC_IDS.forEach((vpcId, i) => {
props.vpcIds.forEach((vpcId, i) => {
const flowLog = new ec2.CfnFlowLog(this, `FlowLog${i}`, {
resourceId: vpcId,
resourceType: "VPC",

View file

@ -3,6 +3,8 @@ import * as budgets from "aws-cdk-lib/aws-budgets";
import { Construct } from "constructs";
export interface GovernanceTogglesProps {
/** Physical name of the AWS Budget (account-scoped, e.g. "seahaven-monthly-cost"). */
readonly budgetName: string;
/** Monthly cost budget ceiling in USD. */
readonly monthlyLimitUsd: number;
/** Email that receives the budget threshold alerts. */
@ -11,7 +13,8 @@ export interface GovernanceTogglesProps {
/**
* Account-level governance toggles that *are* expressible as CloudFormation
* (audit Day 1).
* (audit Day 1). Serves both the management-account baseline and member-account
* baselines (budget name parameterized per account).
*
* Closes:
* M-10 Monthly AWS Budget with 80% / 100% actual + 100% forecast alerts
@ -37,7 +40,7 @@ export class GovernanceToggles extends Construct {
new budgets.CfnBudget(this, "MonthlyCostBudget", {
budget: {
budgetName: "seahaven-monthly-cost",
budgetName: props.budgetName,
budgetType: "COST",
timeUnit: "MONTHLY",
budgetLimit: {

View file

@ -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 (`<namePrefix>-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);
}
}

View file

@ -1,5 +1,5 @@
{
"name": "seahaven-account-baseline",
"name": "seahaven-org-baseline",
"version": "1.0.0",
"bin": {
"app": "bin/app.js"