New to KubeVault? Please start here.
Manage Qdrant credentials using the KubeVault operator
OpenBao’s qdrant-database-plugin manages dynamic credentials for Qdrant using Granular Access API Keys (HS256-signed JSON Web Tokens). When a client requests credentials, OpenBao creates a validation point in Qdrant, signs a JWT carrying collection-level RBAC permissions and a value_exists validation claim, and returns the token and dynamic username. When the credential lease expires or is revoked, OpenBao removes the validation point from Qdrant, immediately invalidating the token.
The same CRD shape is used both for the in-process qdrant-database-plugin and for the hub-spoke remote-qdrant-plugin; the difference is whether the Vault AppBinding referenced by SecretEngine.spec.vaultRef is marked deploymentMode: RemoteAgent (then the SecretEngine controller rewrites plugin_name to remote-qdrant-plugin and attaches spoke_name).
You need to be familiar with the following CRDs:
Before you begin
- Install KubeVault operator in your cluster from here.
- Provision a Qdrant deployment with API-key authentication and JWT RBAC enabled (
jwtRbac: truein KubeDB, orQDRANT__SERVICE__API_KEYandQDRANT__SERVICE__JWT_RBAC=truein container env). - Create a namespace for testing:
$ kubectl create ns demo
namespace/demo created
Vault Server
Deploy a Vault Server using the KubeVault operator: Deploy Vault Server. The KubeVault operator will create an AppBinding wiring up Kubernetes auth.
$ kubectl get appbinding -n demo vault -o yaml
AppBinding for Qdrant
Create an AppBinding pointing at the Qdrant HTTP endpoint. The secret’s password or api-key field carries the Qdrant master API key — KubeVault forwards it to the plugin as api_key=, which the plugin uses for reachability checks against /readyz and for signing dynamic JWTs.
apiVersion: appcatalog.appscode.com/v1alpha1
kind: AppBinding
metadata:
name: qdrant
namespace: demo
spec:
clientConfig:
url: https://qdrant.demo.svc:6333
secret:
kind: Secret
name: qdrant-auth
---
apiVersion: v1
kind: Secret
metadata:
name: qdrant-auth
namespace: demo
type: Opaque
stringData:
api-key: <qdrant-admin-api-key>
Note: If Qdrant runs with mTLS, specify client certificates in the AppBinding or secret.
Enable and configure the Qdrant secret engine
apiVersion: engine.kubevault.com/v1alpha1
kind: SecretEngine
metadata:
name: qdrant-engine
namespace: demo
spec:
vaultRef:
name: vault
qdrant:
databaseRef:
name: qdrant
namespace: demo
pluginName: qdrant-database-plugin
allowedRoles:
- "*"
Apply and wait for the engine to land:
$ kubectl apply -f qdrant-secret-engine.yaml
secretengine.engine.kubevault.com/qdrant-engine created
$ kubectl get secretengines -n demo
NAME STATUS AGE
qdrant-engine Success 10s
Behind the scenes the KubeVault operator writes:
bao write database/config/k8s.<cluster>.demo.qdrant \
plugin_name=qdrant-database-plugin \
url=https://qdrant.demo.svc:6333 \
api_key=<qdrant-api-key> \
allowed_roles="*"
When the referenced AppBinding is deploymentMode: RemoteAgent, the operator substitutes plugin_name=remote-qdrant-plugin and adds spoke_name=<spoke> so the hub forwards the call to the matching bao relay run daemon.
Create a QdrantRole
Define dynamic permissions for the role using creationStatements:
apiVersion: engine.kubevault.com/v1alpha1
kind: QdrantRole
metadata:
name: app
namespace: demo
spec:
secretEngineRef:
name: qdrant-engine
defaultTTL: 24h
maxTTL: 168h
creationStatements:
- |
{
"access": [
{
"collection": "my_collection",
"access": "rw"
}
]
}
$ kubectl apply -f qdrant-role.yaml
qdrantrole.engine.kubevault.com/app created
$ kubectl get qdrantrole -n demo
NAME STATUS AGE
app Success 8s
Generate dynamic credentials via SecretAccessRequest
To generate dynamic credentials, create a SecretAccessRequest:
apiVersion: engine.kubevault.com/v1alpha1
kind: SecretAccessRequest
metadata:
name: qdrant-credentials
namespace: demo
spec:
roleRef:
kind: QdrantRole
name: app
subjects:
- kind: ServiceAccount
name: demo-sa
namespace: demo
ttl: 1h
$ kubectl apply -f secret-access-request.yaml
secretaccessrequest.engine.kubevault.com/qdrant-credentials created
Wait for the request to be approved:
$ kubectl get secretaccessrequest -n demo qdrant-credentials
NAME STATUS SECRET AGE
qdrant-credentials Approved qdrant-credentials-xyz123 10s
Inspect the issued credentials
Extract the generated credentials from the Kubernetes Secret:
$ SECRET_NAME=$(kubectl get secretaccessrequest qdrant-credentials -n demo -o jsonpath='{.status.secret.name}')
$ kubectl get secret $SECRET_NAME -n demo -o yaml
apiVersion: v1
data:
password: <base64-encoded-jwt>
username: <base64-encoded-username>
kind: Secret
...
The Secret contains:
username: Unique dynamic username (e.g.v-kubernet-k8s...).password: Signed HS256 JWT encoding the collection permissions, lease expiry, andvalue_existsrevocation claim.
Clients pass this token to Qdrant via the api-key header or Authorization: Bearer <token>.
Revocation
When the SecretAccessRequest is deleted or the lease TTL expires, KubeVault revokes the lease in OpenBao. OpenBao deletes the validation point from Qdrant, immediately revoking the token across all Qdrant nodes.
$ kubectl delete secretaccessrequest -n demo qdrant-credentials
Cleanup
$ kubectl delete qdrantrole -n demo app
$ kubectl delete secretengine -n demo qdrant-engine








