Interactions — barbican — branch stable/2026.2
Branch
stable/2026.2@de32745a7a4f— repository https://opendev.org/openstack/barbican — analysed on 2026-10-29
1. Database
1.1 Access pattern
- SQLAlchemy 2 ORM on an oslo.db engine (
barbican/model/repositories.py:196) with a thread-localscoped_session(barbican/model/repositories.py:113). Every API request is one transaction: Pecan'sTransactionHookcallsrepositories.start/commit/rollback/cleararound the handler (barbican/api/hooks.py:41); a GET is "read only" for Pecan but uses the same session. POST /v1/orderscommits inside the handler before casting to the worker, so the worker can read the order (barbican/api/controllers/orders.py:197). Quotas are committed explicitly too (barbican/common/quota.py:112).- Workers wrap each task in its own transaction (
barbican/queue/server.py:72); the Keystone listener and the retry scheduler do the same (barbican/tasks/keystone_consumer.py:47,barbican/queue/retry_scheduler.py:135). - No
SELECT … FOR UPDATEanywhere. Concurrency is handled by unique constraints and "catch the duplicate, re-read": project creation (barbican/common/resources.py:52), consumer registration (barbican/model/repositories.py:1460). Concurrent KEK creation can leave several active KEKs; the oldest is kept, the others are deactivated on the next use (barbican/model/repositories.py:965). - Schema:
[DEFAULT] db_auto_create = Truecreates the tables from the models and stamps Alembic head, or runs the upgrade whenalembic_versionexists (barbican/model/repositories.py:230); production runsbarbican-manage db upgrade(barbican/cmd/barbican_manage.py:115). Alembic head on this branch:8c74e2d7f1ff(3 revisions since the Ocata rebase). - Soft delete: most tables carry
deleted/deleted_at(barbican/model/models.py:209); reads filterdeleted = false;barbican-manage db cleanpurges (barbican/model/clean.py:458).
1.2 Tables
| Table | Role | Written by | Read by |
|---|---|---|---|
projects | Barbican's view of a Keystone project (external_id unique) | first request of a project (barbican/common/resources.py:34) | every request |
secrets | secret metadata (no payload): name, type, algorithm, bit_length, mode, expiration, creator_id | POST /v1/secrets, orders | secrets API, containers |
encrypted_data | ciphertext of a secret for store_crypto: cypher_text, kek_meta_extended, content_type, kek_id → kek_data | barbican/plugin/store_crypto.py:303 | barbican/plugin/store_crypto.py:119 |
kek_data | per-project, per-crypto-plugin KEK: plugin_name, kek_label, active, bind_completed, plugin_meta (wrapped KEK) | barbican/model/repositories.py:970, barbican/plugin/store_crypto.py:297 | barbican/plugin/store_crypto.py:277 |
secret_store_metadata | key/value per secret: always plugin_name (store plugin class) and content_type; plus back-end references (key_uuid for KMIP, key_id/version for Vault…) | barbican/plugin/resources.py:338 | barbican/plugin/resources.py:330 |
secret_user_metadata | user key/value metadata | secret metadata API | secret metadata API |
secret_acls, secret_acl_users | per-operation ACL with project_access, user list | ACL API | policy target (barbican/api/controllers/__init__.py:185), acl_only listing |
containers, container_secret | containers and their named secret links | containers API, asymmetric orders (barbican/plugin/resources.py:380) | containers API |
container_acls, container_acl_users | container ACLs | ACL API | policy target |
container_consumer_metadata | (container_id, name, URL) consumers | consumers API | container representation |
secret_consumer_metadata | (secret_id, service, resource_type, resource_id) consumers | consumers API | secret representation (1.1+), delete check (1.2+) |
orders | asynchronous requests: type, meta (JSON), status, secret_id / container_id, error_status_code, error_reason, sub_status* | orders API, worker | orders API, worker |
order_retry_tasks | scheduled retries: retry_task, retry_at, retry_args, retry_kwargs (JSON) | worker (barbican/queue/server.py:173) | retry scheduler |
order_plugin_metadata, order_barbican_metadata | certificate-order metadata | — (feature removed) | — |
transport_keys | plugin_name, transport_key | transport-key API, barbican/plugin/resources.py:41 | idem |
project_quotas | per-project quota overrides (NULL = default) | project-quotas API | quota enforcement |
secret_stores, project_secret_store | configured stores (multiple back ends) and a project's preferred store | start-up sync (barbican/plugin/util/multiple_backends.py:106), preferred API | store selection |
certificate_authorities, certificate_authority_metadata, project_certificate_authorities, preferred_certificate_authorities | certificate authorities | — (feature removed) | delete_all_project_resources cascade only |
The exact DDL is in _rewrite/schema-sqlite-migrated_stable-2026.2.sql (migrations, the reference) and _rewrite/schema-mysql_stable-2026.2.sql / schema-postgresql_stable-2026.2.sql (models); _rewrite/schema-diff_stable-2026.2.md shows no difference between models and migrations.
1.3 Representative queries
- Secret list (
barbican/model/repositories.py:590):sql SELECT secrets.* FROM secrets JOIN projects ON projects.id = secrets.project_id WHERE secrets.deleted = 0 AND (secrets.expiration IS NULL OR secrets.expiration > :now) AND secrets.name LIKE :name ... -- optional filters AND projects.external_id = :project ORDER BY ... LIMIT :limit OFFSET :offset; -- plus a COUNT(*) of the same query for "total"Withacl_only=truethe project join is replaced byJOIN secret_acls JOIN secret_acl_users WHERE secret_acl_users.user_id = :user(barbican/model/repositories.py:640). - Secret by id (
barbican/model/repositories.py:759): by id, not deleted, not expired — without project filter (the project check is the policy's job,barbican/api/controllers/secrets.py:366). - Active KEK (
barbican/model/repositories.py:1000):SELECT * FROM kek_data WHERE project_id = :p AND plugin_name = :fqcn AND active AND NOT deleted ORDER BY created_at. - Quota count:
SELECT COUNT(*)of the project's non-deleted entities of the type (barbican/model/repositories.py:495).
2. AMQP / RabbitMQ (oslo.messaging)
2.1 Topology
| Exchange | Type | Routing key / queue | Producer | Consumer |
|---|---|---|---|---|
[DEFAULT] control_exchange (oslo default openstack) | topic | barbican.workers ([queue] topic) and barbican.workers.barbican.queue (server) | barbican-api, barbican-retry | barbican-worker (barbican/queue/__init__.py:62) |
keystone ([keystone_notifications] control_exchange) | topic | notifications.info ([keystone_notifications] topic + priority), optional pool pool_name | Keystone | barbican-keystone-listener (barbican/queue/__init__.py:88) |
The RPC target is Target(topic='barbican.workers', namespace='barbican', version='1.1', server='barbican.queue') (barbican/common/config.py:116); the server uses DefaultRPCAccessPolicy (barbican/queue/__init__.py:80). Exceptions allowed to travel back: barbican.common.exception (barbican/queue/__init__.py:36).
2.2 RPC methods
| Method | Direction | Mode | Topic / server | Version | Arguments | Response | Defined at |
|---|---|---|---|---|---|---|---|
process_type_order | api / retry → worker | cast | barbican.workers | 1.1 | order_id (Barbican UUID), project_id (Keystone project id), request_id | none | client barbican/queue/client.py:41, server barbican/queue/server.py:205 |
There are no calls (barbican/queue/client.py:58 exists but is unused). When [queue] enable = False the same method is invoked in-process (barbican/queue/client.py:63).
2.3 Sample frame (cast process_type_order)
{"oslo.version": "2.0",
"oslo.message": "{\"method\": \"process_type_order\", \"namespace\": \"barbican\", \"version\": \"1.1\",
\"args\": {\"order_id\": \"5d1f6a8e-...\", \"project_id\": \"8c13fb9eea6a46388a3bd8316f26ce5c\",
\"request_id\": \"req-a0e40e18-...\"}, \"_unique_id\": \"...\"}"}
The context is empty ({}, barbican/queue/client.py:56): no user token or roles travel with the task.
2.4 Consumed notification (Keystone)
| event_type | Priority | Payload used | Effect | Handled at |
|---|---|---|---|---|
identity.project.deleted | info | resource_info (Keystone project id) | delete all Barbican data of the project | barbican/queue/keystone_listener.py:52 |
| any other | any | — | ignored (returns None = acknowledged) | barbican/queue/keystone_listener.py:104 |
Sample frame (Keystone basic notification format):
{"event_type": "identity.project.deleted", "publisher_id": "identity.keystone-host",
"priority": "INFO", "payload": {"resource_info": "2b99a94ad02741978e613fb52dd1f4cd"},
"message_id": "...", "timestamp": "2026-10-29 07:00:00.000000"}
Barbican emits no notifications of its own (the oslo.messaging notification options are only listed for the audit middleware, etc/oslo-config-generator/barbican.conf:15).
3. Keystone
- Token validation by keystonemiddleware
auth_tokenin the/v1pipeline (etc/barbican/barbican-api-paste.ini:16,[keystone_authtoken]); it callsGET /v3/auth/tokenswith its service token, caches results, and setsX-Identity-Status,X-Project-Id,X-User-Id,X-Roles,X-Service-*… thatContextMiddlewareturns into aRequestContext(barbican/api/middleware/context.py:80).is_admin= the role[DEFAULT] admin_role(defaultadmin) is in the token (barbican/api/middleware/context.py:87). - Optional
keystonemiddleware.auditfilter (pipelinebarbican-api-keystone-audit,etc/barbican/barbican-api-paste.ini:20) emits CADF audit events withetc/barbican/api_audit_map.conf. - Barbican makes no other outbound Keystone call.
4. Secret-store and crypto back ends
4.1 Plugin contracts
Secret-store plugin (barbican/plugin/interface/secret_store.py:391, namespace barbican.secretstore.plugin, [secretstore] enabled_secretstore_plugins, default store_crypto):
| Method | Called when | Returns |
|---|---|---|
get_plugin_name() | listing stores | display name |
store_secret_supports(key_spec) / generate_supports(key_spec) | choosing a plugin, in configuration order (barbican/plugin/interface/secret_store.py:573, barbican/plugin/interface/secret_store.py:627) | bool |
store_secret(secret_dto) | POST/PUT payload; secret_dto.secret is base64 text of the raw bytes | dict saved to secret_store_metadata |
get_secret(secret_type, metadata) | GET payload; metadata = the secret's secret_store_metadata | SecretDTO with base64 secret |
generate_symmetric_key(key_spec) / generate_asymmetric_key(key_spec) | orders | metadata dict / AsymmetricKeyMetadataDTO |
delete_secret(metadata) | DELETE secret | — |
get_transport_key() / is_transport_key_current(key) | transport-key flows (Dogtag only) | key or None |
The plugin that stored a secret is found again by its fully qualified class name saved in secret_store_metadata.plugin_name (barbican/plugin/resources.py:345, barbican/plugin/interface/secret_store.py:606).
Crypto plugin (barbican/plugin/crypto/base.py:241, namespace barbican.crypto.plugin, [crypto] enabled_crypto_plugins, default simple_crypto), used only through StoreCryptoAdapterPlugin (barbican/plugin/store_crypto.py:50):
| Method | Input | Output |
|---|---|---|
bind_kek_metadata(kek_meta_dto) | an unbound kek_data row | the DTO with algorithm, bit_length, mode, plugin_meta (wrapped project KEK) |
encrypt(EncryptDTO(raw bytes), kek_meta_dto, external_project_id) | raw secret bytes | ResponseDTO(cypher_text bytes, kek_meta_extended str|None) |
decrypt(DecryptDTO(cypher bytes), kek_meta_dto, kek_meta_extended, external_project_id) | raw bytes | |
generate_symmetric(GenerateDTO) / generate_asymmetric(GenerateDTO) | algorithm, bit length, mode, passphrase | ResponseDTO / three ResponseDTO (private, public, passphrase) |
supports(type, algorithm, bit_length, mode) | ENCRYPT_DECRYPT, SYMMETRIC_KEY_GENERATION, ASYMMETRIC_KEY_GENERATION | bool |
The exact at-rest formats of simple_crypto and p11_crypto are specified in 07 § 3.
4.2 Back ends
| Back end | Protocol / library | Stored in Barbican's DB | Configuration | Defined at |
|---|---|---|---|---|
simple_crypto (default) | none (Fernet in-process) | ciphertext + wrapped project KEK | [simple_crypto_plugin] kek (MultiStr, Fernet keys) | barbican/plugin/crypto/simple_crypto.py:55 |
p11_crypto | PKCS#11 C API via cffi: C_Initialize, C_OpenSession, C_Login, C_FindObjects (MKEK/HMAC by label), C_GenerateKey, C_WrapKey/C_UnwrapKey, C_SignInit/C_Sign/C_Verify (HMAC), C_Encrypt/C_Decrypt, C_GenerateRandom, C_SeedRandom | ciphertext + wrapped project KEK (JSON) | [p11_crypto_plugin] (library_path, login, slot_id / token_serial_number / token_labels, mkek_label, hmac_label, mechanisms…) | barbican/plugin/crypto/p11_crypto.py:134, barbican/plugin/crypto/pkcs11.py:451 |
| KMIP | KMIP 1.x over TLS, PyKMIP ProxyKmipClient (create, create_key_pair, register, get, destroy) | secret_store_metadata.key_uuid | [kmip_plugin] (host, port, username/password or client certificate, pkcs1_only) | barbican/plugin/kmip_secret_store.py:151 |
| Vault | HTTPS KV v1/v2 through castellan VaultKeyManager: POST {vault_url}/v1/{kv_mountpoint}/[data/]{kv_path/}{uuid4hex} with {"type", "value": hex(bytes), "algorithm", "bit_length", "name", "created"} (wrapped in {"data": …} for v2), GET, DELETE (castellan:castellan/key_manager/vault_key_manager.py:386) | secret_store_metadata.key_id (+ version: 1, alg, bit_length for generated pairs) | [vault_plugin] (root_token_id or AppRole, vault_url, kv_mountpoint, kv_path, kv_version, namespace, ssl_ca_crt_file) | barbican/plugin/vault_secret_store.py:77, barbican/plugin/castellan_secret_store.py:30 |
| Dogtag KRA | HTTPS, dogtag-pki client | KRA key ids | [dogtag_plugin] | barbican/plugin/dogtag.py:148 |
Vault secrets written before castellan metadata version 1 are read as legacy (base64 re-encoding heuristics, barbican/plugin/castellan_secret_store.py:57).
4.3 Multiple back ends
With [secretstore] enable_multiple_secret_stores = True, the stores are read from [secretstore:<suffix>] sections listed in stores_lookup_suffix (barbican/plugin/util/multiple_backends.py:32), exactly one of them global_default. New secrets go to the project's preferred store, else to the global default; reads always use the plugin recorded with the secret (barbican/plugin/util/multiple_backends.py:262).
5. Other
- HSM key management CLI (
barbican-manage hsm gen_mkek|check_mkek|gen_hmac|check_hmac|rewrap_pkek,barbican/cmd/barbican_manage.py:171): creates the token objects labelledmkek_label(AES,CKA_TOKEN, wrap/unwrap) andhmac_label(sign/verify). - No subprocess, no files: Barbican writes nothing to disk at run time (except logs); PKCS#11 may read
[p11_crypto_plugin] seed_fileat start (barbican/plugin/crypto/p11_crypto.py:347). - Healthcheck:
GET /healthcheck, disabled by the file/etc/barbican/healthcheck_disable(etc/barbican/barbican-api-paste.ini:59).
Source: reverse-spec-barbican/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.