Break-glass · 05 of 07 · The policies

Six admission policies, one per thing that could go wrong

The backend is the first lock. These are the second, inside each cluster's API server, written so that a bug in Tower, or a compromised Backstage, still cannot turn a debugging session into something bigger.

Six admission policies, one per thing that could go wrong Top: what Backstage asks the API server to do: create a RoleBinding (the grant), add a debug container (ephemeral, or in a copy pod), delete a pod (cleanup), patch a pod (process access); and, from anyone on Flight, exec or attach. Below, six policies named tower-breakglass-something, all failing closed. rolebinding: the name starts tower-breakglass-, the role is debug-live or debug-copy, the only subject is Backstage, a TTL label of 15 to 75 minutes, an app namespace only, and on Flight either an approver who is not the requester or the glass-broken mark, never neither. debug-container: the pinned image digest, runAsNonRoot with a non-zero group, a read-only root, no privilege escalation, all capabilities dropped, every mount read-only, and process access only for the session the pod's annotation names. debug-image: a cosign keyless signature from tower's own workflow on main, logged in the public Rekor; it runs in Kyverno as a webhook scoped to these requests and fails closed too. pod-delete: Backstage may delete only copy pods or pods carrying a debug container, nothing else in the namespace. pod-annotate: the spec and labels are unchanged and only the process-access annotation with the session id may change. no-exec: no exec into app pods, attach only to debug containers, operators excepted, Flight only. Bottom: on Ground they are Kyverno ValidatingPolicies compiled to native admission policies with no webhook; on Flight they are hand-written ValidatingAdmissionPolicies synced by Argo CD before the backend that needs them. WHAT BACKSTAGE ASKS THE API SERVER TO DOTOWER-BREAKGLASS-* · EVERY ONE FAILS CLOSEDCREATE RoleBindingthe grantAdd a debug containerephemeral, or a copy podDELETE a podcleanupPATCH a podprocess accessexec or attachanyone, on Flightrolebindingname tower-breakglass-*role: debug-live, -copysubject: BackstageTTL label 15m to 75mapp namespace onlyFlight: approver is notthe requester, or glassbroken, never neitherdebug-containerimage: pinned digestrunAsNonRoot, gid not 0read-only rootno privilege escalationcapabilities: drop ALLevery mount read-onlyprocess access only forthe session named on itdebug-imagecosign keyless signedfrom tower's workflowon main, public RekorKyverno, as a webhookscoped to break-glassfails closed toopod-deletedeletes copy pods, orpods with a debug-*container, onlynothing else in thenamespacepod-annotatespec unchangedlabels unchangedone annotation maychange: process-accesswith the session idno-execno exec into app podsattach: debug-* onlyoperators exceptedFlight onlyGround: Kyverno ValidatingPolicycompiled to native admission policy · no webhookFlight: hand-written ValidatingAdmissionPolicyArgo CD · synced before the backend that needs itEvery rule is scoped to break-glass objects, so a policy bug can only ever break break-glass.LEGENDA request Backstage makesA request anyone makesPolicyHow it is deployed

Native first

Validation runs as built-in ValidatingAdmissionPolicy, inside the API server. It cannot be down on its own, which matters most during the incident you opened the session for.

One webhook, fenced

Only the image signature needs Kyverno's webhook. Its match conditions are copied onto the webhook, so it never sees a request that is not break-glass.

Learned the hard way

An unguarded CEL field once refused every pod create on the dev cluster for ten minutes. Now every optional field is guarded and every policy is scoped.

Hangar · Tower · Break-glass