End-to-end scenarios — barbican — branch stable/2026.2
Branch
stable/2026.2@de32745a7a4f— repository https://opendev.org/openstack/barbican — analysed on 2026-10-29
All diagrams use the default deployment: store_crypto + simple_crypto, worker enabled ([queue] enable = True). Each scenario was exercised by the functional capture (_rewrite/exchanges-functional_stable-2026.2.json) unless stated otherwise.
1. Store a secret and read it back (castellan, e.g. Cinder volume encryption key)
sequenceDiagram
participant C as castellan (Cinder)
participant KS as Keystone
participant API as barbican-api
participant DB as MariaDB
C->>API: POST /v1/secrets {payload(b64), payload_content_type: application/octet-stream,<br/>payload_content_encoding: base64, secret_type: symmetric, algorithm: AES, bit_length: 256}
API->>KS: GET /v3/auth/tokens (keystonemiddleware, cached)
KS-->>API: 200 token (project, user, roles)
API->>API: policy secrets:post; NewSecretValidator; quota secrets
API->>DB: SELECT projects WHERE external_id (INSERT if new)
API->>DB: SELECT kek_data active for (project, SimpleCryptoPlugin)
alt first secret of the project
API->>DB: INSERT kek_data (unbound)
API->>API: Fernet(master KEK[0]).encrypt(new Fernet key) → plugin_meta
API->>DB: UPDATE kek_data bind_completed, algorithm=aes, bit_length=128, mode=cbc, plugin_meta
end
API->>API: raw = b64decode(payload); token = Fernet(project KEK).encrypt(raw)
API->>DB: INSERT secrets; INSERT encrypted_data (cypher_text = b64(token), kek_id)
API->>DB: INSERT secret_store_metadata (plugin_name, content_type)
API->>DB: COMMIT (TransactionHook)
API-->>C: 201 Location: /v1/secrets/{id} {"secret_ref"}
C->>API: GET /v1/secrets/{id} (Accept: application/json)
API-->>C: 200 {secret_type, algorithm, bit_length, content_types, consumers…}
C->>API: GET /v1/secrets/{id}/payload (Accept: application/octet-stream)
API->>API: policy secret:decrypt (ACL dict + creator + project in target)
API->>DB: SELECT secret, encrypted_data, kek_data (kek_id of the datum)
API->>API: project KEK = MultiFernet(master KEKs).decrypt(plugin_meta);<br/>raw = Fernet(project KEK).decrypt(b64decode(cypher_text))
alt wrong master key / tampered data
API-->>C: 500 "Secret payload retrieval failure seen - please contact site administrator."
else Accept not supported
API-->>C: 406 "Secret payload retrieval issue seen - Wrong payload content-type."
end
API-->>C: 200 raw bytesSource: barbican/api/controllers/secrets.py:473, barbican/plugin/resources.py:67, barbican/plugin/store_crypto.py:67, barbican/plugin/crypto/simple_crypto.py:81, barbican/plugin/resources.py:117, barbican/plugin/store_crypto.py:106. Castellan reads the secret metadata first and then the payload (castellan:castellan/key_manager/barbican_key_manager.py:713).
2. Generate a key through an order (castellan create_key)
sequenceDiagram
participant C as castellan
participant API as barbican-api
participant DB as MariaDB
participant MQ as RabbitMQ
participant W as barbican-worker
C->>API: POST /v1/orders {"type":"key","meta":{"algorithm":"aes","bit_length":256,"mode":"cbc"}}
API->>API: TypeOrderValidator; quota orders
API->>DB: INSERT orders (PENDING); COMMIT
API->>MQ: cast process_type_order(order_id, project_id, request_id)
API-->>C: 202 {"order_ref"}
MQ->>W: process_type_order
W->>DB: SELECT orders WHERE id AND project.external_id
W->>W: pick plugin (generate_supports) → store_crypto → simple_crypto.generate_symmetric (os.urandom)
W->>DB: INSERT secrets, encrypted_data, secret_store_metadata
alt success
W->>DB: UPDATE orders SET status=ACTIVE, secret_id
else failure
W->>DB: UPDATE orders SET status=ERROR, error_status_code, error_reason
end
W->>DB: COMMIT
loop every retry_delay (1 s), up to number_of_retries (60)
C->>API: GET /v1/orders/{id}
API-->>C: 200 {"status": "PENDING" | "ACTIVE" | "ERROR", "secret_ref"…}
end
C->>API: DELETE /v1/orders/{id}
API-->>C: 204Source: barbican/api/controllers/orders.py:163, barbican/queue/server.py:205, barbican/tasks/resources.py:245, barbican/plugin/resources.py:163, castellan:castellan/key_manager/barbican_key_manager.py:511. Without a worker the cast is a direct in-process call made before the 202 is sent (barbican/queue/client.py:76).
3. Delete a secret that is in use (microversion 1.2)
sequenceDiagram
participant N as Nova (castellan add_consumer)
participant U as Owner (barbicanclient)
participant API as barbican-api
participant DB as MariaDB
N->>API: POST /v1/secrets/{id}/consumers {"service":"nova","resource_type":"instance","resource_id":"…"}
API->>DB: INSERT secret_consumer_metadata (or revive soft-deleted row)
API-->>N: 200 {secret representation with consumers}
U->>API: DELETE /v1/secrets/{id} (OpenStack-API-Version: key-manager 1.2)
API->>DB: SELECT consumers of the secret
alt consumers exist and no force
API-->>U: 400 "Secret deletion issue seen - Secret cannot be deleted as it has consumers.."
else ?force=true (barbicanclient sends force=1)
API->>DB: soft delete secret, encrypted_data, metadata; delete ACLs, consumers
API-->>U: 204
endSource: barbican/api/controllers/consumers.py:318, barbican/api/controllers/secrets.py:258, python-barbicanclient:barbicanclient/v1/secrets.py:600. With microversion 1.0/1.1 the delete always succeeds and silently drops the consumers (barbican/api/controllers/secrets.py:264).
4. Keystone project deletion
sequenceDiagram
participant KS as Keystone
participant MQ as RabbitMQ (exchange keystone)
participant L as barbican-keystone-listener
participant DB as MariaDB
KS->>MQ: notification identity.project.deleted {resource_info: <project id>}
MQ->>L: info(ctxt, publisher_id, event_type, payload)
L->>DB: SELECT projects WHERE external_id
alt project unknown to Barbican
L-->>MQ: HANDLED (nothing to do)
else known
L->>DB: soft delete containers, secrets (+ children), kek_data, project — one transaction
alt success
L->>DB: COMMIT
L-->>MQ: HANDLED
else error
L->>DB: ROLLBACK
L-->>MQ: REQUEUE (allow_requeue) or HANDLED (dropped)
end
endSource: barbican/queue/keystone_listener.py:52, barbican/tasks/keystone_consumer.py:102, barbican/model/repositories.py:274. Data stored in external back ends (Vault, KMIP) is not deleted by this path [inferred: delete_project_entities only touches rows, barbican/model/repositories.py:513]. Not covered by the capture (the listener needs a bus).
5. Rotate the simple_crypto master key and a project KEK (operator)
sequenceDiagram participant O as Operator participant M as barbican-manage participant DB as MariaDB O->>O: prepend the new Fernet key to [simple_crypto_plugin] kek (old key kept second) O->>M: simple_crypto rewrap_pkek M->>DB: SELECT kek_data per project (plugin = SimpleCryptoPlugin) M->>M: plugin_meta = MultiFernet([new, old]).rotate(plugin_meta) M->>DB: UPDATE kek_data.plugin_meta (one transaction per KEK) O->>O: remove the old key from the configuration O->>M: simple_crypto new_pkek --project <keystone id> M->>DB: deactivate the active KEK, INSERT a new unbound KEK (bound on next use) O->>M: simple_crypto rewrap_secrets --project <keystone id> M->>M: for each secret: MultiFernet([new pKEK, old pKEK]).rotate(token) M->>DB: UPDATE encrypted_data.cypher_text, kek_id = new KEK
Source: barbican/cmd/simple_crypto.py:90, barbican/cmd/simple_crypto.py:40, barbican/cmd/simple_crypto.py:51, barbican/plugin/crypto/simple_crypto.py:104. Secrets keep pointing to the KEK row that encrypted them (encrypted_data.kek_id), so a deactivated KEK still decrypts its old secrets (barbican/plugin/store_crypto.py:122). The PKCS#11 equivalent is barbican-manage hsm rewrap_pkek / pkcs11-kek-rewrap (barbican/cmd/pkcs11_kek_rewrap.py:49), which unwraps each project KEK with the current MKEK and re-wraps it with the new one.
6. Two-step upload and private secret
POST /v1/secrets {"name": "s"}→201(no payload,GET …/payload→404 … has no payload).PUT /v1/secrets/{id}Content-Type: text/plain, bodymy secret→204; a secondPUT→409.PUT /v1/secrets/{id}/acl {"read": {"project-access": false, "users": ["<user>"]}}→200; another member of the project now gets403 "Secret payload retrieval attempt not allowed …", the listed user and the creator still read it (policy rulesecret_decrypt_non_private_read,barbican/common/policies/base.py:110).
All three steps are in the functional capture (functionaltests.api.v1.smoke.test_secrets.SecretsTestCase.test_secret_update_two_phase, functionaltests.api.v1.functional.test_acls_rbac).
Source: reverse-spec-barbican/04-scenarios_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.