Skip to content

Adr 002 least privilege bootstrap policies

ADR-002: Least-privilege CDK bootstrap policies as code

Section titled “ADR-002: Least-privilege CDK bootstrap policies as code”

Status: accepted Date: 2026-05-19 Implementation: Core shipped (#158, #162). The typed policies (cdk/src/bootstrap/policies/), triple-layer versioning (cdk/src/bootstrap/version.tsBOOTSTRAP_VERSION = '1.1.0', computeBootstrapHash()), the generated bootstrap template (cdk/bootstrap/bootstrap-template.yaml), and the mise //cdk:bootstrap + mise //cdk:bootstrap:generate tasks are all present on main. Two sub-mechanisms remain pending: the synth-time CDK Aspect (#125, depends on the resource-action-map #124) and the deploy-time mise //cdk:preflight validator (#126). See RFC #120 for the original stack.

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.

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.

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.

Constraints:

  • IAM managed policies have a 6,144-character limit — hence the three-policy split (Infrastructure, Application, Observability).
  • Bootstrap must exist before the CDK app can deploy — circular dependency prevents managing bootstrap from within the app stack.
  • The four other bootstrap roles (deploy, lookup, file-publishing, image-publishing) are already scoped by the default template and don’t need modification.

Policies as typed TypeScript code in cdk/src/bootstrap/ (shipped)

Section titled “Policies as typed TypeScript code in cdk/src/bootstrap/ (shipped)”

Rationale for location:

  • Agent routingAGENTS.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.
  • Testability — Jest tests can assert policy size limits, validate structure, and verify coverage against the synthesized template.
  • Co-location — the CDK app defines what resources exist (and therefore what permissions are needed); both live in the same package.
  • Self-containedcdk/ has its own mise.toml, build, and test pipeline.
LayerPurpose
SemverQuick operator answer: “do I need to re-bootstrap?” Major = breaking.
SHA256 hashDetects console drift — manual IAM edits that diverge from code.
Action-set comparisonPrecise gap reporting: exactly which actions are missing.

Semver and hash are computed by cdk/src/bootstrap/version.ts (BOOTSTRAP_VERSION, computeBootstrapHash()) and emitted into the generated template / cdk/bootstrap/{BOOTSTRAP_VERSION,BOOTSTRAP_HASH} files, enabling automated preflight checks.

  1. CDK Aspect (synth-time) (pending — #125) — will run during mise //cdk:synth, visiting every CfnResource, looking up required actions in a resource-action-map (#124), and comparing against declared policy. Catches issues at dev time. Not yet implemented: cdk/src/main.ts currently registers only AwsSolutionsChecks (cdk-nag) — there is no bootstrap-policy aspect.
  2. Live-account validator (deploy-time) (pending — #126)mise //cdk:preflight will read CDKToolkit stack outputs, compare version/hash against requirements, and fail fast with an actionable “re-bootstrap required” message before CloudFormation starts. Not yet implemented: no preflight task exists in cdk/mise.toml.

(shipped) — generated from the policy source code (not hand-maintained) at cdk/bootstrap/bootstrap-template.yaml. Operators run mise //cdk:bootstrap (which depends on mise //cdk:bootstrap:generate to regenerate the policy JSON, template YAML, and version/hash files) 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.

The implementation is decomposed into 8 sub-issues, each independently reviewable and deployable. See RFC #120 for the full stack.

  • (+) Policies are diffable in PRs — IAM changes are code-reviewed like any other code
  • (+) Tests enforce the 6,144-char limit and structural validity on every commit
  • (+) Preflight prevents the “deploy, wait 15 minutes, fail, rollback” loop
  • (+) Single mise //cdk:bootstrap command replaces the multi-step manual process
  • (+) Agents can automatically update policies when they add new resource types
  • (-) Resource-action-map requires maintenance when new AWS resource types are added
  • (-) Rebase complexity from the 8-PR stack
  • (!) Bootstrap template drift — CDK upstream may change defaults; requires rebase on CDK major upgrades
  • (!) Operators with existing deployments must re-bootstrap (documented upgrade path provided)