-
Notifications
You must be signed in to change notification settings - Fork 0
ADR 0007 least privilege iam
Status: Accepted (2026-07-23)
This is the keystone security decision; it binds together earlier ADRs:
- The agent runs in a tooling account (ADR-0001) and reviews workloads that realistically live in other accounts.
- The scanners need broad read-only access to the target account (ADR-0004).
- The only write in the whole system is to the automation's own Well-Architected workload (ADR-0006), plus its report topic and log bucket.
- The Reconcile stage processes untrusted input (HCL, tfstate, findings), so a prompt-injection there must not be able to reach scan or write credentials.
- ADR-0002 already forbids long-lived API keys.
The headline the IAM design must deliver: the agent can read anything in the reviewed account but write nothing there — the only writes anywhere are to resources the automation owns.
- Read-anything / write-nothing in the reviewed account, enforced structurally, not by convention.
- Per-stage blast-radius confinement — each stage holds only its own scope.
- No long-lived credentials — everything STS-temporary.
- Confused-deputy protection and complete audit.
Cross-account access via per-stage role assumption; write is structurally impossible in the reviewed account.
The tooling-account CodeBuild role sts:AssumeRoles into a read-only role
deployed in each target account. That role's trust policy trusts the CodeBuild
role ARN plus an ExternalId (confused-deputy protection) and is deployed by
the account owner via a shared StackSet / IaC module. Single-account review
is the degenerate case (assume within the same account).
The CodeBuild base role is near-empty: sts:AssumeRole to the specific
purpose roles, bedrock:InvokeModel on the two model ARNs (ADR-0002), and
CloudWatch Logs for its own log stream — nothing else. Each stage assumes only
its role; temporary credentials are dropped after the stage.
| Stage | Assumes | Scope |
|---|---|---|
| Scan — live | cross-account read-only role |
SecurityAudit + ViewOnlyAccess as the ceiling, with an explicit Deny on (a) all write/create/update/delete/put actions and (b) data-plane reads — s3:GetObject, secretsmanager:GetSecretValue, kms:Decrypt, DynamoDB item reads. Control-plane Describe*/List*/Get*-config only (composes with ADR-0001's no-VPC posture). |
| Scan — code | tfstate reader |
s3:GetObject on the state bucket/prefix + kms:Decrypt on that one state key only. |
| Reconcile | base role |
bedrock:InvokeModel on the two model ARNs only — no AWS-resource access. |
| Scorer | WA-write role |
wellarchitected:UpdateAnswer + CreateMilestone scoped to the automation workload ARN (ADR-0006) + read APIs; sns:Publish to the report topic ARN; s3:PutObject to the logs prefix. |
-
Permissions boundary on every role; an explicit
Denyon all mutating actions in the target account makes the review tool structurally incapable of changing what it reviews (reinforces ADR-0008). - No long-lived credentials — all STS-temporary, session duration capped to the build length.
-
Scoped ARNs everywhere (models, workload, topic, bucket, state key) —
never
*on resource. -
CloudTrail on every
AssumeRoleand API call — complete audit trail (composes with ADR-0002).
- Write is structurally impossible in the reviewed account — the strongest possible statement for a review/governance tool, and the IAM backing for ADR-0008's no-auto-remediation stance.
- Prompt-injection is contained — a compromise of the Reconcile stage reaches neither the read-only scan role nor the WA-write scope; those creds never exist in that stage.
-
Broad read stays bounded — the scan role is wide but read-only, and the
explicit data-plane
Denykeeps object/secret/key values out of reach even though config is readable. - No secrets to rotate or leak — consistent with ADR-0002.
- Cost: operational complexity — per-stage credential management in the buildspec, and a StackSet the target-account owners must deploy. Accepted as the price of the isolation and the cross-account trust model.
| Option | Verdict | Why |
|---|---|---|
| Per-stage roles + cross-account read-only | Chosen | Confines each capability to its stage; write structurally impossible in the reviewed account. |
| Single CodeBuild role, union of permissions | Rejected | The Reconcile stage (untrusted input) would hold the WA-write and scan scopes — large blast radius. Fallback only with strong compensating controls. |
| Same-account only (no cross-account) | Kept | The single-account degenerate case, not the general multi-account model. |
| Long-lived IAM access keys | Rejected | Rotation and leak risk; contradicts ADR-0002's no-key posture. |
| Broad read including data-plane | Rejected | tfstate/secret/object values are sensitive; config-only read is sufficient for the scanners. |
- If per-target StackSet onboarding friction is too high at scale, consider an AWS Organizations delegated-admin / org-wide read-only role.
- If per-stage credential management proves too heavy for a simple single-account deployment, fall back to the union role with the permissions-boundary write-deny retained as the non-negotiable control.
- ADR-0001 (compute — the CodeBuild base role; control-plane-only, no VPC)
- ADR-0002 (LLM provider — no API keys; STS-temporary throughout)
- ADR-0003 (per-stage pipeline — the isolation unit)
- ADR-0004 (scanner read breadth this scopes)
- ADR-0006 (the single WA-Tool write scope)
- ADR-0008 (no auto-remediation — write is structurally impossible here)
NWAF Agent repository · generated from docs/ — do not edit wiki pages directly.
Design
Decision Records