Attestation design: signer, key ownership, and verification (deferred) #125
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Tracks the attestation design that colibri#94 deferred. The current contract is already recorded in
docs/plans/JEV-DECISION-GATE.md§7.1 anddocs/wiki/credentials.md:caller_attributionis self-declared provenance metadata, and no verified identity field exists yet.Deferred decisions (all owned here)
Constraints
JEV-DECISION-GATE.md§9).Refs
docs/plans/JEV-DECISION-GATE.md§7 (provenance), §9 (no-authority invariant)docs/wiki/credentials.md— principals, profiles, credential lifecyclecaller_attribution), PR #38 (jev.envfix)Status update — 2026-10-07
Following PR #127 (local caller check) and PR #128 (documentation alignment):
1. Local Unix-socket authorization — implemented
UnixStream::peer_cred()(SO_PEERCREDon Linux,getpeereidon FreeBSD);This is complete for the current single-UID local-socket policy (daemon UID or root). Two items remain pending: native FreeBSD 15 validation on Valhala, and any future multi-UID broker authorization model (open decision —
docs/PRIVILEGE-BOUNDARY-SPEC.md§5.6).2. Cross-host transport — bridge exists, app-layer identity does not
Colibri's application-layer RPC is a local Unix domain socket. The optional
colibri-bridge(socat) exposes that socket over the tailnet, but that boundary is transport-level (Tailscale): the bridge connects to the daemon as a single local UID, so remote callers are not individually identified at the application layer. Kernel peer credentials therefore establish the local transport boundary only; they provide no cross-host identity.3. Cryptographic payload attestation — deferred
Issue #125 continues to cover:
Introducing signing envelopes before an authenticated cross-host RPC transport and a key-management design exists would add complexity without changing the current local trust boundary.
Status: #125 remains open and deferred, pending an authenticated cross-host RPC transport, a key-management design, and an operator-approved contract.
Native FreeBSD validation and the multi-UID broker decision remain separate pending work.
Transport identity via the tailnet — what it solves here, and what it does not
Following the
2026-10-07status update: one prerequisite named in this issue — an authenticated cross-host RPC transport — can be satisfied by the tailnet itself. Worth recording, because it changes the scope of the deferred work. This comment is a proposal, not an implementation; no verified identity field exists in code yet.The join path (fresh host → tailnet)
Operator authentication path — two options:
tailscale up --login-server https://mesh.clawdie.si→ prints a register URL → operator runsheadscale nodes register --user <user> --key <nodekey>.headscale preauthkeys create --user <user> --reusable --expiration 1h --tags tag:operator-usb, thentailscale up … --authkey tskey-auth-XXXX. Firstboot cannot drive a browser handshake, and a scoped/short-lived/tagged key is auditable and revocable. The key is delivered out-of-band and never baked into the image (same rule as provider keys).After join
WhoIs(remoteAddr)resolves a connection to a verified node ID, user, and tag — a real identity, not self-declared.tag:colibri-client→tag:colibri-control:PORT).caller_attributionas the separate (unverified) user claim.The clean wire is
tsnet(colibri is Go): embed a Tailscale node, listen on the tailnet interface,WhoIs()per connection. This replaces the socat bridge, which collapses all remote callers into a single local UID — the exact gap noted above. Node→identity, then identity→policy.What this does not cover
*.ts.netcerts; Headscale's support is limited. Colibri TLS stays on Let's Encrypt / an internal CA.docs/wiki/credentials.md(outbound Forgejo/Jev credentials) is unaffected — this addresses inbound caller identity.Suggested split
tsnet+ tag ACLs +WhoIs()→ a verified principal field (field name owned by colibri policy, per naming standards §5).Open questions
tailscaledstate (node identity survives reboot), or use ephemeral keys and re-enroll each boot? This is the one decision that changes the join flow.Refs:
docs/plans/JEV-DECISION-GATE.md§7/§9,docs/wiki/credentials.md,docs/plans/PATH-ENV-IDENTITY-UNIFICATION.md(cross-host attestation gating),docs/PRIVILEGE-BOUNDARY-SPEC.md§5.6.