Skip to content

feat: reload the broker config without a restart - #175

Merged
groupsky merged 1 commit into
mainfrom
feat/29-config-reload
Nov 11, 2026
Merged

groupsky merged 1 commit into
mainfrom
feat/29-config-reload

Conversation

@groupsky

@groupsky groupsky commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Closes #29.

What

  • SIGHUP makes the broker load its config again, with the same code and checks as a start (config.LoadBroker, then the SSH keys).
  • nodkey broker reload [--config FILE] [--timeout DURATION] sends the signal, waits for the result in reload-status.json (next to the audit log), and exits 0 only when the broker loaded the config that is in the file now (it compares the SHA-256). Otherwise it exits 1 with the reason; 64 on wrong usage.
  • A reload may change [client.*], [secret.*], [key.*] and known_hosts. Any other change is refused with "needs a restart" (config.Broker.ReloadFixed compares everything else, also fields added later).
  • An invalid config is refused. The broker keeps the old one, and the journal says why.
  • Grants that the new config allows in the same way stay, with the same uses left and the same end. All others end at once (grant_ended with the new cause config_changed; the message loses its Revoke button).
  • A removed client, or a client with a new pin, gets 403 on its open connection, and the connection closes. New handshakes use the new pins.
  • A request that waits for a tap during a reload starts no grant. Before and after a fetch or a signature, the broker checks the request again: the pin of the client's key, and each secret or key, from the config of the start of the request and from the config that the core decided with, to the config of now.
  • Each reload that the broker took writes a new config_reloaded audit entry.
  • DESIGN.md section 14 "Reload" and section 15, THREAT-MODEL.md T42, and docs/setup.md ("Change the config without a restart", and the places that said "restart the broker" after a config change).

Progress

  • Approval core: Reload, grants kept or ended, no grant from a request checked with an old config
  • Transport: SetClients, per-request pin check
  • Service: config view, recheck before and after fetch or sign
  • nodkey broker SIGHUP, status file; nodkey broker reload
  • Tests (deny paths included), go test -race ./..., go vet, golangci-lint
  • DESIGN.md, THREAT-MODEL.md, docs/setup.md
  • Security review (two passes)
  • CI green

Tests

  • internal/approval: TestReloadKeepsMatchingGrants (same uses left, same end; removed secret ends with config_changed), TestReloadEndsChangedGrants, TestReloadEndsGrantsOfChangedKeyFile, TestReloadRemovedClient, TestReloadDuringWait, TestReloadDuringWaitPreapprove
  • internal/config: TestReloadFixed, TestReloadSame
  • internal/broker: TestStillAllowed
  • cmd/nodkey (real broker in a child process): TestBrokerReload (loaded; invalid config refused and the old one stays; a change that needs a restart refused; a removed key gone at once; a client with a new pin refused on its open connection; config_reloaded entries), TestBrokerReloadDuringTap, TestBrokerReloadNoBroker (no status file, a stale PID, a symbolic link), TestBrokerReloadUsage

Security review

Two passes of .claude/agents/security-reviewer.md. First pass: 2 medium, 4 low, all fixed (the order of the swaps, a pin check per request, a check of the core's decision config, a second check after fetch or sign, ssh-grant answer after a reload, cleaned status text, ReloadFixed by allowed fields). Second pass: no medium or higher; two low findings fixed (ssh-grant now asks the core whether StartGrants skipped the grants; an unused function removed).

Not done, and why: the second pass also asked for more tests: the loadNew path where the service refuses after the server got the new pins (the old pins come back), an SSH login that waits for a tap while a reload changes the key's hosts, and key cases in TestStillAllowed. The code for these paths is covered by the same checks as the tested secret paths; They are filed as #176.

Dependencies

None new.

🤖 Generated with Claude Code

SIGHUP makes the broker load its config again, with the same checks as a
start. `nodkey broker reload` sends the signal, waits for the result in
reload-status.json, and exits 0 only when the broker loaded the config
that is in the file now.

- A reload may change clients, secrets, keys and known_hosts. Any other
  change is refused with "needs a restart". An invalid config is refused;
  the old one stays, and the journal says why.
- Grants that the new config allows in the same way stay, with the same
  uses left and the same end. All others end at once (grant_ended with
  cause config_changed).
- A removed client, or a client with a new pin, gets 403 on its open
  connection and the connection closes.
- A request that waits for a tap during a reload starts no grant, and is
  checked again before and after a fetch or a signature.
- Each reload writes a config_reloaded audit entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@groupsky
groupsky marked this pull request as ready for review November 11, 2026 15:14
@groupsky
groupsky merged commit f168ce6 into main Nov 11, 2026
9 checks passed
@groupsky
groupsky deleted the feat/29-config-reload branch November 11, 2026 15:15
@groupsky groupsky mentioned this pull request Nov 11, 2026
5 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reload the broker config without a restart

1 participant