Agent enrollment by approval (SPEC §7.1.1) #10

Manually merged
krisbuild merged 3 commits from agent-enrollment into main 2026-09-27 20:54:23 +02:00
Owner

Adds approve-to-join agent enrollment (SPEC §7.1.1, §9).

An agent started with --enroll (NixOS: services.krisbuild.agent.enroll = true), when no token file holds a token, generates its own kbat_ token. It persists the token at <work_dir>/agent-token (0600) and presents it with X-KB-Enroll: <node>. The control plane records an unknown token presented this way as a pending request. It refuses the connection with a machine-readable X-KB-Enrollment status: pending, rejected, throttled, disabled or invalid. An operator approves or rejects the request on /ui/tokens or via /api/agent-enrollments. Approving turns it into an ordinary agent_tokens row with the same hash, so the secret is never shown to anyone.

  • A public request can only ever create one bounded pending row. Limits: 32 pending at once, 12 new requests per hour, idle pending rows expire after 24 h, rejected rows are forgotten after 7 days of silence.
  • A request for a node name that already has an active token is flagged. Approving it requires an explicit keep-or-revoke choice (the API answers 409 without one).
  • kb-core changes are additive only (header constants and the status enum). No protocol message or def_hash changes.
  • Enrollment defaults to on in the control plane. The module's enroll option defaults to off, so today's tokenless agents are unaffected.

🤖 Generated with Claude Code

Adds approve-to-join agent enrollment (SPEC §7.1.1, §9). An agent started with `--enroll` (NixOS: `services.krisbuild.agent.enroll = true`), when no token file holds a token, generates its own `kbat_` token. It persists the token at `<work_dir>/agent-token` (0600) and presents it with `X-KB-Enroll: <node>`. The control plane records an unknown token presented this way as a pending request. It refuses the connection with a machine-readable `X-KB-Enrollment` status: pending, rejected, throttled, disabled or invalid. An operator approves or rejects the request on `/ui/tokens` or via `/api/agent-enrollments`. Approving turns it into an ordinary `agent_tokens` row with the same hash, so the secret is never shown to anyone. - A public request can only ever create one bounded pending row. Limits: 32 pending at once, 12 new requests per hour, idle pending rows expire after 24 h, rejected rows are forgotten after 7 days of silence. - A request for a node name that already has an active token is flagged. Approving it requires an explicit keep-or-revoke choice (the API answers 409 without one). - `kb-core` changes are additive only (header constants and the status enum). No protocol message or `def_hash` changes. - Enrollment defaults to on in the control plane. The module's `enroll` option defaults to off, so today's tokenless agents are unaffected. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Agent enrollment by approval (SPEC §7.1.1)
Some checks failed
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/workspace-deps succeeded
krisbuild/kris/krisbuild/nix/clippy succeeded
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild/nix/test succeeded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: all tasks succeeded
krisbuild/queue the merge conflicts in SPEC.md, crates/kb-agent/src/config.rs, crates/kb-cli/src/engine.rs, crates/kb-cli/tests/remote.r
b43471ba17
Adding a node no longer means minting a token on the LAN UI and copying it
to the node. An agent started with --enroll (module: enroll = true) and no
provisioned token generates its own kbat_ token from the OS CSPRNG, keeps
it 0600 in its work dir, and presents it with an X-KB-Enroll header. The
control plane records an unknown token so presented as a pending request
and refuses with X-KB-Enrollment: pending (403), which the agent tells
apart from a plain 401: it logs "awaiting approval" once, with a
fingerprint, and retries on its capped backoff.

An operator approves on /ui/tokens (or POST /api/agent-enrollments/{id}/
approve), choosing label and max_trust (default 0); that inserts an
ordinary agent_tokens row with the same hash, so from then on it is exactly
a minted token. Reject marks the request; its token stays refused while its
agent keeps retrying and is forgotten after 7 idle days.

Since the agent listener is public: verification and the request run in one
database call, a request can only create one bounded inert row (32 pending,
12 new per hour, 24 h idle expiry, retries bump their own row), and nothing
but an operator's approval writes agent_tokens. A request for a name with
an active token is flagged, and approving it requires choosing to keep or
revoke that token. agent_enrollment (default true) turns it off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

Reviewed: public enrollment requests can only create a bounded, inert pending row (well-formed token, global caps, dedupe by hash, first name stands); lookup + request share one DB call; approval is transactional and forces an explicit keep/revoke on name collisions. CI green (graph 561).

@krisbuild r+

Reviewed: public enrollment requests can only create a bounded, inert pending row (well-formed token, global caps, dedupe by hash, first name stands); lookup + request share one DB call; approval is transactional and forces an explicit keep/revoke on name collisions. CI green (graph 561). @krisbuild r+
Collaborator

Removed from the merge queue: the merge conflicts in SPEC.md, crates/kb-agent/src/config.rs, crates/kb-cli/src/engine.rs, crates/kb-cli/tests/remote.rs, docs/forgejo-setup.md, flake.nix.

Removed from the merge queue: the merge conflicts in SPEC.md, crates/kb-agent/src/config.rs, crates/kb-cli/src/engine.rs, crates/kb-cli/tests/remote.rs, docs/forgejo-setup.md, flake.nix.
kris force-pushed agent-enrollment from b43471ba17
Some checks failed
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/workspace-deps succeeded
krisbuild/kris/krisbuild/nix/clippy succeeded
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild/nix/test succeeded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: all tasks succeeded
krisbuild/queue the merge conflicts in SPEC.md, crates/kb-agent/src/config.rs, crates/kb-cli/src/engine.rs, crates/kb-cli/tests/remote.r
to 7d845cdf96
Some checks reported errors
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/clippy succeeded
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild/nix/test superseded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: superseded by 927ea6e3fa752afafadb4891a2a16c58adecf648
2026-09-27 19:27:47 +02:00
Compare
tests: fold agent_enrollment into the control plane's single test binary
All checks were successful
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/workspace-deps succeeded
krisbuild/kris/krisbuild/nix/test succeeded
krisbuild/kris/krisbuild/nix/clippy succeeded
krisbuild/kris/krisbuild/nix/test-deps succeeded
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: all tasks succeeded
krisbuild/queue merged
927ea6e3fa
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

Merged main (#18) and moved agent_enrollment into tests/it/. Build, clippy and kb-check were green on the rebased head (graph 592); its test run was superseded by this push.

@krisbuild r+

Merged main (#18) and moved `agent_enrollment` into `tests/it/`. Build, clippy and kb-check were green on the rebased head (graph 592); its test run was superseded by this push. @krisbuild r+
krisbuild manually merged commit ded08eaa93 into main 2026-09-27 20:54:23 +02:00
Collaborator

Merged as ded08eaa93.

Merged as ded08eaa9357.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kris/krisbuild!10
No description provided.