Skip to main content

Managing OpenBao secrets for a tenant

This tutorial walks through writing, reading, listing, and removing secrets in OpenBao using the bao CLI, based on the tenant-demo reference setup. It can be fully validated on a local cluster started with task run-local; everything below assumes that, but works identically against any environment with the tenant-demo Flux Kustomizations reconciled.

Prerequisites

  • The bao CLI, installed via Mise as part of this repo's pinned tool versions (mise trust && mise install).
  • kubectl access to the cluster.
  • The cluster's OpenBao and tenant-demo Flux Kustomizations already reconciled, so the tenant-demo role/policy and SecretStore exist.

Connecting to OpenBao

Port-forward the OpenBao service and point the CLI at it:

kubectl port-forward -n openbao svc/openbao 8200:8200

In another terminal:

export BAO_ADDR=http://127.0.0.1:8200
bao login

it's also possible to use the UI to insert secrets. Once you've setup the port forward, just go to http://127.0.0.1:8200 in your browser.

bao login needs a token:

  • On a local (dev-mode) cluster, the fixed dev root token is root.

  • On an HA-mode cluster (any non-local overlay), retrieve the generated root token:

    kubectl get secret -n openbao openbao-init-credentials -o jsonpath='{.data.init\.json}' \
    | base64 -d | jq -r '.root_token'

This is the root token, so don't leave it cached in your shell any longer than you need it for.

Writing a secret

tenant-demo's OpenBao role only grants access to the secret/tenant-demo/* path prefix, so any secret you want its SecretStore to read has to be written under that prefix. The tenant-demo overlays already ship an ExternalSecret (openbao-example) that expects a key at tenant-demo/example-secret with properties foo1 and foo2 (see tenants/tenant-demo/overlays/local/example-secret.yaml). Write it with bao kv put:

bao kv put -mount=secret tenant-demo/example-secret foo1=bar1 foo2=bar2

Verifying the sync

Once written, the ExternalSecret picks it up on its next refresh (refreshInterval: 15s):

kubectl get externalsecret -n tenant-demo openbao-example
NAME             STORE             REFRESH INTERVAL   STATUS         READY
openbao-example openbao-backend 15s SecretSynced True

And the materialized Kubernetes Secret (secret-from-bao-via-extsecrets) now holds that data:

kubectl get secret -n tenant-demo secret-from-bao-via-extsecrets -o jsonpath='{.data.foo1}' | base64 -d
bar1

Reading a secret

bao kv get -mount=secret tenant-demo/example-secret

Add -field=<key> to print a single value, e.g. bao kv get -mount=secret -field=foo1 tenant-demo/example-secret.

Listing secrets

bao kv list -mount=secret tenant-demo/

Removing a secret

bao kv delete -mount=secret tenant-demo/example-secret

This is a soft delete (KV v2 keeps prior versions). To permanently remove all versions and metadata:

bao kv metadata delete -mount=secret tenant-demo/example-secret

Deleting tenant-demo/example-secret will make openbao-example's next sync fail until the key exists again, since that ExternalSecret depends on it.