Skip to content

Latest commit

 

History

History
209 lines (173 loc) · 53.8 KB

File metadata and controls

209 lines (173 loc) · 53.8 KB

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

Contract Block

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 MonitoringIdentityAlarms 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).

Out of Scope / Must Not Do

  • No O_AUDIT_1 policy 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 cloudtrail event-bus support — out of scope.
  • No CloudTrail Lake / Athena query templates — out of scope.
  • No data-event-capture wildcards (AWS::S3::Object for all buckets) — when captureS3DataEvents: true, the consumer supplies an explicit list of bucket ARNs (not part of M3 — defaults to false).

Pre-Flight

  1. Complete Global Entry Rules.
  2. Read lessons-m1 and lessons-m2; apply corrections.
  3. Read the design record's AuditTrail section + threat-model rows.
  4. Re-state load-bearing constraints: (i) bucket via SecureBucket; (ii) bucket-policy-vs-trail-arn via Output<> chaining; (iii) retention enum with no rounding; (iv) lifecycle minimums per tier; (v) enableLogFileValidation: true hard-coded; (vi) IdentityAlarms extension is additive.
  5. Verify SecureBucket and AccountFoundation outputs match the shapes consumed by AuditTrail — read both source files.
  6. Verify AWS sandbox available; gracefully skip integration test if not.

Files Allowed To Change

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

Step-by-Step

  1. Pre-flight + re-state constraints.
  2. Write tests/aws/audit-trail.test.ts BDD covering every scenario row below. Add three rows to tests/aws/identity-alarms.test.ts for the additive extension (additive cases only). Run — expect failures.
  3. Implement AuditTrailArgs with retention enum, lifecycle types, optional kmsKeyAliasName.
  4. Implement AuditTrailOutputs.
  5. Implement AuditTrail constructor: (a) run six invariants; (b) determine KMS alias (consumer-supplied OR Hulumi-managed alias/hulumi-audit-trail-<stack> via aws.kms.Key + aws.kms.Alias); (c) create SecureBucket instance with bucket name <componentName>-audit-<account> (bucket name discipline matches existing house style); (d) create aws.cloudwatch.LogGroup with KMS encryption + cwLogsRetentionDays; (e) create IAM role for CloudTrail-to-CW-Logs delivery with minimal logs:CreateLogStream + logs:PutLogEvents policy; (f) create aws.cloudtrail.Trail with enableLogFileValidation: true, isMultiRegionTrail: true, s3BucketName: bucket.name, cloudWatchLogsLogGroupArn: logGroup.arn, cloudWatchLogsRoleArn: role.arn; (g) create aws.s3.BucketPolicy with pulumi.all([bucket.arn, trail.arn]) chaining the trail ARN into the policy condition; (h) emit tag triple. The lifecycle rules go on the SecureBucket (SecureBucket already supports lifecycle args).
  6. Extend identity-alarms.ts additively: new optional arg, three new metric filters gated on it.
  7. Re-export from index.ts.
  8. Run mock-runtime BDD; expect green.
  9. Write integration test gated on HULUMI_INTEGRATION=1. Use prefix hulumi-ops-m3-<test-id>-. Teardown deletes trail, log group, bucket (via the SecureBucket-managed lifecycle), role, KMS key/alias.
  10. Run full suite. Self-Review Gate. Update Tracker, write lessons + completion, strike #47.

BDD Acceptance Scenarios

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

Regression Tests

  • All M1 + M2 BDD scenarios continue to pass.
  • All existing AWS / GitHub / K8s BDD scenarios continue to pass.
  • IdentityAlarms baseline tests (without the new arg) produce identical output unchanged.
  • SecureBucket tests pass unchanged (we only consume it).
  • License-boundary lint, exact-pin guard pass.

Compatibility Checklist

  • All four new symbols re-exported.
  • IdentityAlarmsArgs gains an optional field; existing consumers unaffected.
  • No new dependencies.
  • Static-analysis gates green.
  • License header on every new .ts file.
  • 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, AccountFoundation interfaces unchanged.

E2E Runtime Validation

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.

Smoke Tests

  • 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 preview fails 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 status clean.
  • .gitignore covers any new generated files.

Evidence Log

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

Definition of Done

  • 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 status clean.
  • All M1 + M2 + existing surfaces unchanged.
  • IdentityAlarms additive 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.

Post-Flight

  • docs/slo/completed/RUNBOOK-hulumi-operations.md Milestone Tracker → M3 done.
  • docs/components/audit-trail.md — one-line stub.
  • docs/ARCHITECTURE.md — one paragraph describing AuditTrail + the IdentityAlarms extension.
  • docs/issue-candidates.md — strike #47 (filed → shipped).

Notes

  • 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 raw aws.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 Pulumi Output<> 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 an AccountFoundation-managed KMS alias.
  • Org-trail support (Organization-level CloudTrail) is deferred to v1.3 — note in lessons + open question for the next runbook.