OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

barbican — Scenarios

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 bytes

Source: 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: 204

Source: 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
  end

Source: 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
  end

Source: 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

  1. POST /v1/secrets {"name": "s"} → 201 (no payload, GET …/payload → 404 … has no payload).
  2. PUT /v1/secrets/{id} Content-Type: text/plain, body my secret → 204; a second PUT → 409.
  3. PUT /v1/secrets/{id}/acl {"read": {"project-access": false, "users": ["<user>"]}} → 200; another member of the project now gets 403 "Secret payload retrieval attempt not allowed …", the listed user and the creator still read it (policy rule secret_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.