Repository navigation
feat: etcd's certificates are the root's, and etcd's auth is on (#83) - #90
Merged
Merged
Conversation
marcos-mendez
force-pushed
the
feat/etcd-root-issued-rbac
branch
from
October 8, 2026 08:00
33bbae6 to
812eadb
Compare
marcos-mendez
marked this pull request as ready for review
October 8, 2026 08:09
…#83) The maintainer's decision of 2026-10-07 (keel#83, amending 0048's third round and 0051): each member held an intermediate CA and issued its own certificates, so any member could mint one with any CN, root's included, and etcd's per-user access control meant nothing. - The root CA, on its holder, signs every member's certificate itself: the request relayed by the inviter at a join, sent by the member at a renewal. The CN is the member's mesh identity (keel-<address>), the SANs its /128 and ::1, both fixed by the root. Members hold no CA key. A renewal (30 days) and a new membership wait while the holder cannot be reached; the path through the holder named in the member's state leaves room for 0051's other roots. - The root signs only a request shown to be the member's own (keel.mesh.etcdproof): the request's own signature proves possession of its key, and the member's signature over it with its mesh signing key binds that key to the member. A member renews its own address itself; a node being admitted is relayed by its inviter with the admission evidence it signed, whose signing key the proof verifies with. Evidence naming another member's signing key for the node is refused; a certificate issued under a signing key the trust store later disowns is revoked at tend. So no member gets a certificate for another's address or name, and no inviter holds the identity of the node it admits. The inviter that relayed a request may revoke it, so a join that reverts gives its learner back. - etcd's auth: users are the CNs. keel-member reads /keel/<mesh>/ and writes /keel/<mesh>/etcd/; a VIP's keys are read-write for its pair's two members alone. The holder manages users and roles with an admin certificate (CN root) only it holds (keel.mesh.etcdauth); keel vip pair and tend send a pair's record to it. Whether auth is on is read from etcd (auth status, kept a few seconds), never from a file. Membership changes run on the holder. - keel asks etcd with etcdctl over gRPC (keel.mesh.etcdclient): etcd's JSON gateway calls etcd with the member's own certificate and refuses a CN-bearing client certificate once auth is on, so CN users need gRPC. Argument lists, certificates by path (the VIP controller's memfds as /proc/self/fd paths), -w json, explicit timeouts; a failed or unparseable call raises and is never a renewal. The gateway is off. keel-vip.service's helper writes renewed credentials into the controller's memfds. keel#87's gate and endpoints go through it too. - The CRL stays the root's, signed on the holder; members sign nothing. - keel mesh etcd reissue, on the holder: the live layout moved one member at a time, each serving its new certificate and every voter healthy before the next, nothing restarted; then every intermediate revoked by the root's CRL, then users, roles and auth. --dry-run; --rollback turns auth off. Tests: unit tests for each piece (a recording etcdctl, a fake etcd with auth, every refusal of the root), and tests/test_etcd_auth_netns.py (CI job etcd-auth / trixie): three members of the live layout moved while the VIP is held, at 250 ms ±25 ms with 2% loss measured first; a non-pair member's writes, deletes and revokes on the VIP's keys refused by etcd; a member refused a certificate for another's address; no member can mint; renewal through the root, waiting while it is cut off; rollback.
marcos-mendez
force-pushed
the
feat/etcd-root-issued-rbac
branch
from
October 8, 2026 09:23
e582868 to
6457267
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #83, which the maintainer approved on 2026-10-07. Packaging: Keel-Linux/common#48. Handbook amendment: Keel-Linux/handbook#56.
What changes
The root signs every etcd certificate itself. The inviter relays the request at a join (
issue), and the member sends its own at a renewal. The CN is the member's mesh identity (keel-<address>) and the SANs are its /128 and::1. The root sets both, whatever the request asks. The member checks key, name, address, CA:FALSE and the chain to the root. No member holds a CA key. Renewal (with 10 of 30 days left) and new memberships wait, with an alert, while the holder is unreachable. Issuance goes through the holder named in the member's state, which leaves room for 0051's other roots.Who gets a certificate (
keel.mesh.etcdproof, after the security review). The root signs only a request shown to be the member's own. The CSR's own signature is proof of possession of the CSR key (checked before signing); the member's Ed25519 signature over the CSR with its mesh signing key binds that key to the member. A member renews its own address itself (sender = that address's key, proof verified with the trust store's signing key for it). A node being admitted is relayed by its inviter with the admission evidence it signed (names the node, address and signing key; signed by the sender's trusted key); the proof must verify with the evidence's signing key. Evidence naming the inviter's own or any other member's signing key is refused; where the trust store knows the node, the evidence must name the same key; a certificate issued under a signing key the trust store later disowns is revoked attend(etcdca.impostors). The holder records the signing key and the relaying member with each certificate, and the relay may revoke it, soabandoned()gives a failed join's certificate and learner back.tests/test_mesh_etcd_root.pynow refuses B for C's address in every form;tests/etcd_auth_netns.pydoes the same on the real cluster.etcd auth. Users are the CNs.
keel-member/keel/<mesh>/, write/keel/<mesh>/etcd/(the mesh's own keys, e.g. feat: upgrades keep the VIP: kernel-bounded address, graceful restart, rolling etcd #87's restart lock)keel-vip-<vip>/keel/<mesh>/vip/<vip>/The holder manages users and roles with an admin certificate (CN
root) that only it holds (keel.mesh.etcdauth). Whether auth is on is read from etcd (auth status, cached 5 s), never from a file; every step afterauth enablein reissue is idempotent, so a crash between them resumes on rerun.keel vip pairandtendsend a pair's record to the holder, which checks it like any node does. Membership changes need root once auth is on, so they now run on the holder: learner promote and remove intend, and member removal insiderevoke.etcdctl instead of the JSON gateway (
keel.mesh.etcdclient). The gateway cannot carry a client's CN (etcd's docs, and tested on trixie's 3.5.16). Details:/proc/self/fd/Nviapass_fds;PATHandGOMAXPROCS(fits the controller's TasksMax);-w json, etcd's own timeouts, plus a process kill at +0.5 s;The gateway is now off (
ETCD_ENABLE_GRPC_GATEWAY=false). keel-vip.service's helper rewrites the controller's memfds when the certificate changes (renewal, reissue). Before this, a controller running past 30 days kept a stale certificate.CRL: still signed with the root key on the holder and distributed as before. Members sign nothing. The 0051 note is in handbook#56.
Migration of the live layout:
keel mesh etcd reissue(on the root holder)--dry-runstops here and prints the plan.enroll, then the root signs, thenreissue. The member drops its intermediate. etcd reads the new certificate at its next handshake, so nothing restarts. The next step waits until that member serves the new certificate and every voter is healthy (3 min max, then it stops with the reason; rerun continues).crl).auth enableas root, then reads the mesh as itself to check.Rollback:
keel mesh etcd reissue --rollbackdisables auth. Certificates stay root-issued: the intermediates are revoked, which is the point. A run that stopped part way needs no rollback: both certificate kinds work together until step 4, and a rerun finishes the job.Upgrade order: upgrade keel and keel-overlay-etcd on every node first, one node at a time (applying the new config restarts etcd once for the gateway change). keel
Breaks: keel-overlay-etcd (<< 0.4.0~)so etcdctl comes with keel. Then run reissue once.Known limits
/keel/<mesh>/etcd/, which every member may write. This is keel 0.22.0.Tests
Unit: recording etcdctl, fake etcd with auth, reissue on a cluster of the old layout including every refusal path, receiver kinds, memfd refresh. Coverage stays above the 95% gate.
etcd / trixieandvip / trixienow run on etcdctl.New
etcd-auth / trixie(tests/test_etcd_auth_netns.py): 250 ms ±25 ms with 2% loss, measured first. Three members start in the live layout while the VIP is held at A, and reissue runs. It asserts:permission deniedon put, delete, revoke and txn of the VIP keys, while B passes;It also measures etcdctl's overhead against the controller's 2 s call timeout.