Skip to content

Repository files navigation

vault-valkey-plugin

CI License: MPL-2.0

A HashiCorp Vault / OpenBao database secrets engine plugin (dbplugin v5) that issues dynamic Valkey credentials across a Sentinel-managed primary/replica topology — including correct behaviour across failover — and supports a separate, low-privilege identity for Sentinel discovery.

Status: released (v1.3.0). Production-hardened — unit, live-cluster integration, and real-Vault end-to-end tests pass on Vault 1.14 and 1.21; see CHANGELOG.md.

Why a dedicated plugin

The upstream Vault Redis plugin and OpenBao's native Valkey plugin are single-node only. Neither follows a Sentinel-managed master across failover. This plugin does, and it is built around one empirically established fact:

Valkey ACL users are node-local. Creating a user on the master does not propagate it to replicas, and a replica resync does not carry it. (Reproduce: test/sentinel/spike.sh.) So the plugin provisions, persists, and revokes each dynamic user on every node, and re-resolves the current master through Sentinel on every operation.

Build

go build -o valkey-database-plugin ./cmd/valkey-database-plugin

Use

Register the plugin with Vault, then configure a connection and a role:

# 1. configure the connection (Sentinel mode)
vault write database/config/my-valkey \
    plugin_name="valkey-database-plugin" \
    sentinels="10.0.0.1:26379,10.0.0.2:26379,10.0.0.3:26379" \
    sentinel_master_name="mymaster" \
    sentinel_username="vault-sentinel-ro" \
    sentinel_password="$SENTINEL_PW" \
    username="vault-admin" \
    password="$NODE_ADMIN_PW" \
    persistence_mode="aclfile" \
    allowed_roles="app-reader"

# 2. define a role — prefer ACL *categories* over enumerated commands for
#    version portability; this example grants Streams + read/write on app:* keys
vault write database/roles/app-reader \
    db_name="my-valkey" \
    creation_statements="~app:* +@read +@write +@stream" \
    default_ttl="1h" max_ttl="24h"

# 3. get short-lived credentials
vault read database/creds/app-reader

Configuration reference

Field Required Description
sentinels Sentinel mode Comma-separated host:port of the Sentinels
sentinel_master_name Sentinel mode Monitored primary name (e.g. mymaster)
sentinel_username / sentinel_password no Identity for Sentinel discovery (and, in shared mode, a Sentinel admin that runs ACL SETUSER/DELUSER on the Sentinels)
host / port standalone Single-node fallback (no Sentinel)
username / password yes Node admin identity used to run ACL SETUSER/DELUSER
persistence_mode no aclfile (default, runs ACL SAVE), rewrite (CONFIG REWRITE), or none
tls / insecure_tls no Enable TLS / skip server-cert verification
ca_cert / tls_cert / tls_key no PEM material for TLS
password_hashing no Send a SHA-256 hash to ACL SETUSER instead of cleartext (default true)
username_template no Override the generated dynamic username format
sentinel_identity_mode no separate (default) or shared — see Shared identity
sentinel_persistence_mode no Sentinel-side durability in shared mode: none (default, ephemeral) or aclfile. rewrite is rejected (Sentinels have no CONFIG REWRITE)
sentinel_creation_statements no Override the Sentinel-side discovery ACL (shared mode); default is a narrow read-only set
reconcile no Heal node-local ACL drift on each issuance (default true) — re-assert managed users a returned node is missing, remove orphans a revoke left behind. See Reconciliation
managed_username_prefix no Prefix identifying plugin-managed users for reconcile (default v_); set only if username_template uses a different prefix

Shared identity

By default the dynamic user lives only on the data nodes and Sentinel discovery uses a separate identity (the secure model). For a legacy app that authenticates to both the Valkey nodes and the Sentinels with one credential, set sentinel_identity_mode=shared: the same dynamic user is also provisioned on the Sentinels with a narrow read-only discovery ACL — it can resolve the master but not trigger failover.

vault write database/config/my-valkey \
    plugin_name="valkey-database-plugin" \
    sentinels="10.0.0.1:26379,10.0.0.2:26379,10.0.0.3:26379" \
    sentinel_master_name="mymaster" \
    sentinel_username="vault-sentinel-admin" \
    sentinel_password="$SENTINEL_ADMIN_PW" \
    sentinel_identity_mode="shared" \
    sentinel_persistence_mode="aclfile" \
    username="vault-admin" password="$NODE_ADMIN_PW" \
    allowed_roles="legacy-app"
  • sentinel_username/sentinel_password must be a Sentinel admin (it runs ACL SETUSER/DELUSER on the Sentinels), not just a discovery user.
  • Sentinel-side users are ephemeral unless you configure an aclfile on the Sentinels and set sentinel_persistence_mode=aclfile. Ephemeral is fine where Sentinels are stable and the app re-fetches credentials on restart.
  • Shared identity is less secure than separate identities (the app credential reaches the Sentinel control plane). Prefer separate identities where the client supports them.

Reconciliation

Because ACL users are node-local, a replica that is down when a credential is created never receives that user, and a node down when a lease is revoked keeps a stale one. The plugin heals both on each subsequent issuance (reconcile=true, the default): it treats the master as the source of truth — every create writes the master first and every operation re-resolves to it — and converges each data node to it.

  • A managed user present on the master but missing from a node is cloned from the master's ACL LIST definition (hash included, so no cleartext and no Vault lookup).
  • A managed user on a node but absent from the master is removed as an orphan.
  • Best-effort and non-fatal — the just-issued credential is already provisioned, so a reconcile hiccup only logs. Cheap when clean (one ACL LIST + one ACL USERS per node).

Managed users are identified by managed_username_prefix (default v_); static and admin accounts are never touched. Set reconcile=false to disable.

Static roles (shared credential rotation)

Dynamic creds mint a unique user per lease. If instead you have one shared account used by many clients and a policy to rotate it periodically, use a Vault static role — the plugin rotates the existing user's password across every Sentinel node:

# the shared user must already exist WITH its ACL rules on every node (Vault rotates, not creates);
# name it with the managed prefix (default v_) so reconcile keeps it converged
vault write database/static-roles/app-shared \
    db_name="my-valkey" username="v_app_shared" \
    rotation_period="4380h"          # ~6 months  (or rotation_schedule="0 0 1 1,7 *")

vault read database/static-creds/app-shared   # current shared password + time to next rotation

All clients read static-creds and get the same current password; Vault bounds its age. Note that rotating a shared password affects every current holder — clients should re-fetch (VSO / Vault Agent) and reconnect on WRONGPASS.

For rotation coupled to restarts rather than a timer, contrib/lazy-rotate.sh rotates lazily at pod start when the password is older than a threshold (see its header for the compliance trade-off).

Compatibility

  • Vault 1.14+ (1.x), and OpenBao — one binary targets both via dbplugin v5.
  • Valkey 7.x → latest, and Redis 7.x (RESP/ACL compatible). On Valkey 9.0+ the Sentinel discovery user needs +failover and +client; size its ACL accordingly.

Security notes

  • Passwords are provisioned to nodes as a SHA-256 hash (#<hex>), so cleartext never reaches a node's command log; clients still authenticate with the cleartext Vault issues.
  • creation_statements rejects credential-model-breaking tokens (nopass, off, reset, resetpass) and password directives (>/</#/!) — supply only key/command/channel rules.
  • Use a separate, low-privilege Sentinel discovery user (sentinel_username); on Valkey 9.0+ give it +failover and +client.
  • Enable TLS in production — without it, client AUTH traffic is cleartext.
  • Restrict read on database/config/* to operators — Vault returns plugin-specific secrets (sentinel_password, tls_key) on a connection read; it masks only the built-in password.

Testing

go test ./...                 # unit tests
test/sentinel/spike.sh        # live podman Sentinel topology — proves the invariants
test/integration/run.sh       # plugin code vs live cluster (incl. failover)
test/vault/e2e.sh             # real `vault server -dev` end-to-end

License

MPL-2.0.

About

Vault/OpenBao database secrets engine plugin for Valkey behind Sentinel — dynamic credentials across primary/replica failover, with a separate Sentinel discovery identity. MPL-2.0.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages