Pinterest: Resource Provisioner Pipeline — a Centralized Terraform Engine for Least-Privilege AWS at Scale
Problem + constraints
Pinterest's AWS footprint spans hundreds of Terraform workspaces controlling tens of thousands of resources — IAM policies, VPCs, load balancers, S3 buckets, Kubernetes clusters. The conventional way teams get there is each repo wiring its own GitHub Actions workflow to run terraform plan/apply against AWS, usually via a broadly-scoped CI credential.
That pattern creates two compounding problems at scale. First, blast radius: a CI runner (or its shell, or a leaked token) that can apply Terraform for one team's infra is often, in practice, far more privileged than that team's actual resources require — because scoping IAM tightly per-workspace by hand, hundreds of times over, doesn't happen consistently. Second, fix fan-out: if a systemic weakness is found — a vulnerable CI runner base image, a missing static-analysis check — fixing it means touching every one of those hundreds of independently-configured pipelines, not one place. Neither problem is really a Terraform problem; it's what happens when the same security-critical logic (auth, review gating, scoping) is duplicated per-repo instead of centralized.
Solution
Pinterest built the Resource Provisioner Pipeline (RPP) — a purpose-built Terraform execution engine that every workspace routes through, rather than each team owning its own apply pipeline:
- Workspace-path-role mapping as source of truth. A central config maps each Terraform workspace path to the specific AWS IAM role it's allowed to assume — down-scoped to only what that workspace's resources need. RPP validates the backend and this mapping before any plan/apply runs, so a workspace can't silently drift into requesting broader access than it's declared.
- OIDC role-chaining instead of static CI credentials. Runs authenticate via OIDC and chain into the down-scoped role for that specific workspace, rather than every pipeline sharing one broad, long-lived credential.
- Dual control, split across plan and apply. A human code-review approval on the PR is one control; triggering the actual
applyrequires a separate, explicit PR comment. Planning and applying are kept as distinct, auditable actions rather than one PR-merge automatically applying infrastructure changes. - Centralized composite GitHub Actions. PR-triggered checks — static analysis via custom Semgrep rules, AI-assisted scanning, and optional LocalStack-based dry runs against mocked AWS behavior — live in shared composite actions, not duplicated per-repo.
The payoff of centralizing is the same payoff as any shared-core pattern: a systemic issue (a weak CI runner shell, a missing check) gets fixed in RPP once, and every workspace that routes through it inherits the fix — instead of a fan-out across hundreds of independently-drifted pipelines.
What to steal
- Route security-critical CI/CD logic through one engine, not N copies of a workflow file. The instant a pipeline pattern (auth, approval gating, scoping) is copy-pasted into every repo that needs it, it starts drifting, and a fix becomes an audit-and-patch-everywhere project instead of a one-line change.
- Make the scope-mapping itself a validated, central source of truth, not tribal knowledge encoded per-workspace. Pinterest's workspace-path-role config is the thing that makes least privilege enforceable at hundreds-of-workspaces scale — without it, "least privilege" is just a policy on paper that depends on every team getting IAM right by hand.
- Split plan and apply into separately-gated steps. A single "merge PR → auto-apply" pipeline collapses review-of-intent and authorization-to-execute into one action; requiring a distinct trigger for apply (on top of code review) is a cheap way to keep a compromised or mis-clicked merge from directly mutating production infrastructure.
- Push cheap, deterministic checks (static analysis, mocked dry-runs) before the expensive/risky one (a real
apply). LocalStack dry-runs catch a class of mistakes without ever touching a real AWS account, the same "fail fast and cheap before you fail expensive" instinct that shows up in test pyramids generally.
Engineering Lens
This is the IaC-pipeline version of a pattern that shows up constantly at Principal scope: centralize the redundant, security-critical client instead of asking every team to reimplement it correctly. The naive alternative — "every team owns its own Terraform pipeline, scoped and reviewed however they see fit" — looks like autonomy, but it's actually hundreds of independent opportunities for privilege creep, and a systemic fix requires touching all of them. Pinterest's answer converts "get IAM scoping right" from a per-team discipline problem into a property the platform enforces centrally (the workspace-path-role mapping), which is a much more durable way to get consistent least privilege than a policy doc and hope. In an architecture review, the sharp question for any multi-team CI/CD-to-cloud pipeline is: "if we found a critical flaw in this pipeline tomorrow, how many places would we have to fix it?" — if the honest answer is "every repo," that's the signal to centralize, same as Pinterest did here.