The below instructions are manual steps to validate the implementation of the Standardized Image Encryption on a DevStack as per upstream patchsets uploaded at https://review.opendev.org/q/topic:%22LUKS-image-encryption%22
For further information see the corresponding Glance Spec and Cinder Spec.
Note: the instructions were created during the implementation phase of the patchsets and details about the process of using image encryption might change in the future and may not reflect the behavior of stable OpenStack releases.
source /opt/stack/devstack/openrc admin admin
# security group to allow SSH access
openstack security group create --description "SSH" admin-access-group
openstack security group rule create --proto tcp --dst-port 22 admin-access-group
# prepare networks and router
PUBLIC_NETWORK=... # name of public network with Floating IPs
openstack network create admin-private
openstack subnet create --network admin-private --subnet-range 10.1.2.0/24 admin-private-subnet
openstack router create admin-public-router
openstack router set --external-gateway $PUBLIC_NETWORK admin-public-router
openstack router add subnet admin-public-router admin-private-subnet
# encrypted Volume Type
# (this assumes that you are using LVM-based Cinder backend in DevStack)
BASIC_TYPE=$(openstack volume type list -f value -c Name | grep lvm)
openstack volume type create \
--property volume_backend_name="$BASIC_TYPE" \
--encryption-provider luks \
--encryption-cipher aes-xts-plain64 \
--encryption-key-size 256 \
--encryption-control-location front-end \
"LVM-LUKS"Example image file for testing:
wget https://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img
qemu-img convert -O raw cirros-0.6.2-x86_64-disk.img cirros.raw- image format will be uncompressed LUKS, which can later be directly transferred to a volume by Cinder
- passphrase will be uploaded as "passphrase" secret type in Barbican; this will make Cinder skip
binascii.hexlify()and use it as LUKS passphrase directly
echo -n "muchsecretsuchwow" > secret_file.key
# upload passphrase to Barbican in plain text
SECRET_TYPE="passphrase"
openstack secret store --file secret_file.key \
--name luks-image-passphrase --secret-type $SECRET_TYPE
SECRET_ID=$(openstack secret list -f value --name luks-image-passphrase \
| head -n1 | cut -d' ' -f1 | rev | cut -d'/' -f1 | rev)
echo $SECRET_ID
# encrypt the image with LUKS using qemu-img
qemu-img convert -f raw -O luks \
--object secret,id=sec,file=secret_file.key \
-o key-secret=sec \
-o cipher-alg=aes-256 \
-o cipher-mode=xts \
-o hash-alg=sha256 \
-o ivgen-alg=plain64 \
-o ivgen-hash-alg=sha256 \
cirros.raw cirros.luks
# upload the encrypted image and reference Barbican secret
openstack image create --file cirros.luks \
--disk-format luks \
--property os_encrypt_key_id=$SECRET_ID \
--property os_encrypt_key_deletion_policy=on_image_deletion \
--property os_encrypt_format=LUKSv1 \
cirros-luks- image format will be uncompressed LUKS, which can later be directly transferred to a volume by Cinder
- passphrase will be uploaded as "symmetric" secret type in Barbican; this will make Cinder convert it to passphrase using
binascii.hexlify()(like it does for its own images of LUKS volumes)
echo -n "muchsecretsuchwow" > secret_file.key
# it is important that the file does not end with a newline!
python3 -c "import binascii;
print(binascii.hexlify('$(cat secret_file.key)'\
.encode('utf-8')).decode('utf-8'), end='')" \
> secret_file.hex
# upload passphrase to Barbican in plain text (Cinder will hexlify later)
SECRET_TYPE="symmetric"
openstack secret store --file secret_file.key \
--name luks-image-key --secret-type $SECRET_TYPE
SECRET_ID=$(openstack secret list -f value --name luks-image-key \
| head -n1 | cut -d' ' -f1 | rev | cut -d'/' -f1 | rev)
echo $SECRET_ID
# encrypt the image with LUKS using qemu-img, use hexlified passphrase
qemu-img convert -f raw -O luks \
--object secret,id=sec,file=secret_file.hex \
-o key-secret=sec \
-o cipher-alg=aes-256 \
-o cipher-mode=xts \
-o hash-alg=sha256 \
-o ivgen-alg=plain64 \
-o ivgen-hash-alg=sha256 \
cirros.raw cirros.luks.hexlified
# upload the encrypted image and reference Barbican secret
openstack image create --file cirros.luks.hexlified \
--disk-format luks \
--property os_encrypt_key_id=$SECRET_ID \
--property os_encrypt_key_deletion_policy=on_image_deletion \
--property os_encrypt_format=LUKSv1 \
cirros-luks-hexlified- image format will be qcow2-wrapped LUKS, which will later be converted to raw LUKS in Cinder
- passphrase will be uploaded as "passphrase" secret type in Barbican; this will make Cinder skip
binascii.hexlify()and use it as LUKS passphrase directly
echo -n "muchsecretsuchwow" > secret_file.key
# upload passphrase to Barbican in plain text
SECRET_TYPE="passphrase"
openstack secret store --file secret_file.key \
--name qcow-image-passphrase --secret-type $SECRET_TYPE
SECRET_ID=$(openstack secret list -f value --name qcow-image-passphrase \
| head -n1 | cut -d' ' -f1 | rev | cut -d'/' -f1 | rev)
echo $SECRET_ID
# encrypt the image with qcow2+LUKS using qemu-img
qemu-img convert -f raw -O qcow2 \
--object secret,id=sec,file=secret_file.key \
-o encrypt.format=luks \
-o encrypt.key-secret=sec \
-o encrypt.cipher-alg=aes-256 \
-o encrypt.cipher-mode=xts \
-o encrypt.hash-alg=sha256 \
-o encrypt.ivgen-alg=plain64 \
-o encrypt.ivgen-hash-alg=sha256 \
cirros.raw cirros.luks.qcow2
# upload the encrypted image and reference Barbican secret
openstack image create --file cirros.luks.qcow2 \
--disk-format qcow2 \
--property os_encrypt_key_id=$SECRET_ID \
--property os_encrypt_key_deletion_policy=on_image_deletion \
--property os_encrypt_format=LUKSv1 \
cirros-qcow2luks- image format will be qcow2-wrapped LUKS, which will later be converted to raw LUKS in Cinder
- passphrase will be uploaded as "symmetric" secret type in Barbican; this will make Cinder convert it to passphrase using
binascii.hexlify()(like it does for its own images of LUKS volumes)
echo -n "muchsecretsuchwow" > secret_file.key
# it is important that the file does not end with a newline!
python3 -c "import binascii;
print(binascii.hexlify('$(cat secret_file.key)'\
.encode('utf-8')).decode('utf-8'), end='')" \
> secret_file.hex
# upload passphrase to Barbican in plain text (Cinder will hexlify later)
SECRET_TYPE="symmetric"
openstack secret store --file secret_file.key \
--name qcow-image-key --secret-type $SECRET_TYPE
SECRET_ID=$(openstack secret list -f value --name qcow-image-key \
| head -n1 | cut -d' ' -f1 | rev | cut -d'/' -f1 | rev)
echo $SECRET_ID
# encrypt the image with qcow2+LUKS using qemu-img, use hexlified passphrase
qemu-img convert -f raw -O qcow2 \
--object secret,id=sec,file=secret_file.hex \
-o encrypt.format=luks \
-o encrypt.key-secret=sec \
-o encrypt.cipher-alg=aes-256 \
-o encrypt.cipher-mode=xts \
-o encrypt.hash-alg=sha256 \
-o encrypt.ivgen-alg=plain64 \
-o encrypt.ivgen-hash-alg=sha256 \
cirros.raw cirros.luks.qcow2.hexlified
# upload the encrypted image and reference Barbican secret
openstack image create --file cirros.luks.qcow2.hexlified \
--disk-format qcow2 \
--property os_encrypt_key_id=$SECRET_ID \
--property os_encrypt_key_deletion_policy=on_image_deletion \
--property os_encrypt_format=LUKSv1 \
cirros-qcow2luks-hexlifiedopenstack volume create --type LVM-LUKS --size 1 \
--image cirros-luks vol-from-luks-img
openstack volume create --type LVM-LUKS --size 1 \
--image cirros-luks-hexlified vol-from-luks-hexlified-img
openstack volume create --type LVM-LUKS --size 1 \
--image cirros-qcow2luks vol-from-qcow2luks-img
openstack volume create --type LVM-LUKS --size 1 \
--image cirros-qcow2luks-hexlified vol-from-qcow2luks-hexlified-imgPUBLIC_NETWORK=... # name of public network with Floating IPs
# the commands below only create Floating IPs that do not exist with the same
# description already
if [ $(openstack floating ip list --long -f value -c Description \
| grep "serverIP1" | wc -l) == 0 ]; \
then openstack floating ip create --description "serverIP1" $PUBLIC_NETWORK; fi
if [ $(openstack floating ip list --long -f value -c Description \
| grep "serverIP2" | wc -l) == 0 ]; \
then openstack floating ip create --description "serverIP2" $PUBLIC_NETWORK; fi
if [ $(openstack floating ip list --long -f value -c Description \
| grep "serverIP3" | wc -l) == 0 ]; \
then openstack floating ip create --description "serverIP3" $PUBLIC_NETWORK; fi
if [ $(openstack floating ip list --long -f value -c Description \
| grep "serverIP4" | wc -l) == 0 ]; \
then openstack floating ip create --description "serverIP4" $PUBLIC_NETWORK; fi# get Floating IP addresses
FIP1=$(openstack floating ip list --long -f value -c Description -c "Floating IP Address" \
| grep "serverIP1" | cut -d ' ' -f1)
FIP2=$(openstack floating ip list --long -f value -c Description -c "Floating IP Address" \
| grep "serverIP2" | cut -d ' ' -f1)
FIP3=$(openstack floating ip list --long -f value -c Description -c "Floating IP Address" \
| grep "serverIP3" | cut -d ' ' -f1)
FIP4=$(openstack floating ip list --long -f value -c Description -c "Floating IP Address" \
| grep "serverIP4" | cut -d ' ' -f1)
FLAVOR=m1.tiny
openstack server create --flavor $FLAVOR --network admin-private \
--volume vol-from-luks-img --security-group admin-access-group \
vm-from-luks-img-vol
openstack server create --flavor $FLAVOR --network admin-private \
--volume vol-from-luks-hexlified-img --security-group admin-access-group \
vm-from-luks-img-vol-hex
openstack server create --flavor $FLAVOR --network admin-private \
--volume vol-from-qcow2luks-img --security-group admin-access-group \
vm-from-qcow2luks-img-vol
openstack server create --flavor $FLAVOR --network admin-private \
--volume vol-from-qcow2luks-hexlified-img \
--security-group admin-access-group \
vm-from-qcow2luks-img-vol-hex
openstack server add floating ip vm-from-luks-img-vol $FIP1
openstack server add floating ip vm-from-luks-img-vol-hex $FIP2
openstack server add floating ip vm-from-qcow2luks-img-vol $FIP3
openstack server add floating ip vm-from-qcow2luks-img-vol-hex $FIP4
# OPTION A) console logs
# (wait a bit after server startup before executing these)
openstack console log show vm-from-luks-img-vol | tail -n10
openstack console log show vm-from-luks-img-vol-hex | tail -n10
openstack console log show vm-from-qcow2luks-img-vol | tail -n10
openstack console log show vm-from-qcow2luks-img-vol-hex | tail -n10
# OPTION B) SSH tests (requires provider network connectivity)
# default password for cirros image is 'gocubsgo'
ssh cirros@$FIP1
ssh cirros@$FIP2
ssh cirros@$FIP3
ssh cirros@$FIP4The image import testing requires appropriate multi-store backends being configured. See the Glance documentation on Multi Store Support for more details.
The following are small snippets for configuring two file-based backends that can be used on a DevStack to configure Glance for testing purposes.
sudo mkdir /opt/stack/data/glance/images/local1
sudo mkdir /opt/stack/data/glance/images/local2
sudo chown stack:stack /opt/stack/data/glance/images/local*
sudo mv /opt/stack/data/glance/images/*-* /opt/stack/data/glance/images/local1Adjust the /etc/glance/glance-api.conf file to configure the backends:
[DEFAULT]
...
enabled_backends = local1:file, local2:file
# note: remove any other previous contents of the glance_store section!
[glance_store]
default_backend = local1
[local1]
filesystem_store_datadir = /opt/stack/data/glance/images/local1
[local2]
filesystem_store_datadir = /opt/stack/data/glance/images/local2
# if using multi-store (enabled_backends), then os_glance_tasks_store
# and os_glance_staging_store become mandatory sections if their path
# is to be changed!
[os_glance_tasks_store]
filesystem_store_datadir = /opt/stack/data/glance/images/tasks
[os_glance_staging_store]
filesystem_store_datadir = /opt/stack/data/glance/images/stagingAlso adjust /etc/glance/glance-image-import.conf for the web download test as necessary:
[import_filtering_opts]
allowed_ports = [80,443,33445](port 33445 added for local hosting purposes)
Finally, restart the Glance API service:
sudo systemctl restart devstack@g-apiThe import test cases will use cirros.luks and the corresponding Barbican secret as prepared by LUKS with plain passphrase above.
SECRET_ID=$(openstack secret list -f value --name luks-image-passphrase \
| head -n1 | cut -d' ' -f1 | rev | cut -d'/' -f1 | rev)
openstack image create \
--property os_encrypt_key_id=$SECRET_ID \
--property os_encrypt_key_deletion_policy=none \
--property os_encrypt_format=LUKSv1 \
--disk-format luks \
imported-cirros-luks-img
# image status should be 'queued' now:
openstack image show imported-cirros-luks-img -f value -c status
# upload encrypted image data to the staging area
openstack image stage --file cirros.luks --progress imported-cirros-luks-img
# image status should be 'uploading' now:
openstack image show imported-cirros-luks-img -f value -c status
openstack image import --method glance-direct --wait imported-cirros-luks-img
# image status should be 'active' now:
openstack image show imported-cirros-luks-img -f value -c status
# create volume and server based on imported image
openstack volume create --type lvmdriver-1-LUKS --size 1 \
--image imported-cirros-luks-img test-luks-img-imported
openstack server create --flavor m1.tiny --network admin-private \
--volume test-luks-img-imported --security-group admin-access-group \
vm-luksimg-imported-vol
openstack console log show vm-luksimg-imported-vol | tail -n10openstack image import --method copy-image --store local2 \
--wait imported-cirros-luks-img
IMAGE_ID=$(openstack image show -f value -c id imported-cirros-luks-img)
# this should show two entries with the same hash:
md5sum /opt/stack/data/glance/images/*/$IMAGE_ID
# this should show both files identified as LUKS-encrypted:
file /opt/stack/data/glance/images/*/$IMAGE_IDFirst, start a local HTTP server serving the encrypted image in a dedicated shell:
mkdir serve
cp cirros.luks serve/
cd serve
python3 -m http.server 33445In a separate shell, execute the test case:
URL="http://localhost:33445/cirros.luks"
openstack image create \
--property os_encrypt_key_id=$SECRET_ID \
--property os_encrypt_key_deletion_policy=none \
--property os_encrypt_format=LUKSv1 \
--disk-format luks \
downloaded-cirros-luks-img
openstack image import --method web-download --uri $URL downloaded-cirros-luks-img
# wait for 'active' status
openstack image show downloaded-cirros-luks-img -f value -c status
# create volume and server based on downloaded image
openstack volume create --type lvmdriver-1-LUKS --size 1 \
--image downloaded-cirros-luks-img test-luks-img-downloaded
openstack server create --flavor m1.tiny --network admin-private \
--volume test-luks-img-downloaded --security-group admin-access-group \
vm-luksimg-downloaded-vol
openstack console log show vm-luksimg-downloaded-vol | tail -n10