Learn Secret Management - Integrating OpenBao with Kubernetes
Episode 12 of 21

Learn Secret Management - Integrating OpenBao with Kubernetes

Combining OpenBao with Kubernetes: ServiceAccount authentication via auth/kubernetes, automatic sidecar injection through a mutation webhook, and syncing secrets into native Kubernetes Secrets.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 11 you met the OpenBao Agent and its templates for rendering configuration files containing secrets. Episode 12 brings the Agent to the environment it most often inhabits: Kubernetes. We cover three things: how Pods authenticate themselves to OpenBao via auth/kubernetes, how a mutation webhook injects the Agent sidecar automatically, and how secrets are synced into native Kubernetes Secrets.

The Kubernetes Authentication Method (auth/kubernetes)

In essence, auth/kubernetes is a method that lets a Pod prove its identity without a human password. That identity is taken from the ServiceAccount attached to the Pod.

How ServiceAccount JWT Authentication Works

Every ServiceAccount in Kubernetes has a JWT mounted inside the Pod at /var/run/secrets/kubernetes.io/serviceaccount/token. The authentication flow goes like this:

  1. The application in the Pod sends the ServiceAccount JWT to the OpenBao login endpoint.
  2. OpenBao validates that JWT against the Kubernetes API server using the TokenReview mechanism.
  3. Once valid, OpenBao maps the ServiceAccount and namespace combination to a role.
  4. That role determines the applicable policy, so the Pod only gets the approved access.

Because validation is done by the API server, OpenBao needs to know how to reach the API server and which authority to trust. That is the information configured on the auth method mount.

Enabling auth/kubernetes

First, enable the authentication method:

Enable the kubernetes auth method
bao auth enable kubernetes

Then connect this method to the cluster:

Configure the connection to the Kubernetes API server
bao write auth/kubernetes/config \
  kubernetes_host=https://kubernetes.default.svc \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token

kubernetes_host is the cluster's API server address, kubernetes_ca_cert is the CA certificate for verifying the TLS connection, and token_reviewer_jwt is the JWT of a ServiceAccount with permission to perform TokenReview.

Setting Up Roles and Policies

Roles are the bridge between Pod identity and policies. The following command creates a role binding the ServiceAccount named app in the default namespace to the app-readonly policy:

Create a role for the application ServiceAccount
bao write auth/kubernetes/role/app \
  bound_service_account_names=app \
  bound_service_account_namespaces=default \
  policies=app-readonly \
  ttl=1h

Tip

All the commands above only need to be run once by an administrator. When a Pod logs in, OpenBao only needs to accept the ServiceAccount JWT — for example bao login -method=kubernetes — and the application token is issued automatically. No permanent credentials are stored in the container image.

OpenBao Agent Sidecar Injector

Running the Agent manually in every Pod is tedious and easy to miss. The Sidecar Injector automates the entire process through a mutation webhook: just add a few annotations, and the webhook inserts the Agent container when the Pod is created.

How the Mutation Webhook Works

The injector installs a mutating webhook that listens for Pod creation events. When it finds the openbao.org/agent-inject: "true" annotation, the webhook transparently modifies the Pod spec: it adds the Agent container, adds a shared volume, and installs an init container that fetches the token. From the application's point of view, nothing changes — the application just sees secret files appear in an agreed directory.

Configuration Through Annotations

KubernetesDeployment with agent injector annotations
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  template:
    metadata:
      annotations:
        openbao.org/agent-inject: "true"
        openbao.org/agent-inject-secret-config: "secret/data/app"
        openbao.org/role: "app"
        openbao.org/agent-inject-template-config: |
          {{- with secret "secret/data/app" -}}
          DB_PASSWORD={{ .Data.data.db_password }}
          {{- end }}

The first annotation turns on injection, openbao.org/agent-inject-secret-config points to the secret path to load, and the template is written via the annotation whose name ends with -template-config. For the role, write the Kubernetes auth role name created earlier.

Secrets on an In-Memory Shared Volume

The rendered results are stored on an emptyDir shared volume with Memory medium. The path follows the secret annotation name, i.e. /bao/secrets/config. Because this volume lives in memory, secrets are never written to disk and disappear automatically when the Pod stops. When the secret in OpenBao changes, the Agent updates the file and signals it; the application reading the file just waits for the next update.

Important

Make sure the ServiceAccount token used by the application only has the necessary policy. An in-memory sidecar is indeed safe from the disk side, but an overly loose policy still opens the door for other hacked Pods. The least privilege principle applies at every layer, including here.

Sidecar Injector vs Native Secret Sync

AspectSidecar InjectorNative Kubernetes Secrets Sync
MechanismMutation webhook adds the Agent containerSync component writes Secret objects
Secret mediumIn-memory volume /bao/secrets/configSecret objects stored in etcd
LifecycleDisappears automatically when the Pod diesRemains until deleted
RotationFiles updated directly by the AgentSecret needs updating and the Pod needs a reload
Extra needsWebhook injector installed in the clusterSync controller running in the cluster

Both are legitimate, but for cases where the application cannot be changed, the in-memory sidecar is superior because the application keeps reading a plain file. Native sync is chosen when the ecosystem around you depends on standard Kubernetes Secret objects, such as Helm charts or other operators.

Syncing to Native Kubernetes Secrets

If some components only understand standard Secret objects, sync is the way out. Secrets are read from OpenBao and then written as Secret objects:

Native Kubernetes Secret from sync
apiVersion: v1
kind: Secret
metadata:
  name: app-db
type: Opaque
stringData:
  db_password: "nilai-rahasia-dari-openbao"

Note the trade-off: once a secret enters a Secret object, it is stored in etcd and no longer "lives" following rotation in OpenBao, unless the sync keeps updating it. For the strictest security, prefer the in-memory sidecar; use native sync only when the ecosystem truly requires it.

Conclusion

In this episode 12, you understood ServiceAccount JWT authentication via auth/kubernetes, the kubernetes_host and token_reviewer_jwt configuration, setting up roles that connect Pods with policies, sidecar Agent injection via mutation webhook with the openbao.org/agent-inject annotation, secrets on the in-memory volume /bao/secrets/config, and syncing to native Kubernetes Secrets.

Key takeaways:

  • Pods prove their identity with a ServiceAccount JWT, not a human password.
  • The mutation webhook changes the Pod spec transparently; the application does not need to be changed.
  • Sidecar secrets live in memory and disappear with the Pod — the safest for disk.
  • Native sync helps ecosystems that use Secret objects, but adds rotation responsibility.

In the next episode, episode 13, we leave the cluster and enter the pipeline world: integrating OpenBao with GitHub Actions and GitLab CI to remove long-lived secrets from the repository.

Learn Secret Management - Integrating OpenBao with Kubernetes | Learn Secret Management with OpenBao