Skip to content

feat: Phase A, the API service, membership and WireGuard key exchange - #1

Draft
marcos-mendez wants to merge 3 commits into
masterfrom
feat/phase-a
Draft

marcos-mendez wants to merge 3 commits into
masterfrom
feat/phase-a

Conversation

@marcos-mendez

@marcos-mendez marcos-mendez commented Oct 2, 2026 •

Copy link
Copy Markdown

Phase A of handbook decision 0046 (proposed, handbook#39): the API service, and membership and WireGuard key exchange for the nodes of a set. Nothing about DNS (Phase B) or the registry view (Phase C).

The recommended answers of 0046 are the working defaults, listed in the README as "defaults pending the maintainer's decision", each in one place: Python from Debian 13 only, an account key plus a narrower enrollment key, operator confirmation by default, one reusable entry secret per set, outbound long polling, IPv6 first and TLS only.

What is here

  • keel-cloud-api: aiohttp, TLS only on [::]:8443, as the system user keel-cloud, SQLite storing only key hashes, public node records and the proofs it relays. Admin CLI keel-cloud.
  • keel-overlay-cloud: keel-cloud-node, also keel cloud (needs feat: the spec's cloud section and keel cloud (0046) keel#68 for the spec's cloud section). It admits a peer only when its record proof and its confirmation (or the set's automatic admission proof) verify with the set's entry secret, pins it per set, and writes it into network.overlay.wireguard.peers; keel spec apply --system converges it.
  • python3-keel-cloud: the shared package.

API

GET /v1/health; POST /v1/keys (account); GET /v1/sets, PATCH /v1/sets/{set} (account; automatic admission needs its proof); POST /v1/sets/{set}/peers (register, with the record proof); GET /v1/sets/{set}/peers?since=&wait= (list, long poll); POST /v1/sets/{set}/peers/confirm (account key plus a confirmation made on a node).

Trust model

The service relays three HMAC proofs made with the set's entry secret (record, confirmation, automatic admission) and can make none of them, so a compromised service can only propose; nodes check all three. Admission needs both the entry secret (nodes) and the account key (operator). Pinned peers keep their key and addresses, addresses must lie inside the overlay prefix, and keel cloud forget is permanent. An independent security review was run on the first version; its findings are fixed in the third commit, and the README lists the known limits of Phase A.

Tests

208 tests, 99.8 percent of lines and branches. CI only through lxc-trixie.yml@main: "coverage / trixie" and "build / trixie" (build, lintian with warnings fatal, and an install, run and purge of the service).

Real test on the test VM, 2026-10-02, with the final packages: kc-api, kc-a and kc-b from the Keel Core step 8 image. Both nodes enrolled and were held; the enrollment key could not confirm; a simulated compromised service marking them confirmed and turning automatic admission on admitted nothing; the operator confirmed each from the other; each spec gained the other as a peer; keel spec apply --system plus keel network confirm brought wg0 up and each pinged the other; a moved endpoint and a bogus confirmed peer injected into the database were refused with the spec unchanged; the hardened agent service picked up a peer's new endpoint through the long poll. Containers destroyed.

Test plan

  • "coverage / trixie" and "build / trixie" green
  • The maintainer answers the open questions of 0046; a change of default is one place, listed in the README
  • keel#68 merged before keel-overlay-cloud is published (it depends on keel >= 0.16.0)

Branch protection on master, copied from keel-core: required checks "coverage / trixie" and "build / trixie", strict, pull request required with 0 approvals and stale reviews dismissed, enforced for admins, no force push, no deletion.

navigator added 3 commits October 2, 2026 14:01
The service (keel_cloud.api): an aiohttp API, TLS only, over SQLite
that stores accounts, the SHA-256 of each API key, sets, and per node
its public record and its entry secret proof. Two key scopes, an
account key and a narrower enrollment key. Nodes register with a proof
the service relays but cannot make; new nodes stay pending until the
operator confirms them with the account key; a set's revision drives a
long poll. A registered key keeps its overlay addresses and two nodes
of a set never share one. Failed keys are limited per client.

The node agent (keel_cloud.node, keel-cloud-node, also `keel cloud`):
reads the spec's cloud section and its 0600 secret files, registers
this node, checks every record of its set against the entry secret,
admits only confirmed nodes whose proof verifies, pins them per set,
and writes them into network.overlay.wireguard.peers after keel spec
validate accepts the new spec. It never writes WireGuard's
configuration; keel spec apply converges the spec under 0018's window.

166 tests, 99.7 percent of lines and branches, the agent against the
real service over TLS on [::1].
Three binary packages from one source (0039): python3-keel-cloud,
keel-cloud-api (systemd unit as the keel-cloud system user, [::]:8443,
a self-signed development certificate made at installation) and
keel-overlay-cloud (the agent, its unit installed disabled). Lintian
clean with warnings fatal.

CI runs only through Keel-Linux/.github's lxc-trixie.yml: the suite
with its 95 percent gate ("coverage / trixie"), and the build, lintian
and an install of the service checked as it runs ("build / trixie").

The README lists the recommended answers of 0046 used as defaults
until the maintainer decides them, the API, the trust model, TLS with
ACME for later, and the real test of 2026-10-02 on three containers.
The operator's confirmation was a status the service asserted, so a
compromised service could admit any node holding a valid record proof.
It is now a proof: keel cloud confirm, run on a node of the set, checks
the new node's record proof and makes an HMAC with the entry secret
over its set, key and overlay addresses, sent with the account key.
Automatic admission is turned on the same way. Every node checks both,
so the service can neither confirm a node nor turn admission on by
itself; the instance's command line can only turn it off.

On the node: a peer's addresses must lie inside this node's overlay
prefixes; records dated ahead are refused; peers declared by hand are
left alone; keel cloud forget removes a peer for good; one bad record
or a listing of the wrong shape no longer stops the agent; the spec is
read again after the long poll and keeps its owner; secret files are
opened without following links; the pin state is flushed before it
replaces the old one. The client follows no redirect and bounds what
it reads. Patterns match whole values, so a trailing newline is no
longer a second spelling of a key; scope ids and non-ASCII digits are
refused. Entry secrets are at least 43 characters.

Packaging: tighter sandboxing of both units, the CLI's umask, and a
purge that removes only the development certificate it made. The
package smoke test now installs from an absolute path, which was the
CI failure.
@marcos-mendez

Copy link
Copy Markdown
Author

Paused by the maintainer (2026-10-02) until the etcd mesh (Phase 5 of tracker#46: registry, install by discovery, three nodes without Keel Cloud) is stable. Then the node-side client ships in images, small and disabled, and is enabled later through apt update and apt upgrade. Needs an independent security review before merge.

@marcos-mendez
marcos-mendez marked this pull request as draft October 2, 2026 14:56
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.

1 participant