Secrets
Vigo never stores secrets in config files, environment variables, logs, or database records. All secret values use the secret: prefix and are resolved through a pluggable backend at runtime.
The secret: Prefix
Any config value starting with secret: is resolved through the secrets provider:
database:
dsn: "secret:vigo/db/dsn"
smtp:
password: "secret:vigo/smtp/password"
At startup, the server resolves secret:vigo/db/dsn by calling provider.Get("vigo/db/dsn"). The actual secret value is never written to disk in plaintext.
Two Backends
local (default, recommended)
Secrets are stored as AES-256-GCM encrypted files on disk. The master key is a 32-byte hex key stored in a 0600-permission file.
secrets:
backend: local
key_file: "/srv/vigo/master.key"
secrets_dir: "/srv/vigo/secrets"
Secret path mapping: secret:vigo/db/dsn → file at secrets_dir/vigo/db/dsn.
On first start, the local backend auto-generates:
- The master key (if
key_filedoesn't exist) - TLS certificates (self-signed CA + server cert)
- A random database DSN
isopass (production)
Resolves secrets from an external isopass API server. Supports versioning for rotation detection.
secrets:
backend: isopass
url: "https://isopass.internal:8443"
token_file: "/srv/vigo/isopass-token" # 0600, contains bearer token
The token_file is read at startup. The file should be owned by root with mode 0600.
Secret Resolution Flow
Fail-Fast
If the secrets provider is unreachable or a secret path doesn't exist, the server refuses to start. It never falls back to insecure defaults.
$ vigosrv
ERROR server failed error="loading server config: resolving secret 'vigo/db/dsn': file not found"
Secret Rotation
Local backend
When you change a secret with the CLI, rotation is automatic:
vigocli secrets set vigo/grafana/admin-password "newpass"
Secret "vigo/grafana/admin-password" stored.
Server notified — affected envoys will apply on next check-in.
The CLI writes the encrypted file and notifies the server. The server:
- Reloads config to resolve the new value
- Finds envoys whose configs reference the secret
- Queues
_secret_triggered=truefor resources withwatch_secret: ["path"] - Sets force-push on affected envoys
On the next check-in, watch_secret resources re-execute with the new value.
Isopass backend
The server polls isopass for version changes:
watcher:
enabled: true
poll_interval: "5m"
When a version change is detected, the same rotation flow triggers automatically.
Secrets in Modules
Modules reference secrets via vars:
# nodes.vgo
envoys:
- match: "db*.example.com"
roles: [database]
vars:
db_password: "secret:vigo/db/password"
The content: template renders the resolved value:
# modules/database.vgo
resources:
- name: pg-config
type: file
target_path: /etc/postgresql/conf.d/auth.conf
content: |
password = {{ .Vars.db_password }}
watch_secret: ["vigo/db/password"]
notify: [pg-service]
Exec Secret Safety
Never embed secrets in command strings (they appear in process listings). Use stdin or secret_file:
# GOOD — pipe to stdin, only runs on secret rotation
- name: set-grafana-password
type: exec
command: "grafana-cli admin reset-admin-password --password-from-stdin"
stdin: "secret:vigo/grafana/admin_password"
watch_secret: ["vigo/grafana/admin_password"]
when: "changed"
# GOOD — temporary 0600 file
- name: deploy-with-key
type: exec
command: "deploy.sh --key-file {{secret_file}}"
secret_file: "secret:vigo/deploy/key"
# BAD — secret visible in ps output
- name: bad-example
type: exec
command: "grafana-cli admin reset-admin-password MySecret123"
Related
- Server Configuration — secrets: section
- Resource DAG — watch_secret