Reverse-specification of barbican — branch stable/2026.2
Branch
stable/2026.2@de32745a7a4f— repository https://opendev.org/openstack/barbican — analysed on 2026-10-29
- Repository: https://opendev.org/openstack/barbican (local clone) — specified branch:
stable/2026.2@de32745a7a4f77c98dac5906af128dac577750bf(tag23.0.0+ backports) — analysed on: 2026-10-29. - Scope: the whole service:
barbican-api,barbican-worker,barbican-retry,barbican-keystone-listener,barbican-manage(and the legacybarbican-db-manage,pkcs11-*,barbican-statusscripts); the secret-store plugin contract (store_crypto, KMIP, Vault, Dogtag) and the crypto plugin contract (simple_crypto, PKCS#11); the rewrite pack (step 8). The change history (step 7) covers the 2026.2 cycle since22.0.0. - Clients whose expectations are in the pack: castellan
stable/2026.2@bd6bd342982d(used by Nova, Cinder, Glance), python-barbicanclientstable/2026.2@8162006(and theopenstack secret …OSC commands), Skyline (skyline-apiserver @4af72bfd4577, skyline-console @806d2003), barbican-tempest-pluginmaster@f370cf0.
| File | Content |
|---|---|
| 01-rest-api_stable-2026.2.md | the 57 operations of /v1, technical sheets and functional descriptions, lifecycles |
| 02-errors_stable-2026.2.md | error body, exception catalogue, pecan.abort sites, errors outside the API |
| 03-interactions_stable-2026.2.md | database, AMQP (worker RPC, Keystone notifications), secret-store back ends (HSM, KMIP, Vault, Dogtag), Keystone |
| 04-scenarios_stable-2026.2.md | sequence diagrams: store and read a secret, key order, delete with consumers, project deletion, KEK rotation |
| 05-async-processing_stable-2026.2.md | worker, retry scheduler, Keystone listener, maintenance commands |
| 06-change-history_stable-2026.2.md | 2026.2 cycle (22.0.0..stable/2026.2, 47 commits) as change specifications and a carry-over plan |
| 07-rewrite-pack_stable-2026.2.md | rewrite pack: encryption at rest, contracts, conformance data, client expectations |
| _rewrite/ | machine-readable contracts, captures, test vectors and tools |
| _verification_stable-2026.2.md | every file:line reference mapped to the function it points into |
Overview
Barbican is OpenStack's key manager. Projects store secrets (opaque data, passphrases, symmetric keys, public/private keys, certificates), group them in containers (generic, RSA, certificate), and ask Barbican to generate keys through asynchronous orders. Other services register as consumers of a secret or container, so that the owner sees who uses it and, from microversion 1.2, cannot delete a secret still in use without force. Access is project-based through oslo.policy, refined per secret/container by ACLs (per-operation user lists and a project-access flag). Per-project quotas cap the number of secrets, orders, containers and consumers.
The central property is encryption at rest: Barbican never stores a payload in clear. A secret-store plugin either keeps the secret in an external system (KMIP server, HashiCorp Vault through castellan, Dogtag KRA) and only records a reference, or — the default, store_crypto — encrypts it with a crypto plugin and stores the ciphertext in its own database. The crypto plugin uses a per-project KEK (key-encryption key), itself protected by a master key: a Fernet key in the configuration for simple_crypto, an AES key inside an HSM for p11_crypto. File 07 § 3 gives the exact byte layouts and an executable reference that decrypts every secret written by Python Barbican.
Architecture
flowchart LR
cli[Clients: castellan in Nova/Cinder/Glance,<br/>barbicanclient/OSC, Skyline] -->|HTTPS /v1, X-Auth-Token,<br/>OpenStack-API-Version: key-manager 1.x| api[barbican-api<br/>Pecan behind paste pipeline]
api -->|validate token| ks[Keystone]
api -->|SQL| db[(MariaDB barbican)]
api -->|AMQP cast process_type_order<br/>topic barbican.workers| mq{{RabbitMQ}}
mq --> wk[barbican-worker]
wk -->|SQL| db
rt[barbican-retry] -->|SQL order_retry_tasks| db
rt -->|AMQP cast| mq
ksn[Keystone notifications<br/>identity.project.deleted] -->|AMQP exchange keystone<br/>topic notifications| kl[barbican-keystone-listener]
kl -->|SQL soft delete| db
api -->|crypto plugin| hsm[(HSM PKCS#11)]
api -->|secret store| ext[(KMIP / Vault / Dogtag)]
wk --> hsm
wk --> ext
mg[barbican-manage] -->|SQL, HSM| dbProcesses
| Process | Entry point | Role | Listens to / consumes |
|---|---|---|---|
| barbican-api | barbican/api/app.py:105 create_main_app (paste etc/barbican/barbican-api-paste.ini:5, served by uWSGI/mod_wsgi through barbican/api/app.py:117) | REST API /v1, version discovery /, /healthcheck | HTTP (default port 9311 [inferred from barbican/common/config.py:74 sample]) |
| barbican-worker | barbican/cmd/worker.py:34 → barbican/queue/server.py:214 TaskServer | processes orders (key and asymmetric generation) | AMQP RPC topic barbican.workers, server barbican.queue (barbican/common/config.py:125) |
| barbican-retry | barbican/cmd/retry_scheduler.py:34 → barbican/queue/retry_scheduler.py:45 PeriodicServer | re-enqueues order tasks scheduled for retry | timer ([retry_scheduler], 10 s) |
| barbican-keystone-listener | barbican/cmd/keystone_listener.py:33 → barbican/queue/keystone_listener.py:139 MessageServer | deletes a project's Barbican data when Keystone deletes the project | AMQP notifications, exchange keystone, topic notifications (barbican/common/config.py:142) |
| barbican-manage | barbican/cmd/barbican_manage.py:452 | db (upgrade, clean, sync_secret_stores…), hsm (MKEK/HMAC keys, rewrap), simple_crypto (new/rewrap project KEKs, rewrap secrets) | CLI |
Without [queue] enable = True (default False, barbican/common/config.py:120) there is no worker: the API runs the order task in-process right after committing the order (barbican/queue/client.py:39, barbican/queue/client.py:76).
External dependencies
| System | Protocol | Library | Configuration | Usage |
|---|---|---|---|---|
| MariaDB/MySQL (PostgreSQL, SQLite in tests) | SQL | SQLAlchemy 2 / oslo.db, Alembic | [database] connection | all state; 26 tables |
| Keystone | HTTPS | keystonemiddleware auth_token | [keystone_authtoken] | token validation in the paste pipeline; source of identity.project.deleted notifications |
| RabbitMQ | AMQP 0-9-1 | oslo.messaging | [DEFAULT] transport_url, [queue], [keystone_notifications] | worker RPC casts; Keystone notifications |
| HSM | PKCS#11 (C API through cffi) | barbican/plugin/crypto/pkcs11.py | [p11_crypto_plugin] | master KEK and HMAC key, project-KEK wrapping, secret encryption |
| KMIP server | KMIP over TLS | PyKMIP ProxyKmipClient | [kmip_plugin] | secret storage/generation (optional) |
| HashiCorp Vault | HTTPS (KV v1/v2) | castellan VaultKeyManager | [vault_plugin] | secret storage/generation (optional) |
| Dogtag KRA | HTTPS | dogtag-pki | [dogtag_plugin] | secret storage/generation (optional, legacy) |
Key figures
- 57 operations on 29 paths (
_rewrite/routes_stable-2026.2.json), microversions1.0–1.2, 82 default policy rules, 11 request JSON schemas, 26 tables (migrations and models identical,_rewrite/schema-diff_stable-2026.2.md). - Conformance data: Barbican's own black-box suite (
functionaltests/) run against the real paste pipeline and a real Keystone — 563 tests pass, 69 skipped, 0 failures, 2,792 exchanges; in-process API unit tests — 435 tests, 659 exchanges. - Encryption at rest: the reference decryptor (
_rewrite/tools/at_rest_reference.py) decrypted 665/665 secrets written by Python Barbican during that run byte-for-byte as Barbican does; PKCS#11 layouts checked over a software HSM emulation (8/8).
Points of attention
- Encryption at rest is bound to Python class names.
kek_data.plugin_nameand theplugin_namerow ofsecret_store_metadatahold fully qualified Python class names (barbican.plugin.crypto.simple_crypto.SimpleCryptoPlugin,barbican.plugin.store_crypto.StoreCryptoAdapterPlugin), computed bybarbican/common/utils.py:170and used to find the plugin at read time (barbican/plugin/crypto/manager.py:124,barbican/plugin/interface/secret_store.py:622). A rewrite must keep these strings as identifiers. See 07 § 3. - Two base64 layers. The crypto plugin receives raw bytes, but the database column holds
base64(plugin ciphertext); for simple_crypto the plugin ciphertext is itself a url-safe-base64 Fernet token (barbican/plugin/store_crypto.py:86,barbican/plugin/store_crypto.py:314). - Soft delete keeps ciphertexts. Deleting a secret only sets
deletedon the secret and itsencrypted_datarows (barbican/model/models.py:212); ciphertexts stay untilbarbican-manage db clean(barbican/model/clean.py:458). External stores are deleted immediately (barbican/plugin/resources.py:265). - Each plugin parses its own configuration at import time (
barbican/plugin/crypto/simple_crypto.py:29,barbican/common/config.py:221), from the default oslo.config locations, not from the--config-fileof the process. - **Wrong
Locationon consumer registration**:POST /v1/containers/{id}/consumersanswersLocation: …/v1/consumers/{container_id}/consumers(barbican/common/hrefs.py:46), seen in the functional capture. - Non-GET methods accepted where no method dispatch exists:
/v1/secrets/{id}/payloadrejects non-GET itself (barbican/api/controllers/secrets.py:204), but/v1/secret-stores/global-defaultand/preferredanswer any method with the GET behaviour (barbican/api/controllers/secretstores.py:187,barbican/api/controllers/secretstores.py:202). - **
GET /v1/secrets/{id}with a non-JSONAccept** returns the decrypted payload under the strictersecret:decryptpolicy (deprecated path,barbican/api/controllers/__init__.py:57,barbican/api/controllers/secrets.py:128). - Orders without a worker run synchronously inside the API request and any failure is swallowed (
barbican/queue/client.py:76): the client sees202and must poll the order to findERROR. - Keystone listener error path is broken:
handle_errorreadsproject.project_id, which theProjectmodel does not have (barbican/tasks/keystone_consumer.py:79,barbican/model/models.py:272); the exception is caught and logged (barbican/tasks/resources.py:106). - Quota of 0 disables the resource; negative values mean unlimited (
barbican/common/quota.py:190).
Blind spots / [inferred]
- HSMs were not available: the PKCS#11 byte layouts come from the code and were exercised through a software emulation of the mechanisms (
_rewrite/tools/at_rest_reference.py), not against a real token. Vendor-specific behaviour (e.g. HSM-generated GCM IVs,encryption_gen_iv=False) is [inferred]. - KMIP, Vault and Dogtag back ends were read, not run. Their wire formats belong to PyKMIP, castellan and dogtag-pki.
- Multiple secret stores (
[secretstore] enable_multiple_secret_stores): 30 functional tests that need it were skipped; the behaviour is described from the code. - Certificate orders and CAs: the code paths were removed, but the tables (
certificate_authorities,order_plugin_metadata…) and some exceptions remain (dead data model). barbican-tempest-pluginwas not run (needs a full cloud); its expectations were read.
Source: reverse-spec-barbican/00-summary_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.