Config-server
Management-node config distribution service for C0
Config-server
The config-server is the rete infrastructure service that holds the most recently-published compiled tree and serves each node its own artifact on demand. It runs on a designated management node (conventionally mgmt01) and is reached only over Florete — there is no public endpoint.
It's declared in services.yaml as two separate services — one for reads (nodes) and one for writes (operators) — so Florete's per-service RBAC can gate them cleanly:
# services.yaml — rete infrastructure
services:
config-server: # read-only: serves compiled artifacts to nodes
at: mgmt01
addr: 127.0.0.1:9000
groups: [config-read] # reserved group; read-access for `node` role
config-publisher: # write: accepts new compiled trees from retectl
at: mgmt01
addr: 127.0.0.1:9001 # same process as config-server, different port+endpoint
groups: [config-write] # reserved group; write-access for `operator` role
metrics:
at: mgmt01
addr: 127.0.0.1:9090
groups: [config-read]
# ... workload services
# groups.yaml — reserved groups
groups:
config-read:
config-write:
# roles.yaml — reserved role definitions (must be provided; validator enforces)
roles:
node: { allow: [config-read] } # auto-assigned by compiler to every node
operator: { allow: [config-write] } # manually assigned in users.yamlWhy "config-" and not "mgmt-plane-". The groups gate access to the config-server's HTTP endpoints, not to a plane. The plane (mgmt vs ctrl, when ctrl lands in C1) is an artifact-content distinction encoded in the URL path inside an already-authenticated tunnel — RBAC has no reason to project it. C1's ctrl-fetch endpoint will live in the same config-read group; ctrl-publish stays in config-write because C1's ctrl artifacts are still operator-produced. A B1+ split where the CP becomes an external writer is a B1+ design call; today there's no second writer, so a second write group would be speculative.
Reserved-name conventions:
node,operator(roles),config-read,config-write(groups), and theconfig-server/config-publisherservices are all reserved names. Validator fails if they're missing or redefined with different structure. Florete compiler never writes YAML — these live in the repo as part of the initial rete template.- Role assignment is split: the
noderole is auto-assigned by the compiler to everynode/principal (so each node's compiled artifact has the right egress row to reachconfig-server). Theoperatorrole is assigned manually inusers.yaml(fyodor: { role: operator }) — the validator rejects the rete if no user has it (otherwise nothing can publish).
Why two services for one process. The backend is a single mgmt01-local HTTP server, but Florete RBAC is per-service, so a single "config-server" service would give every node principal both read and write access. Splitting the listener into two ports — one for reads (gated by node role via config-read) and one for writes (gated by operator role via config-write) — lets Florete enforce the read/write boundary without any authZ code inside the HTTP server.
This split doesn't give per-node isolation of reads — every node principal can GET /artifact/<any-node> as long as it reaches the read-service. Tightening this (so alpha can only fetch its own directory) requires passing the verified peer SPIFFE ID to the HTTP layer, which flor deliberately does not do today — flor is a pure L4/L5 forwarder (QUIC/mTLS + TCP bytes), and reaching into HTTP to inject X-Florete-Peer-SpiffeID headers would promote it to an L7 proxy. That's a meaningful architectural shift with knock-on design questions (which component owns L7? is it flor itself, an adjacent sidecar, a side-channel lookup API?), and it needs careful design in C1+ rather than a rushed solution now. Flagged in Open Follow-ups and tied to the broader L7 awareness topic there. For pilots, the exposed ACL-matrix metadata is tolerable.
Wire protocol (C0). Both services speak plain HTTP over the Florete mTLS tunnel.
config-server: GET /artifact/<node>?version=<n>— returns the node's compiled directory (anagent.json+ the vertex configs undervertices/) packaged as a single response if the caller passed theconfig-readingress gate. (No path/identity cross-check in C0; see note above.)config-publisher: POST /tree— accepts a new compiled tree fromretectl. Only reachable by callers withoperatorrole.
The caller of GET /artifact/<node> is the node's flor-agent, acting as the node/<node> principal via the local vertex's SOCKS5 listener. C0 doesn't need anything more elaborate — no streaming, no deltas, no watch. Agents poll on their own cadence via flor agent sync.
Management-node bootstrap (chicken-and-egg). The config-server can't fetch its own artifacts from itself before it's running. Resolution: the management node is bootstrapped manually. The operator runs retectl compile, the enrollment bundle for mgmt01 carries that node's compiled agent.json and vertices/flor.json, and the operator starts flor agent run against that local material. Once it's up (with the config-server service listening on 127.0.0.1:9000 via Florete), the operator runs retectl publish and from then on mgmt01 refreshes itself via the normal flor agent sync flow. The manual step is genuinely one-shot per management node.
Availability. Config-server downtime doesn't break running traffic — nodes keep running their last installed artifact. The only thing that fails is publishing new state. HA (two management nodes with replicated state) is a post-C0 concern; pilots can tolerate short outages.