Skip to content

Security configuration

Paths in the tables are relative to [security], [security.integrity], [mfa] or [ca_rotation]. Keep keys and credentials in protected files. Check parsing and cross-field rules with gdsgate doctor --config <PATH> --json. See also 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.
[security]
profile = "production"
Profile At start-up
unset No checks, no warnings. gdsgate doctor reports that no profile is set.
dev No checks. One line says the deployment is a development one.
pilot Refuses the faults in the table below.
production Refuses the faults below and the additional production checks.

Every check per profile, with the key or flag that satisfies it: Profile matrix.

pilot and production refuse these faults; the error lists all faults found:

Role Refused
Authority store_url does not name a PostgreSQL store. [authority].enroll_cert_file names no certificate for the registration listener.
Proxy No [proxy].public_cert_file pair and no enrolled identity, so the client-facing listener would run without TLS. The connector-facing listeners would run without mutual TLS. A configured [enroll].endpoint is not https://.
Connector The node has no enrolled identity. A configured [enroll].endpoint is not https://.
Any role, started as gdsgate all Authority, Proxy and Connector would run in one process with the certificate authority in memory.

With [audit].export_path unset, both profiles warn and start.

production also refuses:

Role Refused
Authority [oidc].issuer or [oidc].client_id is unset (Authority would accept tokens from the built-in development issuer).
Authority, Proxy with the clientless browser entry [oidc].allow_insecure_http = true (allowed only outside production, for a lab or dev provider).
Authority The two [security.integrity] keyrings are not both set, or are the same file.
Authority A keyring, or a [audit].*_key_path file that exists on disk, is readable by anyone other than the service. See [security.integrity].

After opening the store, production Authority checks the store's rows before serving:

Check Result
A key fingerprint held by some Authority replicas and not others, with rows already sealed under it refused
The same, with no rows sealed under it (a rolling keyring rotation in progress) warning
The newest sealed row of a table names a key that neither keyring holds and that was not declared lost with keys accept-loss refused; usually the wrong keyring file, or a database restored without its keyring

See Administration → gdsgate authority keys status and keys accept-loss.

A lab or dev HTTP OIDC issuer requires an explicit [oidc].allow_insecure_http = true on Authority for a lab or dev provider. Verified bootstrap metadata carries this choice to new clients and enrolled nodes. New clients also receive the public audience, accepted signing algorithms, display-name claims and OIDC timeouts. Enrolled Proxies receive the same policy and Authority's minimum security profile; a stronger local profile is retained. An HTTP issuer without this declaration is refused. This setting does not change TLS verification for cluster entry or node registration; production restrictions still apply.

[security.integrity]

Used by: Authority only.

The two keyrings that seal the cluster's rows. Each field accepts a secret reference, including the ${kms.<name>+...} wrapper. A bare value is a file path.

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/authority/control.keys"
audit_keyring   = "/var/lib/gdsgate/authority/audit.keys"

Use two different files. The same file for both, directly or through another path, is refused under production; pilot warns.

Both fields are optional. Unset, Authority uses the five [audit].*_key_path files (see Operations → [audit]).

Who may read a keyring or a seal key. Only the service. The keyring reader checks this whenever it opens a keyring, under every profile. production also checks both keyrings and every existing [audit].*_key_path file before serving.

Platform Accepted Refused
Linux Owner root or the service account; mode 0640 with group gdsgate. The shipped units run with DynamicUser=yes and SupplementaryGroups=gdsgate; gdsgate setup leaves /etc/gdsgate/*.keys as 0640 with group gdsgate. Any other owner; readable by others (for example 0644); a group the service is not in.
Windows Access entries only for the file owner, SYSTEM, and NT SERVICE\gdsgate-authority, -proxy or -connector. gdsgate service install writes these entries onto the configuration's files. Any other access entry; a file with no access list.

gdsgate authority keys legacy migrate imports the keys of the five [audit].*_key_path files into the two keyrings as verify-only records with their existing fingerprints. Existing rows keep verifying. New rows are sealed with the five key files until a keyring has an active key, so keep the five parameters in the configuration. With a keyring set, gdsgate doctor lists the files not yet imported. See Administration → gdsgate authority keys legacy migrate.

[mfa]

Used by: Authority.

Gateway step-up MFA factors. TOTP (gdsgate mfa totp enroll / verify) needs no configuration. WebAuthn (passkeys) needs [mfa.webauthn]; without it WebAuthn is off and webauthn_satisfied in Cedar is always false.

Key Type Default Purpose
webauthn.rp_id string unset WebAuthn relying-party id: the domain a passkey is bound to. Each origin's host must be rp_id or its subdomain.
webauthn.origins list [] Browser origins (scheme://host[:port]) for the ceremony. Must be https://, except with allow_localhost.
webauthn.allow_localhost bool false Development only. Allow http://localhost origins. Passkeys are then bound to the workstation.
webauthn.allow_any_port bool false Development only. Accept any port on the origin host; the loopback ceremony uses a new port each run. Requires allow_localhost.
webauthn.rp_origin string unset Legacy single-origin alias, added to origins.

In production the ceremony runs on the gateway's browser origin ([proxy].public_origin/web_addr):

[mfa.webauthn]
rp_id = "gate.example.com"
origins = ["https://gate.example.com"]
Users register a passkey with gdsgate mfa passkeys register and step up with gdsgate mfa grants create <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>), and the passkey is bound to localhost. This is refused at start-up unless enabled:

[mfa.webauthn]
rp_id = "localhost"
origins = ["http://localhost"]
allow_localhost = true   # dev only: binds passkeys to the workstation
allow_any_port = true    # dev only: the loopback port varies per run

Migrating off localhost

A passkey registered for localhost does not work on a gateway-domain origin. To move to production: set [proxy].public_origin and web_addr, remove allow_localhost and allow_any_port, and have each user run gdsgate mfa passkeys register again. Old localhost passkeys stop working; nothing needs to be revoked.

[ca_rotation]

Used by: Authority.

Scheduled rotation of the User SSH CA; off by default. Manual rotations of the Onward SSH, HostTls, SPIFFE and JWT-signing CAs use the same timing settings. The manual commands gdsgate authority ca rotate user-ssh, rotate-onward-ca, rotate-host-tls-ca, rotate-spiffe-ca and rotate-jwt-ca work regardless of enabled. Only the User SSH CA rotates on a schedule.

The end of each wait is stored with the rotation phase. A restarted Authority or a new HA leader takes the next step at that stored time or later, whatever its own propagation_secs and retire_secs are; see HA.

For HostTls, set propagation_secs to cover a full mTLS renewal cycle. For JWT-signing, cover how long web lane applications cache the 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 it at or above the issued-certificate TTL.
check_secs u64 3600 How often the controller checks whether a rotation is due.
[ca_rotation]
enabled          = true
interval_secs    = 2592000   # 30 days
proactive_secs   = 604800    # 7 days
propagation_secs = 300
retire_secs      = 3600
check_secs       = 3600