BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

Pattern — Least Privilege

The problem it solves

One credential grew until it held everything. svc-platform was minted with admin across the warehouse, the model registry, the feature store, and the ERP — "temporarily, to unblock the migration" — and a dozen services now authenticate as it (Drill 47). When a laptop with its key in a config file is compromised, the attacker doesn't escalate — there is nothing left to escalate to, and forensics can't scope the damage because twelve services and one attacker all authenticate identically. The blast radius wasn't set by the attack; it was designed years earlier. Least privilege is the discipline that keeps that radius small before anyone breaks in.

The pattern

Grant every identity the minimum permissions its job actually requires, and nothing more — then keep shrinking. Three moves make it real: give each workload its own identity (no shared accounts), scope that identity to named resources and actions (no wildcards, deny-by-default), and segment trust zones so no single identity can span them whatever its policy claims. Standing broad access becomes just-in-time, time-boxed elevation. The payoff is twofold: a stolen credential opens one narrow door, and — because each identity has its own audit trail — after an incident you can answer exactly what was touched instead of writing "potentially all of it" in the disclosure letter.

How to apply it

  1. One workload identity per service — kill the shared account. Issue platform-managed, auto-rotated identities (AWS IAM roles via IRSA/instance profiles, GCP workload identity, SPIFFE/SPIRE) whose credentials are short-lived: session-token TTL 1 hour, hard cap 12 hours. There is then nothing static to paste into a laptop config file. Where a long-lived secret is unavoidable, rotate it every ≤ 90 days and store it in a secrets manager, never in a repo or dotfile.
  2. Deny by default; grant named actions on named resources — no *. A policy reads s3:GetObject on arn:aws:s3:::feature-store-prod/*, not s3:* on *. Ban Action: "*", Resource: "*", and iam:PassRole/iam:* in any workload role via a CI policy-lint gate (e.g. an SCP or OPA/Conftest check) that fails the build on a wildcard. Keep each policy small enough to review in one sitting — a working rule of thumb is ≤ 50 actions with a named owner who can explain every line.
  3. Right-size from real usage, then recertify. Generate the starting policy from the last 90 days of CloudTrail with IAM Access Analyzer (or aws iam last-accessed data), which proposes a policy covering only actions actually called. Then run quarterly recertification that drops any permission unused for 90 days — so scope decays unless defended instead of accreting unless attacked.
  4. Replace standing admin with just-in-time elevation. No human or service holds admin at rest. Break-glass is a separate, time-boxed grant — 60-minute TTL, MFA-required, auto-revoked, and alarmed on every use so that using it is always a conversation, never a habit.
  5. Segment so one identity can't span zones. Put warehouse, model registry, feature store, and ERP in separate trust zones (distinct cloud accounts/VPCs/projects) with no cross-zone role. Add per-identity behavioral baselines: a service account authenticating from a new IP/ASN or outside its normal call pattern pages within minutes — a service account calling from a laptop IP is a scream, not a whisper.
flowchart TD
    A["Deny by default"] --> B["One workload identity<br/>per service — short-lived,<br/>auto-rotated (TTL 1h)"]
    B --> C["Named actions on named<br/>resources — no wildcards<br/>(CI lint fails the build)"]
    C --> D["Quarterly recert drops<br/>perms unused for 90 days"]
    D --> E["Segmentation + JIT:<br/>break-glass is 60-min,<br/>MFA, alarmed"]
    E --> F["Stolen credential opens<br/>ONE narrow scope — blast<br/>radius is one service"]

When to use it — and when not

Reach for it on every non-human identity and every standing human grant that touches production data or infrastructure — this is table stakes, not an optimization. Where it earns nothing, don't over-rotate: a genuinely single-purpose function that already reads one bucket needs no further slicing, and micro-scoping a low-stakes internal tool into forty roles can cost more in broken deploys and on-call toil than the risk it removes. The real cost is operational friction: short TTLs mean tokens refresh, deny-by-default means the first legitimate new call fails until the policy is widened, and recertification is recurring work. If you set it so tight that engineers routinely reach for break-glass to get their job done, you've built a god account with extra steps — tune scopes from real usage, not from fear.

Signals it's working

Seen in the wild

See it

The ChainScene carries the mechanism — deny-by-default → per-service identity → named actions, no wildcards → quarterly recert → blast radius = one service; the CardsScene/TilesScene show the load-bearing config (1-hour token TTL, ≤ 50-action policies, 90-day unused-permission pruning, 60-minute break-glass), and the CompareScene shows the tradeoff: one god account ("potentially all of it") versus scoped identities (an exact, short answer).

Named concepts: least privilege, workload identity, scoped roles, segmentation, just-in-time elevation.