Security configuration¶
The flat configuration hub remains the compatibility route. This page is the canonical source-checked field reference for its family, including nested tables and list rows.
Paths in the tables are relative to [security], [security.integrity],
[mfa] or [ca_rotation]. Each row gives the serde type and source default or
unset meaning; keys and credentials belong in protected files. Check parsing
and security cross-field rules with gdsgate doctor --config <PATH> --json,
then follow the CA rotation guide.
[security]¶
Used by: every role.
| Key | Type | Default | Purpose |
|---|---|---|---|
profile |
string (optional) | unset | The deployment profile this cluster is held to at start-up: dev, pilot, or production. |
Leaving profile unset changes nothing a running cluster already did: no new
refusal, no new warning, no banner. A cluster deployed before this key existed
keeps starting exactly as it did. Unset is not read as dev;
gdsgate doctor reports the omission
instead.
dev refuses nothing. A start prints one line naming the deployment a
development one, once.
pilot and production both refuse the same faults, and a refusal names
every fault present at once rather than stopping at the first one found:
| Role | Refused |
|---|---|
| Authority | store_url does not name a PostgreSQL store. [authority].enroll_cert_file names no certificate for the registration listener. |
| Proxy | The client-facing listener would run in the clear: no [proxy].public_cert_file pair, and no enrolled identity to present one from. The connector-facing listeners would run without mutual TLS. A configured [enroll].endpoint is not https://. |
| Connector | The node holds no enrolled identity. A configured [enroll].endpoint is not https://. |
Any role, started as gdsgate all |
Authority, the Proxy, and a connector run in one process off a certificate authority held in that process's memory. |
[audit].export_path unset warns and starts anyway under both profiles: the
audit chain in the database stays sealed and verified regardless, and what is
missing is the copy a collector would carry away.
production refuses more than pilot does. Authority is refused unless
[oidc].issuer and [oidc].client_id are both set: it is the one role that
verifies a person's identity token, and with either half missing it verifies
tokens against the built-in development issuer, whose secret every copy of
gdsgate carries. No other role is asked for a provider. The Proxy's browser
entry runs the sign-in itself, then hands the token it obtained to Authority to
verify. [oidc].allow_insecure_http set to true is refused wherever the
running role reads it: Authority validating identity tokens against a
configured provider, or the Proxy serving the clientless browser entry.
Authority is refused unless the two [security.integrity] keyrings are both
named, kept apart, and owner-only, and unless every one of the five
[audit].*_key_path files that still names a file on disk is owner-only too; see
[security.integrity].
Once Authority has opened its store, production checks two more things
against the store's own rows before it serves anything. Every Authority
replica that is still confirming has to hold the same keys of each keyring: a
fingerprint some replicas hold and others do not is refused when a row is
already sealed under it, and left as a warning when no row is, which is what a
keyring partway through a rolling
rotation looks like. And the newest
sealed row of every table has to name a key one of the two keyrings here holds,
or a key whose loss was declared with keys accept-loss; a fingerprint neither
answers for is most often the wrong keyring file, or a restore that took the
database and left the ring behind. See
Administration → gdsgate authority keys status
and
keys accept-loss.
[security.integrity]¶
Used by: Authority only.
The two keyrings a cluster's sealed rows are put under. Each field accepts the
secret-reference notation, including the
${kms.<name>+...} wrapper over any of its sources;
a bare value is a file.
| Key | Type | Default | Purpose |
|---|---|---|---|
control_keyring |
string (optional) | unset | Keyring approvals, revocations, delegations, and inventory receipts are sealed under. |
audit_keyring |
string (optional) | unset | Keyring the audit chain's anchors are sealed under. |
[security.integrity]
control_keyring = "/var/lib/gdsgate/control.keys"
audit_keyring = "/var/lib/gdsgate/audit.keys"
The two are kept apart because they are kept for different lengths of time:
the audit ring seals a journal that outlives the decisions it records and may
be handed to a retention system or a customer, while the control ring seals
what the access path itself reads. One keyring for both means one leak forges
the decisions and the record of them together, so naming the same file for
both, directly or through a second path to it, is refused under production.
pilot accepts a shared keyring and warns instead: naming a second one is left
for the operator to arrange before the cluster is one people depend on.
Neither field is required to start a cluster. Unset, Authority goes on reading
the five [audit].*_key_path parameters it always read (see
Operations → [audit]). production requires both
named, kept apart, and owner-only; pilot and dev do not check them.
gdsgate authority keys migrate-legacy brings the keys those five parameters
name into the two keyrings above, as records that verify existing rows and
seal nothing new. Each key keeps the fingerprint its own module already
computed, so every row it sealed keeps verifying without being re-sealed; new
rows keep going under the five key files until a keyring holds an active key
of its own, so the five parameters stay in the configuration and keep being
read. Once a keyring is named, gdsgate doctor reports which of the five
files have not yet been brought in. See
Administration → gdsgate authority keys migrate-legacy.
[mfa]¶
Used by: Authority.
Gateway step-up MFA factors. The TOTP factor (gdsgate mfa enroll / verify)
needs no configuration. The WebAuthn (passkey) factor needs a relying party,
configured under [mfa.webauthn]; without it the WebAuthn factor is off and the
webauthn_satisfied Cedar channel stays false.
| Key | Type | Default | Purpose |
|---|---|---|---|
webauthn.rp_id |
string | unset | The WebAuthn relying-party id, the domain a passkey is bound to. Each origin's host must be rp_id or a subdomain of it. |
webauthn.origins |
list | [] |
The browser origins (scheme://host[:port]) the ceremony may run against. Every origin must be https:// unless it is a localhost dev opt-in (below). |
webauthn.allow_localhost |
bool | false |
Dev only. Permit http://localhost origins. Binds passkeys to the workstation rather than a gateway host. |
webauthn.allow_any_port |
bool | false |
Dev only. Accept any port on the origin host, since the loopback ceremony picks a fresh port per run. Requires allow_localhost. |
webauthn.rp_origin |
string | unset | Legacy single-origin alias, folded into origins. |
In production the ceremony runs on the gateway's own browser origin (set
[proxy].public_origin/web_addr), so a passkey is scoped to the
gateway domain:
gdsgate mfa register-passkey and step up with
gdsgate step-up <resource>; both open the gateway's confirmation page. See
Policy → Two MFA channels.
Development: localhost loopback
Without a gateway web origin the CLI runs the ceremony on a loopback page
(http://localhost:<port>), which binds the passkey to localhost rather
than one gateway host. That is weaker, so it is rejected at startup unless
opted into:
Migrating off localhost
A passkey registered for localhost cannot be used against a gateway-domain
origin, because the relying-party id differs. To move a deployment to
production: set [proxy].public_origin and web_addr, drop
allow_localhost and allow_any_port, and have each user re-run
gdsgate mfa register-passkey against the gateway origin. The old localhost
passkeys stop working, and there is nothing to revoke.
[ca_rotation]¶
Used by: Authority.
Scheduled rotation of the User SSH CA. The same controller settings are consulted
for manual Onward SSH, HostTls, SPIFFE, and JWT-signing rotations. Off by
default; the five manual CA commands are gdsgate authority rotate-ca,
rotate-onward-ca, rotate-host-tls-ca, rotate-spiffe-ca, and
rotate-jwt-ca, and work regardless of enabled. Only the User SSH CA
auto-rotates on a schedule. The other four are manual-only: Onward SSH and
HostTls because operators time the propagation window
themselves (for HostTls, ensure it covers a full mTLS-renew cycle so
every active node refreshes its trust bundle before the signer swap);
JWT-signing because what has to catch up inside propagation_secs is not this
cluster's own nodes but however long the applications behind the web lane hold
onto the published key set, see
Telling the application who came.
| Key | Type | Default | Purpose |
|---|---|---|---|
enabled |
bool | false |
Master switch for the scheduled rotation. |
interval_secs |
u64 | 2592000 (30 days) |
Cadence. 0 disables the schedule. |
proactive_secs |
u64 | 604800 (7 days) |
Also rotate when the active generation has less than this much validity left. 0 disables the safety net. |
propagation_secs |
u64 | 300 (5 min) |
After publishing the candidate, wait this long for verifiers (Proxy, connectors) to refresh their trust bundle before promoting it to the signer. |
retire_secs |
u64 | 3600 (1 h) |
After promotion, wait this long before retiring the old CA. Set this at or above the issued-certificate TTL so existing certificates expire naturally before their signer is removed. |
check_secs |
u64 | 3600 |
How often the controller wakes up to check whether a rotation is due. |