|
| 1 | +--- |
| 2 | +title: 002 least privilege bootstrap policies |
| 3 | +--- |
| 4 | + |
| 5 | +# ADR-002: Least-privilege CDK bootstrap policies as code |
| 6 | + |
| 7 | +**Status:** accepted |
| 8 | +**Date:** 2026-05-19 |
| 9 | +**References:** ADR-001 (delivery methodology) |
| 10 | + |
| 11 | +## Context |
| 12 | + |
| 13 | +CDK bootstrap creates five roles per account/region. The **CloudFormation execution role** (cdk-hnb659fds-cfn-exec-role) receives `AdministratorAccess` by default — CloudFormation assumes it to create, modify, and delete stack resources. This violates least-privilege and may conflict with organizational SCPs or compliance gates. |
| 14 | + |
| 15 | +The ABCA project documented three scoped policies in `docs/design/DEPLOYMENT_ROLES.md` (PR #46), validated against a live deployment through 7 iterations and 36 CloudTrail-discovered actions. However, these policies exist only as JSON blobs in a Markdown file — unversioned, untested, and manually applied. |
| 16 | + |
| 17 | +**Failure mode without automation:** When a new release adds a resource type (e.g., SQS queue), operators who pull and deploy hit a mid-rollback CloudFormation failure because their bootstrap policy predates the new permissions. The deploy fails 15 minutes in with no prior warning. |
| 18 | + |
| 19 | +**Constraints:** |
| 20 | +- IAM managed policies have a 6,144-character limit — hence the three-policy split (Infrastructure, Application, Observability). |
| 21 | +- Bootstrap must exist before the CDK app can deploy — circular dependency prevents managing bootstrap from within the app stack. |
| 22 | +- The four other bootstrap roles (deploy, lookup, file-publishing, image-publishing) are already scoped by the default template and don't need modification. |
| 23 | + |
| 24 | +## Decision |
| 25 | + |
| 26 | +### Policies as typed TypeScript code in `cdk/src/bootstrap/` |
| 27 | + |
| 28 | +Rationale for location: |
| 29 | +- **Agent routing** — `AGENTS.md` routes CDK/IAM changes to `cdk/`. An agent modifying a construct that adds a DynamoDB table naturally looks here for the policy it must update. |
| 30 | +- **Testability** — Jest tests can assert policy size limits, validate structure, and verify coverage against the synthesized template. |
| 31 | +- **Co-location** — the CDK app defines what resources exist (and therefore what permissions are needed); both live in the same package. |
| 32 | +- **Self-contained** — `cdk/` has its own `mise.toml`, build, and test pipeline. |
| 33 | + |
| 34 | +### Triple-layer versioning |
| 35 | + |
| 36 | +| Layer | Purpose | |
| 37 | +|-------|---------| |
| 38 | +| **Semver** | Quick operator answer: "do I need to re-bootstrap?" Major = breaking. | |
| 39 | +| **SHA256 hash** | Detects console drift — manual IAM edits that diverge from code. | |
| 40 | +| **Action-set comparison** | Precise gap reporting: exactly which actions are missing. | |
| 41 | + |
| 42 | +Semver and hash are emitted as CloudFormation outputs on the CDKToolkit stack, enabling automated preflight checks. |
| 43 | + |
| 44 | +### Two-layer preflight validation |
| 45 | + |
| 46 | +1. **CDK Aspect (synth-time)** — runs during `mise //cdk:synth`, visits every `CfnResource`, looks up required actions in a resource-action-map, compares against declared policy. Catches issues at dev time. |
| 47 | +2. **Live-account validator (deploy-time)** — `mise //cdk:preflight` reads CDKToolkit stack outputs, compares version/hash against requirements. Fails fast with an actionable "re-bootstrap required" message before CloudFormation starts. |
| 48 | + |
| 49 | +### Custom bootstrap template |
| 50 | + |
| 51 | +Generated from the policy source code (not hand-maintained). Operators run `mise //cdk:bootstrap` to provision least-privilege roles in a single command. The template replaces `AdministratorAccess` with the three managed policies while retaining all other default bootstrap resources. |
| 52 | + |
| 53 | +### Delivery via stacked PRs (ADR-001) |
| 54 | + |
| 55 | +The implementation is decomposed into 8 sub-issues, each independently reviewable and deployable. See RFC #120 for the full stack. |
| 56 | + |
| 57 | +## Consequences |
| 58 | + |
| 59 | +- (+) Policies are diffable in PRs — IAM changes are code-reviewed like any other code |
| 60 | +- (+) Tests enforce the 6,144-char limit and structural validity on every commit |
| 61 | +- (+) Preflight prevents the "deploy, wait 15 minutes, fail, rollback" loop |
| 62 | +- (+) Single `mise //cdk:bootstrap` command replaces the multi-step manual process |
| 63 | +- (+) Agents can automatically update policies when they add new resource types |
| 64 | +- (-) Resource-action-map requires maintenance when new AWS resource types are added |
| 65 | +- (-) Rebase complexity from the 8-PR stack |
| 66 | +- (!) Bootstrap template drift — CDK upstream may change defaults; requires rebase on CDK major upgrades |
| 67 | +- (!) Operators with existing deployments must re-bootstrap (documented upgrade path provided) |
| 68 | + |
| 69 | +## References |
| 70 | + |
| 71 | +- RFC #120 — parent issue with full design and sub-issue breakdown |
| 72 | +- `docs/design/DEPLOYMENT_ROLES.md` — current documentation (will become generated) |
| 73 | +- PR #46 — original policy derivation and validation methodology |
| 74 | +- [CDK default bootstrap template](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml) |
| 75 | +- [IAM managed policy size limit](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html) |
0 commit comments