Skip to content

feat(fleet): switch ACME and EST on a node and show when it needs a reboot - #129

Merged
Bugs5382 merged 3 commits into
mainfrom
feat/128-protocol-switch
Sep 30, 2026
Merged

Bugs5382 merged 3 commits into
mainfrom
feat/128-protocol-switch

Conversation

@Bugs5382

@Bugs5382 Bugs5382 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

What and why

The Fleet Manager's per-protocol "Enabled" switch only records intent in the manager's catalog; nothing reaches a node. Nodes now carry pki.acme and pki.est with an explicit enabled flag (CryptOS-PKI/api#115, CryptOS-PKI/cryptos-node#286), and every switch is reboot-required. This makes the switch real for ACME and EST, through the fleet contract in CryptOS-PKI/api#123.

  • SetNodeProtocol (admin only) fetches the node's config, sets one protocol block's enabled flag and applies the config back through the node's ApplyConfig, the same whole-config path ApplyProfileToNode uses. The block's other settings go back as the node returned them. EAB keys and EST password digests come back blank from GetConfig and are sent blank, so the node keeps its stored secret for the same key_id or username, and no secret ever reaches the manager. A protocol with no block yet gets a bare enabled: true block for the node to validate. A protocol already in the requested state applies and audits nothing.
  • Node refusals keep their code. A Root refusing enabled: true, or a block that fails validation, comes back with the node's gRPC code and message (for example InvalidArgument) instead of Internal, so the caller sees why. Nothing is audited or recorded.
  • Reboot required. When the node answers requires_reboot, the manager records the switch against the node. ListNodes and GetNode now fill NodeSummary.protocols from the node's NodeStatus.protocols and set reboot_required while the node reports config_reboot_pending or a protocol's reboot_pending, or while a recorded switch hasn't shown up as running. A recorded switch clears once the node reports the protocol running in the new state with nothing pending.
  • Audit. Every switch writes one protocol-enabled or protocol-disabled entry naming the actor, the node and the protocol, with "(takes effect at the next reboot)" when it does. A whole-config ApplyNodeConfig that turns a protocol on or off now gets the same entry (marked "via config apply") ahead of config-applied. To tell, it reads the node's config first when the incoming config carries a protocol block, and applies nothing if it can't read it.
  • Shaped for SCEP. Protocols are a table from cryptos.v1.ServiceProtocol to the Pki block name, and the block is read and written through protobuf reflection on its enabled field. SCEP (CryptOS-PKI/api#117) is one table entry once the pin carries Pki.scep.
  • MCP. SetNodeProtocol is listed as excluded, alongside ApplyNodeConfig.
  • Logging. The handlers log through github.com/Bugs5382/go-log v1.3.0 (new dependency: the manager had no logger, and go-log is the owner's logging package). Entry, decisions, node calls with their duration, the reboot record and its confirmation, and every error. No config or secret is logged. task run sets LOG_LEVEL=trace LOG_FORMAT=console.

Design calls:

  • The other protocol blocks are left out of the apply rather than sent back as read, because an absent block keeps what the node stores: the apply then can't change anything but the one switch, whatever the node's GetConfig returns for the others.
  • The reboot record lives in memory rather than in Postgres, because the node's own config_reboot_pending and per-protocol reboot_pending carry the same fact across a manager restart or across replicas. A schema migration would also collide with feat(nodes): stable node IDs and RenameNode #126's migration v6.
  • A reboot action is left to feat(fleet): surface reboot-required config changes and provide a node reboot action #61: the node has no reboot RPC (only ActivateImage, which boots a staged image), so a manager-side reboot needs an api and cryptos change first. This PR only surfaces "reboot required".

Closes #128

Part of CryptOS-PKI/api#114. Related: #61, #84.

api pin. go.mod pins github.com/CryptOS-PKI/api to 9493f86 on api main, which carries CryptOS-PKI/api#115, CryptOS-PKI/api#123 and the node IDs from CryptOS-PKI/api#110.

Web. The web half isn't in this PR. The web's switch is fleet-wide with no node dimension, so moving it to a per-node switch with a reboot-required badge is a page redesign, not a wiring change.

Docs: README ("What it does") and docs/mcp.md updated here. Companion docs-site commit on CryptOS-PKI/docs#30 (docs/26-fleet-manager-pages), marked as depending on this PR and CryptOS-PKI/api#123: 0d8fe5d (overview: "Switching an enrolment protocol on a node", the admin row, and what is not available; web-ui: the Protocols callout).

Verification

  • Lint clean (golangci-lint 2.12.2, gofmt, go vet, golic)
  • Tests pass (go test ./...)
  • Build succeeds
  • Documentation updated

How this was verified

  • Tests first: internal/fleet/protocols_test.go (12 tests) failed against a stub (Unimplemented, missing fields), then passed. They cover the admin gate, an unswitchable protocol, an unknown node, switching ACME on and EST off with only that block changed and the other block left out, blank secrets kept blank, a missing block, the no-op, a node refusal keeping its code and reason with no audit or record, reboot-required tracking through stored, booted-but-not-running and running states, ListNodes protocol state, per-protocol audit on ApplyNodeConfig, and no apply when the baseline can't be read.
  • TestWireRoundTrip_ProtocolBlocks_KeepEveryField drives GetNodeConfig and ApplyNodeConfig over Connect JSON with every ACME and EST field and checks nothing is dropped (with the old pin the fixture doesn't even decode).
  • Mutation checks: sending the other protocol block back fails both switch tests; clearing a recorded switch without checking the node's report fails the reboot test.
  • task ci (gofmt, vet, golangci-lint, test) passes locally. The Postgres store tests ran without MANAGER_TEST_DATABASE_URL; the store is untouched here.

Overlaps and merge order with the open manager PRs (conflicts checked with git merge-tree):

…eboot

SetNodeProtocol reads the node's config, flips one protocol block's
enabled flag and applies it back, leaving the block's settings and blank
write-only secrets as the node returned them and the other protocol
blocks out of the apply. A reboot-required answer is recorded until the
node reports the protocol running in the new state, and ListNodes and
GetNode carry each node's protocol state and reboot_required.
ApplyNodeConfig audits every protocol it switches.

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
@Bugs5382 Bugs5382 added this to the v1.0.0 milestone Sep 30, 2026
@Bugs5382 Bugs5382 added the enhancement New feature (feat). Minor version bump. label Sep 30, 2026
@Bugs5382 Bugs5382 self-assigned this Sep 30, 2026
Bugs5382 added a commit to CryptOS-PKI/docs that referenced this pull request Sep 30, 2026
Depends on CryptOS-PKI/cryptos-manager#129 and CryptOS-PKI/api#123: the manager's
SetNodeProtocol call, the reboot-required flag on ListNodes and GetNode,
and the protocol-enabled and protocol-disabled audit entries. The web UI's
Protocols page still records intent only, and the manager has no reboot
action yet.

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
@Bugs5382
Bugs5382 marked this pull request as ready for review September 30, 2026 06:54
Bugs5382 added a commit to CryptOS-PKI/docs that referenced this pull request Sep 30, 2026
Depends on CryptOS-PKI/cryptos-manager#129 and CryptOS-PKI/api#123: the manager's
SetNodeProtocol call, the reboot-required flag on ListNodes and GetNode,
and the protocol-enabled and protocol-disabled audit entries. The web UI's
Protocols page still records intent only, and the manager has no reboot
action yet.

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
The reboot-required record was keyed by node name, so renaming a node
dropped a switch still waiting for its reboot, and protocol audit
entries targeted /nodes/<name>. Record switches against the node's
stable ID and write protocol-enabled and protocol-disabled entries
against /nodes/<id>, from SetNodeProtocol and from ApplyNodeConfig
alike. SetNodeProtocol resolves its node through the same lookup as the
other node RPCs.

Re-pin the api module to api main, which carries the SetNodeProtocol
contract and the node ID fields.

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
@Bugs5382

Copy link
Copy Markdown
Contributor Author

Closing summary: the per-node ACME and EST switch is real: SetNodeProtocol applies one block's enabled flag through the node's config, node refusals keep their gRPC code, reboot-required switches are tracked until the node reports them running, ListNodes/GetNode carry protocol state and reboot_required, and every switch is audited. After #126 merged, the branch was brought up to date with it, the reboot record and the protocol audit entries were keyed by node ID (/nodes/<id>) with three new tests, and the api module was re-pinned to api main at 9493f86. task ci passed locally; CI is green on the head. SetNodeProtocolRequest still has no node_id field in the api, so the RPC is addressed by node_name for now.

@Bugs5382
Bugs5382 merged commit 4c01066 into main Sep 30, 2026
15 of 17 checks passed
@Bugs5382
Bugs5382 deleted the feat/128-protocol-switch branch September 30, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature (feat). Minor version bump.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(fleet): switch ACME and EST on a node and show when it needs a reboot

1 participant