block/rbd: Add support for rbd image encryption

Starting from ceph Pacific, RBD has built-in support for image-level encryption.
Currently supported formats are LUKS version 1 and 2.

There are 2 new relevant librbd APIs for controlling encryption, both expect an
open image context:

rbd_encryption_format: formats an image (i.e. writes the LUKS header)
rbd_encryption_load: loads encryptor/decryptor to the image IO stack

This commit extends the qemu rbd driver API to support the above.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
Message-Id: <20210627114635.39326-1-oro@il.ibm.com>
Reviewed-by: Ilya Dryomov <idryomov@gmail.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Or Ozeri 2021-06-27 14:46:35 +03:00 committed by Kevin Wolf
parent 0725570b2d
commit 42e4ac9ef5
2 changed files with 465 additions and 6 deletions

View File

@ -73,6 +73,18 @@
#define LIBRBD_USE_IOVEC 0 #define LIBRBD_USE_IOVEC 0
#endif #endif
#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
static const char rbd_luks_header_verification[
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
};
static const char rbd_luks2_header_verification[
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
};
typedef enum { typedef enum {
RBD_AIO_READ, RBD_AIO_READ,
RBD_AIO_WRITE, RBD_AIO_WRITE,
@ -351,6 +363,203 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
} }
} }
#ifdef LIBRBD_SUPPORTS_ENCRYPTION
static int qemu_rbd_convert_luks_options(
RbdEncryptionOptionsLUKSBase *luks_opts,
char **passphrase,
size_t *passphrase_len,
Error **errp)
{
return qcrypto_secret_lookup(luks_opts->key_secret, (uint8_t **)passphrase,
passphrase_len, errp);
}
static int qemu_rbd_convert_luks_create_options(
RbdEncryptionCreateOptionsLUKSBase *luks_opts,
rbd_encryption_algorithm_t *alg,
char **passphrase,
size_t *passphrase_len,
Error **errp)
{
int r = 0;
r = qemu_rbd_convert_luks_options(
qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
passphrase, passphrase_len, errp);
if (r < 0) {
return r;
}
if (luks_opts->has_cipher_alg) {
switch (luks_opts->cipher_alg) {
case QCRYPTO_CIPHER_ALG_AES_128: {
*alg = RBD_ENCRYPTION_ALGORITHM_AES128;
break;
}
case QCRYPTO_CIPHER_ALG_AES_256: {
*alg = RBD_ENCRYPTION_ALGORITHM_AES256;
break;
}
default: {
r = -ENOTSUP;
error_setg_errno(errp, -r, "unknown encryption algorithm: %u",
luks_opts->cipher_alg);
return r;
}
}
} else {
/* default alg */
*alg = RBD_ENCRYPTION_ALGORITHM_AES256;
}
return 0;
}
static int qemu_rbd_encryption_format(rbd_image_t image,
RbdEncryptionCreateOptions *encrypt,
Error **errp)
{
int r = 0;
g_autofree char *passphrase = NULL;
size_t passphrase_len;
rbd_encryption_format_t format;
rbd_encryption_options_t opts;
rbd_encryption_luks1_format_options_t luks_opts;
rbd_encryption_luks2_format_options_t luks2_opts;
size_t opts_size;
uint64_t raw_size, effective_size;
r = rbd_get_size(image, &raw_size);
if (r < 0) {
error_setg_errno(errp, -r, "cannot get raw image size");
return r;
}
switch (encrypt->format) {
case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
memset(&luks_opts, 0, sizeof(luks_opts));
format = RBD_ENCRYPTION_FORMAT_LUKS1;
opts = &luks_opts;
opts_size = sizeof(luks_opts);
r = qemu_rbd_convert_luks_create_options(
qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
&luks_opts.alg, &passphrase, &passphrase_len, errp);
if (r < 0) {
return r;
}
luks_opts.passphrase = passphrase;
luks_opts.passphrase_size = passphrase_len;
break;
}
case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
memset(&luks2_opts, 0, sizeof(luks2_opts));
format = RBD_ENCRYPTION_FORMAT_LUKS2;
opts = &luks2_opts;
opts_size = sizeof(luks2_opts);
r = qemu_rbd_convert_luks_create_options(
qapi_RbdEncryptionCreateOptionsLUKS2_base(
&encrypt->u.luks2),
&luks2_opts.alg, &passphrase, &passphrase_len, errp);
if (r < 0) {
return r;
}
luks2_opts.passphrase = passphrase;
luks2_opts.passphrase_size = passphrase_len;
break;
}
default: {
r = -ENOTSUP;
error_setg_errno(
errp, -r, "unknown image encryption format: %u",
encrypt->format);
return r;
}
}
r = rbd_encryption_format(image, format, opts, opts_size);
if (r < 0) {
error_setg_errno(errp, -r, "encryption format fail");
return r;
}
r = rbd_get_size(image, &effective_size);
if (r < 0) {
error_setg_errno(errp, -r, "cannot get effective image size");
return r;
}
r = rbd_resize(image, raw_size + (raw_size - effective_size));
if (r < 0) {
error_setg_errno(errp, -r, "cannot resize image after format");
return r;
}
return 0;
}
static int qemu_rbd_encryption_load(rbd_image_t image,
RbdEncryptionOptions *encrypt,
Error **errp)
{
int r = 0;
g_autofree char *passphrase = NULL;
size_t passphrase_len;
rbd_encryption_luks1_format_options_t luks_opts;
rbd_encryption_luks2_format_options_t luks2_opts;
rbd_encryption_format_t format;
rbd_encryption_options_t opts;
size_t opts_size;
switch (encrypt->format) {
case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
memset(&luks_opts, 0, sizeof(luks_opts));
format = RBD_ENCRYPTION_FORMAT_LUKS1;
opts = &luks_opts;
opts_size = sizeof(luks_opts);
r = qemu_rbd_convert_luks_options(
qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
&passphrase, &passphrase_len, errp);
if (r < 0) {
return r;
}
luks_opts.passphrase = passphrase;
luks_opts.passphrase_size = passphrase_len;
break;
}
case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
memset(&luks2_opts, 0, sizeof(luks2_opts));
format = RBD_ENCRYPTION_FORMAT_LUKS2;
opts = &luks2_opts;
opts_size = sizeof(luks2_opts);
r = qemu_rbd_convert_luks_options(
qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
&passphrase, &passphrase_len, errp);
if (r < 0) {
return r;
}
luks2_opts.passphrase = passphrase;
luks2_opts.passphrase_size = passphrase_len;
break;
}
default: {
r = -ENOTSUP;
error_setg_errno(
errp, -r, "unknown image encryption format: %u",
encrypt->format);
return r;
}
}
r = rbd_encryption_load(image, format, opts, opts_size);
if (r < 0) {
error_setg_errno(errp, -r, "encryption load fail");
return r;
}
return 0;
}
#endif
/* FIXME Deprecate and remove keypairs or make it available in QMP. */ /* FIXME Deprecate and remove keypairs or make it available in QMP. */
static int qemu_rbd_do_create(BlockdevCreateOptions *options, static int qemu_rbd_do_create(BlockdevCreateOptions *options,
const char *keypairs, const char *password_secret, const char *keypairs, const char *password_secret,
@ -368,6 +577,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
return -EINVAL; return -EINVAL;
} }
#ifndef LIBRBD_SUPPORTS_ENCRYPTION
if (opts->has_encrypt) {
error_setg(errp, "RBD library does not support image encryption");
return -ENOTSUP;
}
#endif
if (opts->has_cluster_size) { if (opts->has_cluster_size) {
int64_t objsize = opts->cluster_size; int64_t objsize = opts->cluster_size;
if ((objsize - 1) & objsize) { /* not a power of 2? */ if ((objsize - 1) & objsize) { /* not a power of 2? */
@ -393,6 +609,28 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
goto out; goto out;
} }
#ifdef LIBRBD_SUPPORTS_ENCRYPTION
if (opts->has_encrypt) {
rbd_image_t image;
ret = rbd_open(io_ctx, opts->location->image, &image, NULL);
if (ret < 0) {
error_setg_errno(errp, -ret,
"error opening image '%s' for encryption format",
opts->location->image);
goto out;
}
ret = qemu_rbd_encryption_format(image, opts->encrypt, errp);
rbd_close(image);
if (ret < 0) {
/* encryption format fail, try removing the image */
rbd_remove(io_ctx, opts->location->image);
goto out;
}
}
#endif
ret = 0; ret = 0;
out: out:
rados_ioctx_destroy(io_ctx); rados_ioctx_destroy(io_ctx);
@ -405,6 +643,43 @@ static int qemu_rbd_co_create(BlockdevCreateOptions *options, Error **errp)
return qemu_rbd_do_create(options, NULL, NULL, errp); return qemu_rbd_do_create(options, NULL, NULL, errp);
} }
static int qemu_rbd_extract_encryption_create_options(
QemuOpts *opts,
RbdEncryptionCreateOptions **spec,
Error **errp)
{
QDict *opts_qdict;
QDict *encrypt_qdict;
Visitor *v;
int ret = 0;
opts_qdict = qemu_opts_to_qdict(opts, NULL);
qdict_extract_subqdict(opts_qdict, &encrypt_qdict, "encrypt.");
qobject_unref(opts_qdict);
if (!qdict_size(encrypt_qdict)) {
*spec = NULL;
goto exit;
}
/* Convert options into a QAPI object */
v = qobject_input_visitor_new_flat_confused(encrypt_qdict, errp);
if (!v) {
ret = -EINVAL;
goto exit;
}
visit_type_RbdEncryptionCreateOptions(v, NULL, spec, errp);
visit_free(v);
if (!*spec) {
ret = -EINVAL;
goto exit;
}
exit:
qobject_unref(encrypt_qdict);
return ret;
}
static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv, static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
const char *filename, const char *filename,
QemuOpts *opts, QemuOpts *opts,
@ -413,6 +688,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
BlockdevCreateOptions *create_options; BlockdevCreateOptions *create_options;
BlockdevCreateOptionsRbd *rbd_opts; BlockdevCreateOptionsRbd *rbd_opts;
BlockdevOptionsRbd *loc; BlockdevOptionsRbd *loc;
RbdEncryptionCreateOptions *encrypt = NULL;
Error *local_err = NULL; Error *local_err = NULL;
const char *keypairs, *password_secret; const char *keypairs, *password_secret;
QDict *options = NULL; QDict *options = NULL;
@ -441,6 +717,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
goto exit; goto exit;
} }
ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
if (ret < 0) {
goto exit;
}
rbd_opts->encrypt = encrypt;
rbd_opts->has_encrypt = !!encrypt;
/* /*
* Caution: while qdict_get_try_str() is fine, getting non-string * Caution: while qdict_get_try_str() is fine, getting non-string
* types would require more care. When @options come from -blockdev * types would require more care. When @options come from -blockdev
@ -766,12 +1049,24 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
goto failed_open; goto failed_open;
} }
if (opts->has_encrypt) {
#ifdef LIBRBD_SUPPORTS_ENCRYPTION
r = qemu_rbd_encryption_load(s->image, opts->encrypt, errp);
if (r < 0) {
goto failed_post_open;
}
#else
r = -ENOTSUP;
error_setg(errp, "RBD library does not support image encryption");
goto failed_post_open;
#endif
}
r = rbd_get_size(s->image, &s->image_size); r = rbd_get_size(s->image, &s->image_size);
if (r < 0) { if (r < 0) {
error_setg_errno(errp, -r, "error getting image size from %s", error_setg_errno(errp, -r, "error getting image size from %s",
s->image_name); s->image_name);
rbd_close(s->image); goto failed_post_open;
goto failed_open;
} }
/* If we are using an rbd snapshot, we must be r/o, otherwise /* If we are using an rbd snapshot, we must be r/o, otherwise
@ -779,8 +1074,7 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
if (s->snap != NULL) { if (s->snap != NULL) {
r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp); r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp);
if (r < 0) { if (r < 0) {
rbd_close(s->image); goto failed_post_open;
goto failed_open;
} }
} }
@ -790,6 +1084,8 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
r = 0; r = 0;
goto out; goto out;
failed_post_open:
rbd_close(s->image);
failed_open: failed_open:
rados_ioctx_destroy(s->io_ctx); rados_ioctx_destroy(s->io_ctx);
g_free(s->snap); g_free(s->snap);
@ -1060,6 +1356,46 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
return 0; return 0;
} }
static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
Error **errp)
{
BDRVRBDState *s = bs->opaque;
ImageInfoSpecific *spec_info;
char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
int r;
if (s->image_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
r = rbd_read(s->image, 0,
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf);
if (r < 0) {
error_setg_errno(errp, -r, "cannot read image start for probe");
return NULL;
}
}
spec_info = g_new(ImageInfoSpecific, 1);
*spec_info = (ImageInfoSpecific){
.type = IMAGE_INFO_SPECIFIC_KIND_RBD,
.u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
};
if (memcmp(buf, rbd_luks_header_verification,
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
spec_info->u.rbd.data->encryption_format =
RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
spec_info->u.rbd.data->has_encryption_format = true;
} else if (memcmp(buf, rbd_luks2_header_verification,
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
spec_info->u.rbd.data->encryption_format =
RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
spec_info->u.rbd.data->has_encryption_format = true;
} else {
spec_info->u.rbd.data->has_encryption_format = false;
}
return spec_info;
}
static int64_t qemu_rbd_getlength(BlockDriverState *bs) static int64_t qemu_rbd_getlength(BlockDriverState *bs)
{ {
BDRVRBDState *s = bs->opaque; BDRVRBDState *s = bs->opaque;
@ -1253,6 +1589,22 @@ static QemuOptsList qemu_rbd_create_opts = {
.type = QEMU_OPT_STRING, .type = QEMU_OPT_STRING,
.help = "ID of secret providing the password", .help = "ID of secret providing the password",
}, },
{
.name = "encrypt.format",
.type = QEMU_OPT_STRING,
.help = "Encrypt the image, format choices: 'luks', 'luks2'",
},
{
.name = "encrypt.cipher-alg",
.type = QEMU_OPT_STRING,
.help = "Name of encryption cipher algorithm"
" (allowed values: aes-128, aes-256)",
},
{
.name = "encrypt.key-secret",
.type = QEMU_OPT_STRING,
.help = "ID of secret providing LUKS passphrase",
},
{ /* end of list */ } { /* end of list */ }
} }
}; };
@ -1282,6 +1634,7 @@ static BlockDriver bdrv_rbd = {
.bdrv_co_create_opts = qemu_rbd_co_create_opts, .bdrv_co_create_opts = qemu_rbd_co_create_opts,
.bdrv_has_zero_init = bdrv_has_zero_init_1, .bdrv_has_zero_init = bdrv_has_zero_init_1,
.bdrv_get_info = qemu_rbd_getinfo, .bdrv_get_info = qemu_rbd_getinfo,
.bdrv_get_specific_info = qemu_rbd_get_specific_info,
.create_opts = &qemu_rbd_create_opts, .create_opts = &qemu_rbd_create_opts,
.bdrv_getlength = qemu_rbd_getlength, .bdrv_getlength = qemu_rbd_getlength,
.bdrv_co_truncate = qemu_rbd_co_truncate, .bdrv_co_truncate = qemu_rbd_co_truncate,

View File

@ -127,6 +127,18 @@
'extents': ['ImageInfo'] 'extents': ['ImageInfo']
} } } }
##
# @ImageInfoSpecificRbd:
#
# @encryption-format: Image encryption format
#
# Since: 6.1
##
{ 'struct': 'ImageInfoSpecificRbd',
'data': {
'*encryption-format': 'RbdImageEncryptionFormat'
} }
## ##
# @ImageInfoSpecific: # @ImageInfoSpecific:
# #
@ -141,7 +153,8 @@
# If we need to add block driver specific parameters for # If we need to add block driver specific parameters for
# LUKS in future, then we'll subclass QCryptoBlockInfoLUKS # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
# to define a ImageInfoSpecificLUKS # to define a ImageInfoSpecificLUKS
'luks': 'QCryptoBlockInfoLUKS' 'luks': 'QCryptoBlockInfoLUKS',
'rbd': 'ImageInfoSpecificRbd'
} } } }
## ##
@ -3613,6 +3626,94 @@
{ 'enum': 'RbdAuthMode', { 'enum': 'RbdAuthMode',
'data': [ 'cephx', 'none' ] } 'data': [ 'cephx', 'none' ] }
##
# @RbdImageEncryptionFormat:
#
# Since: 6.1
##
{ 'enum': 'RbdImageEncryptionFormat',
'data': [ 'luks', 'luks2' ] }
##
# @RbdEncryptionOptionsLUKSBase:
#
# @key-secret: ID of a QCryptoSecret object providing a passphrase
# for unlocking the encryption
#
# Since: 6.1
##
{ 'struct': 'RbdEncryptionOptionsLUKSBase',
'data': { 'key-secret': 'str' } }
##
# @RbdEncryptionCreateOptionsLUKSBase:
#
# @cipher-alg: The encryption algorithm
#
# Since: 6.1
##
{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
'base': 'RbdEncryptionOptionsLUKSBase',
'data': { '*cipher-alg': 'QCryptoCipherAlgorithm' } }
##
# @RbdEncryptionOptionsLUKS:
#
# Since: 6.1
##
{ 'struct': 'RbdEncryptionOptionsLUKS',
'base': 'RbdEncryptionOptionsLUKSBase',
'data': { } }
##
# @RbdEncryptionOptionsLUKS2:
#
# Since: 6.1
##
{ 'struct': 'RbdEncryptionOptionsLUKS2',
'base': 'RbdEncryptionOptionsLUKSBase',
'data': { } }
##
# @RbdEncryptionCreateOptionsLUKS:
#
# Since: 6.1
##
{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
'base': 'RbdEncryptionCreateOptionsLUKSBase',
'data': { } }
##
# @RbdEncryptionCreateOptionsLUKS2:
#
# Since: 6.1
##
{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
'base': 'RbdEncryptionCreateOptionsLUKSBase',
'data': { } }
##
# @RbdEncryptionOptions:
#
# Since: 6.1
##
{ 'union': 'RbdEncryptionOptions',
'base': { 'format': 'RbdImageEncryptionFormat' },
'discriminator': 'format',
'data': { 'luks': 'RbdEncryptionOptionsLUKS',
'luks2': 'RbdEncryptionOptionsLUKS2' } }
##
# @RbdEncryptionCreateOptions:
#
# Since: 6.1
##
{ 'union': 'RbdEncryptionCreateOptions',
'base': { 'format': 'RbdImageEncryptionFormat' },
'discriminator': 'format',
'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
'luks2': 'RbdEncryptionCreateOptionsLUKS2' } }
## ##
# @BlockdevOptionsRbd: # @BlockdevOptionsRbd:
# #
@ -3628,6 +3729,8 @@
# #
# @snapshot: Ceph snapshot name. # @snapshot: Ceph snapshot name.
# #
# @encrypt: Image encryption options. (Since 6.1)
#
# @user: Ceph id name. # @user: Ceph id name.
# #
# @auth-client-required: Acceptable authentication modes. # @auth-client-required: Acceptable authentication modes.
@ -3650,6 +3753,7 @@
'image': 'str', 'image': 'str',
'*conf': 'str', '*conf': 'str',
'*snapshot': 'str', '*snapshot': 'str',
'*encrypt': 'RbdEncryptionOptions',
'*user': 'str', '*user': 'str',
'*auth-client-required': ['RbdAuthMode'], '*auth-client-required': ['RbdAuthMode'],
'*key-secret': 'str', '*key-secret': 'str',
@ -4403,13 +4507,15 @@
# point to a snapshot. # point to a snapshot.
# @size: Size of the virtual disk in bytes # @size: Size of the virtual disk in bytes
# @cluster-size: RBD object size # @cluster-size: RBD object size
# @encrypt: Image encryption options. (Since 6.1)
# #
# Since: 2.12 # Since: 2.12
## ##
{ 'struct': 'BlockdevCreateOptionsRbd', { 'struct': 'BlockdevCreateOptionsRbd',
'data': { 'location': 'BlockdevOptionsRbd', 'data': { 'location': 'BlockdevOptionsRbd',
'size': 'size', 'size': 'size',
'*cluster-size' : 'size' } } '*cluster-size' : 'size',
'*encrypt' : 'RbdEncryptionCreateOptions' } }
## ##
# @BlockdevVmdkSubformat: # @BlockdevVmdkSubformat: