New to KubeVault? Please start here.

Manage Apache Druid credentials using the KubeVault operator

OpenBao’s druid-database-plugin is a dynamic-credentials database plugin for Apache Druid. The plugin provisions credentials by talking to Druid’s BasicSecurity coordinator REST API: each issued credential becomes a Druid authenticator user and is bound to one or more pre-existing roles on the configured Druid authorizer. The plugin does not create roles; it only manages users and their role bindings, so every role referenced from creationStatements must already exist on the authorizer.

The same CRD shape is used both for the in-process druid-database-plugin and for the hub-spoke remote-druid-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-druid-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.
  • Run an Apache Druid cluster with BasicSecurity enabled. The Druid Quick Start ships with MyBasicMetadataAuthenticator and MyBasicMetadataAuthorizer already configured; for production, configure your own authenticator and authorizer through the Druid runtime properties and pass their names via authenticator / authorizer on the SecretEngine below.
  • Pre-create the Druid roles you want to bind credentials to (e.g. role1, role2). The plugin only binds — it does not create roles.
$ 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 Apache Druid

Create an AppBinding pointing at the Druid coordinator’s REST endpoint. Unlike most database engines, the URL here is not a JDBC/connection URI — it is the HTTP(S) base URL of the Druid coordinator (the BasicSecurity REST API lives under /druid-ext/basic-security/...). The referenced Secret carries the username and password of a Druid BasicSecurity principal that has permission to manage authenticator users.

apiVersion: appcatalog.appscode.com/v1alpha1
kind: AppBinding
metadata:
  name: druid
  namespace: demo
spec:
  clientConfig:
    url: http://druid-coordinator.demo.svc:8081
  secret:
    kind: Secret
    name: druid-cred
---
apiVersion: v1
kind: Secret
metadata:
  name: druid-cred
  namespace: demo
type: kubernetes.io/basic-auth
stringData:
  username: admin
  password: change-me

If you front the Druid coordinator with a self-signed TLS cert (e.g. the Quick Start with TLS enabled), set clientConfig.insecureSkipTLSVerify: true on the AppBinding or provide clientConfig.caBundle. Drop the knob once you front the coordinator with a real CA-issued certificate.

Enable and Configure Druid Secret Engine

When a SecretEngine crd object is created, the KubeVault operator will enable a secret engine on a specified path and configure the secret engine with the given configuration.

A sample SecretEngine for Druid:

apiVersion: engine.kubevault.com/v1alpha1
kind: SecretEngine
metadata:
  name: druid-engine
  namespace: demo
spec:
  vaultRef:
    name: vault
  druid:
    databaseRef:
      name: druid
      namespace: demo
    pluginName: druid-database-plugin            # optional; this is the default
    allowedRoles:
      - "*"
    authenticator: MyBasicMetadataAuthenticator  # optional; this is the Quick Start default
    authorizer: MyBasicMetadataAuthorizer        # optional; this is the Quick Start default

Apply it and wait for STATUS=Success:

$ kubectl apply -f druid-engine.yaml
secretengine.engine.kubevault.com/druid-engine created

$ kubectl get secretengines -n demo
NAME           STATUS    AGE
druid-engine   Success   10s

Use kubectl describe secretengine -n demo druid-engine to inspect error events, if any.

Create a DruidRole

A DruidRole describes how the plugin should mint a dynamic credential. creationStatements is a single-element string slice holding a JSON role document of the form {"roles":["role1","role2"]}. The listed roles must already exist on the authorizer named in SecretEngine.spec.druid.authorizer — the plugin only binds, it does not create roles.

apiVersion: engine.kubevault.com/v1alpha1
kind: DruidRole
metadata:
  name: druid-readonly
  namespace: demo
spec:
  secretEngineRef:
    name: druid-engine
  creationStatements:
    - '{"roles":["role1","role2"]}'
  defaultTTL: 1h
  maxTTL: 24h

Apply and verify:

$ kubectl apply -f druid-role.yaml
druidrole.engine.kubevault.com/druid-readonly created

$ kubectl get druidrole -n demo
NAME             STATUS    AGE
druid-readonly   Success   12s

The role name in Vault follows the format k8s.{clusterName}.{metadata.namespace}.{metadata.name}, so you can verify directly with the Vault CLI:

$ vault read your-database-path/roles/k8s.-.demo.druid-readonly
Key                      Value
---                      -----
creation_statements      [{"roles":["role1","role2"]}]
db_name                  k8s.-.demo.druid
default_ttl              1h
max_ttl                  24h

Deleting the DruidRole removes the role from Vault.

Issue Druid credentials

Request a dynamic credential by creating a SecretAccessRequest:

apiVersion: engine.kubevault.com/v1alpha1
kind: SecretAccessRequest
metadata:
  name: druid-cred-rqst
  namespace: demo
spec:
  roleRef:
    kind: DruidRole
    name: druid-readonly
  subjects:
    - kind: ServiceAccount
      name: demo-sa
      namespace: demo

Approve it through the KubeVault CLI:

$ kubectl vault approve secretaccessrequest druid-cred-rqst -n demo
approved

Once approved, the operator issues the credential, stores it in a Secret, and binds the listed subjects via a Role/RoleBinding. The plugin creates a new authenticator user on Druid and binds it to role1 and role2 on the configured authorizer. The credential lives on the lease until you delete the SecretAccessRequest or it expires; on lease revocation the plugin removes the authenticator user.

$ kubectl get secretaccessrequest druid-cred-rqst -n demo -o json | jq '.status'
{
  "lease": {
    "duration": "1h0m0s",
    "id": "your-database-path/creds/k8s.-.demo.druid-readonly/abc...",
    "renewable": true
  },
  "secret": {
    "name": "druid-cred-rqst-xxxxxx"
  }
}

$ kubectl get secret -n demo druid-cred-rqst-xxxxxx -o jsonpath='{.data.username}' | base64 -d
v-kubernetes-demo-XXXXXXXX

$ kubectl get secret -n demo druid-cred-rqst-xxxxxx -o jsonpath='{.data.password}' | base64 -d
xxxxxxxxxxxxxxxxxx

Use the issued username / password as HTTP Basic-Auth credentials when querying Druid (Brokers, Router, etc.); the credential is revoked when the SecretAccessRequest is deleted.

Further reading