Milestone 3 — AuditTrail + IdentityAlarms extension (closes #47)
Parent runbook: docs/slo/completed/RUNBOOK-hulumi-operations.md. Read Carmack-Style Best Practices, the Global Execution Rules (especially Rule 0, Rule 8 — tier defaults encode breach risk for retention, Rule 10 — no new SNS topics, Rules 11–14) + the Global Entry Rules + docs/slo/lessons/hulumi-operations-m{1,2}.md before starting.
Goal: After M3, @hulumi/baseline.aws.AuditTrail ships as a pulumi.ComponentResource wrapping CloudTrail multi-region trail + log-file validation + KMS-encrypted CW Logs delivery + S3 lifecycle (the bucket created via the existing SecureBucket component, not raw aws.s3.BucketV2). IdentityAlarms is extended (additively) to add three new metric filters on the new CW Logs group: RootAccountUsage, MfaDisabled, CloudTrailUpdateOrStop. Closes #47 verbatim with two design-record revisions: bucket via SecureBucket + KMS alias from AccountFoundation when present.
Context: The design record docs/slo/design/hulumi-for-operations.md § Decision: AuditTrail shape commits the API + the two revisions to #47. The threat model rows tm-hulumi-ops-abuse-trail-tamper, tm-hulumi-ops-abuse-bucket-publicread-bpb, tm-hulumi-ops-abuse-retention-default, tm-hulumi-ops-abuse-sensitive-request-params, tm-hulumi-ops-abuse-lifecycle-too-aggressive, tm-hulumi-ops-abuse-bucket-policy-ordering are the abuse-case surface. The bucket-policy-vs-trail-arn ordering coupling — historically a source of pulumi up retries on first deploy — is solved via Pulumi Output<> chaining: the trail ARN is known at the point the bucket policy is constructed.
Carmack-style reliability goal: Strengthen Rule 5 — invalid-states-unrepresentable (the retention-days enum: 30 | 90 | 365 | 2555 only; CloudWatch's allowed values), Rule 4 — bounded resources (S3 lifecycle archive/expire days have explicit minimums per tier), and Rule 1 — debugger over guessing (the aws cloudtrail get-trail-status describe path is the inspection command for any "trail isn't logging" failure).
Important design rule: The S3 bucket is created via SecureBucket — not aws.s3.BucketV2. This is the load-bearing decision M3 ships. SecureBucket already enforces TLS-only, public-access-block, SSE-KMS, versioning — the bucket-publicread-via-policy abuse case (tm-hulumi-ops-abuse-bucket-publicread-bpb) is eliminated by construction because the BPB rejects any policy that grants Principal: "*" regardless of how the policy was added. The CloudTrail-write policy on the bucket is added as a separate aws.s3.BucketPolicy whose construction depends on the trail ARN being known (Pulumi standard Output<> chain handles ordering). Critically: this means M3 is the first runbook in the project where one Hulumi component (AuditTrail) consumes another Hulumi component (SecureBucket) directly — not via re-export, but via internal use. Document this pattern explicitly in the lessons file.
Refactor budget: Surgical addition only. New audit-trail.{ts,args.ts,outputs.ts} files + tests. Existing files modified: packages/baseline/src/aws/index.ts (re-exports), packages/baseline/src/aws/identity-alarms.ts (one additive extension — three new metric filters routable to the new CW Logs group when an AuditTrail's output is supplied). The IdentityAlarms extension is genuinely additive (new optional arg, no behavioral change when not supplied).
| Field | Value |
|---|---|
| Inputs | new AuditTrail(name, args) where args: AuditTrailArgs requires: tier: Tier; name: string (the trail name). Optional with documented defaults: cwLogsRetentionDays: 30 | 90 | 365 | 2555 (default per tier — Sandbox: 90, StartupHardened: 365); s3LifecycleArchiveDays: number (Sandbox default 90, StartupHardened default 365); s3LifecycleExpireDays: number (Sandbox default 365, StartupHardened default 2555); captureS3DataEvents: boolean (default false); captureLambdaDataEvents: boolean (default false); kmsKeyAliasName?: pulumi.Input<string> (optional — falls back to alias/hulumi-audit-trail-<stack> Hulumi-managed alias). |
| Outputs | AuditTrailOutputs exposes: trailArn: pulumi.Output<string>, trailName: pulumi.Output<string>, logGroupName: pulumi.Output<string>, logGroupArn: pulumi.Output<string>, bucketName: pulumi.Output<string>, bucketArn: pulumi.Output<string>, kmsKeyArn: pulumi.Output<string> (the resolved alias's underlying key ARN). |
| Interfaces touched | New stable surface: @hulumi/baseline.aws#{AuditTrail,AuditTrailArgs,AuditTrailOutputs,AUDIT_TRAIL_COMPONENT_TYPE}. Component type: "hulumi:aws:AuditTrail". Modified surface: @hulumi/baseline.aws.IdentityAlarmsArgs gains an optional auditTrailLogGroupName?: pulumi.Input<string> field that, when supplied, wires three new metric filters (RootAccountUsage, MfaDisabled, CloudTrailUpdateOrStop) into the alarms set. No backward incompatibility — the field is optional. |
| Data classification | Confidential. CloudTrail captures management-event request parameters which can include iam.PutUserPolicy documents, secretsmanager.PutSecretValue parameter names, kms.GenerateDataKey key IDs. Even after AWS's redaction of obvious sensitive parameters, the audit log carries data the consumer treats as confidential. Threat-model row tm-hulumi-ops-abuse-sensitive-request-params documents the residual risk. The Confidential classification triggers v4's "must include at least one abuse-case scenario" requirement (satisfied — six rows below). |
| Proactive controls in play | (a) C1 Define Security Requirements — design record + threat model are the source. (b) C5 Validate All Inputs — retention enum, lifecycle minimums, kmsKeyAliasName regex (alias/...). (c) @hulumi/baseline.aws.SecureBucket (existing) — the bucket-creation surface; consumed internally by AuditTrail. (d) @hulumi/baseline.aws.AccountFoundation (existing) — the KMS alias surface; consumed optionally. (e) C9 Implement Security Logging and Monitoring — IdentityAlarms extension wires the audit-trail metrics into the alarm set. (f) C10 Handle All Errors and Exceptions — bucket-policy-vs-trail-arn ordering via Output<> chaining; no race-as-error. |
| Abuse acceptance scenarios | Six BDD rows below cite tm-hulumi-ops-abuse-N: tm-hulumi-ops-abuse-trail-tamper (CloudTrail-of-CloudTrail pattern — M3 documents the wiring; the actual tamper-trail is a aws.cloudtrail.Trail whose log group has a metric filter on cloudtrail:Update*|Stop*|Delete* → routed via the new IdentityAlarms.CloudTrailUpdateOrStop filter), tm-hulumi-ops-abuse-bucket-publicread-bpb (eliminated by-construction via SecureBucket), tm-hulumi-ops-abuse-retention-default (Sandbox default 90 days, StartupHardened 365 days; O_AUDIT_1 policy rule lands in M4 to flag <90 days), tm-hulumi-ops-abuse-sensitive-request-params (residual risk; documented in component reference doc; no code mitigation in M3), tm-hulumi-ops-abuse-lifecycle-too-aggressive (constructor refuses values below tier minima), tm-hulumi-ops-abuse-bucket-policy-ordering (Pulumi Output<> chaining test: assert that bucket policy is constructed with trail ARN already resolved). |
| Files allowed to change | NEW: packages/baseline/src/aws/audit-trail.{ts,args.ts,outputs.ts}, packages/baseline/tests/aws/audit-trail.test.ts, packages/baseline/tests/integration/aws-ops/audit-trail.aws-ops.test.ts, docs/components/audit-trail.md, docs/slo/lessons/hulumi-operations-m3.md, docs/slo/completion/hulumi-operations-m3.md. MODIFIED: packages/baseline/src/aws/index.ts (re-exports), packages/baseline/src/aws/identity-alarms.ts (additive extension — three new metric-filter definitions, all gated on auditTrailLogGroupName arg being supplied), packages/baseline/src/aws/identity-alarms.args.ts (additive arg), packages/baseline/tests/aws/identity-alarms.test.ts (three new BDD rows for the additive extension), docs/slo/completed/RUNBOOK-hulumi-operations.md Milestone Tracker, docs/slo/runbook-milestones/hulumi-operations-m3.md (this file — Evidence Log only), docs/ARCHITECTURE.md (one paragraph), docs/issue-candidates.md (strike #47). REFUSE TO TOUCH: Ec2PatchBaseline (M1), DetectiveServicesEnable (M2), packages/policies/, packages/drift/, packages/k8s-baseline/, any SecureBucket source files (we consume SecureBucket, never modify it). |
| Files to read before changing anything | docs/slo/completed/RUNBOOK-hulumi-operations.md; docs/slo/design/hulumi-for-operations.md § Decision: AuditTrail; threat-model AuditTrail STRIDE rows; lessons-m1, lessons-m2; packages/baseline/src/aws/secure-bucket.ts (the component this milestone consumes internally); packages/baseline/src/aws/identity-alarms.ts + .args.ts (the surface this milestone extends); packages/baseline/src/aws/account-foundation.ts (the KMS alias surface); the verbatim text of #47. |
| New files allowed | All NEW entries above. |
| New dependencies allowed | none. |
| Migration allowed | no — additive only. The IdentityAlarms extension is an additive optional arg. |
| Compatibility commitments | All four new symbols stable from M3. The "bucket via SecureBucket" decision is load-bearing and irreversible. IdentityAlarms extension is additive — existing consumers see no behavior change. M1 + M2 components unchanged. |
| Resource bounds introduced/changed (v4 Rule 4) | (a) cwLogsRetentionDays: discrete enum (30 | 90 | 365 | 2555); refuse non-enum values. (b) s3LifecycleArchiveDays: tier-aware minimum — Sandbox ≥ 30, StartupHardened ≥ 90. (c) s3LifecycleExpireDays: tier-aware minimum — Sandbox ≥ 90, StartupHardened ≥ 365. (d) s3LifecycleArchiveDays < s3LifecycleExpireDays: refuse otherwise. (e) The component creates a fixed number of child resources: 1 SecureBucket + 1 aws.s3.BucketPolicy + 1 aws.cloudtrail.Trail + 1 aws.cloudwatch.LogGroup + 1 IAM role + 1 IAM role policy = 6 child resources. Bound documented + tested. |
| Invariants/assertions required (v4 Rule 3) | (i) cwLogsRetentionDays is in the allowed enum. (ii) Lifecycle minimums per tier (b/c above). (iii) s3LifecycleArchiveDays < s3LifecycleExpireDays. (iv) Trail ARN resolves to non-empty before bucket policy is constructed (pulumi.all([trailArn]).apply(([arn]) => assert(arn.length > 0)) — race-prevention assertion). (v) The CloudTrail-write bucket policy has exactly one Statement and Principal.Service: "cloudtrail.amazonaws.com" (assertion on JSON shape). (vi) kmsKeyAliasName (when supplied) matches /^alias\/[a-zA-Z0-9/_-]+$/. |
| Debugger / inspection expectation (v4 Rule 1) | Real-AWS inspectable via aws cloudtrail describe-trails --trail-name-list <name>, aws cloudtrail get-trail-status --name <name> (the canonical "is the trail logging?" check), aws logs describe-log-groups --log-group-name-prefix <prefix>, aws s3api get-bucket-policy --bucket <name>, aws kms describe-key --key-id <alias>. If a test fails non-obviously (especially "trail created but not logging"), get-trail-status is the first inspection command — not log-grep. |
| Static analysis gates (v4 Rule 2) | Same as M1/M2. |
| Forbidden shortcuts | (a) NEVER use raw aws.s3.BucketV2 for the audit bucket — must use SecureBucket. (b) NEVER ship a bucket policy that grants Principal: "*" for any reason (BPB will reject anyway, but the policy must not even attempt it). (c) NEVER create a new SNS topic (Rule 10). (d) NEVER ship a Hulumi-authored Lambda (Rule 0). (e) NEVER set cwLogsRetentionDays to a non-enum value — CloudWatch silently rounds, which would surface as drift later. (f) NEVER allow lifecycle archive ≥ expire — would archive then immediately expire. (g) NEVER swallow CloudTrail's "log-file-validation must be on" — enableLogFileValidation: true is hard-coded; not configurable. (h) NEVER allow data-event capture (S3 / Lambda) on by default — both default false (cost). (i) NEVER add a wildcard CW Logs metric-filter pattern (every filter has explicit JSON keys). |
- No
O_AUDIT_1policy rule — that's M4. - No skill scenario
aws-audit-pipeline-broken— that's M5. - No new SNS topics. No Hulumi Lambdas.
- No org-trail support (Organization-level CloudTrail) — single-account at v1.2; v1.3 if demand emerges.
- No EventBridge
cloudtrailevent-bus support — out of scope. - No CloudTrail Lake / Athena query templates — out of scope.
- No data-event-capture wildcards (
AWS::S3::Objectfor all buckets) — whencaptureS3DataEvents: true, the consumer supplies an explicit list of bucket ARNs (not part of M3 — defaults tofalse).
- Complete Global Entry Rules.
- Read lessons-m1 and lessons-m2; apply corrections.
- Read the design record's
AuditTrailsection + threat-model rows. - Re-state load-bearing constraints: (i) bucket via
SecureBucket; (ii) bucket-policy-vs-trail-arn viaOutput<>chaining; (iii) retention enum with no rounding; (iv) lifecycle minimums per tier; (v)enableLogFileValidation: truehard-coded; (vi)IdentityAlarmsextension is additive. - Verify
SecureBucketandAccountFoundationoutputs match the shapes consumed byAuditTrail— read both source files. - Verify AWS sandbox available; gracefully skip integration test if not.
| File | Planned Change |
|---|---|
packages/baseline/src/aws/audit-trail.ts |
NEW: AuditTrail extends pulumi.ComponentResource; consumes SecureBucket internally |
packages/baseline/src/aws/audit-trail.args.ts |
NEW: AuditTrailArgs |
packages/baseline/src/aws/audit-trail.outputs.ts |
NEW: outputs |
packages/baseline/src/aws/index.ts |
MODIFY: re-export |
packages/baseline/src/aws/identity-alarms.ts |
MODIFY (additive): three new metric filters gated on auditTrailLogGroupName arg |
packages/baseline/src/aws/identity-alarms.args.ts |
MODIFY (additive): one optional auditTrailLogGroupName?: pulumi.Input<string> field |
packages/baseline/tests/aws/audit-trail.test.ts |
NEW: BDD |
packages/baseline/tests/aws/identity-alarms.test.ts |
MODIFY (additive): three new rows for the additive extension |
packages/baseline/tests/integration/aws-ops/audit-trail.aws-ops.test.ts |
NEW: real-AWS sandbox test |
docs/components/audit-trail.md |
NEW (one-line stub) |
docs/slo/completed/RUNBOOK-hulumi-operations.md Milestone Tracker |
MODIFY: M3 done |
docs/slo/runbook-milestones/hulumi-operations-m3.md |
MODIFY: Evidence Log fill-in |
docs/slo/lessons/hulumi-operations-m3.md |
NEW per v4 lessons template |
docs/slo/completion/hulumi-operations-m3.md |
NEW per v4 completion template |
docs/issue-candidates.md |
MODIFY: strike #47 |
docs/ARCHITECTURE.md |
MODIFY: one paragraph |
- Pre-flight + re-state constraints.
- Write
tests/aws/audit-trail.test.tsBDD covering every scenario row below. Add three rows totests/aws/identity-alarms.test.tsfor the additive extension (additive cases only). Run — expect failures. - Implement
AuditTrailArgswith retention enum, lifecycle types, optionalkmsKeyAliasName. - Implement
AuditTrailOutputs. - Implement
AuditTrailconstructor: (a) run six invariants; (b) determine KMS alias (consumer-supplied OR Hulumi-managedalias/hulumi-audit-trail-<stack>viaaws.kms.Key+aws.kms.Alias); (c) createSecureBucketinstance with bucket name<componentName>-audit-<account>(bucket name discipline matches existing house style); (d) createaws.cloudwatch.LogGroupwith KMS encryption +cwLogsRetentionDays; (e) create IAM role for CloudTrail-to-CW-Logs delivery with minimallogs:CreateLogStream+logs:PutLogEventspolicy; (f) createaws.cloudtrail.TrailwithenableLogFileValidation: true,isMultiRegionTrail: true,s3BucketName: bucket.name,cloudWatchLogsLogGroupArn: logGroup.arn,cloudWatchLogsRoleArn: role.arn; (g) createaws.s3.BucketPolicywithpulumi.all([bucket.arn, trail.arn])chaining the trail ARN into the policy condition; (h) emit tag triple. The lifecycle rules go on theSecureBucket(SecureBucketalready supports lifecycle args). - Extend
identity-alarms.tsadditively: new optional arg, three new metric filters gated on it. - Re-export from
index.ts. - Run mock-runtime BDD; expect green.
- Write integration test gated on
HULUMI_INTEGRATION=1. Use prefixhulumi-ops-m3-<test-id>-. Teardown deletes trail, log group, bucket (via theSecureBucket-managed lifecycle), role, KMS key/alias. - Run full suite. Self-Review Gate. Update Tracker, write lessons + completion, strike #47.
Feature: @hulumi/baseline.aws.AuditTrail — multi-region trail + log-file validation + KMS-encrypted CW Logs + lifecycle bucket via SecureBucket
| Scenario | Category | Given | When | Then | Threat-model row | Control |
|---|---|---|---|---|---|---|
| Happy path — Sandbox tier with all defaults | happy path | tier: Sandbox, name: "management-events" |
constructor | one SecureBucket registered, one LogGroup (90 days, KMS-encrypted), one Trail (multi-region, log-file-validation on), one BucketPolicy with trail ARN resolved into the policy Resource, exactly six child resources |
n/a | n/a |
| Happy path — StartupHardened tier defaults | happy path | tier: StartupHardened |
constructor | retention 365 days; lifecycle archive ≥ 90, expire ≥ 365 | n/a | Rule 8 |
| Happy path — consumer-supplied KMS alias | happy path | kmsKeyAliasName: "alias/my-account-logs" |
constructor | no Hulumi-managed KMS key/alias registered; LogGroup.kmsKeyId resolves to the consumer's alias's underlying key ARN |
n/a | n/a |
| Happy path — Hulumi-managed KMS alias when none supplied | happy path | kmsKeyAliasName: undefined |
constructor | one aws.kms.Key + one aws.kms.Alias (alias/hulumi-audit-trail-<stack>) registered; LogGroup.kmsKeyId references the new key |
n/a | n/a |
Invalid input — cwLogsRetentionDays: 100 |
invalid input | cwLogsRetentionDays: 100 |
constructor | throws Error('AuditTrail.cwLogsRetentionDays must be one of [30, 90, 365, 2555]') |
n/a | C5 + Rule 4 |
Invalid input — s3LifecycleArchiveDays >= s3LifecycleExpireDays |
invalid input | s3LifecycleArchiveDays: 365, s3LifecycleExpireDays: 365 |
constructor | throws Error('AuditTrail: s3LifecycleArchiveDays must be less than s3LifecycleExpireDays') |
tm-hulumi-ops-abuse-lifecycle-too-aggressive |
C5 + Forbidden (f) |
Invalid input — Sandbox s3LifecycleExpireDays: 7 |
invalid input | tier: Sandbox, s3LifecycleExpireDays: 7 |
constructor | throws Error('AuditTrail: s3LifecycleExpireDays must be ≥ 90 at tier Sandbox') |
tm-hulumi-ops-abuse-lifecycle-too-aggressive |
Rule 4 |
Invalid input — invalid kmsKeyAliasName shape |
invalid input | kmsKeyAliasName: "my-key-no-alias-prefix" |
constructor | throws Error('AuditTrail.kmsKeyAliasName must match /^alias\\/[a-zA-Z0-9/_-]+$/') |
n/a | C5 + invariant (vi) |
Empty state — no consumer KMS alias supplied AND no AccountFoundation |
empty state | kmsKeyAliasName: undefined |
constructor | falls back to Hulumi-managed alias; explicit pulumi.log.info('AuditTrail: using Hulumi-managed KMS alias alias/hulumi-audit-trail-<stack>') emitted |
n/a | C9 |
| Dependency failure — bucket policy attempted before trail ARN known | partial failure | mock-runtime; assert ordering | constructor | the bucket policy resource's policy arg is constructed inside pulumi.all([trail.arn]).apply(([arn]) => ...); the policy JSON contains the trail ARN; assertion (iv) fires if trail ARN resolves to empty |
tm-hulumi-ops-abuse-bucket-policy-ordering |
Rule 11 + invariant (iv) |
| Resource bound — exactly six child resources | resource bound | happy-path args | constructor | the component registers exactly 6 child resources (counted in mock-runtime); 7 fails the test, 5 fails the test | n/a | Rule 4 |
Invariant — enableLogFileValidation: true hard-coded |
assertion violation | constructor invocation | inspect generated trail | aws.cloudtrail.Trail.enableLogFileValidation resolves to literal true; assertion fails the test if false or undefined |
tm-hulumi-ops-abuse-trail-tamper |
Forbidden (g) |
Invariant — isMultiRegionTrail: true hard-coded |
assertion violation | constructor invocation | inspect generated trail | aws.cloudtrail.Trail.isMultiRegionTrail resolves to literal true |
n/a | Rule 11 |
Compatibility — IdentityAlarms works without auditTrailLogGroupName |
compatibility | existing IdentityAlarms consumer not supplying the new arg |
construction | no behavioral change; the three new metric filters are NOT registered | n/a | additive arg |
Compatibility — IdentityAlarms with auditTrailLogGroupName adds three filters |
compatibility | auditTrailLogGroupName: <name from AuditTrail> |
construction | three new aws.cloudwatch.LogMetricFilter resources registered: RootAccountUsage, MfaDisabled, CloudTrailUpdateOrStop; their alarms route through the existing MonitoringFoundation.high.arn |
n/a | C9 |
Abuse case — SecureBucket-by-construction blocks public bucket policy |
abuse case | constructor invocation | inspect bucket | the underlying bucket has BPB enabled; even attempting aws.s3.BucketPolicy with Principal: "*" would be rejected at apply time by BPB; test asserts BlockPublicAcls: true and RestrictPublicBuckets: true on the SecureBucket child |
tm-hulumi-ops-abuse-bucket-publicread-bpb |
Forbidden (b) |
| Abuse case — bucket policy has exactly one Statement with CloudTrail principal | abuse case | constructor invocation | inspect bucket policy JSON | parsed JSON has exactly one Statement; Principal.Service: "cloudtrail.amazonaws.com"; Action: ["s3:PutObject"] (or AWS-canonical equivalent) |
n/a | Forbidden (b) + (i) |
Abuse case — O_AUDIT_1 retention default at Sandbox = 90 days |
abuse case | tier: Sandbox, no explicit cwLogsRetentionDays |
constructor | LogGroup.retentionInDays resolves to literal 90; M4's O_AUDIT_1 rule will flag any value < 90, but M3 itself defaults to 90 |
tm-hulumi-ops-abuse-retention-default |
Rule 8 |
- All M1 + M2 BDD scenarios continue to pass.
- All existing AWS / GitHub / K8s BDD scenarios continue to pass.
IdentityAlarmsbaseline tests (without the new arg) produce identical output unchanged.SecureBuckettests pass unchanged (we only consume it).- License-boundary lint, exact-pin guard pass.
- All four new symbols re-exported.
-
IdentityAlarmsArgsgains an optional field; existing consumers unaffected. - No new dependencies.
- Static-analysis gates green.
- License header on every new
.tsfile. - DCO sign-off carried over.
- All M1 + M2 + existing AWS / GitHub / K8s BDD scenarios produce valid output unchanged.
- No
child_process.exec,eval,@aws-sdk/*in new files. -
Tier,MonitoringFoundation,SecureBucket,AccountFoundationinterfaces unchanged.
File: packages/baseline/tests/integration/aws-ops/audit-trail.aws-ops.test.ts.
| E2E Test | What It Proves | Pass Criteria |
|---|---|---|
creates_multi_region_trail_with_log_file_validation |
The whole pipeline works on real AWS | After pulumi up: aws cloudtrail describe-trails --trail-name-list hulumi-ops-m3-<test-id>-management-events returns one trail with IsMultiRegionTrail: true, LogFileValidationEnabled: true; aws cloudtrail get-trail-status shows IsLogging: true. |
bucket_policy_resolves_with_trail_arn |
Output-chaining ordering works | After pulumi up: aws s3api get-bucket-policy --bucket <bucket-name> returns a policy whose JSON contains the trail's actual ARN (not a placeholder, not empty). |
cw_logs_group_kms_encrypted |
The KMS encryption requirement is honored | After pulumi up: aws logs describe-log-groups --log-group-name-prefix /aws/cloudtrail/hulumi-ops-m3- returns one group with kmsKeyId non-null and matching the resolved alias's underlying key ARN. |
bucket_publicread_bpb_blocks_attempted_public_policy |
Abuse case bucket-publicread-bpb is honored |
Test attempts aws s3api put-bucket-policy --bucket <bucket-name> --policy '{"Statement":[{"Effect":"Allow","Principal":"*",...}]}'; AWS rejects with AccessDenied because BPB is on; test asserts the rejection. |
identity_alarms_extension_creates_three_metric_filters |
IdentityAlarms extension wires correctly |
Construct IdentityAlarms with auditTrailLogGroupName: <log-group-name>; assert via aws logs describe-metric-filters --log-group-name <name> that three filters exist: RootAccountUsage, MfaDisabled, CloudTrailUpdateOrStop. |
- Full static-analysis suite green.
- (Optional) Real-AWS integration test green or skipped.
- In a Pulumi program:
new AuditTrail("foo", { tier: "Sandbox", name: "x", cwLogsRetentionDays: 100 } as any)→pulumi previewfails with documented error. - In a Pulumi program:
new AuditTrail("foo", { tier: "Sandbox", name: "x", s3LifecycleArchiveDays: 200, s3LifecycleExpireDays: 100 } as any)→ fails with archive-vs-expire error. -
git statusclean. -
.gitignorecovers any new generated files.
| Step | Command / Check | Expected Result | Actual Result | Pass/Fail | Notes |
|---|---|---|---|---|---|
| Baseline tests | pnpm -r build && pnpm -r test |
green | filled during execution | pending | |
| BDD tests created | pnpm --filter @hulumi/baseline test -- audit-trail |
fail for expected reason | filled during execution | pending | |
IdentityAlarms additive BDD added |
pnpm --filter @hulumi/baseline test -- identity-alarms |
three new rows fail then pass | filled during execution | pending | |
| E2E stubs created | filesystem | file present | filled during execution | pending | |
| Implementation | filesystem | source files + re-exports | filled during execution | pending | |
| Formatter | pnpm -r format:check |
clean | filled during execution | pending | |
| Typecheck | pnpm -r typecheck |
clean | filled during execution | pending | |
| Static analyzer | pnpm -r lint |
clean | filled during execution | pending | |
| License-boundary lint | pnpm run lint:license-boundary |
OK | filled during execution | pending | |
| Exact-pin guard | pnpm run lint:exact-pin-guard |
OK | filled during execution | pending | |
| Mock-runtime BDD | pnpm --filter @hulumi/baseline test -- audit-trail |
all rows pass | filled during execution | pending | |
| Real-AWS integration test | HULUMI_INTEGRATION=1 ... pnpm --filter @hulumi/baseline test:integration:aws-ops -- audit-trail |
green or skipped | filled during execution | pending | |
| Build/boot | pnpm -r build |
builds cleanly | filled during execution | pending | |
| Resource-bound verification | mock-runtime child-resource count assertion | exactly 6 (or 7 with Hulumi-managed KMS) | filled during execution | pending | |
| Invariant verification | mock-runtime: enableLogFileValidation: true hard-coded |
invariant fires; structured error | filled during execution | pending | |
| Debugger / state inspection | aws cloudtrail get-trail-status if integration test fails |
hypothesis confirmed | filled during execution | pending | |
| Smoke tests | (manual list) | all checked | filled during execution | pending | |
| Test artifact cleanup | git status --short |
only intentional files | filled during execution | pending | |
| .gitignore review | review | current | filled during execution | pending | |
| Compatibility checks | (manual list) | no regressions | filled during execution | pending |
- All BDD scenarios pass.
- All E2E runtime validations pass (or skip cleanly).
- Static-analysis gates green.
- Smoke tests checked off.
- Compatibility checklist complete.
- Resource bounds (Rule 4) and invariants (Rule 3) encoded + tested.
- No forbidden shortcuts.
git statusclean.- All M1 + M2 + existing surfaces unchanged.
IdentityAlarmsadditive extension preserves backward compatibility (existing consumers unaffected).- Self-Review Gate answered with
yes. - Lessons + completion files written per v4 templates.
- Issue #47 struck in
docs/issue-candidates.md. - Milestone Tracker updated to
done.
docs/slo/completed/RUNBOOK-hulumi-operations.mdMilestone Tracker → M3done.docs/components/audit-trail.md— one-line stub.docs/ARCHITECTURE.md— one paragraph describingAuditTrail+ theIdentityAlarmsextension.docs/issue-candidates.md— strike #47 (filed → shipped).
- The "bucket via
SecureBucket" decision is the single most important design decision in this milestone — record in the lessons file as a deliberate rejection of #47's draft text using rawaws.s3.BucketV2. This is the first runbook where one Hulumi component consumes another internally; the lessons file should capture the pattern and any surprises (especially around PulumiOutput<>chaining for the bucket-policy-vs-trail-arn ordering). - The Hulumi-managed KMS alias fallback (
alias/hulumi-audit-trail-<stack>) is documented in the component reference doc; the Sandbox-tier expectation is that consumers eventually consolidate onto anAccountFoundation-managed KMS alias. - Org-trail support (Organization-level CloudTrail) is deferred to v1.3 — note in lessons + open question for the next runbook.