ADR-0004: SPIFFE identity scheme and sandbox username derivation¶
Status: Accepted ยท Date: 2026-09-26
Context¶
Every component in the sandbox trust chain needs a stable, verifiable name, and three of
them (the broker, the in-guest agent, the SPIRE selector) must independently derive the
same Linux username for a subject. Both are one-way doors: the trust domain is baked into
every SVID, and the username scheme is baked into registration entries, useradd calls
and nftables rules on running VMs. The runbook's open decisions #1 and #2 required an ADR
before building; the dev-mode loop now implements both, so the decision is recorded here.
Constraints:
- Linux usernames are at most 32 characters,
[a-z_][a-z0-9_-]*. - Entra's
subis pairwise (per-app) and opaque; itsoidis the stable subject key. Dex with GitHub (useLoginAsID: true) puts the stable key inpreferred_username. - A username must not collide across issuers even when two issuers know the same login.
Decision¶
Trust domain. One per environment, org-scoped and stable, never a hostname
(e.g. sandbox.home.example in the homelab). It is configuration (trust_domain),
not code.
SPIFFE ID paths, implemented in mediatore-spire::Naming:
| ID | Held by | Created by |
|---|---|---|
spiffe://<td>/spire/agent/tpm/<ek-hash> |
the SPIRE agent on the VM | TPM node attestation |
spiffe://<td>/banlieue/node/<ek-hash> |
the in-guest agent (root) | broker, on seeing a new tpm agent |
spiffe://<td>/banlieue/claim/<claim-uid> |
the jailed workload | broker, on claim Bound |
spiffe://<td>/mediatore |
broker pods | ClusterSPIFFEID (k8s PSAT) |
The agent ID is the parent of both workload entries on a node. The <ek-hash> is the
SHA-256 of the endorsement public key, computed identically by the SPIRE TPM attestor
plugin and by mediatore-guest.
Username derivation, implemented in mediatore-proto::Subject::sandbox_username:
sb- followed by the first 12 lowercase hex characters of
sha256(issuer || "|" || subject.id), where subject.id is oid on Entra and
preferred_username (the GitHub login) on Dex. The human-readable name (UPN or login)
goes in GECOS, never in the username. 15 characters total, always a valid username,
issuer-scoped so octocat at two issuers yields two users.
Consequences¶
- The broker, the guest agent and the SPIRE
unix:userselector agree by construction: all three call the same function inmediatore-proto. - 48 bits of hash suffix makes accidental collision negligible at sandbox scale; a malicious collision requires controlling the issuer string, which the VAP issuer allowlist prevents.
- Changing the trust domain or the derivation later invalidates every registration entry and every provisioned user; both would be a new major version and a drained pool.
- Node SPIFFE IDs carry no provider name; the broker's
nodestable records the provider for audit instead (runbook, Provider variants).