A HashiCorp Vault secrets engine plugin for Keycloak. Performs on-demand, audit-logged user password rotation via the Keycloak Admin REST API. Each rotation generates a cryptographically random password, sets it on the Keycloak account, and returns the new value — with no credential stored inside Vault.
- Vault Plugin: Keycloak Secrets Engine
This plugin mounts as a Vault secrets engine and provides endpoints to:
- List and read users in the target Keycloak realm.
- Rotate a user password on demand via
users/<username>/rotate(fire-and-forget — no lease, no expiration). The new password is returned to the caller and remains valid in Keycloak until the next explicit rotation. - Sync rotated passwords to a KV v2 secret (v0.2.0+) — optionally PATCH the new password into another Vault KV v2 path after rotation (useful for Kubernetes secret operators).
- Issue ephemeral, lease-bound credentials via
creds/<role>(alpha). The password is returned with a Vault lease; on lease expiry or explicit revocation, the plugin resets the Keycloak password to a random discarded value, invalidating both sides.
- Create or delete Keycloak users. The plugin only manages passwords for existing users.
- Auto-rotate passwords on a schedule. There is no background task or periodic rotation. All rotations are triggered by an explicit API call.
- Store passwords inside Vault. Rotated passwords are returned to the caller and (optionally) synced to a KV v2 path, but the plugin itself retains no record of them.
flowchart TD
A[Operator calls Vault path] --> B{Path}
B -->|keycloak/config| C[Store config in Vault storage]
C --> D[Test Keycloak connection via admin token]
B -->|keycloak/roles/name| E[Store role → keycloak_username mapping]
B -->|keycloak/creds/role| F[Load role and config]
F --> G[Generate random password]
G --> H[Call Keycloak Admin API reset-password]
H --> I{KV sync configured?}
I -->|yes| I2[PATCH password into KV v2 secret]
I2 --> I3[Return username and new password]
I -->|no| I3
B -->|keycloak/users| J[List users in target realm]
B -->|keycloak/users/username| K[Read user details]
B -->|keycloak/users/username/rotate| L[Generate password + reset in Keycloak]
L --> M[Return username and new password]
Pre-built binaries for Linux, macOS, Windows, and FreeBSD (amd64 and arm64 where applicable) are published on the Releases page.
Download the binary for your platform and verify the SHA-256 checksum from
checksums.txt:
# Example: Linux amd64
curl -LO https://github.com/RoFz/vault-plugin-secrets-keycloak/releases/latest/download/vault-plugin-secrets-keycloak_linux_amd64
curl -LO https://github.com/RoFz/vault-plugin-secrets-keycloak/releases/latest/download/checksums.txt
sha256sum --check --ignore-missing checksums.txtRequires Go 1.25+.
git clone https://github.com/RoFz/vault-plugin-secrets-keycloak.git
cd vault-plugin-secrets-keycloak
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -o vault-plugin-secrets-keycloak ./cmd/vault-plugin-secrets-keycloakAdjust GOOS and GOARCH for your target platform.
The plugin binary must reside in Vault's plugin directory.
Requirements before deploying:
- A running Vault instance with a writable plugin directory (e.g.
/vault/plugins). - A Vault token with permission to register and enable plugins.
- Keycloak admin credentials for the target realm.
Manage the plugin volume using your FluxCD Kustomization and a HelmRelease patch.
- Add a PVC manifest to the same Flux-managed folder used by your Vault release.
- Add a patch file targeting your Vault HelmRelease to mount the PVC at
/vault/plugins. - Reference both in the Kustomization (
resources+patches/patchesStrategicMerge). - Commit and push, then reconcile Flux.
Example HelmRelease patch snippet:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: vault
namespace: vault
spec:
values:
server:
extraVolumes:
- name: plugin-dir
persistentVolumeClaim:
claimName: vault-plugin-pvc
volumeMounts:
- name: plugin-dir
mountPath: /vault/pluginsExample Flux reconcile command:
flux reconcile kustomization <vault-kustomization-name> -n flux-systemExample PVC manifest for plugin storage:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vault-plugin-pvc
namespace: vault
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1GiExample StatefulSet volume wiring (required so /vault/plugins exists in the pod):
spec:
template:
spec:
containers:
- name: vault
volumeMounts:
- name: plugin-dir
mountPath: /vault/plugins
volumes:
- name: plugin-dir
persistentVolumeClaim:
claimName: vault-plugin-pvcCopy binary to the active Vault pod:
kubectl cp ./vault-plugin-secrets-keycloak vault/vault-0:/vault/plugins/vault-plugin-secrets-keycloak
kubectl exec -n vault vault-0 -- chmod 0755 /vault/plugins/vault-plugin-secrets-keycloakCompute SHA256 from inside the pod (used for plugin registration):
kubectl exec -n vault vault-0 -- sha256sum /vault/plugins/vault-plugin-secrets-keycloakIn an HA cluster with a shared plugin volume, verify all Vault server pods see the same binary:
for pod in $(kubectl get pods -n vault -l component=server -o name); do
echo "$pod:"
kubectl exec -n vault "$pod" -- sha256sum /vault/plugins/vault-plugin-secrets-keycloak
doneAll SHA256 values must match before proceeding.
Register and enable the plugin (the -version flag must match the version
reported by the binary):
SHA256=$(kubectl exec -n vault vault-0 -- sha256sum /vault/plugins/vault-plugin-secrets-keycloak | cut -d' ' -f1)
VERSION="vX.Y.Z"
vault plugin register -sha256="$SHA256" -version="$VERSION" secret vault-plugin-secrets-keycloak
vault secrets enable -path=keycloak vault-plugin-secrets-keycloakTo upgrade or remove the plugin, first disable the secrets engine, then deregister the plugin with the version it was registered under:
vault secrets disable keycloak/
vault plugin deregister -version="$VERSION" secret vault-plugin-secrets-keycloakWrite the plugin configuration (one config per mount):
vault write keycloak/config \
url="https://keycloak.example.com" \
realm="master" \
target_realm="myrealm" \
master_admin_username="admin" \
master_admin_password='<admin-password>'When a password is rotated via creds/<role> or users/<username>/rotate,
the plugin can optionally PATCH the new password into a KV v2 secret in
Vault. This is useful for syncing rotated credentials to Kubernetes secrets
(via the Vault Secrets Operator or External Secrets Operator).
To enable KV sync, add the KV fields to the config:
vault write keycloak/config \
url="https://keycloak.example.com" \
master_admin_username="admin" \
master_admin_password='<admin-password>' \
kv_mount_path="k8s" \
kv_secret_path="keycloak/realm-users" \
kv_token="hvs.<token>" \
kv_api_addr="https://vault.vault.svc.cluster.local:8200"Then set kv_password_key on each role:
vault write keycloak/roles/myuser \
keycloak_username="myuser" \
kv_password_key="myuser-password"After each vault read keycloak/creds/myuser, the plugin PATCHes
k8s/data/keycloak/realm-users with { "myuser-password": "<new-pw>" }.
If the KV secret does not yet exist, a PUT (create) is used instead.
For ad-hoc rotations via the users path, pass it as a parameter:
vault write keycloak/users/myuser/rotate kv_password_key="myuser-password"KV sync failures are non-fatal — the rotation still succeeds and a warning is returned in the response.
The KV sync token needs create, update, and patch capabilities on the
target KV data path. Create a scoped policy and an orphan token:
vault policy write keycloak-kv-sync - <<'POLICY'
path "k8s/data/keycloak/realm-users" {
capabilities = ["create", "update", "patch"]
}
POLICY
vault token create \
-policy=keycloak-kv-sync \
-orphan \
-explicit-max-ttl=8760h \
-ttl=8760h \
-display-name=keycloak-kv-sync
explicit-max-ttlvsmax_lease_ttl: The token auth mount has amax_lease_ttl(default 768h / 32 days) that caps the initial TTL. The-explicit-max-ttlflag sets the absolute maximum lifetime of the token, up to which it can be renewed. The token must be renewed before its current TTL expires. For example, with-ttl=8760hand a mountmax_lease_ttlof 768h, the token is created with a 768h TTL but can be renewed repeatedly until theexplicit-max-ttlof 8760h is reached.
Adjust the policy path to match your kv_mount_path and kv_secret_path.
Untested: multiple mount paths are expected to work based on how Vault handles plugin mounts, but this has not been validated against multiple Keycloak realms or deployments.
The plugin stores one config per mount path. To manage multiple Keycloak deployments or realms, enable the plugin at multiple mount paths:
vault secrets enable -path=keycloak-appA vault-plugin-secrets-keycloak
vault secrets enable -path=keycloak-appB vault-plugin-secrets-keycloak
vault write keycloak-appA/config \
url="https://keycloak.example.com" \
realm="master" \
target_realm="appA" \
master_admin_username="admin" \
master_admin_password='<appA-admin-password>'
vault write keycloak-appB/config \
url="https://keycloak-b.example.com" \
realm="master" \
target_realm="appB" \
master_admin_username="admin" \
master_admin_password='<appB-admin-password>'Check logs from the active Vault pod:
kubectl logs -n vault vault-0 --tail=200Filter only plugin-relevant messages:
kubectl logs -n vault vault-0 --tail=500 \
| grep -E 'keycloak|password rotated|failed to create Keycloak client|connection test failed|failed to initialise'Operational/healthy examples:
keycloak secrets engine loaded successfullykeycloak config saved and connection test succeededpassword rotated successfullywith fields such asroleandkeycloak_usernamekv secret updated successfullywith fields such askv_secret_pathandkv_password_key
Error examples:
keycloak secrets engine failed to initialisefailed to create Keycloak clientkeycloak config saved but connection test failedfailed to rotate passwordkv sync failed after password rotation
All paths below are relative to the mount point (default keycloak/).
Configure the Keycloak backend. The plugin authenticates as an admin user via the Resource Owner Password Credentials (ROPC) grant.
| Method | Vault CLI |
|---|---|
| Create / Update | vault write keycloak/config ... |
| Read | vault read keycloak/config |
| Delete | vault delete keycloak/config |
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url |
string | yes | — | Base URL of the Keycloak server. |
realm |
string | no | master |
Auth realm used to obtain admin tokens. |
target_realm |
string | no | value of realm |
Realm whose users will be managed. |
client_id |
string | no | admin-cli |
OIDC client used for the ROPC grant. |
master_admin_username |
string | yes | — | Username of the master realm admin. |
master_admin_password |
string | yes | — | Password of the master realm admin. |
kv_mount_path |
string | no | — | KV v2 mount name for KV sync after rotation. |
kv_secret_path |
string | no | — | Path within the KV v2 mount to PATCH. |
kv_api_addr |
string | no | https://127.0.0.1:8200 |
Vault API address for KV sync requests. |
kv_tls_skip_verify |
bool | no | false |
Skip TLS verification for the KV API. |
kv_token |
string | no | — | Vault token with create/update/patch on the KV data path. |
Map a Vault role name to a Keycloak username. Used by the alpha
creds/<name> lease-based path.
| Method | Vault CLI |
|---|---|
| Create / Update | vault write keycloak/roles/<name> ... |
| Read | vault read keycloak/roles/<name> |
| Delete | vault delete keycloak/roles/<name> |
| List | vault list keycloak/roles |
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | yes | — | Vault role name. |
keycloak_username |
string | yes | — | Keycloak username whose password will be rotated. |
ttl |
duration | no | 3600 (1 h) |
Lease duration before automatic revocation. |
max_ttl |
duration | no | 86400 (24 h) |
Maximum lease duration. |
kv_password_key |
string | no | — | KV v2 key to PATCH with the new password after rotation via creds/<role>. |
| Method | Vault CLI |
|---|---|
| List | vault list keycloak/users |
Returns the usernames of all users in the target realm (up to 500).
| Method | Vault CLI |
|---|---|
| Read | vault read keycloak/users/<username> |
Returns the user's username, internal Keycloak ID, enabled status, email, first name, and last name.
| Method | Vault CLI |
|---|---|
| Update | vault write -force keycloak/users/<username>/rotate |
Generates a cryptographically random password (crypto/rand), sets it on
the Keycloak user via the Admin REST API, and returns { username, password }.
The previous password is immediately invalidated. No lease is created.
Optional parameter:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
kv_password_key |
string | no | — | KV v2 key to PATCH with the new password. Omit to skip KV sync. |
| Method | Vault CLI |
|---|---|
| Read | vault read keycloak/creds/<name> |
Generates a password and sets it on the Keycloak user bound to <name>.
Returns { username, password } with a Vault lease. On lease revocation the
password is rotated again to a discarded value, invalidating the issued
credential. On lease renewal the TTL is extended without rotation.
Alpha: automatic lease expiry and revocation have not been fully validated end-to-end. See the Credential lifecycle section for caveats.
List all users in the configured target realm:
vault list keycloak/usersRead a specific user's details:
vault read keycloak/users/<keycloak-username>Rotate a user's password and return the new value:
vault write -force keycloak/users/<keycloak-username>/rotateThe command generates a cryptographically random password (crypto/rand),
sets it on the Keycloak account via the Admin REST API, and returns
{ username, password }. The previous password is immediately invalidated.
No lease is created; Vault retains no record of the issued credential.
The supported rotation path is fire-and-forget via
vault write -force keycloak/users/<username>/rotate.
The returned password remains valid in Keycloak indefinitely until the next explicit rotation call. Vault retains no record of it and performs no automatic revocation. Every call is recorded in the Vault audit log (caller identity, mount path, timestamp).
Alpha — not recommended for production use yet: The plugin also implements a role-based, lease-bound issuance path (
vault read keycloak/creds/<role>) where Vault manages a TTL and automatically invalidates the credential on expiry by re-rotating the password to a discarded value. Automatic lease expiry and revocation have not been fully validated end-to-end and are considered alpha. Seepath_credentials.goin the source for implementation details.Alpha caveat — Vault availability at revocation time: Keycloak has no awareness of Vault leases. If Vault is unavailable when a lease TTL expires, the revocation callback is deferred and the issued password remains valid in Keycloak until Vault resumes. This is a known limitation of the alpha lease path and does not affect the supported fire-and-forget rotation path.
See CONTRIBUTING.md for development setup, testing, linting, and the Conventional Commits guidelines used in this project.
To report a security vulnerability, please use GitHub Security Advisories rather than a public issue. See SECURITY.md for the full policy.