How-to: Enable Volume Encryption with Barbican
An encrypted volume is a volume whose type carries an encryption profile. Cinder asks its key manager for a key when the volume is created, the NFS driver writes the file as a LUKS container, and the key manager holds the passphrase for the life of the volume. This guide creates such a type on the ControlPlane devstack, creates a volume of it, proves the key reached Barbican, and deletes both again.
For the full field reference, see the Cinder CRD and the Barbican Operator.
Prerequisites
Devstack
This guide is written against the Quick Start (ControlPlane) devstack. Stand it up first:
KIND_HOST_PORT=8443 WITH_CONTROLPLANE=true WITH_NFS=true make deploy-infraFollow that tutorial through the block-storage block of Step 4 and the Create a first volume check in Step 7, so the projected controlplane-cinder child is Ready in openstack with nfs1 and nfsbk attached. Every resource name in the examples below is one that devstack produces.
Set storage on the ControlPlane, never on the projected children
The controlplane-cinder Cinder CR, the CinderBackend nfs1 and the CinderBackupBackend nfsbk are projected by the c5c3-operator. A child you edit, or delete, by hand is reverted (or recreated) on the next reconcile. Change services.cinder on the ControlPlane CR and let the operator project it down; that block is the single source of truth for the projected storage.
- The
OS_*environment variables from the tutorial's token-issue step, and thepython-barbicanclientplugin its prerequisites name, which is where theopenstack secretsubcommands come from.
What the ControlPlane projects
Nothing in services.cinder turns encryption on. The key manager follows the sibling block: while services.barbican is declared, the reconciler gives the Cinder child a keyManager of type Barbican pointing at the projected Barbican's in-cluster Service, and leaves the block absent otherwise. The devstack declares Barbican, so the child already carries it:
kubectl get cinder controlplane-cinder -n openstack \
-o jsonpath='{.spec.keyManager}' | jq{
"type": "Barbican",
"barbican": { "endpoint": "http://controlplane-barbican.openstack.svc:9311" }
}Castellan authenticates with the same credentials the service user holds, which is why setting a key manager requires keystoneEndpoint beside it.
The roles that service user holds are the other half. The KeystoneService registration the ControlPlane projects for Cinder creates the account in a project of its own, service-cinder, with the roles service and admin. No peer registration takes the second one. Cinder deletes the Barbican secret of an encrypted volume as a fallback when the volume's owner cannot, and every Barbican the operator renders carries [oslo_policy] enforce_new_defaults = true, whose secure-RBAC defaults put secret:get and secret:delete across projects behind a bare admin. Without that role the fallback fails and the key outlives the volume. The ControlPlane CRD API Reference carries the same reasoning field by field.
What the admin role opens
Across every project, not only Cinder's own. secret:get and secret:delete accept a bare admin wherever it is held, so anything that reaches this service user's credential — a compromised Cinder pod, which carries the password in its environment, or a reader of the controlplane-cinder-credentials Secret — can read and permanently delete every tenant's volume encryption key. A deleted key makes its LUKS volume unrecoverable; nothing else holds the passphrase.
What the role buys is one fallback path: the key of a volume deleted by someone other than its owner. Weigh the two. Where an orphaned key is the cheaper failure, grant service alone and sweep orphans out of band. Where the role is kept, restrict get on Secrets in the service namespace to the operator's ServiceAccount, and alert on a secret:delete this user issues against a project that is not its own.
Step 1 — Create an encrypted type and a volume
Count the secrets first, so the next command has a baseline to move:
openstack --insecure secret list -f value -c 'Secret href' | wc -lCreate a volume type with a LUKS encryption profile, then a volume of that type:
openstack --insecure volume type create --encryption-provider luks \
--encryption-cipher aes-xts-plain64 --encryption-key-size 256 \
--encryption-control-location front-end demo-luks
openstack --insecure volume create --size 1 --type demo-luks demo-enc
openstack --insecure volume show demo-enc -c status -f valueOnce the volume reads available, count again:
openstack --insecure secret list -f value -c 'Secret href' | wc -lThe count is one higher. That single number is what separates a working key manager from a decorative one: the type and the volume are created just the same against a key manager that is never called, and only the secret proves cinder reached Barbican.
Step 2 — Where the LUKS format happens
The format is the driver's work, not a later step's. For a volume whose type carries an encryption_key_id, the NFS driver's _create_encrypted_volume_file in cinder/volume/drivers/remotefs.py fetches the key from the key manager, writes the passphrase to a temporary file, and creates the backing file itself:
qemu-img create -f qcow2 \
-o encrypt.format=luks,encrypt.key-secret=sec1,encrypt.cipher-alg=...,encrypt.cipher-mode=...,encrypt.ivgen-alg=... \
--object secret,id=sec1,format=raw,file=<passphrase file>That branch is taken before the driver consults its format options, so it applies under the nfs_qcow2_volumes = false the operator renders for every backend: an unencrypted volume on this backend is a raw file, an encrypted one is a qcow2 LUKS container.
The key itself is older than the file. It is created in the API, in the ExtractVolumeRequestTask of the volume-create flow, with the request context of the user issuing the create. The volume service then fetches it back through castellan when it writes the file.
Step 3 — Delete the volume
Deleting the volume is what returns the key:
openstack --insecure volume delete demo-enc
openstack --insecure secret list -f value -c 'Secret href' | wc -lThe count is back at the baseline from Step 1. A key still there after the volume is gone is the symptom the admin role exists to prevent, and it points at the service user's roles rather than at cinder.
Remove the type:
openstack --insecure volume type delete demo-luksNo hypervisor on this stack
Unlocking the LUKS container is os-brick's work in the compute service at attach time, which is what --encryption-control-location front-end selects. This devstack runs no hypervisor that could attach a volume. An encrypted volume can therefore be created, listed, backed up and deleted here, and never attached or written on.
Standalone Cinder, without a ControlPlane
Without a ControlPlane nothing derives the key manager from a sibling service, so the block goes on the Cinder CR you own:
apiVersion: cinder.openstack.c5c3.io/v1alpha1
kind: Cinder
metadata:
name: cinder
namespace: openstack
spec:
keyManager:
type: Barbican
barbican:
endpoint: http://barbican.openstack.svc.cluster.local:9311A CEL rule requires keystoneEndpoint beside it, because castellan authenticates as the same service user. The endpoint has to parse to a URL with a host, and the webhook checks that too.
Grant that service user the admin role on its project in addition to service. Under Barbican's secure-RBAC defaults a project-scoped delete is not enough for the fallback path, so a volume deleted by anyone other than its owner leaves its key behind in Barbican with nothing left to name it. The grant reaches further than that path: admin held on any project carries secret:get and secret:delete on every project's secrets, so this service user can read and destroy key material Cinder does not own. Weigh that against leaving the fallback path off, as What the ControlPlane projects sets out above, and scope who may read the Secret holding this password accordingly.
See also
- Cinder CRD — the
KeyManagerSpecunion rule and thekeystoneEndpointdependency. - Barbican Operator — why
enforce_new_defaultsis on for every Barbican and what the new rules grant. - ControlPlane CRD API Reference — the projected registration, its two roles, and the derived
keyManager. - Attach an NFS Backend to Cinder — the backend whose driver writes the LUKS container.
Tested by
The encrypted-type block of tests/e2e/c5c3/full-controlplane-keystone/01-openstack-verify-job.yaml runs the same four commands against the projected ControlPlane: it creates the LUKS type, creates a volume of it, deletes the volume, and deletes the type. It counts the Barbican secrets on both sides of each half, and fails the run when the count does not rise by one after the create or fall back after the delete.
chainsaw test --test-dir tests/e2e/c5c3/full-controlplane-keystoneThis guide embeds no fixture. Those commands live inside that Job's container script, a YAML block scalar, and the column-0 # region marker a VitePress snippet import needs would end the scalar where it was written.