Trust model
This page is for readers who want to know exactly where trust comes from and how it’s enforced. If you want a quick summary first: everything that ends up on disk or in shared memory was signed by a key whose public half was pinned at build time, verified before the bytes were written.
The longer version below traces signatures from the operator’s release key down through cells and into per-lease tokens.
The keys
| Key | Where the private half lives | Where the public half is pinned |
|---|---|---|
| Release minisign key | Operator Mac, gitignored at tenura/keys/release-private.key | Hardcoded in apps/install/install.sh (TENURA_RELEASE_PUBKEY=...) and baked into the grafos CLI at compile time |
| Tenura controller key | Tenura account API (Cloudflare Worker secret) | Pinned in firmware bootstrap; TOFU thereafter |
| Per-cell identity key | The cell’s secure key store | Registered with the scheduler at bind time, verified on every cell ↔ scheduler call |
| Per-tenant signing key | Local credential store on the developer’s machine | Registered with the account API after grafos account verify; the account API uses it to authorize the local CLI |
Each key has a clear scope. No key is reused across scopes. A compromise of one key affects only its scope until rotation.
Signed artifacts
Anything the system distributes for installation or execution is signed.
Release artifacts
Every binary published to releases.tenura.systems is signed with the release minisign key. The installer at https://get.tenura.systems/install.sh verifies:
- The
SHA256SUMSminisign signature against the pinned release pubkey. - The tarball’s minisign signature against the pinned release pubkey.
- The tarball’s SHA-256 against the line in
SHA256SUMS.
Any failure aborts before any byte is written to disk. Walk the release process runbook for the operator side.
Wire protocol messages
ANNOUNCE, WITHDRAW, REVOKE_BROADCAST MUST be signed by the controller key (or a delegated signer). Receivers verify the signature before any decompression or deep parsing. Unsigned ANNOUNCEs are dropped silently; the rate limiter on unsigned messages is aggressive to prevent floods.
The signing happens in crates/fabricbios-core/src/identity.rs; the verification is shared between the daemon (fabricbiosd) and the relay state machine.
Capability tokens
Every lease produces a token signed by the issuing cell’s identity key. Programs present the token on every data-plane op; cells verify the signature before acting on the op. Signature verify happens before any lookup, before any state change, before any side-effect.
See Capability tokens for the token shape and caveats.
Cell-side firmware
Cells running on bare metal verify their own firmware at boot via secure boot and (where available) measured boot into a TPM. The fabricBIOS bootloader path is signed by the platform’s own keychain — Tenura does not act as the secure-boot root for those hosts. What Tenura controls is the grafOS layer: cells register with the scheduler using a Tenura-issued cert that the scheduler verifies.
Fail-closed: what it means here
“Fail closed” means: if a verification step cannot succeed, the operation is refused. There is no “best-effort” path, no partial trust, no “well, the signature is missing but the bytes look fine.”
Concretely:
- Missing signature → drop. ANNOUNCE / WITHDRAW / REVOKE without a signature are dropped, audited, and rate-limited at the source IP.
- Invalid signature → refuse + audit. Same audit kind as missing.
- Expired token → refuse op. Cell returns the typed
lease_expiredcode without serving the requested byte. - Mismatched audience → refuse. A token signed for cell A presented at cell B is rejected; both cells audit.
- Unsupported caveat → refuse. A token with a caveat the cell doesn’t understand is rejected. Cells never silently ignore caveats.
- Teardown failure → fence. A cell that can’t tear down a lease’s data-plane binding fences the underlying resource. No new leases on that resource until it is observably reset.
The fence rule is the sharpest: an error in teardown becomes a hard refusal to lease, not a silent corruption window.
What’s NOT in the trust boundary
Worth being explicit about:
- The local developer’s machine. Once you have valid credentials in
~/.config/grafos/credentials.json, anything running on your machine can use them. The bearer is mode-0600, but if your laptop is compromised, the attacker has the same access you do until yougrafos revoke. - The transport between operator and Cloudflare. Wrangler OAuth → CF API. Standard HTTPS; the Cloudflare cert chain is the trust boundary.
- The Tenura controller key issuance ceremony. First-bootstrap of a new cluster requires manual key handoff (operator → controller). Documented but not automatable.
- Side-channels not in the threat model today. Power-analysis attacks on cell hardware, NUMA-domain leakage between strict-isolated tenants on the same physical host, etc. Phase 206 (
/spec/resource-isolation-and-exclusivity) addresses some of this; not all.
Auditing the trust path
Two ways to walk the trust path end-to-end:
- Static audit. Read
crates/fabricbios-core/src/identity.rs,bootstrap.rs,token.rs,revocation.rs. Walk from the controller key, through ANNOUNCE-time signature, through TOFU pinning, through token mint, through every signature verify call. ~600 lines of Rust. - Runtime audit. Every signature verify, every fence event, every lease state transition writes an audit row. Cells emit structured events that flow to
grafos-observe. The audit log is enough to reconstruct what every signature said and what every cell did.
The admin stats console surfaces a small audit sampler today (account_turnstile_failed, beta_request_rate_limited); the full audit table is queryable via D1 / your scheduler’s audit store for compliance reads.
Where to next
/spec/control-plane-tls-plan— staged TLS rollout for control-plane traffic./spec/rdma-revoke-semantics— how RDMA-backed leases tear down on revoke.- Lease primitive and Capability tokens — the moving parts the trust model wraps.