Read-only Tier-2 idle/anomalous-spend + idle-resource posture checker for the R720 agent-team (design D5 / §4 / §6.3 / §7 Phase 3). Mirrors the Tier-1 checker conventions verbatim (flags --canary/--dry-run/--no-api/--targets, mode-600 report under $REPORT_ROOT/aws-posture/<date>/, ALARM-only, finding.schema.json spirit, exit 0/2/3, shared substrate redact/post_slack_alarm). Detectors (complement GuardDuty/SecurityHub/Config, do not replace): - anomalous Cost Explorer deltas (ce get-anomalies, $-impact threshold) - stopped EC2 still paying for attached EBS - unattached EBS volumes - unassociated Elastic IPs - idle NAT gateways (≈0 bytes out) - idle load balancers (0 healthy targets) - idle RDS (0 connections over window) Live AWS calls are PROVISIONING-GATED: they run ONLY when Roles Anywhere creds are available (STS identity probe) AND not --no-api/--canary. With no creds or --no-api/--canary the checker SKIPS live calls and notes them — NEVER alarms on missing data (memory feedback_cloudwatch_alarms). Roles Anywhere/step-ca are not stood up (IAM cross-review PASSED 2026-06-18; see security-review/iam/). Offline canary: fixtures of mocked AWS responses (cost/describe-* JSON) under fixtures/aws-posture/ + EXPECTED_FINDING_COUNT=7, asserted fully offline (no aws, no network). Identical detector code runs online and offline. shellcheck-clean (only accepted SC1091), chmod +x.
34 lines
2.5 KiB
Markdown
34 lines
2.5 KiB
Markdown
# aws-posture canary fixtures
|
|
|
|
Mocked AWS API responses for `checkers/aws-posture.sh --canary` (offline — **no `aws` calls, no
|
|
network, no credentials**). The canary feeds these files to the SAME detectors the live path runs
|
|
against real `aws` CLI output, and asserts the total finding count equals `EXPECTED_FINDING_COUNT`
|
|
(anti-complacency floor, design §6.4). If a detector regresses (stops firing), the count drops and
|
|
the canary FAILS (exit 3).
|
|
|
|
These are plain JSON files (not git fixtures — aws-posture scans an AWS account, not a repo tree),
|
|
so there is no `dotgit/` / `.fixture` rename trick here; the offline-vs-live seam is the
|
|
`--canary`/`--no-api`/no-credentials guard inside the checker (mirrors compliance-drift's
|
|
API-skip pattern). Each file is shaped like the real `aws ... --output json` response it stands in
|
|
for; a few `_Fixture*` helper keys carry the per-resource metric the live path derives from
|
|
CloudWatch (so the canary stays deterministic and offline).
|
|
|
|
| Fixture file | Stands in for | Planted finding | Count |
|
|
|---|---|---|---|
|
|
| `cost-anomalies.json` | `aws ce get-anomalies` | 1 anomaly TotalImpact ≥ threshold (the other is below threshold → must NOT fire) | 1 |
|
|
| `describe-instances.json` | `aws ec2 describe-instances` | 1 `stopped` instance still paying for its EBS root (the `running` one must NOT fire) | 1 |
|
|
| `describe-volumes.json` | `aws ec2 describe-volumes` | 1 `available` (unattached) volume (the `in-use` one must NOT fire) | 1 |
|
|
| `describe-addresses.json` | `aws ec2 describe-addresses` | 1 EIP with no association (the associated one must NOT fire) | 1 |
|
|
| `describe-nat-gateways.json` | `aws ec2 describe-nat-gateways` | 1 `available` NAT with ~0 bytes out / 14d (the busy one must NOT fire) | 1 |
|
|
| `describe-load-balancers.json` | `aws elbv2 describe-load-balancers` | 1 ALB with 0 healthy targets (the one with 3 must NOT fire) | 1 |
|
|
| `describe-db-instances.json` | `aws rds describe-db-instances` | 1 `available` RDS with 0 connections / 14d (the busy one must NOT fire) | 1 |
|
|
|
|
Total = **7** (`EXPECTED_FINDING_COUNT`).
|
|
|
|
aws-posture **complements** GuardDuty / Security Hub / Config (design §4 / Tier-2) — it is an
|
|
idle/anomalous-**spend** + idle-resource posture watch, not a threat detector, and never alarms on
|
|
missing data (a skipped/credential-less live call is noted, never counted — memory
|
|
`feedback_cloudwatch_alarms`).
|
|
|
|
When you add/remove a detector or fixture, update both the fixture and `EXPECTED_FINDING_COUNT`
|
|
in the same commit (the canary edit is itself caught on the next run — design §6.4).
|