Security Guide
This guide summarizes security-related settings and best practices when deploying Nexus GSLB.
Principles
- Secure by default: HTTPS health checks verify TLS by default; GitOps requires signed commits when enabled.
- Least privilege: limit credentials and subject permissions to the minimum required.
- Defense in depth: mTLS for NATS, signed GitOps config, and restricted runtime privileges.
- HTTP/HTTPS health checks
- Default behavior: when
health.http.tls: true, TLS certificates are verified. - Only set
health.http.insecureSkipVerify: truefor lab/testing or when using self-signed certs and you fully understand the risk. - Prefer using proper certificates (internal CA or public CA) and correct
http.hostto match the cert.
Example (secure):
health:
type: http
port: 443
checkinterval: 10s
timeout: 2s
http:
path: "/healthz"
expectedstatus: 200
tls: true
insecureSkipVerify: false
- GitOps signing and access
gitops.requireSignature: trueenforces signature verification on the latest commit checked out.gitops.allowedSignersrestricts accepted signatures to a set of GPG key fingerprints.- Deploy key: provide a read-only SSH key at
gitops.auth.sshKeyPath; permissions should be0600and owned by the service user. - Trust model: ensure the signer keys are trusted on hosts or rely on
allowedSignersallowlist.
- NATS and JetStream
- Use TLS with server authentication at minimum; prefer mTLS (client cert auth) or NATS accounts with JWT.
- Restrict subjects and KV permissions to the cluster namespace:
gslb.<cluster>.*. - Rotate client certificates and credentials regularly. Plan for CA rotation.
- Monitor
gslbd_state_nats_connectedand set alerts on disconnect.
- Runtime privileges
- Binding to high ports (≥1024) avoids root. Default DNS port is 5353; if you must use port 53, either:
- run as root (not preferred), or
- grant
cap_net_bind_serviceto the binary:setcap 'cap_net_bind_service=+ep' /usr/local/bin/gslbdand run as non-root.
- Limit file system permissions for
/etc/gslband any private keys.
- Secrets and licensing
- Provide license secrets via environment variables when possible:
GSLB_LICENSE_SECRETGSLB_LICENSE_KEY
- Use environment management in systemd (
Environment=) or container secrets.
- Supply chain and binaries
- Build from source in a controlled CI/CD or use signed release artifacts (future work).
- Pin container base images and scan them for vulnerabilities.
- Logging and PII
- Logs contain operational info (e.g., commit SHAs, signer fingerprints) but not sensitive payloads. Avoid logging credentials.
- Authentication and user accounts
- Set
GSLB_API_KEY(32-byte hex,openssl rand -hex 32) in the systemd EnvironmentFile before exposing the API. Without it, the daemon runs in dev mode — all requests are treated as system admin. - Set
GSLB_SECRET_KEY(also 32-byte hex) before enrolling any TOTP devices. The daemon will refuse to start if TOTP users exist and the key is absent. - Store both values in
/etc/gslb/envwithchmod 600, owned by the service user. Reference it from the systemd unit withEnvironmentFile=/etc/gslb/env. - Create named user accounts (
tenant_admin,operator,viewer) instead of sharing the system API key. The system key is a break-glass credential, not a day-to-day login. - Treat
GSLB_API_KEYas a root password: rotate it if it is ever exposed, and restrict who knows it. - Keep
bcryptCostat 12 or higher in production.
See Authentication & User Management for full setup instructions.
Checklist
- DNSSEC Pleiades signs DNS responses online using ECDSA P-256 (algorithm 13). KSK signs the DNSKEY RRset; ZSK signs all other RRsets.
Key generation:
# KSK
openssl ecparam -name prime256v1 -genkey -noout | \
openssl pkcs8 -topk8 -nocrypt -out /etc/gslb/ksk.pem
# ZSK
openssl ecparam -name prime256v1 -genkey -noout | \
openssl pkcs8 -topk8 -nocrypt -out /etc/gslb/zsk.pem
chmod 640 /etc/gslb/ksk.pem /etc/gslb/zsk.pem
chown root:gslbd /etc/gslb/ksk.pem /etc/gslb/zsk.pemConfiguration:
dnssec:
enabled: true
zone: "example.com."
ksk:
pemFile: "/etc/gslb/ksk.pem"
expiryDate: "2027-01-01"
zsk:
pemFile: "/etc/gslb/zsk.pem"
expiryDate: "2026-07-01"
signatureValidityDays: 7Alternatively load keys from environment variables:
dnssec:
enabled: true
zone: "example.com."
ksk:
envVar: "GSLB_KSK_PEM"
zsk:
envVar: "GSLB_ZSK_PEM"Key rotation:
- Generate new ZSK; update config to point at new file.
- Restart
gslbd— new signatures use new ZSK immediately. - For KSK rotation, generate new KSK, export new DS record, submit to registrar, wait for TTL expiry, then restart.
- Monitor
gslbd_dnssec_key_days_remainingand alert before expiry.
DS record export:
gslbctl dnssec ds --zone example.com. --ksk-file /etc/gslb/ksk.pem- Health-check safety (script and webhook checks) Health checks can execute code or make outbound requests from the node, so both kinds are hardened by default.
Script checks (
type: script) run arbitrary commands as thegslbduser on every node and are disabled by default. Enable them only if you trust every operator who can create a health check:health: allowScriptChecks: true # off by defaultEven when enabled, a script check may only be created via the API by a system administrator — never a tenant/operator key. Prefer a
webhookcheck to run custom logic off-node.Webhook checks (
type: webhook) makegslbdrequest a URL you supply. To prevent Server-Side Request Forgery (SSRF) they may target public addresses only by default — loopback, link-local (including the cloud metadata IP169.254.169.254), private/ULA, and CGNAT ranges are blocked. This is enforced both when the check is created and at request time on the resolved address (defeating DNS rebinding; redirects are not followed). To allow a specific internal host, list its CIDR/IP:health: webhookAllowedHosts: - "10.20.0.0/24" # internal monitoring subnet - "192.0.2.50" # a single hostPublic destinations remain allowed; entries override the deny list for those ranges only. See Health Checks for details.
- API behind a reverse proxy If
gslbd's API sits behind a reverse proxy (Caddy, nginx, a load balancer), setapi.trustedProxiesto the proxy's CIDR(s). The real client IP is then read fromX-Forwarded-For(rightmost entry that is not itself a trusted proxy) for requests originating from those sources; otherwise the connection address is used andX-Forwarded-Foris ignored (so it cannot be spoofed by direct clients).
api:
trustedProxies:
- "10.0.0.0/8"This is required for correct RUM client attribution and per-client rate limiting behind a proxy — without it every visitor collapses into the proxy's single IP. Leave it empty when the API is exposed directly.
- WebSocket authentication The live-event WebSocket endpoint (
/api/v1/ws) is gated by a short-lived JWT signed withGSLB_WS_SECRET, falling back toGSLB_API_KEY. When neither is set (dev mode) the daemon generates a random per-process secret and logs a warning — it never signs tokens with a shipped constant. SettingGSLB_API_KEYin production (section 0) covers this automatically.
NSEC3: denial-of-existence records are generated on-the-fly per RFC 5155 §7.1. No static NSEC3 chain is maintained.