Skip to content

The configuration file

A node runs from one file, and the file configures that process: where it listens, which raft cluster it belongs to, which identity providers it trusts, where its admin door is and what its admin policy says. What the deployment is, the providers, credentials, budgets, profiles, access rules and guardrails, is not in the file of a clustered node. It lives in the cluster, written through the admin API, and every node serves what the cluster stores.

The file holds every key that names a fact about one machine. A listener address, a data directory, a certificate path, a socket. It holds the trusted issuers and the admin policy, which decide who may change configuration and so cannot be configuration themselves. The policy is written inline under admin.policy or in a file named by admin.policy_file, whichever place it is reviewed in.

A deployment key written at the top level is refused at load, with the key named. A deployment key on a clustered node is refused too: the cluster is where that node’s deployment lives, and a document in the file would be a second statement of what the deployment is.

The deployment document holds every key the cluster stores. A path never appears in it. A credential is a reference, ${secret:NAME}, that each node resolves for itself, and a model is a content-addressed manifest each node materializes. The document has to make sense on a node that did not write it, and a path does not.

The cluster stores it as named sources, one per writer: an operator’s, a Terraform provider’s, the console’s. They compose disjoint or are refused, and the API refuses a write to a source whose last writer is somebody else’s tool. See Manage the configuration through the API.

A cluster starts with nothing stored. Its data plane answers 503, with Retry-After, until a source is written, because an empty deployment would answer every request with a refusal that reads as policy. GET /admin/v1/config lists the sources the served configuration was composed from, and an empty list is that state.

The two have two lifecycles. The file is read at start, by this process, and it is never stored. Listeners, the cluster block, tls, the issuers and the admin block are what the process started with, and an edit to them takes effect on restart, all of them together. A reload that swapped the issuers and not the policy that names their roles would leave the two planes trusting different signers, which is why there is no reload: an edit to the file is a restart.

The deployment changes hands without a restart, through a source write, and lands on every node at once. A file on each host used to seed the cluster and apply itself over the stored document when it changed, and every rule that arbitrated between the two, the seed on first boot, the memory of which documents the cluster had held, the report of where the file stood, the one source the API could not write, was the cost of having two places that could say what the deployment is.

An inspector beside somebody else’s gateway has no cluster and so no store, and nowhere to read a policy from but its file. That node keeps deployment: the document inline under the key, or the path of a file holding it, relative to the file. Its deployment is restart-owned like the rest of its file, and it is the one shape where the file says what the deployment is.

The deployment document is agreed by every node and built by each. A reference in it is resolved on the node that serves it: an issuer under auth.issuers is bound to the file’s issuers by name, ${secret:NAME} is read from that node’s file or environment, and a model artifact is fetched into that node’s cache. A write through the API is validated on the node that took it, against its own file. A node whose file lacks the name fails its rebuild, logs it, and keeps serving the previous configuration.

So the order of a change that adds a reference is the file first, on every host, then the document that names it. The node that fell behind says so: GET /admin/v1/config on it carries rebuild_error and behind, and kirto_gate_config_rebuild_failed is 1 on that node alone.

The file has a JSON Schema, generated from the configuration structs and committed as api/node.schema.json. The deployment document’s own schema, api/config.schema.json, is the same walk from the other root, and the node file schema carries it as the shape of an inline deployment. Point an editor at it with a modeline on the first line:

# yaml-language-server: $schema=./node.schema.json
listen: ":8484"

The schema is shape only. It says which keys exist and what each takes, and that unknown keys are refused, which is what the strict parse says. It cannot say what the build refuses, a provider naming a credential nothing defines, because that depends on the other sources and the running deployment. The admin policy is a string to it. What a policy may say is a Cedar schema of its own, admin.cedarschema, generated from the gateway’s operations and enforced at start.

The Helm chart renders one file per pod into a ConfigMap. config is the node file, minus the keys the chart writes itself, and admin.policy goes under its key. deployment is rendered under a key of its own, deployment.yaml, which no pod reads: it is the document the release applies through the admin API once the cluster is up. An edit to config rolls the pods; an edit to deployment is a source write.

On a fleet of machines the file differs per host in its cluster block. The deployment is written after the cluster is up, with kirto-gate config apply, and a config-management run that edits it is a source write rather than a restart. See Run the front door on VMs.

A tool that needs only the deployment document takes it directly. A guardrails evaluation and a rule suite read one, and kirto-gate check takes the node file and, for a clustered node, the document to check as that node’s under -deployment.