Skip to main content

Module signing

Module signing 

Source
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 run publishes 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§

VerifyError
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 kid that 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 CommandKeys array 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 body against the signature headers using ring.