mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-03 15:53:54 -06:00
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:
parent
0725570b2d
commit
42e4ac9ef5
2 changed files with 465 additions and 6 deletions
361
block/rbd.c
361
block/rbd.c
|
@ -73,6 +73,18 @@
|
|||
#define LIBRBD_USE_IOVEC 0
|
||||
#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 {
|
||||
RBD_AIO_READ,
|
||||
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. */
|
||||
static int qemu_rbd_do_create(BlockdevCreateOptions *options,
|
||||
const char *keypairs, const char *password_secret,
|
||||
|
@ -368,6 +577,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
|
|||
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) {
|
||||
int64_t objsize = opts->cluster_size;
|
||||
if ((objsize - 1) & objsize) { /* not a power of 2? */
|
||||
|
@ -393,6 +609,28 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
|
|||
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;
|
||||
out:
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
const char *filename,
|
||||
QemuOpts *opts,
|
||||
|
@ -413,6 +688,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
|
|||
BlockdevCreateOptions *create_options;
|
||||
BlockdevCreateOptionsRbd *rbd_opts;
|
||||
BlockdevOptionsRbd *loc;
|
||||
RbdEncryptionCreateOptions *encrypt = NULL;
|
||||
Error *local_err = NULL;
|
||||
const char *keypairs, *password_secret;
|
||||
QDict *options = NULL;
|
||||
|
@ -441,6 +717,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
|
|||
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
|
||||
* 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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (r < 0) {
|
||||
error_setg_errno(errp, -r, "error getting image size from %s",
|
||||
s->image_name);
|
||||
rbd_close(s->image);
|
||||
goto failed_open;
|
||||
goto failed_post_open;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp);
|
||||
if (r < 0) {
|
||||
rbd_close(s->image);
|
||||
goto failed_open;
|
||||
goto failed_post_open;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -790,6 +1084,8 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
r = 0;
|
||||
goto out;
|
||||
|
||||
failed_post_open:
|
||||
rbd_close(s->image);
|
||||
failed_open:
|
||||
rados_ioctx_destroy(s->io_ctx);
|
||||
g_free(s->snap);
|
||||
|
@ -1060,6 +1356,46 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
|
|||
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)
|
||||
{
|
||||
BDRVRBDState *s = bs->opaque;
|
||||
|
@ -1253,6 +1589,22 @@ static QemuOptsList qemu_rbd_create_opts = {
|
|||
.type = QEMU_OPT_STRING,
|
||||
.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 */ }
|
||||
}
|
||||
};
|
||||
|
@ -1282,6 +1634,7 @@ static BlockDriver bdrv_rbd = {
|
|||
.bdrv_co_create_opts = qemu_rbd_co_create_opts,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_get_info = qemu_rbd_getinfo,
|
||||
.bdrv_get_specific_info = qemu_rbd_get_specific_info,
|
||||
.create_opts = &qemu_rbd_create_opts,
|
||||
.bdrv_getlength = qemu_rbd_getlength,
|
||||
.bdrv_co_truncate = qemu_rbd_co_truncate,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue