AWS evidence collectors and IAM

Task 6226 · Done · Coder · created · updated

Part of epic 6213. Child C3 of 9. Depends on C1 (store, bucket) and C2 (the `Collector` contract, `run_collector`, alarm prefix, and the migration head this child's migration chains after). ## Original requirement (verbatim) > "Identify which security and compliance certifications Jaah's competitors have — SOC 2, HIPAA, ISO 27001, etc. — and close the gap. Without breaking anything :)" **R2 — SOC 2 Type II readiness: in-house recurring evidence (D-4)** (verbatim): - Every SOC 2 check in the readiness checklist that an agent can run unattended runs on a schedule as a Jaah system cron and files a dated artifact to the evidence store through `tools/evidence_store.py` (existing folders only, or a folder added there). - A check that fails or drifts raises the existing alarm/paging path — never silent. - The same collectors serve ISO 27001/42001 evidence where controls overlap (no duplicate collectors). Operator decision Q4: **Read-only, own account** — a new read-only IAM statement on the api task role, scoped to the account's own services; no cross-account role. ## Scope (architecture §3.2) Seven collectors on C2's contract (literal `_system(EVIDENCE_<ID>, …)` per collector, kebab-case module constants, no loop; staggered daily 04:00–05:30 UTC), using the container's own AWS credentials: | id | source | folder | criteria | drift | |---|---|---|---|---| | `cloudwatch-alarms` | `cloudwatch:DescribeAlarms` | `alarms` | CC7.2, A1.1 | an alarm in ALARM not listed in an accepted risk row | | `paging-subscriptions` | `sns:ListSubscriptionsByTopic` on paging + cp-alarms topics | `alarms` | CC2.2 | no confirmed subscription | | `securityhub` | `securityhub:GetFindings` (own account, FAILED/ACTIVE/HIGH+CRITICAL, not SUPPRESSED) | `control-checks` | CC6.6, CC7.1 | any finding | | `edge-tls` | `elasticloadbalancing:Describe*`, `wafv2:ListWebACLs`, `wafv2:GetWebACLForResource` | `control-checks` | CC6.6, CC6.7 | listener not on pinned TLS policy, port-80 listener not redirecting, or public ALB without WAF | | `backups` | `backup:ListBackupJobs`, `backup:ListCopyJobs`, `rds:DescribeDBInstances` | `control-checks` | CC9.1, A1.2 | no COMPLETED job/copy in 26 h, or DB not Multi-AZ | | `storage-lifecycle` | `s3:GetLifecycleConfiguration` on transcripts bucket | `offboarding` | C1.2 | the 180-day rule absent | | `ecr-scan` | `ecr:DescribeRepositories`, `ecr:DescribeImageScanFindings` | `ecr-scan` | CC6.8 | HIGH/CRITICAL finding past SLA | **IAM**: new `data "aws_iam_policy_document" "task_compliance_evidence"` in `infra/jaah/terraform/modules/ecs/main.tf`, attached beside `task_security_archive`; exactly the read actions above, `Resource="*"` (only valid scope for these Describe/List APIs) except `s3:GetLifecycleConfiguration` scoped to the transcripts bucket ARN; NO write actions. Check `workload_permissions_boundary` (declared in this repo) permits these reads; if not, widen it with exactly these read actions in the same PR (otherwise prod apply fails at promotion). **Seeding**: own alembic migration seeding the 7 rows `is_system=true`, `enabled=true`, same ON CONFLICT shape; **down_revision = C2's migration** (one head). Add rows to `apps/api/tests/test_cron_system_activities.py` `_EXPECTED`/`_SEEDED_ENABLED` additively only. Add the 7 ids to the readiness-checklist collector→criterion section C2 created. Expected on dev: several collectors will drift (no paging email, no WAF…) — intended; the dedup key keeps one alarm Task per collector, resolved on recovery. Credentials & reach: AC4, AC5: Crons page, trigger collector, see run detail with `s3://` key and alarm Task | `jaah/debug/main/operator-login` (operator login, valid on dev and main) / operator / dev CP `app.dev.jaah.ai` | **verified**: credential fetch succeeded; `GET https://app.dev.jaah.ai/api/public/settings` answered 200 from `h2`. Terraform plan for dev/main | none needed: the plan runs in Jaah Deploy's own CI with its OIDC role; the runner reads the plan output with `jaah-forge` | **verified**. AC-X2 | runner's own forge login via `jaah-forge` | **verified**. Out of scope: NO write IAM actions, NO cross-account role, NO management/security-account access (entitlement export and aggregated Security Hub stay owner-run). Do NOT change the C2 framework contract except additively. Do NOT alter existing IAM statements, alarms, WAF, TLS or backup config to make a collector pass — drift is evidence. Epic-wide (PRD §6, verbatim): "**Do not** obtain, buy, sign or contract anything; **do not** contact outside parties. **Do not** claim any certification as held. **Do not** flip the **prod** legal publish switch or promote to `master` (owner action). **Do not** widen the audit boundary to dev; **do not** alter existing controls' behaviour (MFA, sessions, retention, IAM) to satisfy ISO — record a gap row instead unless the change is clearly non-breaking and additive. **Do not** put instance data (people, account ids) in reusable docs. **Do not** add a new evidence-store folder outside `tools/evidence_store.py`'s list." Done means: - AC4 (R2) Each unattended-runnable readiness check runs on its schedule on the **dev** control plane and a dated artifact appears in the evidence store per run. States: pass → artifact filed; fail/drift → artifact filed AND alarm fires; collector error → alarm fires (never silent). → verify: on dev console Crons page, trigger a collector, observe a success journal entry and the new evidence key; force a failing check (e.g. a test fixture or a known-failing dev setting) and observe the alarm. (Every one of the 7 triggered on dev; none ends in an AccessDenied error leg — that would mean IAM is wrong.) - AC5 (R2) The collectors are ON by default in the real deployment (registered system crons, enabled), not behind a flag left off. → verify: dev console lists them enabled with a next-run time. - Terraform plan (dev and main) shows only the added read-only policy (and boundary reads if needed), no write action — oracle: plan excerpt in PR body; `alembic heads` = one head. - AC-X1 (A3) Nothing breaks: `./dc check full` green on every PR; existing crons behave unchanged. → verify: gate output; existing cron tests pass unchanged. - AC-X2 (A3) Every PR's Jaah Deploy succeeds on dev. → verify: GitHub deploy run status.

Outcome

7 AWS SOC 2 evidence collectors + read-only IAM shipped (#5397; fixes #5400, #5407), full gate green ×3, dev deploys ok. Dev boundary applied via org/ (inputs now in repo). All 7 run on dev: 3 pass, 4 drift, none AccessDenied.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Awaiting response
  • Awaiting response
  • Runner resumed
  • Runner ended (done)