vigocli secrets
Manage the secrets vault — reversibly-encrypted-at-rest values under the master-key AES-256-GCM envelope, gated by an operator-side unlock passphrase.
The vault model
- Storage: Every secret is a single AES-256-GCM ciphertext file under
--secrets-dir(default/srv/vigo/secrets/). The wrapped value is the verbatim plaintext — no second hashing layer. Consumers (the agent'suser:executor for/etc/shadow, the server's basic-auth provider for web logins) hash or compare at point of use. - Master key:
<secrets-dir>/.master.key, mode 0600, root-owned. Generated byinit. vigosrv reads this autonomously at startup to validatesecret:references during config publish — the unlock gate is independent of the cryptographic envelope. - Unlock gate: Every gated
vigocli secrets *verb refuses unless an unlock sentinel is present at/srv/vigo/.secrets-unlocked(24h sliding window). The gate is an operator-hygiene boundary — it deters accidents, not a root attacker. Anyone with read access to.master.keyplus the encrypted blobs can recover plaintext without ever touching the gate.
How secrets flow to envoys
Resolved secret values never cross the agent gRPC wire as plaintext.
- At config load (publish or reload), the loader walks every
secret:<key>reference in configcrates, resource attributes, vars, and network-device credentials. Each ref is validated against the configuredsecrets.Provider(missing secrets fail the load, same as before) but the resolved plaintext is never stored on the in-memory config — the loader substitutes a placeholder marker__VIGO_SECRET__<key>__END_VIGO_SECRET__in its place. - The
PolicyBundlethat the server returns on every CheckIn carries these placeholders, not values. The bundle also carries asecrets_epochcounter that bumps on every successful reload and on watcher-detected secret rotation. - The agent receives the bundle, walks vars + configcrate attributes +
when:strings for placeholder markers, batches every distinct ref into a singleVigoAgent.ResolveSecretsRPC, and substitutes the resolved values in place before passing attributes to executors. Substitution failure aborts the apply with a clear error rather than letting a placeholder string land on disk. - Server-side,
ResolveSecretsenforces per-envoy entitlement: an envoy can only resolve refs whose placeholder appears in its own resolved bundle. Un-entitled requests fail withPermissionDeniedand emit asecret.entitlement_deniedaudit event. Successful resolutions emitsecret.epoch_resolved. Audit log records refs only, never values. - The agent caches resolved values in memory for the lifetime of the process; the cache invalidates whenever
secrets_epochadvances. A persistent at-rest cache (encrypted under a key derived from the agent's enrollment ed25519 identity) is a follow-up; until it lands, a cold agent restart with the server unreachable cannot apply secret-bearing resources — convergence fails loud and resumes once contact returns.
Operators don't need to change anything in their configcrates: secret:<path> keeps working the same way it always has. The placeholder pipeline is internal to vigosrv and the agent.
Subcommands
| Subcommand | Description |
|---|---|
init |
Bootstrap — generate the master key and prompt for the initial unlock passphrase |
generate-certs |
Generate self-signed CA and server TLS certificates |
unlock |
Open the vault for the current operator session (24h sliding TTL) |
lock |
Close the vault — gated verbs refuse until next unlock |
rotate |
Replace the unlock passphrase — requires the current one |
reset |
Break-glass: replace the passphrase by proving you can read .master.key |
set raw <key> |
Store an arbitrary value verbatim (API keys, DSNs, certs) |
set password <key> |
Store a human-typed credential with complexity validation |
reveal <key> |
Decrypt and print a stored value |
delete <key> |
Remove a stored value |
list |
List all stored keys |
init, generate-certs, and the lifecycle verbs (unlock/lock/rotate/reset) work pre-unlock. Everything else requires the vault to be unlocked.
Persistent Flags
| Flag | Default | Description |
|---|---|---|
--key-file |
(auto from --secrets-dir) |
Path to master encryption key |
--secrets-dir |
/srv/vigo/secrets |
Directory for secret files |
init
Bootstrap the vault. Generates a 256-bit master key, prompts for the initial unlock passphrase, writes the Argon2id verifier. Idempotent on upgrade: if the master key already exists but the verifier doesn't, only the verifier is written — existing data is preserved.
vigocli secrets init --key-file /srv/vigo/secrets/.master.key
Master key written to /srv/vigo/secrets/.master.key (mode 0600).
Keep this file safe — losing it means losing access to all encrypted secrets.
Set the unlock passphrase for this vault.
This passphrase gates every `vigocli secrets` command except init/generate-certs/unlock/lock/rotate/reset.
Enter unlock passphrase:
Confirm passphrase:
Unlock verifier written to /srv/vigo/secrets-verifier.json.
Run `vigocli secrets unlock` to open the vault.
Refuses to run only when both the master key AND the verifier already exist — use rotate to change the passphrase in that case.
unlock
Open the vault for the operator session. Prompts for the unlock passphrase; on match, writes a 24h-from-now expiry sentinel. Every successful gated call after this slides the expiry forward by another 24h.
vigocli secrets unlock
Enter unlock passphrase:
Vault unlocked. Idle timeout: 24h0m0s.
lock
Close the vault immediately by removing the sentinel. Gated verbs refuse until the next unlock.
vigocli secrets lock
rotate
Replace the unlock passphrase. Prompts for the current passphrase, verifies, then prompts for a new one (with confirmation). Clears any existing sentinel — every operator must re-unlock with the new passphrase before gated verbs work again.
vigocli secrets rotate
reset
Break-glass: replace the unlock passphrase without knowing the current one. Requires read access to the master-key file — the same access level needed to decrypt secrets directly. Typed RESET confirmation prevents accidents.
vigocli secrets reset
This will replace the unlock passphrase without verifying the old one.
Type RESET to proceed: RESET
Enter new passphrase:
Confirm passphrase:
Passphrase reset. Run `vigocli secrets unlock` with the new passphrase.
set raw
Store an arbitrary value verbatim under the master-key envelope. No complexity validation. Accepts the value via --from-file, stdin pipe, or interactive prompt.
vigocli secrets set raw <key> [--from-file <path>]
Examples
Interactive (prompt with input hidden):
vigocli secrets set raw vigo/db/password
From a file:
vigocli secrets set raw vigo/db/dsn --from-file /tmp/dsn.txt
From stdin:
printf 's3cret' | vigocli secrets set raw vigo/api/key
After every set, the server is notified so envoys with matching watch_secret resources converge on the new value at their next check-in.
set password
Store a human-typed credential. Interactive prompt only, with HIPAA-grade complexity validation (12+ characters, uppercase, lowercase, digit, special). The plaintext is stored verbatim under the master-key envelope; the agent's user: executor and the server's web-auth provider apply their own hashing or constant-time compare at point of use.
vigocli secrets set password <key>
Set an OS user password (consumed by the user: executor on the envoy)
vigocli secrets set password vigo/os-users/dan
Then reference it in a user: resource:
resources:
- name: dan
type: user
username: dan
password: "secret:vigo/os-users/dan"
The agent computes $6$<salt>$<hash> from the plaintext at apply time and feeds it to usermod -p / useradd -p.
Set a Vigo web UI password (consumed by the basic auth provider)
vigocli secrets set password vigo/web/auth/dan
The key must follow the pattern vigo/web/auth/<username> to match the account created with vigocli webusers create. (Admin webusers use real human OS usernames per auth setup; admin is not auto-seeded as a default.) The server's basic-auth provider resolves the stored plaintext via the secrets Provider on every login attempt and subtle.ConstantTimeCompares against the submitted password.
reveal
Decrypt and print a stored value. Each reveal is recorded in the tamper-evident audit trail as a secret_accessed event.
vigocli secrets reveal vigo/db/dsn
postgres://vigo:secret@localhost:5432/vigo?sslmode=disable
delete
Remove a stored value.
vigocli secrets delete vigo/test/key
list
List all stored keys (values are not shown).
vigocli secrets list
generate-certs
Generate a self-signed CA keypair and server TLS certificate as plain PEM files. Uses ECDSA P-256 keys.
vigocli secrets generate-certs [flags]
Flags
| Flag | Default | Description |
|---|---|---|
--tls-dir |
/srv/vigo/tls |
Directory for TLS certificate files |
--san |
Additional SANs (DNS names or IPs), repeatable | |
--ca-days |
3650 |
CA certificate validity in days |
--server-days |
365 |
Server certificate validity in days |
--force |
false |
Overwrite existing certificates |
Generated files
| File | Description |
|---|---|
tls/ca |
CA certificate (PEM) |
tls/ca_key |
CA private key (PEM) |
tls/cert |
Server certificate (PEM) |
tls/key |
Server private key (PEM) |
The server certificate automatically includes SANs for localhost, 127.0.0.1, and the system hostname.
vigocli secrets generate-certs --san vigo.example.com --san 10.0.1.5
Related
Confidential — Alexander4, LLC. Not for redistribution. See license.