Feature Specification: Resource generateFiles
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code at 093e68b6f (core/resources/generateFiles/default.nix, core/resources/generateFiles/consumer.nix, core/resources/generateFiles/spire-login.nix, core/resources/generateFiles/test.nix, core/setups/wiring/vars-plumbing.nix, core/setups/wiring/resource-routing.nix, core/setups/hosts-common.nix, core/checks/default.nix), pull requests #1184 and #1238, and issues #527, #1205 and #1206.
User Scenarios & Testing (mandatory)
User Story 1 - Generate a secret once and read it on every node (Priority: P1)
A component needs a generated value, for example a password or a key pair.
It states an instance with its files and a script. The vars provider runs
the script once, stores the files in OpenBao KV, and renders them back on
each node that reads them. A rebuild keeps the same value.
Why this priority: most components need secrets that nobody types in. Many of them are shared between nodes.
Independent Test: nix-unit-resources-generateFiles, and the NixOS
checks contracts-generatefiles-vars and contracts-generatefiles-vars-openbao.
Acceptance Scenarios:
- Given an instance
xwith a filef, When the deployment is evaluated, Then the node wantscontracts.generateFiles.want.x, and the file bridges tocontracts.fileSecrets.want.vars.x_f. - Given a component reads the result, When it reads
contracts.fileSecrets.results.vars.x_f.path, Then it gets the path of the rendered file. - Given no node has a request, When the deployment is evaluated, Then the resource adds no agent and no want.
- Given a script, When the generator runs, Then it writes each file to
$out/<file>and reads each dependency from$in/<dependency>/.
User Story 2 - Log a node in to OpenBao with its SPIRE identity (Priority: P1)
A node must authenticate to OpenBao before it can read or write KV. The
setup adds spire-login.nix through extraModules. The login service
fetches the X.509 SVID of the node from the SPIRE agent and logs in with the
cert method. It writes the token and publishes the SVID pair at stable
paths. The openbao-agent default instance logs in by itself with cert
auto-auth, with that SVID pair and as the same role. upload-vars-openbao,
systemd-creds-openbao, the openbao-pki issuing units and the netbox,
windmill and valkey scripts use the token.
Why this priority: without a login, no secret renders on the node.
Independent Test: nix-unit-resources-generateFiles checks that the
agent uses cert auto-auth with the SVID pair, the role name and the CA,
that its vault block has no client certificate, that it starts after the
login and that the login does not restart it, that a node without the SVID
pair uses token_file, and that the agent restarts past its start limit.
integrations-spire-openbao checks that the agent renders a secret with no
token file, and that a re-login does not restart the agent.
Acceptance Scenarios:
- Given the SPIRE agent is healthy, When
vars-openbao-loginruns, Then it runsbao login -method=cert name=<loginName>and writes the token atomically to/run/vars-openbao/token. - Given the SPIRE agent stays unhealthy past
spireWaitTimeout, When the deadline comes, Then the login exits3, and systemd does not restart it. - Given a login completes, When
systemd-creds-openbaois on, Then the login reloads or restarts it. The login does not restartopenbao-agent-default. - Given a new SVID pair, When the agent logs in again at the end of its token TTL, Then it reads the new pair from disk.
- Given the login timer, When 30 minutes pass after boot or after the last run, Then the login runs again.
User Story 3 - Grow an instance without new values for the old files (Priority: P2)
An instance gets a new file in a later version of a component. An atomic
instance refuses a partial state. An instance with independent = true
generates only the missing files and keeps the others.
Why this priority: a new file must not rotate a secret that is in use.
Independent Test: contracts-generatefiles-vars-independent.
Acceptance Scenarios:
- Given an
independentinstance with one stored file, When a second file is added, Then the generator writes only the second file. - Given an atomic instance with a partial state, When the generator runs, Then it refuses to continue.
User Story 4 - Grant a node write or read access to vars it does not generate (Priority: P3)
Some applications write their own vars, or only read the vars of another
node. They state writtenInstances or readInstances. The setup grants
the matching OpenBao paths and adds no generator.
Why this priority: the grants must follow the requests, not a roster kept by hand.
Independent Test: no check covers writtenInstances and
readInstances alone.
Acceptance Scenarios:
- Given a request with
writtenInstances = [ "x" ], When the rosters are built, ThenvarsProviderRostergrants the node write onvars/secret/x/. - Given a request with
readInstances = [ "x" ], When the rosters are built, ThenvarsReaderRostergrants the node read onx.
Edge Cases
- The OpenBao address is
https://andcaCertFileis set: the agent getsca_cert. Otherwise it gets none. clientCertFileandclientKeyFileare not set, as in a test with a static token: the agent usestoken_fileauto-auth withtokenFile.- The SVID pair is not published yet when the agent starts: the login fails and the agent tries again with a
max_backoffof5s. The agent does not exit. - The SVID of a login expires before the token: the token renewals fail with a backoff, and near the end of the token TTL the agent logs in again with the pair on disk.
- A node with no request still needs the agent, for example for the OIDC client secret. The setup adds
consumerModule, which has the plumbing and no request. - The resource imports
consumer.nixby path, so two imports of it merge into one. Each import also setsservices.vars-openbao.{address,tokenFile,caCertFile}. All nodes of a deployment use the same values, so the definitions agree. - The generator runs with
PATHset toruntimeInputsonly. A script that uses a program outsideruntimeInputsfails at run time with exit127.
Requirements (mandatory)
Functional Requirements
- FR-001: The request MUST accept
writtenInstances,readInstancesandinstances. - FR-002: An instance MUST accept
files,script,runtimeInputs,dependenciesandindependent(defaultfalse). - FR-003: A file MUST accept
secret(defaulttrue),owner(defaultroot),group(defaultroot) andmode(default0400). - FR-004: The policy MUST accept
routing,address(defaulthttp://127.0.0.1:8200),tokenFile(default/run/keys/vars-openbao-token),caCertFile(default empty) andextraModules, and MUST expose the read-onlyconsumerModule. - FR-005:
processMUST merge theinstancesof all apps by instance name. - FR-006:
applyMUST emit nothing for an empty set of instances. - FR-007: The consumer module MUST default
generateFilesto thevarsprovider,varsBackendtoopenbaoandenvSecretstoopenbao-agent, all withmkDefault. - FR-008: When
clientCertFileandclientKeyFileare set, the openbao-agentdefaultinstance MUST usecertauto-auth with that pair,nameset tologinName,reload = trueand theca_certof thevaultblock. Otherwise it MUST usetoken_fileauto-auth withtokenFile. Both MUST use amax_backoffof5s. Thevaultblock MUST NOT name a client certificate. The agent MUST start afternetwork-online.target. - FR-009:
vars-openbao-loginMUST restart on failure every 10 seconds with no start limit, except after exit3. - FR-010:
openbao-agent-defaultMUST restart every 10 seconds with no start limit, because a missingvault.ca_certstops it. - FR-011:
upload-vars-openbaoandsystemd-creds-openbaoMUST start aftervars-openbao-login. - FR-012: The login MUST read the SVID from the socket
/run/spire/agent/public/api.sock.
Key Entities
- Instance: a named set of files with one generator script. It maps to the KV paths
vars/secret/<instance>/<file>andvars/public/<instance>/<file>. varsprovider: the provider innix/contractsthat runs generators and bridges files tofileSecrets.- Consumer module:
consumer.nix, the OpenBao agent and thesystemd-creds-openbaoservice of a node. - SPIRE login:
spire-login.nix. On a deployed node it publishes the SVID pair that the agent logs in with, and writes the token for the units that call OpenBao directly.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001:
nix-unit-resources-generateFilespasses. - SC-002:
contracts-generatefiles-vars,contracts-generatefiles-vars-independentandcontracts-generatefiles-vars-openbaopass. - SC-003:
integrations-apex-key-mintpasses: a generator runs under exactly itsruntimeInputs.
Assumptions
- The setup sets
addressto the mTLS listener of OpenBao on port8200, not to nginx, because nginx removes the client certificate. - The setup sets
loginNametocertAuthLabelof the node andsvidNameto the node name. The roster rules are in setups and wiring. postgresql,redisandgaragerender their credentials through the openbao-agentdefaultinstance of this resource.