Hardened AWS account baseline — composes CloudTrail, AWS Config,
GuardDuty, Security Hub, IAM password policy + Access Analyzer, and a
KMS key ring into one tiered Pulumi ComponentResource. All
sub-resources are children of the
hulumi:baseline:aws:AccountFoundation component; every taggable child
carries the hulumi:component, hulumi:tier, and hulumi:controls
tag triple.
Stability: stable from v0.3 per
interfaces.md §1.
Ships: M3.
Paired policies: HulumiHardeningPack H1–H4 (M2),
CisV5Pack sections 1–3 (M3).
import { AccountFoundation } from "@hulumi/baseline/aws";
const sandbox = new AccountFoundation("baseline", {
tier: "sandbox",
iacRoleArn: "arn:aws:iam::111122223333:role/hulumi-sandbox-iac-role",
});
export const detectorId = sandbox.guardDutyDetectorId;Emits: 4 KMS CMKs (logs / data / secrets / config) with rotation, account password policy, single-region CloudTrail, AWS Config recorder
- delivery channel, GuardDuty basic detector, Security Hub + CIS v5.0.
import { AccountFoundation } from "@hulumi/baseline/aws";
const baseline = new AccountFoundation("baseline", {
tier: "startup-hardened",
iacRoleArn: "arn:aws:iam::111122223333:role/hulumi-prod-iac-role",
cisVersion: "v5.0.0",
region: "us-east-1",
orgAccountIds: ["111111111111", "222222222222"],
});
export const detectorId = baseline.guardDutyDetectorId;
export const trailArn = baseline.cloudTrailArn;
export const trailLogGroupName = baseline.cloudTrailLogGroupName;Emits everything in the Sandbox tier PLUS:
- Multi-region CloudTrail with log-file validation + S3 data events on the log bucket.
- AWS Config aggregator pulling from
orgAccountIds. - GuardDuty 5 extended features (S3 data events, EKS audit logs, EBS malware protection, RDS login events, runtime monitoring).
- Security Hub NIST 800-53 Rev 5 standard subscribed alongside CIS v5.0.
- IAM Access Analyzer at account scope.
- KMS deny-without-tag policy on every CMK (requires
aws:PrincipalTag/hulumi:iac-role=trueto perform encrypt/decrypt actions; defaultautomode applies only whenorgAccountIdsis supplied). - CloudWatch Logs group for CloudTrail integration (CIS §3.4).
The full tier matrix lives in ../tiers.md § AccountFoundation — tier matrix.
| Arg | Type | Required? | Default |
|---|---|---|---|
tier |
"sandbox" | "startup-hardened" |
yes | — |
iacRoleArn |
Input<string> |
yes | — (must be non-empty; should carry hulumi:iac-role=true) |
cisVersion |
"v5.0.0" | "v7.0.0" |
no | v5.0.0 (v7.0.0 accepted with a warning — AWS Security Hub maxes at v5.0.0) |
region |
Input<string> |
no | Pulumi provider's region |
logBucketForceDestroy |
Input<boolean> |
no | false; intended only for ephemeral test stacks |
existingGuardDutyDetectorId |
Input<string> |
no | —; references an existing regional detector instead of creating one |
useExistingSecurityHubAccount |
boolean |
no | false; references an already-enabled regional Security Hub account |
orgAccountIds |
readonly string[] |
no (Startup-Hardened only) | — |
kmsDenyWithoutTag |
"auto" | "force" | "off" |
no (Startup-Hardened only) | auto; preserve the org-account gated deny policy |
kmsDenyWithoutTag controls the Startup-Hardened KMS key-policy
statement that denies encrypt/decrypt/data-key/re-encrypt operations
unless the calling principal carries hulumi:iac-role=true.
| Mode | Behavior |
|---|---|
auto |
Default. Applies the deny statement only when orgAccountIds is supplied. |
force |
Applies the deny statement in Startup-Hardened single-account stacks, scoped to this account. |
off |
Suppresses the deny statement even when orgAccountIds is supplied. |
For single-account stacks, use a two-phase opt-in:
- Apply
AccountFoundationwith the defaultautomode and noorgAccountIds, or explicitly setkmsDenyWithoutTag: "off". - Confirm the Pulumi execution role is tagged
hulumi:iac-role=true. - Change to
kmsDenyWithoutTag: "force"and apply again.
If the rollout locks out the expected principal, use a break-glass
administrator or root-account path to remove the
DenyKmsActionsWithoutHulumiIacRoleTag statement from the four CMK
policies, restore kmsDenyWithoutTag: "off", and re-apply. Do not
retry force until the IaC role tag is visible on the principal used by
Pulumi.
| Output | Type | Description |
|---|---|---|
cloudTrailArn |
Output<string> |
Multi-region (Startup-Hardened) or single-region trail ARN. |
cloudTrailLogGroupName |
Output<string | undefined> |
CloudTrail CloudWatch Logs group name when emitted by the tier. |
configRecorderArn |
Output<string> |
Config recorder ARN. |
guardDutyDetectorId |
Output<string> |
GuardDuty detector ID. |
securityHubHubArn |
Output<string> |
Security Hub hub ARN. |
kmsKeyArns |
Output<Record<string, string>> |
KMS CMK ARNs keyed by service: logs, data, secrets, config. |
iamBaselinePolicyArns |
Output<string[]> |
Account password policy ID + (Startup-Hardened) Access Analyzer ARN. |
Use cloudTrailLogGroupName as the canonical input for
IdentityAlarms.trailLogGroupName when wiring CloudTrail metric filters
from a Startup-Hardened AccountFoundation stack:
const trailLogGroupName = baseline.cloudTrailLogGroupName.apply((name) => {
if (name === undefined) {
throw new Error("AccountFoundation did not emit a CloudTrail log group for this tier");
}
return name;
});
new IdentityAlarms("identity-alarms", {
tier: "startup-hardened",
trailLogGroupName,
criticalTopicArn: monitoring.critical.arn,
highTopicArn: monitoring.high.arn,
});All taggable children carry:
| Tag key | Example value | Purpose |
|---|---|---|
hulumi:component |
AccountFoundation |
Attribution. |
hulumi:tier |
sandbox | startup-hardened |
Tier metadata; consumed by H4 / H3 + the M4 drift classifier. |
hulumi:controls |
comma-separated framework IDs | Which CCM / CIS / NIST IDs this component claims to address (≥18 entries). |
Tag-key schema is stable per
interfaces.md §6.
AccountFoundation addresses the union of SecureBucket's IDs (since the log bucket is a SecureBucket child) plus the AccountFoundation-specific ones:
- CCM v4.1: DSP-01, CEK-04, CEK-01, DSP-17, LOG-01, IAM-01, LOG-02.
- CIS AWS v5.0.0: 2.1.1, 2.1.2, 2.1.4, 2.1.5, 2.1.6, 1.6, 1.19, 3.1, 3.2, 3.7, 3.8.
- NIST 800-53 Rev 5: AC-3, SC-8, SC-12, SC-13, SC-28, AU-2, AU-12, CP-9, CA-7.
- MITRE ATLAS v5.1: AML.T0001 (via SecureBucket).
IDs only — prose is intentionally NOT embedded (per the IDs-only license boundary in ../licensing.md).
AWS service enablement is asynchronous. AccountFoundation orders its
sub-resources via Pulumi dependsOn:
flowchart LR
KMS[KMS Ring × 4 CMKs] --> LogBucket[SecureBucket log bucket]
KMS --> Trail
LogBucket --> Trail[CloudTrail]
LogBucket --> Config[Config Recorder + Delivery]
GD[GuardDuty Detector] --> GDFeatures[5 DetectorFeatures]
GD --> SH[Security Hub Account]
GDFeatures --> SH
SH --> CISSub[StandardsSubscription CIS v5.0]
SH --> NISTSub[StandardsSubscription NIST 800-53 r5]
The original M3 design used a pulumi.dynamic.Resource polling probe
between GuardDuty and Security Hub. That conflicts with vitest's
worker pool — Pulumi's closure-serialization needs Node's
trace_events. We dropped the probe in favor of direct dependsOn on
the Detector + every DetectorFeature; AWS's CreateDetector API call
itself blocks until status === ENABLED, which gives us equivalent
ordering for real deployments. The escape hatch
(packages/baseline/src/aws/probes/poll.ts) is preserved for v1.1+
probes where dependsOn alone is insufficient. See
M3 lessons for the full
rationale.
tieroutside the union → constructor throwsInvalid Hulumi tier "..."; expected one of: sandbox, startup-hardened.iacRoleArnempty string → constructor throwsiacRoleArn must be a non-empty string ARN.kmsDenyWithoutTagoutsideauto | force | off→ constructor throws with the accepted mode list.cisVersion: "v7.0.0"accepted but logs a warning that AWS Security Hub currently maxes at v5.0.0; falls through to v5.0.0 behaviour.
The component instantiates normally under
pulumi.runtime.setMocks(). See
../../packages/baseline/tests/account-foundation.test.ts
for the BDD test suite and
../../examples/account-foundation-smoke/
for a minimal end-to-end example.
Real-AWS integration runs weekly via
.github/workflows/weekly-integration.yml.
See ../integration-testing.md for the
auth, cost, and teardown contract.
- v0.4 (M4): Drift classifier wires the
hulumi:iac-roleprincipal-attribution into its CloudTrail adapter. - v1.0 (M5): SLSA Build L3 attestation on the published
@hulumi/baselinepackage; SCP templatedocs/deployment/scp.jsonpairs with H3→mandatory.