The controller, OpenBao Pods, and lifecycle Jobs use different identities. Keep each Kubernetes ServiceAccount aligned with its projected token, OpenBao JWT role, and policy.
Map the identities
| Actor | Kubernetes identity | OpenBao authentication | Boundary |
|---|---|---|---|
| Provisioner | Provisioner ServiceAccount in the operator namespace | None | Kubernetes RBAC only |
| Controller | Controller ServiceAccount in the operator namespace | Shared or target-specific JWT bound to openbao-operator | Routine lifecycle and maintenance |
| OpenBao Pods | Per-cluster ServiceAccount | Runtime and unseal integrations | Server workload identity |
| Backup Job | Generated backup ServiceAccount | Projected JWT or backup token Secret | Snapshot read and object storage |
| Restore Job | Generated restore ServiceAccount | Projected JWT or restore token Secret | Destructive snapshot restore |
| Upgrade Job | Generated upgrade ServiceAccount | Projected JWT | Rolling or BlueGreen orchestration |
Changing JWT transport does not merge these identities. Each actor retains its own ServiceAccount, role, audience, and policy.
Select the controller credential
For new clusters that use self-init OIDC, set spec.controllerJWTMode: Target. This configuration fragment selects
short-lived credentials for the controller; keep your human access requests in spec.selfInit.requests.
spec:
controllerJWTMode: Target
selfInit:
enabled: true
oidc:
enabled: true
The controller requests a ten-minute Kubernetes ServiceAccount JWT with the sole audience
urn:openbao:controller:<metadata.uid>. The token is bound to the controller Pod. The controller caches it in memory
until one minute before the API server’s returned expiration. Each cluster UID has a separate credential.
Self-init binds the openbao-operator role to this audience and the controller ServiceAccount subject.
Raft maintenance and approved policy reconciliation use the selected credential. Executor Jobs retain their existing
ServiceAccounts and installation-scoped audiences.
Shared, or an omitted field, preserves the projected installation-wide credential. There is no CRD default or
mutating webhook that changes existing objects. An operator upgrade does not migrate OpenBao roles. Use the
migration procedure before selecting Target on an initialized cluster.
Enforced admission prevents removing Target or changing it to Shared. Authentication failures never trigger shared fallback.
The installation grants create on serviceaccounts/token only for the controller ServiceAccount in the operator
namespace. Custom installations must also provide POD_NAME and POD_UID through the Downward API, alongside
POD_NAMESPACE and OPERATOR_SERVICE_ACCOUNT_NAME.
Select the JWT transport
In Shared mode, the controller Deployment uses a projected one-hour ServiceAccount token. Its audience and
OPENBAO_JWT_AUDIENCE both default to openbao-internal. OpenBao validates the JWT against the bound audience and
the controller ServiceAccount subject.
The default inline strategy sends the JWT in the request-specific OpenBao inline-auth headers. Use standard only
when a proxy or intermediary cannot carry those headers; it performs a JWT login and sends the resulting OpenBao token
as X-Vault-Token.
Use the standard JWT transport
kubectl -n openbao-operator-system set env \
deployment/openbao-operator-controller \
OPENBAO_JWT_AUTH_STRATEGY=standard
kubectl -n openbao-operator-system rollout status \
deployment/openbao-operator-controllerLeave the variable unset, or set it to inline, for the default path. The controller propagates the selected strategy
to JWT-backed backup, restore, and upgrade Jobs.
Bootstrap operator and human access
spec.selfInit.enabled: true makes OpenBao execute the initialization requests and revoke the root token.
spec.selfInit.oidc.enabled: true adds the operator JWT auth method, role, and policies to that bootstrap. It does not
create human access.
Self-init is one-shot. Later policy changes require administrator action. Optional approved policy reconciliation can restore built-in policy contents within an administrator-approved grant. Auth methods and JWT roles remain administrator-managed after initialization.
Configure a manual controller role
Use manual JWT configuration only for a controlled bootstrap or a custom install that cannot use self-init OIDC. The following example uses Shared mode. Render the operator installation first, then substitute the actual namespace, ServiceAccount, and audience. For Target mode on an initialized cluster, follow the migration procedure instead.
Bind a custom controller identity
bao write auth/jwt-operator/role/openbao-operator \
role_type=jwt \
bound_audiences=openbao-internal \
user_claim=sub \
bound_subject=system:serviceaccount:platform-security:demo-openbao-operator-controller \
token_policies=openbao-operator \
token_ttl=1h \
token_max_ttl=1h \
token_no_default_policy=trueThe controller policy needs the steady-state maintenance paths:
Define the controller policy
path "sys/health" {
capabilities = ["read"]
}
path "sys/step-down" {
capabilities = ["sudo", "update"]
}
path "sys/storage/raft/configuration" {
capabilities = ["read"]
}
path "sys/storage/raft/remove-peer" {
capabilities = ["update"]
}
path "sys/storage/raft/autopilot/configuration" {
capabilities = ["read", "update"]
}
path "sys/storage/raft/autopilot/state" {
capabilities = ["read"]
}Do not add backup, restore, or upgrade permissions to this policy. Those belong to job-specific roles.
Verify a custom installation
Check these values together:
- The rendered controller ServiceAccount name and operator namespace.
- The selected
controllerJWTMode; omission means Shared. - For Shared mode, the projected audience and
OPENBAO_JWT_AUDIENCE; for Target, the cluster UID audience and token issuance RBAC. - The JWT role’s
bound_audiencesandbound_subject. - OpenBao’s reachability to the configured OIDC discovery or JWKS endpoint.
- The separate ServiceAccounts and JWT roles generated for lifecycle Jobs.
Troubleshoot authentication
| Symptom | Likely cause | Check first |
|---|---|---|
Controller receives permission denied immediately | Audience or bound-subject mismatch | Rendered controller identity and JWT role |
| Self-init finishes but operator auth does not settle | Discovery or JWKS cannot be reached or validated | Issuer, discovery URL, CA, and network path |
| Backup or restore auth fails while the controller works | Job-specific identity or role is missing | The Job’s ServiceAccount and JWT role |
| A custom namespace or prefix breaks auth | OpenBao still trusts the default subject | All rendered identity references and bound_subject |
If JWT login succeeds but a specific request is denied, continue with operator authorization.