DocsAdministrationCardinal 0.7.0

Configuration

The settings that exist, the three encryption keys that must differ, and how to see what is actually in effect.

llms.txt

One TOML file, passed with -config. Every setting can also come from the environment, and cardinal-server config prints what is in effect and where each value came from, including the settings left at a default nobody chose.

cardinal-server config -config /etc/cardinal/cardinal.toml

Settings that are accepted but read by nothing are always listed, however the output is filtered. That report exists because Cardinal had four of them: a setting that is parsed, validated and then ignored looks supported, so somebody tunes it and believes the tuning took effect.

The sections

Section What lives there
[server] listen, public_url, cookie_domain, trusted_proxies
[database] dsn, max_conns, conn_max_lifetime
[webauthn] rp_id, rp_display_name, origins
[sessions] idle, absolute, admin_window
[oidc] enabled, signing_key_encryption_key
[ssh] enabled, ca_encryption_key
[x509] enabled, ca_encryption_key, public_url
[posix] range_low, range_high
[mail] relay settings for enrolment and recovery links
[recovery] email_domains; single_administrator, single_admin_delay (the single-administrator mode)
[alerts] reported_host_retention: how long a host name reported by a database command is kept when its alert is not delivered. 7 days unless set, from 1 hour to 30 days

The three encryption keys

oidc.signing_key_encryption_key, ssh.ca_encryption_key and x509.ca_encryption_key protect different things, and Cardinal refuses to start if any two are equal.

They are separate because their blast radii are separate: the OIDC key forges tokens for every registered application, the SSH CA key logs into every host as anybody, and the X.509 key issues certificates for any name the fleet trusts. One leaked configuration file should not yield all three.

Each is generated with openssl rand -base64 32 and belongs wherever the rest of the deployment's secrets do. They live in configuration rather than the database on purpose: a database read must not be enough.

Two settings that are easy to get wrong

server.trusted_proxies is empty by default, which means trusting nothing. X-Forwarded-For is attacker-controlled unless a proxy overwrites it, so set this only when Cardinal is genuinely unreachable except through the proxy. If anything can connect directly (a pod network, a debug port, a misrouted service), this setting becomes the vulnerability rather than the fix. It must agree with the proxy's trustForwardHeader: false.

server.cookie_domain is what makes one sign-in cover every application behind the proxy. It scopes the proxy cookie, which only forwardAuth accepts, and nothing else: the console's session cookie is always host-only (forwardAuth says why). Leave it empty when nothing is reached through forwardAuth. Browsers discard a Domain attribute that is a public suffix, and a single-label domain like localhost is one. Cardinal ran that way for months, silently throwing the session cookie away on every response while the entire Go test suite passed, because Go's cookie jar accepts what browsers refuse. Configuration validation now rejects it at startup.

Sessions

Three clocks. idle slides forward as the session is used; absolute never extends, and is what makes "everyone re-authenticates eventually" true rather than aspirational. admin_window governs administration whatever the session's age: changing the directory needs a device-bound key used within it, fifteen minutes by default, one minute to an hour, counted from the key's last use and not extended by activity. The rule that enforces it is in the policy set and reads this value; a policy activated before 0.7.0 carries its own five minutes until the new built-in policy is activated.