Expand description
Command provenance: the backend signs, the agent verifies (#1165).
The agent authorises a command by the subject it arrived on and runs it
without checking who wrote it. #1155 measured why that is not fixable with
credentials: deliver_subject is not authorization-checked, so a connected
party can place attacker-authored bytes on commands.all /
commands.pc.<id> through a push consumer, and subject permissions cannot
express a rule against it. Signing attacks a different axis — the agent
checks provenance regardless of how the bytes arrived.
This is the code-signing model (signed packages, Authenticode), not a login handshake. The asymmetry is the whole point: the private key lives only on the backend, and agents hold only public verify keys, which are not secrets. Compromising one of hundreds of endpoints therefore yields nothing that can command another — the endpoint holds no key capable of authoring a valid command.
§Where the signature travels
In NATS headers, over the message body’s exact bytes — not in an envelope wrapping the body.
Both are detached signatures and both sidestep canonicalisation (verify the
received bytes, then deserialize). Headers win on migration: the body
stays a bare serialized Command, so an agent that predates this feature
parses a signed message exactly as it parses an unsigned one and is
unaffected by the rollout. An envelope would change the body’s shape, which
means every agent must understand both shapes for the whole of stages 1–2 —
a dual-parse path across a fleet-wide upgrade window, to buy nothing.
The frame plane already does this (#1140: metadata in headers, raw payload) for the same reason: don’t wrap the body when the transport has a place for metadata.
§Multiple signers from the start
SIG_KID is populated and checked from the first release, even while
only one key exists. Adding a key id later is a wire break across the whole
fleet; reserving it now costs nothing, and it buys two things:
- Rotation is not a separate mechanism. Rotating = two valid kids for a window, which is the same code path as having two signers. A rotation procedure that shares its implementation with everyday operation is one that still works the day it is needed.
- The recovery-path decision can wait.
kanade runpublishes its own commands (crates/kanade/src/cmd/run.rs) and is the documented backend-down recovery route; whether it gets a break-glass key, routes through the backend, or is retired does not have to be settled before the wire format ships.
A KeyRing therefore maps kid → (key, policy) rather than holding a
bare set of keys. Policy is what makes more keys safer rather than merely
wider: a break-glass key should not silently carry the same authority as
the backend’s.
Structs§
- KeyPolicy
- What a key is allowed to authorise.
- KeyRing
- The public keys an agent trusts, keyed by
kid. - SigHeaders
- The signature headers carried alongside a message body.
- Signer
- The signing half, bound to the id agents know it by.
- Verified
- A verified message: which key vouched for it, and under what policy.
Enums§
- Verify
Error - Why a message was not accepted as backend-authored.
Constants§
- ALG_
ED25519 - The only algorithm currently emitted or accepted.
- REG_
BACKEND_ SUBKEY - Registry subkey + value holding the backend’s private signing key.
- REG_
SIGNING_ KEY - REG_
SIGNING_ KID - Registry value holding the
kidthat names the key beside it. - SIG
- Header carrying the base64 (standard, padded) Ed25519 signature over the message body.
- SIG_ALG
- Header naming the algorithm. Ed25519 today; present so a future migration is a value change rather than a format change.
- SIG_AT
- Header carrying when the message was signed, as decimal milliseconds since
the Unix epoch. Covered by the signature — see
signed_material. - SIG_KID
- Header naming which key signed it. Present from the first release; see the module doc on why it is not deferred.
Functions§
- decode_
secret - encode_
public - encode_
secret - Base64 of the 32-byte seed. This is the secret; it is written to the registry and never printed by any code path that logs.
- generate_
keypair - Mint a fresh signing keypair.
- keyring_
entry - The JSON object an agent’s
CommandKeysarray holds for this key. - sign
- Sign
body, producing the headers to publish alongside it. - signed_
material - The bytes a signature actually covers: the signing time, then the message.
- verify
- Verify
bodyagainst the signature headers usingring.