OpenBao Setup

The provider expects an existing OpenBao deployment with the Transit secrets engine enabled, a single named key per Kubernetes cluster, a least-privilege policy, and one release-supported auth method for the host running the provider. This page lists the OpenBao-side commands in order. Run them as an OpenBao administrator before installing the provider; the static policy template in Step 4 is the bootstrap path before the provider binary and configuration file exist.

Prerequisites

  • A reachable OpenBao instance (HTTPS endpoint, valid TLS).
  • An OpenBao token with administrative capabilities for sys/, auth/, and transit/ paths.
  • A deterministic name for the Kubernetes Transit key. The naming convention used in this guide is k8s-<workload>-etcd. Replace workload-a with your environment-specific identifier in every example below.
  • A stable OpenBao instance ID and Transit mount ID for provider configuration. These are non-secret identity values used in Kubernetes key_id and AAD derivation.
  • Optional: an OpenBao namespace for this Kubernetes cluster when a single OpenBao cluster serves multiple Kubernetes clusters. Configure it as openbao.namespace; auth and Transit paths in this guide remain relative to that namespace.

For background on why each choice is made, see Architecture: Transit Key Model and Security: Auth Model .

Step 1: Enable The Transit Mount

Enable a dedicated Transit mount for Kubernetes KMS keys:

bao secrets enable -path=transit transit

Disable upsert on the mount so an encrypt call to a misspelled key name does not silently create a new key:

bao write transit/config/keys disable_upsert=true

Use a dedicated Transit mount for the Kubernetes KMS keys. disable_upsert is configured at the mount level and would affect any other workloads sharing the same mount.

Step 2: Create The Transit Key

Create the key with the recommended profile:

bao write transit/keys/k8s-workload-a-etcd \
  type=aes256-gcm96 \
  exportable=false \
  allow_plaintext_backup=false

Recommended properties:

PropertyValue
key typeaes256-gcm96
derivedfalse
convergent encryptionfalse
exportablefalse
plaintext backupfalse
deletion allowedfalse
auto-rotate period0 (rotation is operator-driven)

For the current release line, aes256-gcm96 is the only tested and supported key type. Other AEAD Transit key types need implementation, compatibility testing, and documentation before they can be supported.

Once the key exists, do not enable exportable or allow_plaintext_backup: OpenBao treats both settings as irreversible once enabled. Keep deletion_allowed=false as well; unlike those two flags it is a tunable deletion guard, but enabling it permits catastrophic key deletion if a token also has delete capability.

Step 3: Capture The Key Lineage ID

Generate a stable, non-secret identifier for this Transit key creation event:

openssl rand -hex 16

Example output:

7d34fb7df15f4e4c95d6c2a50fe90d84

Store the lineage ID in platform configuration management and supply it to the provider through transit.keyIdScope.keyLineageId (see Configuration ).

The lineage ID is not a secret. It must be:

  • generated once when the Transit key is created,
  • stable for the full lifetime of that key generation,
  • unique across deleted and recreated keys,
  • independent of the key name, mount path, OpenBao URL, or cluster name.

An existing platform inventory ID or ULID can be used if it has the same properties. Do not derive the lineage ID from mutable topology strings.

If the Transit key is deleted and recreated, generate a new lineage ID and treat the event as a destructive migration. The provider uses this ID to reject decrypt requests carrying ciphertext from a different key generation.

Step 4: Create The Policy

Write the least-privilege policy before creating the auth role:

cat >/tmp/openbao-kms-workload-a.hcl <<'HCL'
# Read Transit key metadata.
path "transit/keys/k8s-workload-a-etcd" {
  capabilities = ["read"]
}

# Encrypt with the existing key.
path "transit/encrypt/k8s-workload-a-etcd" {
  capabilities = ["update"]
}

# Decrypt existing ciphertext.
path "transit/decrypt/k8s-workload-a-etcd" {
  capabilities = ["update"]
}

# Inspect Transit disable_upsert.
path "transit/config/keys" {
  capabilities = ["read"]
}

# Allow doctor to inspect this token's capabilities.
path "sys/capabilities-self" {
  capabilities = ["update"]
}

# Allow token renewal when the auth role disables the default policy.
path "auth/token/renew-self" {
  capabilities = ["update"]
}
HCL

Apply the policy:

bao policy write openbao-kms-workload-a /tmp/openbao-kms-workload-a.hcl

After the provider binary and /etc/openbao-kms/config.yaml exist, you can generate the hot-path Transit policy from the active configuration and compare the rendered paths with the policy above:

bao-kms-provider policy openbao \
  --config /etc/openbao-kms/config.yaml

The generated policy includes the Transit and sys/capabilities-self paths. Keep the token-renewal self path in the applied policy when the provider configuration uses token renewal and the auth role sets token_no_default_policy=true.

The policy must not grant:

  • create on transit/encrypt/* (key creation through encrypt is what disable_upsert blocks at the mount level; the policy enforces it again at the token level),
  • update on transit/keys/* (this is the rotation capability and stays with operators or platform automation),
  • delete on any Transit key path,
  • read on transit/export/*,
  • read on plaintext backup paths,
  • broad sudo or admin permissions.

For policy variants and rationale see Reference: Transit Policy Examples .

Step 5: Configure JWT Auth

JWT auth is the default preview build and release path. This getting-started path uses OIDC discovery for a concrete sequential setup. For JWKS, pinned local public keys, or PKCS#11 certificate auth variants, use Reference: Transit Policy Examples after this happy path is understood.

Enable JWT auth at a dedicated path:

bao auth enable -path=k8s-workload-a-jwt jwt

Configure JWT validation for the issuer that signs the provider host JWT:

bao write auth/k8s-workload-a-jwt/config \
  oidc_discovery_url="https://issuer.example.internal" \
  bound_issuer="https://issuer.example.internal"

Create a role bound to the control-plane plugin identity:

bao write auth/k8s-workload-a-jwt/role/openbao-kms-control-plane \
  role_type=jwt \
  bound_audiences='["bao-kms-provider"]' \
  bound_subject="system:openbao-kms:workload-a" \
  user_claim="sub" \
  token_policies='["openbao-kms-workload-a"]' \
  token_ttl="30m" \
  token_max_ttl="1h" \
  token_no_default_policy=true

Recommended role constraints:

  • bound issuer,
  • bound audience,
  • bound subject,
  • bound cluster or environment claim,
  • short token TTL,
  • no default policy,
  • the narrow policy from Step 4.

The provider reads JWTs from a host-mounted file. The JWT issuer should be reachable independently of the protected Kubernetes API server. Avoid using a Kubernetes ServiceAccount token from the same protected cluster as the only credential source. If that API server is unavailable, refreshing the token may be impossible during the recovery the provider is meant to support. See Security: Auth Model for the trust-boundary discussion.

Step 6: Verify

After installing the provider, bao-kms-provider doctor validates the OpenBao side end-to-end:

  • TLS connection to OpenBao succeeds.
  • The configured auth material is locally valid.
  • OpenBao auth login succeeds.
  • The token can read Transit key metadata.
  • The token can encrypt and decrypt probe data.
  • The token cannot rotate, export, back up, or delete the key.
  • The key type and flags match the recommended profile.
  • disable_upsert is enabled on the Transit mount.

Doctor failures during initial setup are usually policy-related. See Operations: Troubleshooting for common cases.

Before wiring Kubernetes encryption, run the focused key check as well:

bao-kms-provider verify-key \
  --config /etc/openbao-kms/config.yaml

verify-key checks Transit metadata, key type, export settings, plaintext backup settings, deletion settings, and version restrictions. It is useful before changing API server encryption because it narrows OpenBao setup problems away from Kubernetes socket and API server wiring.

  1. Install to fetch the provider binary and verify the local environment.
  2. Deployment: Choosing A Model to run the provider on every control-plane node.
  3. Kubernetes Encryption Config once the provider runs and exposes its Unix socket.