Skip to content

Secret Management

Design Principle

ALL secrets are centrally managed via OpenBao. OpenBao is the single source of truth for all credentials in CobaltCore — bootstrap passwords, service credentials, database credentials, Ceph keys, kubeconfigs, TLS certificates, and messaging credentials.

Integration is done via the External Secrets Operator (ESO), which runs in each cluster and reads secrets from OpenBao. Existing operators continue to read Kubernetes Secrets — no code changes to operators needed. PushSecret CRDs write operator-generated secrets (Ceph Keys, the K-ORC admin Application Credential, and per-pod service-user passwords) back to OpenBao.

OpenBao Architecture

OpenBao runs as an HA cluster in the Management Cluster (namespace openbao-system):

text
┌─────────────────────────────────────────────────────────────────────────────┐
│                    MANAGEMENT CLUSTER — openbao-system                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐           │
│  │  OpenBao Node 0  │  │  OpenBao Node 1  │  │  OpenBao Node 2  │           │
│  │  (Leader/Standby)│  │  (Standby)       │  │  (Standby)       │           │
│  ├──────────────────┤  ├──────────────────┤  ├──────────────────┤           │
│  │ Raft Storage     │◀─┼──────────────────┼──▶│ Raft Storage    │           │
│  │ Seal: Transit/   │  │ Raft Storage     │  │ Seal: Transit/   │           │
│  │       Auto-Unseal│  │ Seal: Transit/   │  │       Auto-Unseal│           │
│  └──────────────────┘  │       Auto-Unseal│  └──────────────────┘           │
│                        └──────────────────┘                                 │
│                                                                             │
│  Raft Consensus: 3 Replicas, integrated storage                             │
│  Listener: HTTPS + mutual TLS on Port 8200 (CC-0107)                        │
│  Service: openbao.openbao-system.svc.cluster.local                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

mTLS admission gate (CC-0107). The OpenBao listener does not merely terminate server TLS — it sets tls_require_and_verify_client_cert = true, so every client (ESO, the Raft retry_join peers, and the in-pod bao CLI) must present a certificate signed by a dedicated OpenBao CA (openbao-ca-issuer). ESO therefore authenticates with a client certificate (eso-openbao-client-tls) on top of Kubernetes auth: a leaked Kubernetes ServiceAccount token alone cannot reach the OpenBao API. See OpenBao Deployment — Deployment via FluxCD.

Secret Engines

EngineMount PathPurposeExample Paths
KV v2kv-v2/bootstrap/Bootstrap credentialskv-v2/bootstrap/keystone-admin, kv-v2/bootstrap/service-passwords
KV v2kv-v2/openstack/OpenStack service secretskv-v2/openstack/admin/app-credential, kv-v2/openstack/nova/pods/nova-api-0/user, kv-v2/openstack/nova/db
KV v2kv-v2/infrastructure/Infrastructure credentialskv-v2/infrastructure/mariadb, kv-v2/infrastructure/rabbitmq, kv-v2/infrastructure/valkey
KV v2kv-v2/ceph/Ceph auth keyskv-v2/ceph/client-nova, kv-v2/ceph/client-cinder, kv-v2/ceph/client-glance
PKIpki/TLS certificatespki/issue/openstack-internal, pki/issue/api-external
Databasedatabase/mariadb/Dynamic DB credentialsdatabase/mariadb/creds/nova-rw, database/mariadb/creds/nova-ro

Auth Methods

Auth MethodMount PathUsageCluster
Kuberneteskubernetes/managementESO, FluxCD in Management ClusterManagement
Kuberneteskubernetes/control-planeESO in Control Plane ClusterControl Plane
Kuberneteskubernetes/hypervisorESO in Hypervisor ClusterHypervisor
Kuberneteskubernetes/storageESO in Storage ClusterStorage
AppRoleapprole/ci-cdCI/CD pipelines for secret provisioningExternal

Policies (Least Privilege)

RoleAllowed PathsCapabilities
eso-control-planekv-v2/data/bootstrap/*, kv-v2/data/openstack/*, kv-v2/data/infrastructure/*, kv-v2/data/ceph/*read
eso-hypervisorkv-v2/data/ceph/client-nova, kv-v2/data/openstack/nova/compute-*, kv-v2/data/openstack/nova/pods/*/userread
eso-storagekv-v2/data/ceph/*read, create, update
eso-managementkv-v2/data/bootstrap/*, kv-v2/data/infrastructure/*read
push-ceph-keyskv-v2/data/ceph/*create, update
push-admin-app-credkv-v2/data/openstack/admin/app-credentialcreate, update
push-pod-userskv-v2/data/openstack/*/pods/*/usercreate, update

The "never in a pod" invariant is enforced at the ExternalSecret/namespace level, not by these coarse per-cluster read policies. The admin password (kv-v2/bootstrap/keystone-admin) and the admin App Cred (kv-v2/openstack/admin/app-credential) are materialized only into the Keystone bootstrap Job, the c5c3-operator, and orc-system (K-ORC) — never via an ExternalSecret targeting a workload namespace, and never mounted into a workload pod. Per-pod user passwords (…/pods/<pod>/user) are each materialized into exactly one pod. | ci-cd-provisioner | kv-v2/data/* | create, update, read | | db-exporter | database/mariadb/creds/*-ro | read | | pki-issuer | pki/issue/* | create, update |

ESO Integration

The External Secrets Operator (ESO) runs in each cluster and synchronizes secrets from OpenBao. ESO uses ClusterSecretStore resources (cluster-wide scope, as opposed to namespace-scoped SecretStore) to define the connection to OpenBao:

text
┌─────────────────────────────────────────────────────────────────────────────┐
│                       ESO INTEGRATION PATTERN                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Per Cluster:                                                               │
│                                                                             │
│  ┌────────────────────────┐     ┌────────────────────────┐                  │
│  │   ClusterSecretStore   │     │    ExternalSecret      │                  │
│  │   ───────────────────  │     │    ──────────────      │                  │
│  │   provider: vault      │     │    secretStoreRef:     │                  │
│  │   server: https://     │◀────│      clusterStore      │                  │
│  │     openbao.mgmt:8200  │     │    target:             │                  │
│  │   auth:                │     │      name: <k8s-sec>   │                  │
│  │     kubernetes:        │     │    data:               │                  │
│  │       mountPath: ...   │     │      - remoteRef:      │                  │
│  │       role: eso-<cls>  │     │          key: <path>   │                  │
│  └────────────────────────┘     └────────────────────────┘                  │
│                                                                             │
│  ┌────────────────────────┐                                                 │
│  │     PushSecret         │     (Return channel: K8s Secret → OpenBao)      │
│  │     ──────────         │                                                 │
│  │     selector:          │                                                 │
│  │       name: <k8s-sec>  │                                                 │
│  │     data:              │                                                 │
│  │       - match:         │                                                 │
│  │          remoteRef:    │                                                 │
│  │            remoteKey:  │                                                 │
│  │              <vaultpath>                                                 │
│  └────────────────────────┘                                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Database Secret Engine

The OpenBao Database Secret Engine dynamically generates short-lived database credentials. This is used by the OpenStack Database Exporters to obtain read-only access to individual service databases.

Each OpenStack service database has a corresponding read-only role configured in OpenBao:

RoleDatabasePermissionsConsumer
nova-ronovaSELECTopenstack-database-exporter-nova
neutron-roneutronSELECTopenstack-database-exporter-neutron
cinder-rocinderSELECTopenstack-database-exporter-cinder
glance-roglanceSELECTopenstack-database-exporter-glance
keystone-rokeystoneSELECTopenstack-database-exporter-keystone
heat-roheatSELECTopenstack-database-exporter-heat
ironic-roironicSELECTopenstack-database-exporter-ironic
octavia-rooctaviaSELECTopenstack-database-exporter-octavia
placement-roplacementSELECTopenstack-database-exporter-placement

Each role creates a MariaDB user with SELECT-only grants on the respective database. Credentials have a configurable TTL (default: 1h) and are automatically revoked by OpenBao after expiry. ESO refreshes the credentials before expiry to ensure uninterrupted exporter operation.

Complete Secret Inventory

Secret TypeOpenBao PathEngineConsumer(s)Cluster
Keystone Admin Password (root of trust)kv-v2/bootstrap/keystone-adminKV v2Keystone Bootstrap Job; c5c3-operator (mints/rotates admin App Cred) — never a workload podControl Plane
Admin Application Credentialkv-v2/openstack/admin/app-credentialKV v2K-ORC Controller (orc-system) — never a workload podControl Plane
Per-Pod Service User Passwordkv-v2/openstack/<service>/pods/<pod>/userKV v2exactly one workload pod (OS_KEYSTONE_AUTHTOKEN__*)Control Plane / Hypervisor
MariaDB Root Credentialskv-v2/infrastructure/mariadbKV v2MariaDB OperatorControl Plane
RabbitMQ Credentialskv-v2/infrastructure/rabbitmqKV v2RabbitMQ OperatorControl Plane
Valkey Authkv-v2/infrastructure/valkeyKV v2Valkey OperatorControl Plane
Nova DB Credentialskv-v2/openstack/nova/dbKV v2Nova APIControl Plane
Neutron DB Credentialskv-v2/openstack/neutron/dbKV v2Neutron APIControl Plane
Glance DB Credentialskv-v2/openstack/glance/dbKV v2Glance APIControl Plane
Cinder DB Credentialskv-v2/openstack/cinder/dbKV v2Cinder APIControl Plane
Ceph Client Key (Nova)kv-v2/ceph/client-novaKV v2Nova Compute, Hypervisor Node AgentHypervisor
Ceph Client Key (Cinder)kv-v2/ceph/client-cinderKV v2Cinder VolumeControl Plane
Ceph Client Key (Glance)kv-v2/ceph/client-glanceKV v2Glance APIControl Plane
Nova Compute Credentialskv-v2/openstack/nova/compute-configKV v2Nova Compute AgentHypervisor
OVN Configkv-v2/openstack/ovn/configKV v2ovn-controllerHypervisor
Kubeconfig Control Planekv-v2/infrastructure/kubeconfig-cpKV v2FluxCDManagement
Kubeconfig Hypervisorkv-v2/infrastructure/kubeconfig-hvKV v2FluxCDManagement
Kubeconfig Storagekv-v2/infrastructure/kubeconfig-stKV v2FluxCDManagement
DB Exporter RO Creds (Nova)database/mariadb/creds/nova-roDBopenstack-database-exporter-novaControl Plane
DB Exporter RO Creds (Neutron)database/mariadb/creds/neutron-roDBopenstack-database-exporter-neutronControl Plane
DB Exporter RO Creds (Cinder)database/mariadb/creds/cinder-roDBopenstack-database-exporter-cinderControl Plane
DB Exporter RO Creds (Glance)database/mariadb/creds/glance-roDBopenstack-database-exporter-glanceControl Plane
DB Exporter RO Creds (Keystone)database/mariadb/creds/keystone-roDBopenstack-database-exporter-keystoneControl Plane
DB Exporter RO Creds (Heat)database/mariadb/creds/heat-roDBopenstack-database-exporter-heatControl Plane
DB Exporter RO Creds (Ironic)database/mariadb/creds/ironic-roDBopenstack-database-exporter-ironicControl Plane
DB Exporter RO Creds (Octavia)database/mariadb/creds/octavia-roDBopenstack-database-exporter-octaviaControl Plane
DB Exporter RO Creds (Placement)database/mariadb/creds/placement-roDBopenstack-database-exporter-placementControl Plane
TLS Certificatespki/issue/openstack-internalPKIOpenStack APIsControl Plane

Multi-Cluster Secret Distribution

text
┌─────────────────────────────────────────────────────────────────────────────┐
│                  MULTI-CLUSTER SECRET DISTRIBUTION                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                    ┌─────────────────────────┐                              │
│                    │      OpenBao            │                              │
│                    │  (Management Cluster)   │                              │
│                    │                         │                              │
│                    │  kv-v2/bootstrap/*      │                              │
│                    │  kv-v2/openstack/*      │                              │
│                    │  kv-v2/infrastructure/* │                              │
│                    │  kv-v2/ceph/*           │                              │
│                    │  pki/*                  │                              │
│                    └───┬─────┬─────┬─────────┘                              │
│                        │     │     │                                        │
│               ┌────────┘     │     └────────┐                               │
│               │              │              │                               │
│               ▼              ▼              ▼                               │
│  ┌────────────────┐ ┌────────────────┐ ┌────────────────┐                   │
│  │ ESO            │ │ ESO            │ │ ESO            │                   │
│  │ Control Plane  │ │ Hypervisor     │ │ Storage        │                   │
│  ├────────────────┤ ├────────────────┤ ├────────────────┤                   │
│  │ ExternalSecret │ │ ExternalSecret │ │ PushSecret     │                   │
│  │ → K8s Secrets: │ │ → K8s Secrets: │ │ (Ceph Keys →  │                    │
│  │  - DB Creds    │ │  - Ceph Keys   │ │  OpenBao)      │                   │
│  │  - SvcPasswords│ │  - Nova Config │ │                │                   │
│  │  - AppCreds    │ │  - OVN Config  │ │ ExternalSecret │                   │
│  │  - Ceph Keys   │ │                │ │ → K8s Secrets: │                   │
│  └────────────────┘ └────────────────┘ │  - Ceph Config │                   │
│                                        └────────────────┘                   │
│                                                                             │
│  ESO in Management Cluster itself:                                          │
│  ExternalSecret → K8s Secrets: Kubeconfigs, FluxCD Secrets                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Ceph Key Flow

The flow for Ceph credentials from Rook Operator via OpenBao to the Libvirt daemon:

text
┌─────────────────────────────────────────────────────────────────────────────┐
│                    CEPH KEY FLOW (via OpenBao)                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  STORAGE CLUSTER                                                            │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 1. Rook Operator creates CephClient + K8s Secret                    │    │
│  │    Secret: rook-ceph-client-openstack-nova (key: AQBxxxx==)         │    │
│  │                                                                     │    │
│  │ 2. PushSecret writes key to OpenBao                                 │    │
│  │    PushSecret → kv-v2/ceph/client-nova                              │    │
│  └───────────────────────────────┬─────────────────────────────────────┘    │
│                                  │                                          │
│                                  ▼                                          │
│  MANAGEMENT CLUSTER                                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 3. OpenBao stores Ceph key                                          │    │
│  │    Path: kv-v2/data/ceph/client-nova                                │    │
│  └───────────────────────────────┬─────────────────────────────────────┘    │
│                                  │                                          │
│                                  ▼                                          │
│  HYPERVISOR CLUSTER                                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ 4. ESO ExternalSecret reads from OpenBao                            │    │
│  │    → Creates K8s Secret: ceph-client-nova (namespace: openstack)    │    │
│  │                                                                     │    │
│  │ 5. Nova Compute DaemonSet mounts secret                             │    │
│  │    → /etc/ceph/ceph.client.nova.keyring                             │    │
│  │                                                                     │    │
│  │ 6. Hypervisor Node Agent creates Libvirt Secret on each node        │    │
│  │    → virsh secret-define + virsh secret-set-value                   │    │
│  │                                                                     │    │
│  │ 7. Libvirt uses secret for RBD access                               │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Bootstrap Sequence

The bootstrap sequence with OpenBao as central secret store:

PhaseDescriptionSecret Source
Phase 0Initialize OpenBao, Unseal, configure Secret Engines + Auth MethodsManual / CI-CD
Phase 1Write bootstrap secrets to OpenBao (Admin PW, Service Passwords)CI-CD → OpenBao
Phase 2Deploy ESO in all clusters, configure ClusterSecretStoresFluxCD
Phase 3ESO creates K8s Secrets from OpenBao in all clustersESO → OpenBao
Phase 4Infrastructure Operators start (MariaDB, RabbitMQ, Valkey)K8s Secrets
Phase 5Keystone Bootstrap with Admin credentials from OpenBaoK8s Secrets
Phase 6c5c3-operator mints the admin App Cred, then creates Keystone Services, Endpoints, and per-pod Service Users (via K-ORC)Keystone API
Phase 7PushSecrets write the admin App Cred and per-pod user passwords back to OpenBaoPushSecret → OpenBao
Phase 8ESO distributes all secrets to target clusters, services startESO → K8s Secrets

Credentials Never Live in ConfigMaps

A cross-cutting rule for all service operators (CC-0080): resolved credentials are never rendered into the immutable config ConfigMap. The Keystone Operator materializes the full database connection URL (including the password) into a derived <name>-db-connection Secret and injects it into every workload via the OS_DATABASE__CONNECTION environment variable. The keystone.conf ConfigMap carries no password. This keeps the at-rest credential exposure confined to objects treated as Secrets.

Credential Rotation

Three distinct rotation mechanisms exist; do not conflate them:

1. Keystone cryptographic-key rotation (built, keystone-operator). The operator rotates Fernet token keys and credential encryption keys using a split-compute-write boundary (CC-0081): the rotation CronJob — holding only narrow get+patch RBAC — writes new key material to a dedicated staging Secret; the operator then validates it (correct length, no duplicates, count in range) and applies it to the production Secret with its own privileged ServiceAccount. This keeps token-forgery primitives out of the CronJob's reach. Keys are projected into pods and rotate in place (no rolling restart, CC-0074), and credential-key rotation additionally runs keystone-manage credential_migrate to re-encrypt stored credentials. Backups are pushed per-CR to OpenBao at openstack/keystone/{name}/{fernet,credential}-keys (CC-0093), and an OpenBao finalizer purges them on deletion (CC-0079). A separate trust-flush CronJob purges expired trust delegations (CC-0057).

2. Workload service-user rotation = pod recreation (planned, c5c3-operator). Per-pod service users are not rotated in place; a fresh credential is produced by recreating the pod-identity (issue #30, D4). Any deploy / rollout / reschedule / crash-restart (ephemeral) or intentional recreation (per-replica), and maxAge-driven rolling recreation for long-lived pods, deletes the old Keystone user (finalizer revokes its tokens) and provisions a new one with a freshly generated password. There is no per-service Application Credential rotation. See Credential Lifecycle — Credential Rotation.

2b. Admin Application Credential rotation (planned, c5c3-operator). The single admin App Cred — K-ORC's only credential — is rotated by restricted + password-driven re-mint: the operator mints a fresh restricted App Cred from the admin password (OpenBao), writes it (PushSecret → OpenBao → ESO → k-orc-clouds-yaml), and deletes the old one after a short grace window. This is the only target of the CredentialRotation CRD.

3. Admin credential rotation (planned, keystone-operator). The Keystone admin password — written once during bootstrap — has no rotation path today. The planned design re-runs the idempotent keystone-manage bootstrap Job whenever ESO syncs a new password from kv-v2/bootstrap/keystone-admin: the bootstrap pod template carries a hash of the password so a rotation changes the Job's PodSpec hash and RunJob (CC-0005) re-runs it, applying the new password to Keystone. The new password is produced either externally in OpenBao (default) or by an opt-in operator-scheduled CronJob using the same split-compute-write boundary as key rotation (CC-0081). The admin password is single-valued (a hard cutover, no grace window) but low blast radius — running services use their own application credentials, not the admin password. See Admin Credential Rotation for the full mechanism.

For the CRD definitions of SecretAggregate and CredentialRotation, see CRDs.

Further Reading