Configuration
The settings that exist, the three encryption keys that must differ, and how to see what is actually in effect.
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.