Security
VeltrixDB provides encryption at rest, TLS for both client and inter-node traffic, role-based access control, admin API access control, and an append-only audit log. This page covers how to enable each control and what it does and does not guarantee.
Encryption at rest
VeltrixDB encrypts stored values with AES-256-GCM. Encryption is off by default and is enabled per-node with a server flag:
veltrixdb -addr :9000 -data-dirs /mnt/nvme0,/mnt/nvme1 \
-encrypt-at-rest
The encryption key is not passed on the command line. It is read from the VELTRIXDB_ENCRYPTION_KEY environment variable at startup:
export VELTRIXDB_ENCRYPTION_KEY=$(openssl rand 32 | base64)
veltrixdb -encrypt-at-rest -data-dirs /mnt/nvme0,/mnt/nvme1
veltrix migrate). See the Disaster Recovery runbook for the exact sequence.
On Kubernetes, supply the key through a Secret rather than a plain environment value baked into a manifest:
kubectl create secret generic veltrixdb-enc \
--from-literal=key=BASE64_32B
If the encryption key is missing at startup, nodes fail to open the VLog with an error resembling encryption: no key in VELTRIXDB_ENCRYPTION_KEY. For HSM/KMS-backed key sourcing, mount the secret through a CSI driver (for example the AWS Secrets Manager CSI driver) so the raw key material is never stored in cluster state at rest.
TLS for client connections
The TCP data server can require TLS for client connections using a server certificate and key:
veltrixdb -addr :9000 \
-tls-cert /etc/veltrixdb/tls/server.crt \
-tls-key /etc/veltrixdb/tls/server.key
VeltrixDB negotiates TLS 1.3 and falls back to a restricted TLS 1.2 cipher suite list (TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) — older ciphers are rejected. Without -tls-cert/-tls-key, the listener serves plaintext.
Inter-node TLS and mutual TLS
Client-facing TLS and inter-node (Raft / replication) TLS are configured independently, because they usually terminate at different network boundaries. Use the -cluster-tls-* flags to secure Raft, replication, and gossip traffic between nodes:
veltrixdb -mode raft -node-id n1 -addr 127.0.0.1:9000 \
-peers n1@127.0.0.1:9000,n2@127.0.0.1:9100,n3@127.0.0.1:9200 \
-cluster-tls-cert /etc/veltrixdb/tls/cluster.crt \
-cluster-tls-key /etc/veltrixdb/tls/cluster.key \
-cluster-tls-ca /etc/veltrixdb/tls/cluster-ca.crt \
-cluster-mtls
Setting -cluster-mtls switches the inter-node listener into mutual-TLS mode: every peer connection must present a certificate signed by -cluster-tls-ca, and connections without a valid client certificate are rejected outright. Without -cluster-mtls, -cluster-tls-ca still enables server-side TLS, but peer identity is not verified.
| Flag | Applies to | Effect |
|---|---|---|
-tls-cert / -tls-key | Client-facing TCP data port | Server-side TLS for application connections |
-cluster-tls-cert / -cluster-tls-key / -cluster-tls-ca | Raft / replication / gossip listeners | TLS between nodes; CA enables client-cert verification |
-cluster-mtls | Raft / replication / gossip listeners | Requires and verifies peer certificates (mutual TLS) |
Role-based access control
Authentication and authorization are driven by a JSON config file passed via -auth-config. Each entry maps a username to a password hash and a role:
{
"users": [
{ "username": "alice", "password_hash": "pbkdf2$sha256$210000$<salt-b64>$<dk-b64>", "role": "admin" }
]
}
Hashes are generated with veltrix-admin hash-password — see Password hashing below for the hash format and the legacy-hash migration path.
Three built-in roles are supported:
| Role | Permissions |
|---|---|
admin | read + write + delete + admin (full access) |
readwrite | read + write + delete |
readonly | read only (GET, PING, INFO) |
Clients authenticate over the text protocol with:
AUTH alice secretpassword
OK
The binary protocol carries the same operation as command 0x09 with a length-prefixed username and password. Password hashes are compared with a constant-time comparison, and lookups against unknown usernames run a dummy constant-time comparison as well, to reduce user-enumeration via timing. When -auth-config is not supplied, authentication is disabled and every connection is granted full permissions — do not expose an unauthenticated data port to an untrusted network.
Password hashing
Password hashes in the auth config use PBKDF2-HMAC-SHA256 with 210,000 iterations, a random per-user 16-byte salt, and a 32-byte derived key. The stored value is self-describing:
pbkdf2$sha256$<iterations>$<salt-base64>$<derived-key-base64>
Because the iteration count and salt are embedded in each hash, entries generated at different work factors verify correctly side by side. Generate a hash — together with a ready-to-paste auth config snippet — using the admin CLI:
veltrix-admin hash-password --user alice --password secret
Legacy hashes still work. The previous scheme — a bare 64-character hex digest of SHA-256(password + username) — is still verified for backward compatibility, so existing config files keep working. However, the server logs a deprecation warning for every legacy entry when the auth config is loaded. Re-hash those users with veltrix-admin hash-password and update the config file. Both schemes are compared in constant time.
veltrix-admin hash-password accepts a --legacy-sha256 flag that emits the old SHA-256(password + username) hash instead of PBKDF2. Use it only when a config must be consumed by a pre-upgrade server; new deployments should always use the default PBKDF2 output.
Admin API access control
As of v1.1.0 the /admin/* endpoints on the admin/metrics HTTP port (-metrics-addr, default :2112) are guarded. With no token configured, they accept loopback connections only — a request from any non-loopback address receives 403. To allow remote admin access, start the server with a bearer token:
veltrixdb -admin-token "$(openssl rand -hex 32)"
# or via the environment:
export VELTRIX_ADMIN_TOKEN=...
Once a token is set, every /admin/* request — including from loopback — must present it in either header, or it receives 401:
curl -H "Authorization: Bearer $VELTRIX_ADMIN_TOKEN" http://localhost:2112/admin/stats
curl -H "X-Admin-Token: $VELTRIX_ADMIN_TOKEN" http://localhost:2112/admin/stats
Token comparison is constant-time. /metrics, /healthz, and /readyz are deliberately not guarded, so Prometheus scrapes and Kubernetes probes work with no configuration. The repl-ship tool authenticates against a guarded source with --src-token (or VELTRIX_ADMIN_TOKEN) — see Replication & CDC.
-metrics-addr behind a network policy / firewall as before — the bearer token protects the admin surface, but the port also serves unauthenticated metrics and probe endpoints.
Audit log
VeltrixDB can record every authenticated mutating operation to an append-only, newline-delimited JSON (JSONL) file:
veltrixdb -audit-log /var/log/veltrixdb/audit.jsonl
Each line is a single JSON object:
{"ts":"2026-05-10T11:35:55Z","op":"PUT","actor":"user-bob","key":"orders/4521","ns":"default","status":"ok"}
Fields: ts (RFC 3339 nanosecond UTC), op (PUT, DEL, CAS, INCR, …), actor (authenticated username, empty when auth is disabled), key, ns (namespace, when distinct), status, and err when the status is an error.
Audit writes are asynchronous: records are enqueued to a buffered channel and a background goroutine appends them to disk with periodic fsync. The audit path is intentionally never allowed to block the data plane — if the channel fills up, records are dropped and the drop count is tracked internally rather than backpressuring writes.
Compliance posture
VeltrixDB ships a control mapping document (docs/SOC2_CONTROLS.md) that maps its built-in capabilities — encryption at rest, RBAC, mTLS, the audit log, Prometheus-based monitoring, and backup/restore — to SOC 2 Trust Service Criteria (CC6.x, CC7.x, CC8.1, A1.x, C1.x, PI1.x).
The control-mapping document also lists gaps to disclose to an auditor rather than paper over: the audit log is not hash-chained, at-rest encryption applies to whole values (no per-field/column-level encryption), the master key is sourced from an environment variable rather than a built-in KMS/HSM integration, and there is no built-in PII detection or redaction scanner. Each gap has a documented mitigation (external immutable log storage, application-layer field encryption, CSI-based KMS secret injection, and external DLP scanning, respectively).
Operators preparing for an audit should be able to produce, at minimum: proof the encryption secret exists, the current auth-config user/role list with sample login traces, a TLS/mTLS handshake trace, recent backup manifests or volume snapshots, a sample of the shipped audit log, and Grafana/alert history. See Monitoring & Observability for the metrics and alerting side of that evidence set.