qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/13] LUKS: encryption slot management using amend interface
@ 2020-01-14 19:33 Maxim Levitsky
  2020-01-14 19:33 ` [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment Maxim Levitsky
                   ` (14 more replies)
  0 siblings, 15 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Hi!

Here is the updated series of my patches, incorporating all the feedback I received.

Patches are strictly divided by topic to 3 groups, and each group depends on former groups.

* Patches 1,2 implement qcrypto generic amend interface, including definition
  of structs used in crypto.json and implement this in luks crypto driver
  Nothing is exposed to the user at this stage

* Patches 3-9 use the code from patches 1,2 to implement qemu-img amend based encryption slot management
  for luks and for qcow2, and add a bunch of iotests to cover that.

* Patches 10-13 add x-blockdev-amend (I'll drop the -x prefix if you like), and wire it
  to luks and qcow2 driver to implement qmp based encryption slot management also using
  the code from patches 1,2, and also add a bunch of iotests to cover this.

Best regards,
        Maxim Levitsky

Maxim Levitsky (13):
  qcrypto: add generic infrastructure for crypto options amendment
  qcrypto-luks: implement encryption key management
  block: amend: add 'force' option
  block: amend: separate amend and create options for qemu-img
  block/crypto: rename two functions
  block/crypto: implement the encryption key management
  qcow2: extend qemu-img amend interface with crypto options
  iotests: filter few more luks specific create options
  qemu-iotests: qemu-img tests for luks key management
  block: add generic infrastructure for x-blockdev-amend qmp command
  block/crypto: implement blockdev-amend
  block/qcow2: implement blockdev-amend
  iotests: add tests for blockdev-amend

 block.c                          |   4 +-
 block/Makefile.objs              |   2 +-
 block/amend.c                    | 108 +++++++++
 block/crypto.c                   | 204 +++++++++++++++--
 block/crypto.h                   |  34 +++
 block/qcow2.c                    | 269 +++++++++++++++-------
 crypto/block-luks.c              | 374 ++++++++++++++++++++++++++++++-
 crypto/block.c                   |  31 +++
 crypto/blockpriv.h               |   8 +
 include/block/block.h            |   1 +
 include/block/block_int.h        |  24 +-
 include/crypto/block.h           |  22 ++
 qapi/block-core.json             |  68 ++++++
 qapi/crypto.json                 |  64 ++++++
 qapi/job.json                    |   4 +-
 qemu-img-cmds.hx                 |   4 +-
 qemu-img.c                       |  26 ++-
 qemu-img.texi                    |   6 +-
 tests/qemu-iotests/087.out       |   6 +-
 tests/qemu-iotests/134.out       |   2 +-
 tests/qemu-iotests/158.out       |   4 +-
 tests/qemu-iotests/188.out       |   2 +-
 tests/qemu-iotests/189.out       |   4 +-
 tests/qemu-iotests/198.out       |   4 +-
 tests/qemu-iotests/300           | 207 +++++++++++++++++
 tests/qemu-iotests/300.out       |  99 ++++++++
 tests/qemu-iotests/301           |  90 ++++++++
 tests/qemu-iotests/301.out       |  30 +++
 tests/qemu-iotests/302           | 284 +++++++++++++++++++++++
 tests/qemu-iotests/302.out       |  40 ++++
 tests/qemu-iotests/303           | 235 +++++++++++++++++++
 tests/qemu-iotests/303.out       |  33 +++
 tests/qemu-iotests/common.filter |   6 +-
 tests/qemu-iotests/group         |   6 +
 34 files changed, 2174 insertions(+), 131 deletions(-)
 create mode 100644 block/amend.c
 create mode 100755 tests/qemu-iotests/300
 create mode 100644 tests/qemu-iotests/300.out
 create mode 100755 tests/qemu-iotests/301
 create mode 100644 tests/qemu-iotests/301.out
 create mode 100644 tests/qemu-iotests/302
 create mode 100644 tests/qemu-iotests/302.out
 create mode 100644 tests/qemu-iotests/303
 create mode 100644 tests/qemu-iotests/303.out

-- 
2.17.2



^ permalink raw reply	[flat|nested] 84+ messages in thread

* [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-28 16:59   ` Daniel P. Berrangé
  2020-01-14 19:33 ` [PATCH 02/13] qcrypto-luks: implement encryption key management Maxim Levitsky
                   ` (13 subsequent siblings)
  14 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This will be used first to implement luks keyslot management.

block_crypto_amend_opts_init will be used to convert
qemu-img cmdline to QCryptoBlockAmendOptions

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/crypto.c         | 17 +++++++++++++++++
 block/crypto.h         |  3 +++
 crypto/block.c         | 31 +++++++++++++++++++++++++++++++
 crypto/blockpriv.h     |  8 ++++++++
 include/crypto/block.h | 22 ++++++++++++++++++++++
 qapi/crypto.json       | 16 ++++++++++++++++
 6 files changed, 97 insertions(+)

diff --git a/block/crypto.c b/block/crypto.c
index 24823835c1..ecf96a7a9b 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -184,6 +184,23 @@ block_crypto_create_opts_init(QDict *opts, Error **errp)
     return ret;
 }
 
+QCryptoBlockAmendOptions *
+block_crypto_amend_opts_init(QDict *opts, Error **errp)
+{
+    Visitor *v;
+    QCryptoBlockAmendOptions *ret;
+
+    v = qobject_input_visitor_new_flat_confused(opts, errp);
+    if (!v) {
+        return NULL;
+    }
+
+    visit_type_QCryptoBlockAmendOptions(v, NULL, &ret, errp);
+
+    visit_free(v);
+    return ret;
+}
+
 
 static int block_crypto_open_generic(QCryptoBlockFormat format,
                                      QemuOptsList *opts_spec,
diff --git a/block/crypto.h b/block/crypto.h
index b935695e79..06e044c9be 100644
--- a/block/crypto.h
+++ b/block/crypto.h
@@ -91,6 +91,9 @@
 QCryptoBlockCreateOptions *
 block_crypto_create_opts_init(QDict *opts, Error **errp);
 
+QCryptoBlockAmendOptions *
+block_crypto_amend_opts_init(QDict *opts, Error **errp);
+
 QCryptoBlockOpenOptions *
 block_crypto_open_opts_init(QDict *opts, Error **errp);
 
diff --git a/crypto/block.c b/crypto/block.c
index 325752871c..0ce67db641 100644
--- a/crypto/block.c
+++ b/crypto/block.c
@@ -115,6 +115,37 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
 }
 
 
+int qcrypto_block_amend_options(QCryptoBlock *block,
+                                QCryptoBlockReadFunc readfunc,
+                                QCryptoBlockWriteFunc writefunc,
+                                void *opaque,
+                                QCryptoBlockAmendOptions *options,
+                                bool force,
+                                Error **errp)
+{
+    if (options->format != block->format) {
+        error_setg(errp,
+                   "Cannot amend encryption format");
+        return -1;
+    }
+
+    if (!block->driver->amend) {
+        error_setg(errp,
+                   "Crypto format %s doesn't support format options amendment",
+                   QCryptoBlockFormat_str(block->format));
+        return -1;
+    }
+
+    return block->driver->amend(block,
+                                readfunc,
+                                writefunc,
+                                opaque,
+                                options,
+                                force,
+                                errp);
+}
+
+
 QCryptoBlockInfo *qcrypto_block_get_info(QCryptoBlock *block,
                                          Error **errp)
 {
diff --git a/crypto/blockpriv.h b/crypto/blockpriv.h
index 71c59cb542..3c7ccea504 100644
--- a/crypto/blockpriv.h
+++ b/crypto/blockpriv.h
@@ -62,6 +62,14 @@ struct QCryptoBlockDriver {
                   void *opaque,
                   Error **errp);
 
+    int (*amend)(QCryptoBlock *block,
+                 QCryptoBlockReadFunc readfunc,
+                 QCryptoBlockWriteFunc writefunc,
+                 void *opaque,
+                 QCryptoBlockAmendOptions *options,
+                 bool force,
+                 Error **errp);
+
     int (*get_info)(QCryptoBlock *block,
                     QCryptoBlockInfo *info,
                     Error **errp);
diff --git a/include/crypto/block.h b/include/crypto/block.h
index d49d2c2da9..e4553cf33d 100644
--- a/include/crypto/block.h
+++ b/include/crypto/block.h
@@ -144,6 +144,28 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
                                    void *opaque,
                                    Error **errp);
 
+/**
+ * qcrypto_block_amend_options:
+ * @block: the block encryption object
+ *
+ * @readfunc: callback for reading data from the volume header
+ * @writefunc: callback for writing data to the volume header
+ * @opaque: data to pass to @readfunc and @writefunc
+ * @options: the new/amended encryption options
+ * @force: hint for the driver to allow unsafe operation
+ * @errp: error pointer
+ *
+ * Changes the crypto options of the encryption format
+ *
+ */
+int qcrypto_block_amend_options(QCryptoBlock *block,
+                                QCryptoBlockReadFunc readfunc,
+                                QCryptoBlockWriteFunc writefunc,
+                                void *opaque,
+                                QCryptoBlockAmendOptions *options,
+                                bool force,
+                                Error **errp);
+
 
 /**
  * qcrypto_block_get_info:
diff --git a/qapi/crypto.json b/qapi/crypto.json
index b2a4cff683..9faebd03d4 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -309,3 +309,19 @@
   'base': 'QCryptoBlockInfoBase',
   'discriminator': 'format',
   'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
+
+
+
+##
+# @QCryptoBlockAmendOptions:
+#
+# The options that are available for all encryption formats
+# when initializing a new volume
+#
+# Since: 5.0
+##
+{ 'union': 'QCryptoBlockAmendOptions',
+  'base': 'QCryptoBlockOptionsBase',
+  'discriminator': 'format',
+  'data': {
+            } }
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
  2020-01-14 19:33 ` [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-21  7:54   ` Markus Armbruster
                     ` (2 more replies)
  2020-01-14 19:33 ` [PATCH 03/13] block: amend: add 'force' option Maxim Levitsky
                   ` (12 subsequent siblings)
  14 siblings, 3 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Next few patches will expose that functionality
to the user.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 crypto/block-luks.c | 374 +++++++++++++++++++++++++++++++++++++++++++-
 qapi/crypto.json    |  50 +++++-
 2 files changed, 421 insertions(+), 3 deletions(-)

diff --git a/crypto/block-luks.c b/crypto/block-luks.c
index 4861db810c..349e95fed3 100644
--- a/crypto/block-luks.c
+++ b/crypto/block-luks.c
@@ -32,6 +32,7 @@
 #include "qemu/uuid.h"
 
 #include "qemu/coroutine.h"
+#include "qemu/bitmap.h"
 
 /*
  * Reference for the LUKS format implemented here is
@@ -70,6 +71,9 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
 
 #define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL
 
+#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS 2000
+#define QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS 40
+
 static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = {
     'L', 'U', 'K', 'S', 0xBA, 0xBE
 };
@@ -219,6 +223,9 @@ struct QCryptoBlockLUKS {
 
     /* Hash algorithm used in pbkdf2 function */
     QCryptoHashAlgorithm hash_alg;
+
+    /* Name of the secret that was used to open the image */
+    char *secret;
 };
 
 
@@ -1069,6 +1076,112 @@ qcrypto_block_luks_find_key(QCryptoBlock *block,
     return -1;
 }
 
+/*
+ * Returns true if a slot i is marked as active
+ * (contains encrypted copy of the master key)
+ */
+static bool
+qcrypto_block_luks_slot_active(const QCryptoBlockLUKS *luks,
+                               unsigned int slot_idx)
+{
+    uint32_t val = luks->header.key_slots[slot_idx].active;
+    return val ==  QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED;
+}
+
+/*
+ * Returns the number of slots that are marked as active
+ * (slots that contain encrypted copy of the master key)
+ */
+static unsigned int
+qcrypto_block_luks_count_active_slots(const QCryptoBlockLUKS *luks)
+{
+    size_t i = 0;
+    unsigned int ret = 0;
+
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        if (qcrypto_block_luks_slot_active(luks, i)) {
+            ret++;
+        }
+    }
+    return ret;
+}
+
+/*
+ * Finds first key slot which is not active
+ * Returns the key slot index, or -1 if it doesn't exist
+ */
+static int
+qcrypto_block_luks_find_free_keyslot(const QCryptoBlockLUKS *luks)
+{
+    size_t i;
+
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        if (!qcrypto_block_luks_slot_active(luks, i)) {
+            return i;
+        }
+    }
+    return -1;
+
+}
+
+/*
+ * Erases an keyslot given its index
+ * Returns:
+ *    0 if the keyslot was erased successfully
+ *   -1 if a error occurred while erasing the keyslot
+ *
+ */
+static int
+qcrypto_block_luks_erase_key(QCryptoBlock *block,
+                             unsigned int slot_idx,
+                             QCryptoBlockWriteFunc writefunc,
+                             void *opaque,
+                             Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
+    g_autofree uint8_t *garbagesplitkey = NULL;
+    size_t splitkeylen = luks->header.master_key_len * slot->stripes;
+    size_t i;
+
+    assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+    assert(splitkeylen > 0);
+
+    garbagesplitkey = g_new0(uint8_t, splitkeylen);
+
+    /* Reset the key slot header */
+    memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN);
+    slot->iterations = 0;
+    slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
+
+    qcrypto_block_luks_store_header(block,  writefunc, opaque, errp);
+
+    /*
+     * Now try to erase the key material, even if the header
+     * update failed
+     */
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) {
+        if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) {
+            /*
+             * If we failed to get the random data, still write
+             * at least zeros to the key slot at least once
+             */
+            if (i > 0) {
+                return -1;
+            }
+        }
+
+        if (writefunc(block,
+                      slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
+                      garbagesplitkey,
+                      splitkeylen,
+                      opaque,
+                      errp) != splitkeylen) {
+            return -1;
+        }
+    }
+    return 0;
+}
 
 static int
 qcrypto_block_luks_open(QCryptoBlock *block,
@@ -1099,6 +1212,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
 
     luks = g_new0(QCryptoBlockLUKS, 1);
     block->opaque = luks;
+    luks->secret = g_strdup(options->u.luks.key_secret);
 
     if (qcrypto_block_luks_load_header(block, readfunc, opaque, errp) < 0) {
         goto fail;
@@ -1164,6 +1278,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
  fail:
     qcrypto_block_free_cipher(block);
     qcrypto_ivgen_free(block->ivgen);
+    g_free(luks->secret);
     g_free(luks);
     return -1;
 }
@@ -1204,7 +1319,7 @@ qcrypto_block_luks_create(QCryptoBlock *block,
 
     memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts));
     if (!luks_opts.has_iter_time) {
-        luks_opts.iter_time = 2000;
+        luks_opts.iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
     }
     if (!luks_opts.has_cipher_alg) {
         luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
@@ -1244,6 +1359,8 @@ qcrypto_block_luks_create(QCryptoBlock *block,
                    optprefix ? optprefix : "");
         goto error;
     }
+    luks->secret = g_strdup(options->u.luks.key_secret);
+
     password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
     if (!password) {
         goto error;
@@ -1471,10 +1588,260 @@ qcrypto_block_luks_create(QCryptoBlock *block,
     qcrypto_block_free_cipher(block);
     qcrypto_ivgen_free(block->ivgen);
 
+    g_free(luks->secret);
     g_free(luks);
     return -1;
 }
 
+/*
+ * Given LUKSKeyslotUpdate command, return @slots_bitmap with all slots
+ * that will be updated with new password (or erased)
+ * returns number of affected slots
+ */
+static int qcrypto_block_luks_get_slots_bitmap(QCryptoBlock *block,
+                                               QCryptoBlockReadFunc readfunc,
+                                               void *opaque,
+                                               const LUKSKeyslotUpdate *command,
+                                               unsigned long *slots_bitmap,
+                                               Error **errp)
+{
+    const QCryptoBlockLUKS *luks = block->opaque;
+    size_t i;
+    int ret = 0;
+
+    if (command->has_keyslot) {
+        /* keyslot set, select only this keyslot */
+        int keyslot = command->keyslot;
+
+        if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
+            error_setg(errp,
+                       "Invalid slot %u specified, must be between 0 and %u",
+                       keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
+            goto error;
+        }
+        bitmap_set(slots_bitmap, keyslot, 1);
+        ret++;
+
+    } else if (command->has_old_secret) {
+        /* initially select all active keyslots */
+        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+            if (qcrypto_block_luks_slot_active(luks, i)) {
+                bitmap_set(slots_bitmap, i, 1);
+                ret++;
+            }
+        }
+    } else {
+        /* find a free keyslot */
+        int slot = qcrypto_block_luks_find_free_keyslot(luks);
+
+        if (slot == -1) {
+            error_setg(errp,
+                       "Can't add a keyslot - all key slots are in use");
+            goto error;
+        }
+        bitmap_set(slots_bitmap, slot, 1);
+        ret++;
+    }
+
+    if (command->has_old_secret) {
+        /* now deselect all keyslots that don't contain the password */
+        g_autofree uint8_t *tmpkey = g_new0(uint8_t,
+                                            luks->header.master_key_len);
+
+        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+            g_autofree char *old_password = NULL;
+            int rv;
+
+            if (!test_bit(i, slots_bitmap)) {
+                continue;
+            }
+
+            old_password = qcrypto_secret_lookup_as_utf8(command->old_secret,
+                                                         errp);
+            if (!old_password) {
+                goto error;
+            }
+
+            rv = qcrypto_block_luks_load_key(block,
+                                             i,
+                                             old_password,
+                                             tmpkey,
+                                             readfunc,
+                                             opaque,
+                                             errp);
+            if (rv == -1)
+                goto error;
+            else if (rv == 0) {
+                bitmap_clear(slots_bitmap, i, 1);
+                ret--;
+            }
+        }
+    }
+    return ret;
+error:
+    return -1;
+}
+
+/*
+ * Apply a single keyslot update command as described in @command
+ * Optionally use @unlock_secret to retrieve the master key
+ */
+static int
+qcrypto_block_luks_apply_keyslot_update(QCryptoBlock *block,
+                                        QCryptoBlockReadFunc readfunc,
+                                        QCryptoBlockWriteFunc writefunc,
+                                        void *opaque,
+                                        LUKSKeyslotUpdate *command,
+                                        const char *unlock_secret,
+                                        uint8_t **master_key,
+                                        bool force,
+                                        Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    g_autofree unsigned long *slots_bitmap = NULL;
+    int64_t iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
+    int slot_count;
+    size_t i;
+    char *new_password;
+    bool erasing;
+
+    slots_bitmap = bitmap_new(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+    slot_count = qcrypto_block_luks_get_slots_bitmap(block, readfunc, opaque,
+                                                     command, slots_bitmap,
+                                                     errp);
+    if (slot_count == -1) {
+        goto error;
+    }
+    /* no matching slots, so nothing to do */
+    if (slot_count == 0) {
+        error_setg(errp, "Requested operation didn't match any slots");
+        goto error;
+    }
+    /*
+     * slot is erased when the password is set to null, or empty string
+     * (for compatibility with command line)
+     */
+    erasing = command->new_secret->type == QTYPE_QNULL ||
+              strlen(command->new_secret->u.s) == 0;
+
+    /* safety checks */
+    if (!force) {
+        if (erasing) {
+            if (slot_count == qcrypto_block_luks_count_active_slots(luks)) {
+                error_setg(errp,
+                           "Requested operation will erase all active keyslots"
+                           " which will erase all the data in the image"
+                           " irreversibly - refusing operation");
+                goto error;
+            }
+        } else {
+            for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+                if (!test_bit(i, slots_bitmap)) {
+                    continue;
+                }
+                if (qcrypto_block_luks_slot_active(luks, i)) {
+                    error_setg(errp,
+                               "Refusing to overwrite active slot %zu - "
+                               "please erase it first", i);
+                    goto error;
+                }
+            }
+        }
+    }
+
+    /* setup the data needed for storing the new keyslot */
+    if (!erasing) {
+        /* Load the master key if it wasn't already loaded */
+        if (!*master_key) {
+            g_autofree char *old_password;
+            old_password = qcrypto_secret_lookup_as_utf8(unlock_secret,  errp);
+            if (!old_password) {
+                goto error;
+            }
+            *master_key = g_new0(uint8_t, luks->header.master_key_len);
+
+            if (qcrypto_block_luks_find_key(block, old_password, *master_key,
+                                            readfunc, opaque, errp) < 0) {
+                error_append_hint(errp, "Failed to retrieve the master key");
+                goto error;
+            }
+        }
+        new_password = qcrypto_secret_lookup_as_utf8(command->new_secret->u.s,
+                                                     errp);
+        if (!new_password) {
+            goto error;
+        }
+        if (command->has_iter_time) {
+            iter_time = command->iter_time;
+        }
+    }
+
+    /* new apply the update */
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        if (!test_bit(i, slots_bitmap)) {
+            continue;
+        }
+        if (erasing) {
+            if (qcrypto_block_luks_erase_key(block, i,
+                                             writefunc,
+                                             opaque,
+                                             errp)) {
+                error_append_hint(errp, "Failed to erase keyslot %zu", i);
+                goto error;
+            }
+        } else {
+            if (qcrypto_block_luks_store_key(block, i,
+                                             new_password,
+                                             *master_key,
+                                             iter_time,
+                                             writefunc,
+                                             opaque,
+                                             errp)) {
+                error_append_hint(errp, "Failed to write to keyslot %zu", i);
+                goto error;
+            }
+        }
+    }
+    return 0;
+error:
+    return -EINVAL;
+}
+
+static int
+qcrypto_block_luks_amend_options(QCryptoBlock *block,
+                                 QCryptoBlockReadFunc readfunc,
+                                 QCryptoBlockWriteFunc writefunc,
+                                 void *opaque,
+                                 QCryptoBlockAmendOptions *options,
+                                 bool force,
+                                 Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    QCryptoBlockAmendOptionsLUKS *options_luks = &options->u.luks;
+    LUKSKeyslotUpdateList *ptr;
+    g_autofree uint8_t *master_key = NULL;
+    int ret;
+
+    char *unlock_secret = options_luks->has_unlock_secret ?
+                          options_luks->unlock_secret :
+                          luks->secret;
+
+    for (ptr = options_luks->keys; ptr; ptr = ptr->next) {
+        ret = qcrypto_block_luks_apply_keyslot_update(block, readfunc,
+                                                      writefunc, opaque,
+                                                      ptr->value,
+                                                      unlock_secret,
+                                                      &master_key,
+                                                      force, errp);
+
+        if (ret != 0) {
+            goto error;
+        }
+    }
+    return 0;
+error:
+    return -1;
+}
 
 static int qcrypto_block_luks_get_info(QCryptoBlock *block,
                                        QCryptoBlockInfo *info,
@@ -1523,7 +1890,9 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block,
 
 static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
 {
-    g_free(block->opaque);
+    QCryptoBlockLUKS *luks = block->opaque;
+    g_free(luks->secret);
+    g_free(luks);
 }
 
 
@@ -1560,6 +1929,7 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block,
 const QCryptoBlockDriver qcrypto_block_driver_luks = {
     .open = qcrypto_block_luks_open,
     .create = qcrypto_block_luks_create,
+    .amend = qcrypto_block_luks_amend_options,
     .get_info = qcrypto_block_luks_get_info,
     .cleanup = qcrypto_block_luks_cleanup,
     .decrypt = qcrypto_block_luks_decrypt,
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 9faebd03d4..e83847c71e 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -1,6 +1,8 @@
 # -*- Mode: Python -*-
 #
 
+{ 'include': 'common.json' }
+
 ##
 # = Cryptography
 ##
@@ -310,6 +312,52 @@
   'discriminator': 'format',
   'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
 
+##
+# @LUKSKeyslotUpdate:
+#
+# @keyslot:         If specified, will update only keyslot with this index
+#
+# @old-secret:      If specified, will only update keyslots that
+#                   can be opened with password which is contained in
+#                   QCryptoSecret with @old-secret ID
+#
+#                   If neither @keyslot nor @old-secret is specified,
+#                   first empty keyslot is selected for the update
+#
+# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
+#                   key to place in all matching keyslots.
+#                   null/empty string erases all matching keyslots unless
+#                   last valid keyslot is erased.
+#
+# @iter-time:       number of milliseconds to spend in
+#                   PBKDF passphrase processing
+# Since: 5.0
+##
+{ 'struct': 'LUKSKeyslotUpdate',
+  'data': {
+           '*keyslot': 'int',
+           '*old-secret': 'str',
+           'new-secret' : 'StrOrNull',
+           '*iter-time' : 'int' } }
+
+
+##
+# @QCryptoBlockAmendOptionsLUKS:
+#
+# The options that can be changed on existing luks encrypted device
+# @keys:           list of keyslot updates to perform
+#                  (updates are performed in order)
+# @unlock-secret:  use this secret to retrieve the current master key
+#                  if not given will use the same secret as one
+#                  that was used to open the image
+#
+# Since: 5.0
+##
+{ 'struct': 'QCryptoBlockAmendOptionsLUKS',
+  'data' : {
+            'keys': ['LUKSKeyslotUpdate'],
+             '*unlock-secret' : 'str' } }
+
 
 
 ##
@@ -324,4 +372,4 @@
   'base': 'QCryptoBlockOptionsBase',
   'discriminator': 'format',
   'data': {
-            } }
+          'luks': 'QCryptoBlockAmendOptionsLUKS' } }
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 03/13] block: amend: add 'force' option
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
  2020-01-14 19:33 ` [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment Maxim Levitsky
  2020-01-14 19:33 ` [PATCH 02/13] qcrypto-luks: implement encryption key management Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-14 19:33 ` [PATCH 04/13] block: amend: separate amend and create options for qemu-img Maxim Levitsky
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

'force' option will be used for some unsafe amend operations.

This includes things like erasing last keyslot in luks based formats
(which destroys the data, unless the master key is backed up
by external means), but that _might_ be desired result.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
 block.c                   | 4 +++-
 block/qcow2.c             | 1 +
 include/block/block.h     | 1 +
 include/block/block_int.h | 1 +
 qemu-img-cmds.hx          | 4 ++--
 qemu-img.c                | 8 +++++++-
 qemu-img.texi             | 6 +++++-
 7 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/block.c b/block.c
index ecd09dbbfd..cb9d5f1965 100644
--- a/block.c
+++ b/block.c
@@ -6164,6 +6164,7 @@ void bdrv_remove_aio_context_notifier(BlockDriverState *bs,
 
 int bdrv_amend_options(BlockDriverState *bs, QemuOpts *opts,
                        BlockDriverAmendStatusCB *status_cb, void *cb_opaque,
+                       bool force,
                        Error **errp)
 {
     if (!bs->drv) {
@@ -6175,7 +6176,8 @@ int bdrv_amend_options(BlockDriverState *bs, QemuOpts *opts,
                    bs->drv->format_name);
         return -ENOTSUP;
     }
-    return bs->drv->bdrv_amend_options(bs, opts, status_cb, cb_opaque, errp);
+    return bs->drv->bdrv_amend_options(bs, opts, status_cb,
+                                       cb_opaque, force, errp);
 }
 
 /* This function will be called by the bdrv_recurse_is_first_non_filter method
diff --git a/block/qcow2.c b/block/qcow2.c
index cef9d72b3a..6bcf4a5fc4 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -5150,6 +5150,7 @@ static void qcow2_amend_helper_cb(BlockDriverState *bs,
 static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
                                BlockDriverAmendStatusCB *status_cb,
                                void *cb_opaque,
+                               bool force,
                                Error **errp)
 {
     BDRVQcow2State *s = bs->opaque;
diff --git a/include/block/block.h b/include/block/block.h
index e9dcfef7fa..d0cd1b2da0 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -399,6 +399,7 @@ typedef void BlockDriverAmendStatusCB(BlockDriverState *bs, int64_t offset,
                                       int64_t total_work_size, void *opaque);
 int bdrv_amend_options(BlockDriverState *bs_new, QemuOpts *opts,
                        BlockDriverAmendStatusCB *status_cb, void *cb_opaque,
+                       bool force,
                        Error **errp);
 
 /* external snapshots */
diff --git a/include/block/block_int.h b/include/block/block_int.h
index dd033d0b37..810a9ecb86 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -427,6 +427,7 @@ struct BlockDriver {
     int (*bdrv_amend_options)(BlockDriverState *bs, QemuOpts *opts,
                               BlockDriverAmendStatusCB *status_cb,
                               void *cb_opaque,
+                              bool force,
                               Error **errp);
 
     void (*bdrv_debug_event)(BlockDriverState *bs, BlkdebugEvent event);
diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
index 1c93e6d185..323ea10ad0 100644
--- a/qemu-img-cmds.hx
+++ b/qemu-img-cmds.hx
@@ -14,9 +14,9 @@ STEXI
 ETEXI
 
 DEF("amend", img_amend,
-    "amend [--object objectdef] [--image-opts] [-p] [-q] [-f fmt] [-t cache] -o options filename")
+    "amend [--object objectdef] [--image-opts] [-p] [-q] [-f fmt] [-t cache] [--force] -o options filename")
 STEXI
-@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename}
+@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] [--force] -o @var{options} @var{filename}
 ETEXI
 
 DEF("bench", img_bench,
diff --git a/qemu-img.c b/qemu-img.c
index 6233b8ca56..a79f3904db 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -70,6 +70,7 @@ enum {
     OPTION_PREALLOCATION = 265,
     OPTION_SHRINK = 266,
     OPTION_SALVAGE = 267,
+    OPTION_FORCE = 268,
 };
 
 typedef enum OutputFormat {
@@ -3900,6 +3901,7 @@ static int img_amend(int argc, char **argv)
     BlockBackend *blk = NULL;
     BlockDriverState *bs = NULL;
     bool image_opts = false;
+    bool force = false;
 
     cache = BDRV_DEFAULT_CACHE;
     for (;;) {
@@ -3907,6 +3909,7 @@ static int img_amend(int argc, char **argv)
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
             {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
+            {"force", no_argument, 0, OPTION_FORCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, ":ho:f:t:pq",
@@ -3962,6 +3965,9 @@ static int img_amend(int argc, char **argv)
         case OPTION_IMAGE_OPTS:
             image_opts = true;
             break;
+        case OPTION_FORCE:
+            force = true;
+            break;
         }
     }
 
@@ -4039,7 +4045,7 @@ static int img_amend(int argc, char **argv)
 
     /* In case the driver does not call amend_status_cb() */
     qemu_progress_print(0.f, 0);
-    ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, &err);
+    ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, force, &err);
     qemu_progress_print(100.f, 0);
     if (ret < 0) {
         error_report_err(err);
diff --git a/qemu-img.texi b/qemu-img.texi
index b5156d6316..b6ed4357e8 100644
--- a/qemu-img.texi
+++ b/qemu-img.texi
@@ -201,11 +201,15 @@ Command description:
 
 @table @option
 
-@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename}
+@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] [--force] -o @var{options} @var{filename}
 
 Amends the image format specific @var{options} for the image file
 @var{filename}. Not all file formats support this operation.
 
+--force allows some unsafe operations. Currently for -f luks,
+it allows to erase last encryption key, and to overwrite an active
+encryption key.
+
 @item bench [-c @var{count}] [-d @var{depth}] [-f @var{fmt}] [--flush-interval=@var{flush_interval}] [-n] [--no-drain] [-o @var{offset}] [--pattern=@var{pattern}] [-q] [-s @var{buffer_size}] [-S @var{step_size}] [-t @var{cache}] [-w] [-U] @var{filename}
 
 Run a simple sequential I/O benchmark on the specified image. If @code{-w} is
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 04/13] block: amend: separate amend and create options for qemu-img
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (2 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 03/13] block: amend: add 'force' option Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-28 17:23   ` Daniel P. Berrangé
  2020-01-14 19:33 ` [PATCH 05/13] block/crypto: rename two functions Maxim Levitsky
                   ` (10 subsequent siblings)
  14 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Some options are only useful for creation
(or hard to be amended, like cluster size for qcow2), while some other
options are only useful for amend, like upcoming keyslot management
options for luks

Since currently only qcow2 supports amend, move all its options
to a common macro and then include it in each action option list.

In future it might be useful to remove some options which are
not supported anyway from amend list, which currently
cause an error message if amended.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/qcow2.c             | 160 +++++++++++++++++++++-----------------
 include/block/block_int.h |   4 +
 qemu-img.c                |  18 ++---
 3 files changed, 100 insertions(+), 82 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 6bcf4a5fc4..c6c2deee75 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -5445,83 +5445,96 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
     s->signaled_corruption = true;
 }
 
+#define QCOW_COMMON_OPTIONS                                         \
+    {                                                               \
+        .name = BLOCK_OPT_SIZE,                                     \
+        .type = QEMU_OPT_SIZE,                                      \
+        .help = "Virtual disk size"                                 \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_COMPAT_LEVEL,                             \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Compatibility level (v2 [0.10] or v3 [1.1])"       \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_BACKING_FILE,                             \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "File name of a base image"                         \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_BACKING_FMT,                              \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Image format of the base image"                    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_DATA_FILE,                                \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "File name of an external data file"                \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_DATA_FILE_RAW,                            \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "The external data file must stay valid "           \
+                "as a raw image"                                    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_ENCRYPT,                                  \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "Encrypt the image with format 'aes'. (Deprecated " \
+                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
+    },                                                              \
+    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
+        "ID of secret providing qcow AES key or LUKS passphrase"),  \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
+    {                                                               \
+        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
+        .type = QEMU_OPT_SIZE,                                      \
+        .help = "qcow2 cluster size",                               \
+        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_PREALLOC,                                 \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Preallocation mode (allowed values: off, "         \
+                "metadata, falloc, full)"                           \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "Postpone refcount updates",                        \
+        .def_value_str = "off"                                      \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_REFCOUNT_BITS,                            \
+        .type = QEMU_OPT_NUMBER,                                    \
+        .help = "Width of a reference count entry in bits",         \
+        .def_value_str = "16"                                       \
+    }                                                               \
+
 static QemuOptsList qcow2_create_opts = {
     .name = "qcow2-create-opts",
     .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
     .desc = {
-        {
-            .name = BLOCK_OPT_SIZE,
-            .type = QEMU_OPT_SIZE,
-            .help = "Virtual disk size"
-        },
-        {
-            .name = BLOCK_OPT_COMPAT_LEVEL,
-            .type = QEMU_OPT_STRING,
-            .help = "Compatibility level (v2 [0.10] or v3 [1.1])"
-        },
-        {
-            .name = BLOCK_OPT_BACKING_FILE,
-            .type = QEMU_OPT_STRING,
-            .help = "File name of a base image"
-        },
-        {
-            .name = BLOCK_OPT_BACKING_FMT,
-            .type = QEMU_OPT_STRING,
-            .help = "Image format of the base image"
-        },
-        {
-            .name = BLOCK_OPT_DATA_FILE,
-            .type = QEMU_OPT_STRING,
-            .help = "File name of an external data file"
-        },
-        {
-            .name = BLOCK_OPT_DATA_FILE_RAW,
-            .type = QEMU_OPT_BOOL,
-            .help = "The external data file must stay valid as a raw image"
-        },
-        {
-            .name = BLOCK_OPT_ENCRYPT,
-            .type = QEMU_OPT_BOOL,
-            .help = "Encrypt the image with format 'aes'. (Deprecated "
-                    "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",
-        },
-        {
-            .name = BLOCK_OPT_ENCRYPT_FORMAT,
-            .type = QEMU_OPT_STRING,
-            .help = "Encrypt the image, format choices: 'aes', 'luks'",
-        },
-        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
-            "ID of secret providing qcow AES key or LUKS passphrase"),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
-        {
-            .name = BLOCK_OPT_CLUSTER_SIZE,
-            .type = QEMU_OPT_SIZE,
-            .help = "qcow2 cluster size",
-            .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)
-        },
-        {
-            .name = BLOCK_OPT_PREALLOC,
-            .type = QEMU_OPT_STRING,
-            .help = "Preallocation mode (allowed values: off, metadata, "
-                    "falloc, full)"
-        },
-        {
-            .name = BLOCK_OPT_LAZY_REFCOUNTS,
-            .type = QEMU_OPT_BOOL,
-            .help = "Postpone refcount updates",
-            .def_value_str = "off"
-        },
-        {
-            .name = BLOCK_OPT_REFCOUNT_BITS,
-            .type = QEMU_OPT_NUMBER,
-            .help = "Width of a reference count entry in bits",
-            .def_value_str = "16"
-        },
+        QCOW_COMMON_OPTIONS,
+        { /* end of list */ }
+    }
+};
+
+static QemuOptsList qcow2_amend_opts = {
+    .name = "qcow2-amend-opts",
+    .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
+    .desc = {
+        QCOW_COMMON_OPTIONS,
         { /* end of list */ }
     }
 };
@@ -5581,6 +5594,7 @@ BlockDriver bdrv_qcow2 = {
     .bdrv_inactivate            = qcow2_inactivate,
 
     .create_opts         = &qcow2_create_opts,
+    .amend_opts          = &qcow2_amend_opts,
     .strong_runtime_opts = qcow2_strong_runtime_opts,
     .mutable_opts        = mutable_opts,
     .bdrv_co_check       = qcow2_co_check,
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 810a9ecb86..6f0abf8544 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -407,6 +407,10 @@ struct BlockDriver {
 
     /* List of options for creating images, terminated by name == NULL */
     QemuOptsList *create_opts;
+
+    /* List of options for image amend*/
+    QemuOptsList *amend_opts;
+
     /*
      * If this driver supports reopening images this contains a
      * NULL-terminated list of the runtime options that can be
diff --git a/qemu-img.c b/qemu-img.c
index a79f3904db..befd53943b 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -3878,11 +3878,11 @@ static int print_amend_option_help(const char *format)
         return 1;
     }
 
-    /* Every driver supporting amendment must have create_opts */
-    assert(drv->create_opts);
+    /* Every driver supporting amendment must have amend_opts */
+    assert(drv->amend_opts);
 
     printf("Creation options for '%s':\n", format);
-    qemu_opts_print_help(drv->create_opts, false);
+    qemu_opts_print_help(drv->amend_opts, false);
     printf("\nNote that not all of these options may be amendable.\n");
     return 0;
 }
@@ -3892,7 +3892,7 @@ static int img_amend(int argc, char **argv)
     Error *err = NULL;
     int c, ret = 0;
     char *options = NULL;
-    QemuOptsList *create_opts = NULL;
+    QemuOptsList *amend_opts = NULL;
     QemuOpts *opts = NULL;
     const char *fmt = NULL, *filename, *cache;
     int flags;
@@ -4031,11 +4031,11 @@ static int img_amend(int argc, char **argv)
         goto out;
     }
 
-    /* Every driver supporting amendment must have create_opts */
-    assert(bs->drv->create_opts);
+    /* Every driver supporting amendment must have amend_opts */
+    assert(bs->drv->amend_opts);
 
-    create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
-    opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
+    amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
+    opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
     qemu_opts_do_parse(opts, options, NULL, &err);
     if (err) {
         error_report_err(err);
@@ -4058,7 +4058,7 @@ out:
 out_no_progress:
     blk_unref(blk);
     qemu_opts_del(opts);
-    qemu_opts_free(create_opts);
+    qemu_opts_free(amend_opts);
     g_free(options);
 
     if (ret) {
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 05/13] block/crypto: rename two functions
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (3 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 04/13] block: amend: separate amend and create options for qemu-img Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-14 19:33 ` [PATCH 06/13] block/crypto: implement the encryption key management Maxim Levitsky
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

rename the write_func to create_write_func, and init_func to create_init_func.
This is preparation for other write_func that will be used to update the encryption keys.

No functional changes

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
 block/crypto.c | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/block/crypto.c b/block/crypto.c
index ecf96a7a9b..0b37dae564 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -78,12 +78,12 @@ struct BlockCryptoCreateData {
 };
 
 
-static ssize_t block_crypto_write_func(QCryptoBlock *block,
-                                       size_t offset,
-                                       const uint8_t *buf,
-                                       size_t buflen,
-                                       void *opaque,
-                                       Error **errp)
+static ssize_t block_crypto_create_write_func(QCryptoBlock *block,
+                                              size_t offset,
+                                              const uint8_t *buf,
+                                              size_t buflen,
+                                              void *opaque,
+                                              Error **errp)
 {
     struct BlockCryptoCreateData *data = opaque;
     ssize_t ret;
@@ -96,11 +96,10 @@ static ssize_t block_crypto_write_func(QCryptoBlock *block,
     return ret;
 }
 
-
-static ssize_t block_crypto_init_func(QCryptoBlock *block,
-                                      size_t headerlen,
-                                      void *opaque,
-                                      Error **errp)
+static ssize_t block_crypto_create_init_func(QCryptoBlock *block,
+                                             size_t headerlen,
+                                             void *opaque,
+                                             Error **errp)
 {
     struct BlockCryptoCreateData *data = opaque;
 
@@ -296,8 +295,8 @@ static int block_crypto_co_create_generic(BlockDriverState *bs,
     };
 
     crypto = qcrypto_block_create(opts, NULL,
-                                  block_crypto_init_func,
-                                  block_crypto_write_func,
+                                  block_crypto_create_init_func,
+                                  block_crypto_create_write_func,
                                   &data,
                                   errp);
 
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 06/13] block/crypto: implement the encryption key management
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (4 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 05/13] block/crypto: rename two functions Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-28 17:27   ` Daniel P. Berrangé
  2020-01-14 19:33 ` [PATCH 07/13] qcow2: extend qemu-img amend interface with crypto options Maxim Levitsky
                   ` (8 subsequent siblings)
  14 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This implements the encryption key management using the generic code in
qcrypto layer and exposes it to the user via qemu-img

This code adds another 'write_func' because the initialization
write_func works directly on the underlying file, and amend
works on instance of luks device.

This commit also adds a 'hack/workaround' I and Kevin Wolf (thanks)
made to make the driver both support write sharing (to avoid breaking the users),
and be safe against concurrent  metadata update (the keyslots)

Eventually the write sharing for luks driver will be deprecated
and removed together with this hack.

The hack is that we ask (as a format driver) for BLK_PERM_CONSISTENT_READ
and then when we want to update the keys, we unshare that permission.
So if someone else has the image open, even readonly, encryption
key update will fail gracefully.

Also thanks to Daniel Berrange for the idea of
unsharing read, rather that write permission which allows
to avoid cases when the other user had opened the image read-only.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/crypto.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++--
 block/crypto.h |  31 ++++++++++++
 2 files changed, 158 insertions(+), 3 deletions(-)

diff --git a/block/crypto.c b/block/crypto.c
index 0b37dae564..081880bced 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -36,6 +36,7 @@ typedef struct BlockCrypto BlockCrypto;
 
 struct BlockCrypto {
     QCryptoBlock *block;
+    bool updating_keys;
 };
 
 
@@ -70,6 +71,24 @@ static ssize_t block_crypto_read_func(QCryptoBlock *block,
     return ret;
 }
 
+static ssize_t block_crypto_write_func(QCryptoBlock *block,
+                                       size_t offset,
+                                       const uint8_t *buf,
+                                       size_t buflen,
+                                       void *opaque,
+                                       Error **errp)
+{
+    BlockDriverState *bs = opaque;
+    ssize_t ret;
+
+    ret = bdrv_pwrite(bs->file, offset, buf, buflen);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not write encryption header");
+        return ret;
+    }
+    return ret;
+}
+
 
 struct BlockCryptoCreateData {
     BlockBackend *blk;
@@ -148,6 +167,22 @@ static QemuOptsList block_crypto_create_opts_luks = {
 };
 
 
+static QemuOptsList block_crypto_amend_opts_luks = {
+    .name = "crypto",
+    .head = QTAILQ_HEAD_INITIALIZER(block_crypto_create_opts_luks.head),
+    .desc = {
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.0."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.1."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.2."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.3."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.4."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.5."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.6."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.7."),
+        { /* end of list */ }
+    },
+};
+
 QCryptoBlockOpenOptions *
 block_crypto_open_opts_init(QDict *opts, Error **errp)
 {
@@ -661,6 +696,95 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp)
     return spec_info;
 }
 
+static int
+block_crypto_amend_options(BlockDriverState *bs,
+                           QemuOpts *opts,
+                           BlockDriverAmendStatusCB *status_cb,
+                           void *cb_opaque,
+                           bool force,
+                           Error **errp)
+{
+    BlockCrypto *crypto = bs->opaque;
+    QDict *cryptoopts = NULL;
+    QCryptoBlockAmendOptions *amend_options = NULL;
+    int ret;
+
+    assert(crypto);
+    assert(crypto->block);
+    crypto->updating_keys = true;
+
+    ret = bdrv_child_refresh_perms(bs, bs->file, errp);
+    if (ret < 0) {
+        goto cleanup;
+    }
+
+    cryptoopts = qemu_opts_to_qdict(opts, NULL);
+    qdict_put_str(cryptoopts, "format", "luks");
+    amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
+    if (!amend_options) {
+        ret = -EINVAL;
+        goto cleanup;
+    }
+
+    ret = qcrypto_block_amend_options(crypto->block,
+                                      block_crypto_read_func,
+                                      block_crypto_write_func,
+                                      bs,
+                                      amend_options,
+                                      force,
+                                      errp);
+cleanup:
+    crypto->updating_keys = false;
+    bdrv_child_refresh_perms(bs, bs->file, errp);
+    qapi_free_QCryptoBlockAmendOptions(amend_options);
+    qobject_unref(cryptoopts);
+    return ret;
+}
+
+
+static void
+block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c,
+                         const BdrvChildRole *role,
+                         BlockReopenQueue *reopen_queue,
+                         uint64_t perm, uint64_t shared,
+                         uint64_t *nperm, uint64_t *nshared)
+{
+
+    BlockCrypto *crypto = bs->opaque;
+
+    bdrv_filter_default_perms(bs, c, role, reopen_queue,
+            perm, shared, nperm, nshared);
+    /*
+     * Ask for consistent read permission so that if
+     * someone else tries to open this image with this permission
+     * neither will be able to edit encryption keys, since
+     * we will unshare that permission while trying to
+     * update the encryption keys
+     */
+    if (!(bs->open_flags & BDRV_O_NO_IO)) {
+        *nperm |= BLK_PERM_CONSISTENT_READ;
+    }
+    /*
+     * This driver doesn't modify LUKS metadata except
+     * when updating the encryption slots.
+     * Thus unlike a proper format driver we don't ask for
+     * shared write/read permission. However we need it
+     * when we are updating the keys, to ensure that only we
+     * have access to the device.
+     *
+     * Encryption update will set the crypto->updating_keys
+     * during that period and refresh permissions
+     *
+     */
+    if (crypto->updating_keys) {
+        /* need exclusive write access for header update */
+        *nperm |= BLK_PERM_WRITE;
+        /* unshare read and write permission */
+        *nshared &= ~(BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE);
+    }
+}
+
+
 static const char *const block_crypto_strong_runtime_opts[] = {
     BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
 
@@ -673,13 +797,12 @@ static BlockDriver bdrv_crypto_luks = {
     .bdrv_probe         = block_crypto_probe_luks,
     .bdrv_open          = block_crypto_open_luks,
     .bdrv_close         = block_crypto_close,
-    /* This driver doesn't modify LUKS metadata except when creating image.
-     * Allow share-rw=on as a special case. */
-    .bdrv_child_perm    = bdrv_filter_default_perms,
+    .bdrv_child_perm    = block_crypto_child_perms,
     .bdrv_co_create     = block_crypto_co_create_luks,
     .bdrv_co_create_opts = block_crypto_co_create_opts_luks,
     .bdrv_co_truncate   = block_crypto_co_truncate,
     .create_opts        = &block_crypto_create_opts_luks,
+    .amend_opts         = &block_crypto_amend_opts_luks,
 
     .bdrv_reopen_prepare = block_crypto_reopen_prepare,
     .bdrv_refresh_limits = block_crypto_refresh_limits,
@@ -688,6 +811,7 @@ static BlockDriver bdrv_crypto_luks = {
     .bdrv_getlength     = block_crypto_getlength,
     .bdrv_get_info      = block_crypto_get_info_luks,
     .bdrv_get_specific_info = block_crypto_get_specific_info_luks,
+    .bdrv_amend_options = block_crypto_amend_options,
 
     .strong_runtime_opts = block_crypto_strong_runtime_opts,
 };
diff --git a/block/crypto.h b/block/crypto.h
index 06e044c9be..4af5ab4c94 100644
--- a/block/crypto.h
+++ b/block/crypto.h
@@ -41,6 +41,10 @@
 #define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg"
 #define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg"
 #define BLOCK_CRYPTO_OPT_LUKS_ITER_TIME "iter-time"
+#define BLOCK_CRYPTO_OPT_LUKS_KEYSLOT "keyslot"
+#define BLOCK_CRYPTO_OPT_LUKS_OLD_SECRET "old-secret"
+#define BLOCK_CRYPTO_OPT_LUKS_NEW_SECRET "new-secret"
+
 
 #define BLOCK_CRYPTO_OPT_DEF_LUKS_KEY_SECRET(prefix)                    \
     BLOCK_CRYPTO_OPT_DEF_KEY_SECRET(prefix,                             \
@@ -88,6 +92,33 @@
         .help = "Time to spend in PBKDF in milliseconds",     \
     }
 
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(prefix)           \
+    {                                                       \
+        .name = prefix BLOCK_CRYPTO_OPT_LUKS_KEYSLOT,       \
+        .type = QEMU_OPT_NUMBER,                            \
+        .help = "Select keyslot to modify explicitly",      \
+    }
+
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(prefix)            \
+    {                                                           \
+        .name = prefix BLOCK_CRYPTO_OPT_LUKS_OLD_SECRET,        \
+        .type = QEMU_OPT_STRING,                                \
+        .help = "Modify all keyslots that match this password", \
+    }
+
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(prefix)            \
+    {                                                           \
+        .name = prefix BLOCK_CRYPTO_OPT_LUKS_NEW_SECRET,        \
+        .type = QEMU_OPT_STRING,                                \
+        .help = "New secret to set in the matching keyslots",   \
+    }
+
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE(prefix)  \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(prefix),            \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(prefix),         \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(prefix),         \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix)           \
+
 QCryptoBlockCreateOptions *
 block_crypto_create_opts_init(QDict *opts, Error **errp);
 
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 07/13] qcow2: extend qemu-img amend interface with crypto options
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (5 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 06/13] block/crypto: implement the encryption key management Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-28 17:30   ` Daniel P. Berrangé
  2020-01-14 19:33 ` [PATCH 08/13] iotests: filter few more luks specific create options Maxim Levitsky
                   ` (7 subsequent siblings)
  14 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Now that we have all the infrastructure in place,
wire it in the qcow2 driver and expose this to the user.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/qcow2.c | 101 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 79 insertions(+), 22 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index c6c2deee75..1b01174aed 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -173,6 +173,19 @@ static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
     return ret;
 }
 
+static QDict*
+qcow2_extract_crypto_opts(QemuOpts *opts, const char *fmt, Error **errp)
+{
+    QDict *cryptoopts_qdict;
+    QDict *opts_qdict;
+
+    /* Extract "encrypt." options into a qdict */
+    opts_qdict = qemu_opts_to_qdict(opts, NULL);
+    qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
+    qobject_unref(opts_qdict);
+    qdict_put_str(cryptoopts_qdict, "format", "luks");
+    return cryptoopts_qdict;
+}
 
 /* 
  * read qcow2 extension and fill bs
@@ -4631,20 +4644,18 @@ static ssize_t qcow2_measure_crypto_hdr_write_func(QCryptoBlock *block,
 static bool qcow2_measure_luks_headerlen(QemuOpts *opts, size_t *len,
                                          Error **errp)
 {
-    QDict *opts_qdict;
-    QDict *cryptoopts_qdict;
     QCryptoBlockCreateOptions *cryptoopts;
+    QDict* crypto_opts_dict;
     QCryptoBlock *crypto;
 
-    /* Extract "encrypt." options into a qdict */
-    opts_qdict = qemu_opts_to_qdict(opts, NULL);
-    qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
-    qobject_unref(opts_qdict);
+    crypto_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp);
+    if (!crypto_opts_dict) {
+        return false;
+    }
+
+    cryptoopts = block_crypto_create_opts_init(crypto_opts_dict, errp);
+    qobject_unref(crypto_opts_dict);
 
-    /* Build QCryptoBlockCreateOptions object from qdict */
-    qdict_put_str(cryptoopts_qdict, "format", "luks");
-    cryptoopts = block_crypto_create_opts_init(cryptoopts_qdict, errp);
-    qobject_unref(cryptoopts_qdict);
     if (!cryptoopts) {
         return false;
     }
@@ -5083,6 +5094,7 @@ typedef enum Qcow2AmendOperation {
     QCOW2_NO_OPERATION = 0,
 
     QCOW2_UPGRADING,
+    QCOW2_UPDATING_ENCRYPTION,
     QCOW2_CHANGING_REFCOUNT_ORDER,
     QCOW2_DOWNGRADING,
 } Qcow2AmendOperation;
@@ -5167,6 +5179,7 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
     int ret;
     QemuOptDesc *desc = opts->list->desc;
     Qcow2AmendHelperCBInfo helper_cb_info;
+    bool encryption_update = false;
 
     while (desc && desc->name) {
         if (!qemu_opt_find(opts, desc->name)) {
@@ -5215,9 +5228,17 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
                 return -ENOTSUP;
             }
         } else if (g_str_has_prefix(desc->name, "encrypt.")) {
-            error_setg(errp,
-                       "Changing the encryption parameters is not supported");
-            return -ENOTSUP;
+            if (!s->crypto) {
+                error_setg(errp,
+                           "Can't amend encryption options - encryption not present");
+                return -EINVAL;
+            }
+            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
+                error_setg(errp,
+                           "Only LUKS encryption options can be amended");
+                return -ENOTSUP;
+            }
+            encryption_update = true;
         } else if (!strcmp(desc->name, BLOCK_OPT_CLUSTER_SIZE)) {
             cluster_size = qemu_opt_get_size(opts, BLOCK_OPT_CLUSTER_SIZE,
                                              cluster_size);
@@ -5267,7 +5288,8 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
         .original_status_cb = status_cb,
         .original_cb_opaque = cb_opaque,
         .total_operations = (new_version != old_version)
-                          + (s->refcount_bits != refcount_bits)
+                          + (s->refcount_bits != refcount_bits) +
+                            (encryption_update == true)
     };
 
     /* Upgrade first (some features may require compat=1.1) */
@@ -5280,6 +5302,33 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
         }
     }
 
+    if (encryption_update) {
+        QDict *amend_opts_dict;
+        QCryptoBlockAmendOptions *amend_opts;
+
+        helper_cb_info.current_operation = QCOW2_UPDATING_ENCRYPTION;
+        amend_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp);
+        if (!amend_opts_dict) {
+            return -EINVAL;
+        }
+        amend_opts = block_crypto_amend_opts_init(amend_opts_dict, errp);
+        qobject_unref(amend_opts_dict);
+        if (!amend_opts) {
+            return -EINVAL;
+        }
+        ret = qcrypto_block_amend_options(s->crypto,
+                                          qcow2_crypto_hdr_read_func,
+                                          qcow2_crypto_hdr_write_func,
+                                          bs,
+                                          amend_opts,
+                                          force,
+                                          errp);
+        qapi_free_QCryptoBlockAmendOptions(amend_opts);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
     if (s->refcount_bits != refcount_bits) {
         int refcount_order = ctz32(refcount_bits);
 
@@ -5488,14 +5537,6 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
         .type = QEMU_OPT_STRING,                                    \
         .help = "Encrypt the image, format choices: 'aes', 'luks'", \
     },                                                              \
-    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
-        "ID of secret providing qcow AES key or LUKS passphrase"),  \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
     {                                                               \
         .name = BLOCK_OPT_CLUSTER_SIZE,                             \
         .type = QEMU_OPT_SIZE,                                      \
@@ -5526,6 +5567,14 @@ static QemuOptsList qcow2_create_opts = {
     .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
     .desc = {
         QCOW_COMMON_OPTIONS,
+        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
+            "ID of secret providing qcow AES key or LUKS passphrase"),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
         { /* end of list */ }
     }
 };
@@ -5535,6 +5584,14 @@ static QemuOptsList qcow2_amend_opts = {
     .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
     .desc = {
         QCOW_COMMON_OPTIONS,
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.0."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.1."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.2."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.3."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.4."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.5."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.6."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.7."),
         { /* end of list */ }
     }
 };
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 08/13] iotests: filter few more luks specific create options
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (6 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 07/13] qcow2: extend qemu-img amend interface with crypto options Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-28 17:36   ` Daniel P. Berrangé
  2020-01-14 19:33 ` [PATCH 09/13] qemu-iotests: qemu-img tests for luks key management Maxim Levitsky
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This allows more tests to be able to have same output on both qcow2 luks encrypted images
and raw luks images

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 tests/qemu-iotests/087.out       | 6 +++---
 tests/qemu-iotests/134.out       | 2 +-
 tests/qemu-iotests/158.out       | 4 ++--
 tests/qemu-iotests/188.out       | 2 +-
 tests/qemu-iotests/189.out       | 4 ++--
 tests/qemu-iotests/198.out       | 4 ++--
 tests/qemu-iotests/common.filter | 6 ++++--
 7 files changed, 15 insertions(+), 13 deletions(-)

diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index 2d92ea847b..b61ba638af 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -34,7 +34,7 @@ QMP_VERSION
 
 === Encrypted image QCow ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
 Testing:
 QMP_VERSION
 {"return": {}}
@@ -46,7 +46,7 @@ QMP_VERSION
 
 === Encrypted image LUKS ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encrypt.format=luks encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
 Testing:
 QMP_VERSION
 {"return": {}}
@@ -58,7 +58,7 @@ QMP_VERSION
 
 === Missing driver ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
 Testing: -S
 QMP_VERSION
 {"return": {}}
diff --git a/tests/qemu-iotests/134.out b/tests/qemu-iotests/134.out
index 09d46f6b17..4abc5b5f7d 100644
--- a/tests/qemu-iotests/134.out
+++ b/tests/qemu-iotests/134.out
@@ -1,5 +1,5 @@
 QA output created by 134
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
 
 == reading whole image ==
 read 134217728/134217728 bytes at offset 0
diff --git a/tests/qemu-iotests/158.out b/tests/qemu-iotests/158.out
index 6def216e55..f28a17626b 100644
--- a/tests/qemu-iotests/158.out
+++ b/tests/qemu-iotests/158.out
@@ -1,6 +1,6 @@
 QA output created by 158
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=134217728 encryption=on
 
 == writing whole image ==
 wrote 134217728/134217728 bytes at offset 0
@@ -10,7 +10,7 @@ wrote 134217728/134217728 bytes at offset 0
 read 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base encryption=on
 
 == writing part of a cluster ==
 wrote 1024/1024 bytes at offset 0
diff --git a/tests/qemu-iotests/188.out b/tests/qemu-iotests/188.out
index c568ef3701..5426861b18 100644
--- a/tests/qemu-iotests/188.out
+++ b/tests/qemu-iotests/188.out
@@ -1,5 +1,5 @@
 QA output created by 188
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216
 
 == reading whole image ==
 read 16777216/16777216 bytes at offset 0
diff --git a/tests/qemu-iotests/189.out b/tests/qemu-iotests/189.out
index a0b7c9c24c..bc213cbe14 100644
--- a/tests/qemu-iotests/189.out
+++ b/tests/qemu-iotests/189.out
@@ -1,6 +1,6 @@
 QA output created by 189
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216
 
 == writing whole image ==
 wrote 16777216/16777216 bytes at offset 0
@@ -10,7 +10,7 @@ wrote 16777216/16777216 bytes at offset 0
 read 16777216/16777216 bytes at offset 0
 16 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base
 
 == writing part of a cluster ==
 wrote 1024/1024 bytes at offset 0
diff --git a/tests/qemu-iotests/198.out b/tests/qemu-iotests/198.out
index 831ce3a289..acfdf96b0c 100644
--- a/tests/qemu-iotests/198.out
+++ b/tests/qemu-iotests/198.out
@@ -1,12 +1,12 @@
 QA output created by 198
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216
 
 == writing whole image base ==
 wrote 16777216/16777216 bytes at offset 0
 16 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base
 
 == writing whole image layer ==
 wrote 16777216/16777216 bytes at offset 0
diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter
index 3f8ee3e5f7..bcc4495d52 100644
--- a/tests/qemu-iotests/common.filter
+++ b/tests/qemu-iotests/common.filter
@@ -150,8 +150,10 @@ _filter_img_create()
         -e "s# block_state_zero=\\(on\\|off\\)##g" \
         -e "s# log_size=[0-9]\\+##g" \
         -e "s# refcount_bits=[0-9]\\+##g" \
-        -e "s# key-secret=[a-zA-Z0-9]\\+##g" \
-        -e "s# iter-time=[0-9]\\+##g" \
+        -e "s# \\(encrypt\\.\\)\\?key-secret=[a-zA-Z0-9]\\+##g" \
+        -e "s# \\(encrypt\\.\\)\\?slot=[0-9]\\+##g" \
+        -e "s# \\(encrypt\\.\\)\\?iter-time=[0-9]\\+##g" \
+        -e "s# encrypt\\.format=[a-zA-Z0-9]\\+##g" \
         -e "s# force_size=\\(on\\|off\\)##g"
 }
 
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 09/13] qemu-iotests: qemu-img tests for luks key management
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (7 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 08/13] iotests: filter few more luks specific create options Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-14 19:33 ` [PATCH 10/13] block: add generic infrastructure for x-blockdev-amend qmp command Maxim Levitsky
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This commit adds two tests, which test the new amend interface
of both luks raw images and qcow2 luks encrypted images.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 tests/qemu-iotests/300     | 207 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/300.out |  99 ++++++++++++++++++
 tests/qemu-iotests/301     |  90 ++++++++++++++++
 tests/qemu-iotests/301.out |  30 ++++++
 tests/qemu-iotests/group   |   3 +
 5 files changed, 429 insertions(+)
 create mode 100755 tests/qemu-iotests/300
 create mode 100644 tests/qemu-iotests/300.out
 create mode 100755 tests/qemu-iotests/301
 create mode 100644 tests/qemu-iotests/301.out

diff --git a/tests/qemu-iotests/300 b/tests/qemu-iotests/300
new file mode 100755
index 0000000000..b1a6b37772
--- /dev/null
+++ b/tests/qemu-iotests/300
@@ -0,0 +1,207 @@
+#!/usr/bin/env bash
+#
+# Test encryption key management with luks
+# Based on 134
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# creator
+owner=mlevitsk@redhat.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+status=1	# failure is the default!
+
+_cleanup()
+{
+	_cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt qcow2 luks
+_supported_proto file #TODO
+
+QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
+
+if [ "$IMGFMT" = "qcow2" ] ; then
+	PR="encrypt."
+	EXTRA_IMG_ARGS="-o encrypt.format=luks"
+fi
+
+
+# secrets: you are supposed to see the password as *******, see :-)
+S0="--object secret,id=sec0,data=hunter0"
+S1="--object secret,id=sec1,data=hunter1"
+S2="--object secret,id=sec2,data=hunter2"
+S3="--object secret,id=sec3,data=hunter3"
+S4="--object secret,id=sec4,data=hunter4"
+SECRETS="$S0 $S1 $S2 $S3 $S4"
+
+# image with given secret
+IMGS0="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec0"
+IMGS1="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec1"
+IMGS2="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec2"
+IMGS3="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec3"
+IMGS4="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec4"
+
+
+echo "== creating a test image =="
+_make_test_img $S0 $EXTRA_IMG_ARGS -o ${PR}key-secret=sec0,${PR}iter-time=10 32M
+
+echo
+echo "== test that key 0 opens the image =="
+$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== adding a password to slot 4 =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret=sec4,${PR}keys.0.iter-time=10,${PR}keys.0.keyslot=4
+echo "== adding a password to slot 1 =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret=sec1,${PR}keys.0.iter-time=10
+echo "== adding a password to slot 3 =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret=sec3,${PR}keys.0.iter-time=10,${PR}keys.0.keyslot=3
+
+echo "== adding a password to slot 2 =="
+$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec2,${PR}keys.0.iter-time=10
+
+
+echo "== erase slot 4 =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=4 | _filter_img_create
+
+
+echo
+echo "== all secrets should work =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+echo
+echo "== erase slot 0 and try it =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec0 | _filter_img_create
+$QEMU_IO $SECRETS -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== erase slot 2 and try it =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=2 | _filter_img_create
+$QEMU_IO $SECRETS -c "read 0 4096" $IMGS2 | _filter_qemu_io | _filter_testdir
+
+
+# at this point slots 1 and 3 should be active
+
+echo
+echo "== filling  4 slots with secret 2 =="
+for i in $(seq 0 3) ; do
+	$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec2,${PR}keys.0.iter-time=10
+done
+
+echo
+echo "== adding secret 0 =="
+	$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec0,${PR}keys.0.iter-time=10
+
+echo
+echo "== adding secret 3 (last slot) =="
+	$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec3,${PR}keys.0.iter-time=10
+
+echo
+echo "== trying to add another slot (should fail) =="
+$QEMU_IMG amend $SECRETS $IMGS2 -o ${PR}keys.0.new-secret=sec3,${PR}keys.0.iter-time=10
+
+echo
+echo "== all secrets should work again =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+
+echo
+
+echo "== erase all keys of secret 2=="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec2
+
+echo "== erase all keys of secret 1=="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec1
+
+echo "== erase all keys of secret 0=="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec0
+
+echo "== erasing secret3 will fail now since it is the only secret (in 3 slots) =="
+$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec3
+
+echo
+echo "== only secret3 should work now  =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+echo
+echo "== add secret0  =="
+$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec0,${PR}keys.0.iter-time=10
+
+echo "== erase secret3 =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec3
+
+echo
+echo "== only secret0 should work now  =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+echo
+echo "== replace secret0 with secret1 (should fail)  =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret=sec1,${PR}keys.0.keyslot=0
+
+echo
+echo "== replace secret0 with secret1 with force (should work)  =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret=sec1,${PR}keys.0.iter-time=10,${PR}keys.0.keyslot=0 --force
+
+echo
+echo "== only secret1 should work now  =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+
+echo
+echo "== erase last secret (should fail)  =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=0
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec1
+
+
+echo "== erase non existing secrets (should fail)  =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec5 --force
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec0 --force
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=1 --force
+
+echo
+echo "== erase last secret with force by slot (should work)  =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=0 --force
+
+echo
+echo "== we have no secrets now, data is lost forever =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
+
diff --git a/tests/qemu-iotests/300.out b/tests/qemu-iotests/300.out
new file mode 100644
index 0000000000..7816c36044
--- /dev/null
+++ b/tests/qemu-iotests/300.out
@@ -0,0 +1,99 @@
+QA output created by 300
+== creating a test image ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432
+
+== test that key 0 opens the image ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== adding a password to slot 4 ==
+== adding a password to slot 1 ==
+== adding a password to slot 3 ==
+== adding a password to slot 2 ==
+== erase slot 4 ==
+
+== all secrets should work ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== erase slot 0 and try it ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== erase slot 2 and try it ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== filling  4 slots with secret 2 ==
+
+== adding secret 0 ==
+
+== adding secret 3 (last slot) ==
+
+== trying to add another slot (should fail) ==
+qemu-img: Can't add a keyslot - all key slots are in use
+
+== all secrets should work again ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== erase all keys of secret 2==
+== erase all keys of secret 1==
+== erase all keys of secret 0==
+== erasing secret3 will fail now since it is the only secret (in 3 slots) ==
+qemu-img: Requested operation would erase all active keyslots which will erase all the data in the image irreversibly - refusing operation
+
+== only secret3 should work now  ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== add secret0  ==
+== erase secret3 ==
+
+== only secret0 should work now  ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== replace secret0 with secret1 (should fail)  ==
+qemu-img: Refusing to overwrite active slot 0 - please erase it first
+
+== replace secret0 with secret1 with force (should work)  ==
+
+== only secret1 should work now  ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== erase last secret (should fail)  ==
+qemu-img: Requested operation would erase all active keyslots which will erase all the data in the image irreversibly - refusing operation
+qemu-img: Requested operation would erase all active keyslots which will erase all the data in the image irreversibly - refusing operation
+== erase non existing secrets (should fail)  ==
+qemu-img: No secret with id 'sec5'
+qemu-img: Requested operation didn't match any slots
+
+== erase last secret with force by slot (should work)  ==
+
+== we have no secrets now, data is lost forever ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+*** done
diff --git a/tests/qemu-iotests/301 b/tests/qemu-iotests/301
new file mode 100755
index 0000000000..56ac3d8ef2
--- /dev/null
+++ b/tests/qemu-iotests/301
@@ -0,0 +1,90 @@
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# creator
+owner=mlevitsk@redhat.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+status=1	# failure is the default!
+
+_cleanup()
+{
+	_cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt luks
+_supported_proto file #TODO
+
+QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
+
+# you are supposed to see the password as *******, see :-)
+S0="--object secret,id=sec0,data=hunter0"
+S1="--object secret,id=sec1,data=hunter1"
+SECRETS="$S0 $S1"
+
+
+IMGS0="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,key-secret=sec0"
+IMGS1="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,key-secret=sec1"
+
+echo "== creating a test image =="
+_make_test_img $S0 -o "key-secret=sec0,iter-time=10" 32M
+
+echo
+echo "== test that key 0 opens the image =="
+$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== adding a password to slot 1 =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o keys.0.new-secret=sec1,keys.0.keyslot=1,keys.0.iter-time=10
+
+echo
+echo "== 'backup' the image header =="
+dd if=$TEST_IMG_FILE of=${TEST_IMG_FILE}.bk bs=4K skip=0 count=1
+
+echo
+echo "== erase slot 0 =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o keys.0.new-secret="",keys.0.keyslot=0 | _filter_img_create
+
+echo
+echo "== test that key 0 doesn't open the image =="
+$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== 'restore' the image header =="
+dd if=${TEST_IMG_FILE}.bk of=${TEST_IMG_FILE} bs=4K skip=0 count=1 conv=notrunc
+
+echo
+echo "== test that key 0 still doesn't open the image (key material is erased) =="
+$QEMU_IO $SECRETS -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== test that key 1 still works =="
+$QEMU_IO $SECRETS -c "read 0 4096" $IMGS1 | _filter_qemu_io | _filter_testdir
+
+echo "*** done"
+rm -f $seq.full
+status=0
+
+
+exit 0
diff --git a/tests/qemu-iotests/301.out b/tests/qemu-iotests/301.out
new file mode 100644
index 0000000000..e653c30330
--- /dev/null
+++ b/tests/qemu-iotests/301.out
@@ -0,0 +1,30 @@
+QA output created by 301
+== creating a test image ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432
+
+== test that key 0 opens the image ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== adding a password to slot 1 ==
+
+== 'backup' the image header ==
+1+0 records in
+1+0 records out
+
+== erase slot 0 ==
+
+== test that key 0 doesn't open the image ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== 'restore' the image header ==
+1+0 records in
+1+0 records out
+
+== test that key 0 still doesn't open the image (key material is erased) ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== test that key 1 still works ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+*** done
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index cb2b789e44..34e3139ad7 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -288,3 +288,6 @@
 277 rw quick
 279 rw backing quick
 280 rw migration quick
+
+300 rw auto
+301 rw auto quick
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 10/13] block: add generic infrastructure for x-blockdev-amend qmp command
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (8 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 09/13] qemu-iotests: qemu-img tests for luks key management Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-21  7:59   ` Markus Armbruster
  2020-01-14 19:33 ` [PATCH 11/13] block/crypto: implement blockdev-amend Maxim Levitsky
                   ` (4 subsequent siblings)
  14 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

blockdev-amend will be used similiar to blockdev-create
to allow on the fly changes of the structure of the format based block devices.

Current plan is to first support encryption keyslot management for luks
based formats (raw and embedded in qcow2)

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/Makefile.objs       |   2 +-
 block/amend.c             | 108 ++++++++++++++++++++++++++++++++++++++
 include/block/block_int.h |  21 +++++---
 qapi/block-core.json      |  42 +++++++++++++++
 qapi/job.json             |   4 +-
 5 files changed, 169 insertions(+), 8 deletions(-)
 create mode 100644 block/amend.c

diff --git a/block/Makefile.objs b/block/Makefile.objs
index 330529b0b7..eb5ddb7158 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -18,7 +18,7 @@ block-obj-y += block-backend.o snapshot.o qapi.o
 block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o
 block-obj-$(CONFIG_POSIX) += file-posix.o
 block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
-block-obj-y += null.o mirror.o commit.o io.o create.o
+block-obj-y += null.o mirror.o commit.o io.o create.o amend.o
 block-obj-y += throttle-groups.o
 block-obj-$(CONFIG_LINUX) += nvme.o
 
diff --git a/block/amend.c b/block/amend.c
new file mode 100644
index 0000000000..2db7b1eafc
--- /dev/null
+++ b/block/amend.c
@@ -0,0 +1,108 @@
+/*
+ * Block layer code related to image options amend
+ *
+ * Copyright (c) 2018 Kevin Wolf <kwolf@redhat.com>
+ * Copyright (c) 2019 Maxim Levitsky <mlevitsk@redhat.com>
+ *
+ * Heavily based on create.c
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "block/block_int.h"
+#include "qemu/job.h"
+#include "qemu/main-loop.h"
+#include "qapi/qapi-commands-block-core.h"
+#include "qapi/qapi-visit-block-core.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/error.h"
+
+typedef struct BlockdevAmendJob {
+    Job common;
+    BlockdevAmendOptions *opts;
+    BlockDriverState *bs;
+    bool force;
+} BlockdevAmendJob;
+
+static int coroutine_fn blockdev_amend_run(Job *job, Error **errp)
+{
+    BlockdevAmendJob *s = container_of(job, BlockdevAmendJob, common);
+    int ret;
+
+    job_progress_set_remaining(&s->common, 1);
+    ret = s->bs->drv->bdrv_co_amend(s->bs, s->opts, s->force, errp);
+    job_progress_update(&s->common, 1);
+    qapi_free_BlockdevAmendOptions(s->opts);
+    return ret;
+}
+
+static const JobDriver blockdev_amend_job_driver = {
+    .instance_size = sizeof(BlockdevAmendJob),
+    .job_type      = JOB_TYPE_AMEND,
+    .run           = blockdev_amend_run,
+};
+
+void qmp_x_blockdev_amend(const char *job_id,
+                          const char *node_name,
+                          BlockdevAmendOptions *options,
+                          bool has_force,
+                          bool force,
+                          Error **errp)
+{
+    BlockdevAmendJob *s;
+    const char *fmt = BlockdevDriver_str(options->driver);
+    BlockDriver *drv = bdrv_find_format(fmt);
+    BlockDriverState *bs = bdrv_find_node(node_name);
+
+    /*
+     * If the driver is in the schema, we know that it exists. But it may not
+     * be whitelisted.
+     */
+    assert(drv);
+    if (bdrv_uses_whitelist() && !bdrv_is_whitelisted(drv, false)) {
+        error_setg(errp, "Driver is not whitelisted");
+        return;
+    }
+
+    if (bs->drv != drv) {
+        error_setg(errp,
+                   "x-blockdev-amend doesn't support changing the block driver");
+        return;
+    }
+
+    /* Error out if the driver doesn't support .bdrv_co_amend */
+    if (!drv->bdrv_co_amend) {
+        error_setg(errp, "Driver does not support x-blockdev-amend");
+        return;
+    }
+
+    /* Create the block job */
+    s = job_create(job_id, &blockdev_amend_job_driver, NULL,
+                   bdrv_get_aio_context(bs), JOB_DEFAULT | JOB_MANUAL_DISMISS,
+                   NULL, NULL, errp);
+    if (!s) {
+        return;
+    }
+
+    s->bs = bs,
+    s->opts = QAPI_CLONE(BlockdevAmendOptions, options),
+    s->force = has_force ? force : false;
+    job_start(&s->common);
+}
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 6f0abf8544..025d1f298a 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -133,11 +133,26 @@ struct BlockDriver {
     int (*bdrv_file_open)(BlockDriverState *bs, QDict *options, int flags,
                           Error **errp);
     void (*bdrv_close)(BlockDriverState *bs);
+
+
     int coroutine_fn (*bdrv_co_create)(BlockdevCreateOptions *opts,
                                        Error **errp);
     int coroutine_fn (*bdrv_co_create_opts)(const char *filename,
                                             QemuOpts *opts,
                                             Error **errp);
+
+    int coroutine_fn (*bdrv_co_amend)(BlockDriverState *bs,
+                                      BlockdevAmendOptions *opts,
+                                      bool force,
+                                      Error **errp);
+
+    int (*bdrv_amend_options)(BlockDriverState *bs,
+                              QemuOpts *opts,
+                              BlockDriverAmendStatusCB *status_cb,
+                              void *cb_opaque,
+                              bool force,
+                              Error **errp);
+
     int (*bdrv_make_empty)(BlockDriverState *bs);
 
     /*
@@ -428,12 +443,6 @@ struct BlockDriver {
                                       BdrvCheckResult *result,
                                       BdrvCheckMode fix);
 
-    int (*bdrv_amend_options)(BlockDriverState *bs, QemuOpts *opts,
-                              BlockDriverAmendStatusCB *status_cb,
-                              void *cb_opaque,
-                              bool force,
-                              Error **errp);
-
     void (*bdrv_debug_event)(BlockDriverState *bs, BlkdebugEvent event);
 
     /* TODO Better pass a option string/QDict/QemuOpts to add any rule? */
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 7ff5e5edaf..601f7dc9a4 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -4743,6 +4743,48 @@
   'data': { 'job-id': 'str',
             'options': 'BlockdevCreateOptions' } }
 
+##
+# @BlockdevAmendOptions:
+#
+# Options for amending an image format
+#
+# @driver           block driver that is suitable for the image
+#
+# Since: 5.0
+##
+{ 'union': 'BlockdevAmendOptions',
+  'base': {
+      'driver':         'BlockdevDriver' },
+  'discriminator': 'driver',
+  'data': {
+  } }
+
+##
+# @x-blockdev-amend:
+#
+# Starts a job to amend format specific options of an existing open block device
+# The job is automatically finalized, but a manual job-dismiss is required.
+#
+# @job-id:          Identifier for the newly created job.
+#
+# @node-name:       Name of the block node to work on
+#
+# @options:         Options (driver specific)
+#
+# @force:           Allow unsafe operations, format specific
+#                   For luks that allows erase of the last active keyslot
+#                   (permanent loss of data),
+#                   and replacement of an active keyslot
+#                   (possible loss of data if IO error happens)
+#
+# Since: 5.0
+##
+{ 'command': 'x-blockdev-amend',
+  'data': { 'job-id': 'str',
+            'node-name': 'str',
+            'options': 'BlockdevAmendOptions',
+            '*force': 'bool' } }
+
 ##
 # @blockdev-open-tray:
 #
diff --git a/qapi/job.json b/qapi/job.json
index a121b615fb..362b634ec1 100644
--- a/qapi/job.json
+++ b/qapi/job.json
@@ -19,10 +19,12 @@
 #
 # @create: image creation job type, see "blockdev-create" (since 3.0)
 #
+# @amend: image options amend job type, see "x-blockdev-amend" (since 5.0)
+#
 # Since: 1.7
 ##
 { 'enum': 'JobType',
-  'data': ['commit', 'stream', 'mirror', 'backup', 'create'] }
+  'data': ['commit', 'stream', 'mirror', 'backup', 'create', 'amend'] }
 
 ##
 # @JobStatus:
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 11/13] block/crypto: implement blockdev-amend
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (9 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 10/13] block: add generic infrastructure for x-blockdev-amend qmp command Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-28 17:40   ` Daniel P. Berrangé
  2020-01-14 19:33 ` [PATCH 12/13] block/qcow2: " Maxim Levitsky
                   ` (3 subsequent siblings)
  14 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/crypto.c       | 70 ++++++++++++++++++++++++++++++++------------
 qapi/block-core.json | 14 ++++++++-
 2 files changed, 64 insertions(+), 20 deletions(-)

diff --git a/block/crypto.c b/block/crypto.c
index 081880bced..6836337863 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -697,32 +697,21 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp)
 }
 
 static int
-block_crypto_amend_options(BlockDriverState *bs,
-                           QemuOpts *opts,
-                           BlockDriverAmendStatusCB *status_cb,
-                           void *cb_opaque,
-                           bool force,
-                           Error **errp)
+block_crypto_amend_options_generic(BlockDriverState *bs,
+                                   QCryptoBlockAmendOptions *amend_options,
+                                   bool force,
+                                   Error **errp)
 {
     BlockCrypto *crypto = bs->opaque;
-    QDict *cryptoopts = NULL;
-    QCryptoBlockAmendOptions *amend_options = NULL;
     int ret;
 
     assert(crypto);
     assert(crypto->block);
-    crypto->updating_keys = true;
 
+    /* apply for exclusive read/write permissions to the underlying file*/
+    crypto->updating_keys = true;
     ret = bdrv_child_refresh_perms(bs, bs->file, errp);
-    if (ret < 0) {
-        goto cleanup;
-    }
-
-    cryptoopts = qemu_opts_to_qdict(opts, NULL);
-    qdict_put_str(cryptoopts, "format", "luks");
-    amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
-    if (!amend_options) {
-        ret = -EINVAL;
+    if (ret) {
         goto cleanup;
     }
 
@@ -734,13 +723,55 @@ block_crypto_amend_options(BlockDriverState *bs,
                                       force,
                                       errp);
 cleanup:
+    /* release exclusive read/write permissions to the underlying file*/
     crypto->updating_keys = false;
     bdrv_child_refresh_perms(bs, bs->file, errp);
-    qapi_free_QCryptoBlockAmendOptions(amend_options);
+    return ret;
+}
+
+static int
+block_crypto_amend_options(BlockDriverState *bs,
+                           QemuOpts *opts,
+                           BlockDriverAmendStatusCB *status_cb,
+                           void *cb_opaque,
+                           bool force,
+                           Error **errp)
+{
+    BlockCrypto *crypto = bs->opaque;
+    QDict *cryptoopts = NULL;
+    QCryptoBlockAmendOptions *amend_options = NULL;
+    int ret = -EINVAL;
+
+    assert(crypto);
+    assert(crypto->block);
+
+    cryptoopts = qemu_opts_to_qdict(opts, NULL);
+    qdict_put_str(cryptoopts, "format", "luks");
+    amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
     qobject_unref(cryptoopts);
+    if (!amend_options) {
+        goto cleanup;
+    }
+    ret = block_crypto_amend_options_generic(bs, amend_options, force, errp);
+cleanup:
+    qapi_free_QCryptoBlockAmendOptions(amend_options);
     return ret;
 }
 
+static int
+coroutine_fn block_crypto_co_amend(BlockDriverState *bs,
+                                   BlockdevAmendOptions *opts,
+                                   bool force,
+                                   Error **errp)
+{
+    QCryptoBlockAmendOptions amend_opts;
+
+    amend_opts = (QCryptoBlockAmendOptions) {
+        .format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
+        .u.luks = *qapi_BlockdevAmendOptionsLUKS_base(&opts->u.luks),
+    };
+    return block_crypto_amend_options_generic(bs, &amend_opts, force, errp);
+}
 
 static void
 block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c,
@@ -812,6 +843,7 @@ static BlockDriver bdrv_crypto_luks = {
     .bdrv_get_info      = block_crypto_get_info_luks,
     .bdrv_get_specific_info = block_crypto_get_specific_info_luks,
     .bdrv_amend_options = block_crypto_amend_options,
+    .bdrv_co_amend      = block_crypto_co_amend,
 
     .strong_runtime_opts = block_crypto_strong_runtime_opts,
 };
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 601f7dc9a4..790aa40991 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -4743,6 +4743,18 @@
   'data': { 'job-id': 'str',
             'options': 'BlockdevCreateOptions' } }
 
+##
+# @BlockdevAmendOptionsLUKS:
+#
+# Driver specific image amend options for LUKS.
+#
+# Since: 5.0
+##
+{ 'struct': 'BlockdevAmendOptionsLUKS',
+  'base': 'QCryptoBlockAmendOptionsLUKS',
+  'data': { }
+}
+
 ##
 # @BlockdevAmendOptions:
 #
@@ -4757,7 +4769,7 @@
       'driver':         'BlockdevDriver' },
   'discriminator': 'driver',
   'data': {
-  } }
+      'luks':           'BlockdevAmendOptionsLUKS' } }
 
 ##
 # @x-blockdev-amend:
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 12/13] block/qcow2: implement blockdev-amend
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (10 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 11/13] block/crypto: implement blockdev-amend Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-28 17:41   ` Daniel P. Berrangé
  2020-01-14 19:33 ` [PATCH 13/13] iotests: add tests for blockdev-amend Maxim Levitsky
                   ` (2 subsequent siblings)
  14 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Currently the implementation only supports amending the encryption
options, unlike the qemu-img version

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/qcow2.c        | 39 +++++++++++++++++++++++++++++++++++++++
 qapi/block-core.json | 16 +++++++++++++++-
 2 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 1b01174aed..8b74471bc6 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -5444,6 +5444,44 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
     return 0;
 }
 
+static int coroutine_fn qcow2_co_amend(BlockDriverState *bs,
+                                       BlockdevAmendOptions *opts,
+                                       bool force,
+                                       Error **errp)
+{
+    BlockdevAmendOptionsQcow2 *qopts = &opts->u.qcow2;
+    BDRVQcow2State *s = bs->opaque;
+    int ret = 0;
+
+    if (qopts->has_encrypt) {
+        if (!s->crypto) {
+            error_setg(errp, "image is not encrypted, can't amend");
+            return -EOPNOTSUPP;
+        }
+
+        if (qopts->encrypt->format != Q_CRYPTO_BLOCK_FORMAT_LUKS) {
+            error_setg(errp,
+                       "Amend can't be used to change the qcow2 encryption format");
+            return -EOPNOTSUPP;
+        }
+
+        if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
+            error_setg(errp,
+                       "Only LUKS encryption options can be amended for qcow2 with blockdev-amend");
+            return -EOPNOTSUPP;
+        }
+
+        ret = qcrypto_block_amend_options(s->crypto,
+                                          qcow2_crypto_hdr_read_func,
+                                          qcow2_crypto_hdr_write_func,
+                                          bs,
+                                          qopts->encrypt,
+                                          force,
+                                          errp);
+    }
+    return ret;
+}
+
 /*
  * If offset or size are negative, respectively, they will not be included in
  * the BLOCK_IMAGE_CORRUPTED event emitted.
@@ -5656,6 +5694,7 @@ BlockDriver bdrv_qcow2 = {
     .mutable_opts        = mutable_opts,
     .bdrv_co_check       = qcow2_co_check,
     .bdrv_amend_options  = qcow2_amend_options,
+    .bdrv_co_amend       = qcow2_co_amend,
 
     .bdrv_detach_aio_context  = qcow2_detach_aio_context,
     .bdrv_attach_aio_context  = qcow2_attach_aio_context,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 790aa40991..2cf1f443e5 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -4755,6 +4755,19 @@
   'data': { }
 }
 
+##
+# @BlockdevAmendOptionsQcow2:
+#
+# Driver specific image amend options for qcow2.
+# For now, only encryption options can be amended
+#
+# @encrypt          Encryption options to be amended
+#
+# Since: 5.0
+##
+{ 'struct': 'BlockdevAmendOptionsQcow2',
+  'data': { '*encrypt':         'QCryptoBlockAmendOptions' } }
+
 ##
 # @BlockdevAmendOptions:
 #
@@ -4769,7 +4782,8 @@
       'driver':         'BlockdevDriver' },
   'discriminator': 'driver',
   'data': {
-      'luks':           'BlockdevAmendOptionsLUKS' } }
+      'luks':           'BlockdevAmendOptionsLUKS',
+      'qcow2':          'BlockdevAmendOptionsQcow2' } }
 
 ##
 # @x-blockdev-amend:
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* [PATCH 13/13] iotests: add tests for blockdev-amend
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (11 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 12/13] block/qcow2: " Maxim Levitsky
@ 2020-01-14 19:33 ` Maxim Levitsky
  2020-01-14 21:16 ` [PATCH 00/13] LUKS: encryption slot management using amend interface no-reply
  2020-01-14 21:17 ` no-reply
  14 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-14 19:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This commit adds two tests that cover the
new blockdev-amend functionality of luks and qcow2 driver

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 tests/qemu-iotests/302     | 284 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/302.out |  40 ++++++
 tests/qemu-iotests/303     | 235 ++++++++++++++++++++++++++++++
 tests/qemu-iotests/303.out |  33 +++++
 tests/qemu-iotests/group   |   3 +
 5 files changed, 595 insertions(+)
 create mode 100644 tests/qemu-iotests/302
 create mode 100644 tests/qemu-iotests/302.out
 create mode 100644 tests/qemu-iotests/303
 create mode 100644 tests/qemu-iotests/303.out

diff --git a/tests/qemu-iotests/302 b/tests/qemu-iotests/302
new file mode 100644
index 0000000000..bc507377a5
--- /dev/null
+++ b/tests/qemu-iotests/302
@@ -0,0 +1,284 @@
+#!/usr/bin/env python
+#
+# Test case QMP's encrypted key management
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import iotests
+import os
+import time
+import json
+
+test_img = os.path.join(iotests.test_dir, 'test.img')
+
+class Secret:
+    def __init__(self, index):
+        self._id = "keysec" + str(index)
+        # you are not supposed to see the password...
+        self._secret = "hunter" + str(index)
+
+    def id(self):
+        return self._id
+
+    def secret(self):
+        return self._secret
+
+    def to_cmdline_object(self):
+        return  [ "secret,id=" + self._id + ",data=" + self._secret]
+
+    def to_qmp_object(self):
+        return { "qom_type" : "secret", "id": self.id(),
+                 "props": { "data": self.secret() } }
+
+################################################################################
+class EncryptionSetupTestCase(iotests.QMPTestCase):
+
+    # test case startup
+    def setUp(self):
+        # start the VM
+        self.vm = iotests.VM()
+        self.vm.launch()
+
+        # create the secrets and load 'em into the VM
+        self.secrets = [ Secret(i) for i in range(0, 6) ]
+        for secret in self.secrets:
+            result = self.vm.qmp("object-add", **secret.to_qmp_object())
+            self.assert_qmp(result, 'return', {})
+
+        if iotests.imgfmt == "qcow2":
+            self.pfx = "encrypt."
+            self.img_opts = [ '-o', "encrypt.format=luks" ]
+        else:
+            self.pfx = ""
+            self.img_opts = []
+
+    # test case shutdown
+    def tearDown(self):
+        # stop the VM
+        self.vm.shutdown()
+
+    ###########################################################################
+    # create the encrypted block device
+    def createImg(self, file, secret):
+
+        iotests.qemu_img(
+            'create',
+            '--object', *secret.to_cmdline_object(),
+            '-f', iotests.imgfmt,
+            '-o', self.pfx + 'key-secret=' + secret.id(),
+            '-o', self.pfx + 'iter-time=10',
+            *self.img_opts,
+            file,
+            '1M')
+
+    ###########################################################################
+    # open an encrypted block device
+    def openImageQmp(self, id, file, secret, read_only = False):
+
+        encrypt_options = {
+            'key-secret' : secret.id()
+        }
+
+        if iotests.imgfmt == "qcow2":
+            encrypt_options = {
+                'encrypt': {
+                    'format':'luks',
+                    **encrypt_options
+                }
+            }
+
+        result = self.vm.qmp('blockdev-add', **
+            {
+                'driver': iotests.imgfmt,
+                'node-name': id,
+                'read-only': read_only,
+
+                **encrypt_options,
+
+                'file': {
+                    'driver': 'file',
+                    'filename': test_img,
+                }
+            }
+        )
+        self.assert_qmp(result, 'return', {})
+
+    # close the encrypted block device
+    def closeImageQmp(self, id):
+        result = self.vm.qmp('blockdev-del', **{ 'node-name': id })
+        self.assert_qmp(result, 'return', {})
+
+    ###########################################################################
+    # add a key to an encrypted block device
+    def addKeyQmp(self, id, secret, unlock_secret = None,
+                  slot = None, force = False):
+
+        keyupdate0 = {
+            'new-secret' : secret.id(),
+            'iter-time' : 10
+        }
+
+        if slot != None:
+            keyupdate0['keyslot'] = slot
+
+        crypt_options = {
+            'keys' : [ keyupdate0 ]
+        }
+
+        if unlock_secret != None:
+            crypt_options['unlock-secret'] = unlock_secret.id()
+
+        if iotests.imgfmt == "qcow2":
+            crypt_options['format'] = 'luks'
+            crypt_options = {
+                'encrypt': crypt_options
+            }
+
+        args = {
+            'node-name': id,
+            'job-id' : 'job_add_key',
+            'options' : {
+                    'driver' : iotests.imgfmt,
+                    **crypt_options
+                },
+        }
+
+        if force == True:
+            args['force'] = True
+
+        #TODO: check what jobs return
+        result = self.vm.qmp('x-blockdev-amend', **args)
+        assert result['return'] == {}
+        self.vm.run_job('job_add_key')
+
+    # erase a key from an encrypted block device
+    def eraseKeyQmp(self, id, secret = None, slot = None, force = False):
+
+        keyupdate0 = {
+            'new-secret': None
+        }
+
+        if slot != None:
+            keyupdate0['keyslot'] = slot
+        if secret != None:
+            keyupdate0['old-secret'] = secret.id()
+
+        crypt_options = {
+            'keys' : [ keyupdate0 ]
+        }
+
+        if iotests.imgfmt == "qcow2":
+            crypt_options['format'] = 'luks'
+            crypt_options = {
+                'encrypt': crypt_options
+            }
+
+        args = {
+            'node-name': id,
+            'job-id' : 'job_erase_key',
+            'options' : {
+                    'driver' : iotests.imgfmt,
+                    **crypt_options
+                },
+        }
+
+        if force == True:
+            args['force'] = True
+
+        result = self.vm.qmp('x-blockdev-amend', **args)
+        assert result['return'] == {}
+        self.vm.run_job('job_erase_key')
+
+    ###########################################################################
+    # create image, and change its key
+    def testChangeKey(self):
+
+        # create the image with secret0 and open it
+        self.createImg(test_img, self.secrets[0]);
+        self.openImageQmp("testdev", test_img, self.secrets[0])
+
+        # add key to slot 1
+        self.addKeyQmp("testdev", secret = self.secrets[1])
+
+        # add key to slot 5
+        self.addKeyQmp("testdev", secret = self.secrets[2], slot=5)
+
+        # erase key from slot 0
+        self.eraseKeyQmp("testdev", secret = self.secrets[0])
+
+        #reopen the image with secret1
+        self.closeImageQmp("testdev")
+        self.openImageQmp("testdev", test_img, self.secrets[1])
+
+        # close and erase the image for good
+        self.closeImageQmp("testdev")
+        os.remove(test_img)
+
+    # test that if we erase the old password,
+    # we can still change the encryption keys using 'old-secret'
+    def testOldPassword(self):
+
+        # create the image with secret0 and open it
+        self.createImg(test_img, self.secrets[0]);
+        self.openImageQmp("testdev", test_img, self.secrets[0])
+
+        # add key to slot 1
+        self.addKeyQmp("testdev", secret = self.secrets[1])
+
+        # erase key from slot 0
+        self.eraseKeyQmp("testdev", secret = self.secrets[0])
+
+        # this will fail as the old password is no longer valid
+        self.addKeyQmp("testdev", secret = self.secrets[2])
+
+        # this will work
+        self.addKeyQmp("testdev", secret = self.secrets[2], unlock_secret = self.secrets[1])
+
+        # close and erase the image for good
+        self.closeImageQmp("testdev")
+        os.remove(test_img)
+
+    def testUseForceLuke(self):
+
+        self.createImg(test_img, self.secrets[0]);
+        self.openImageQmp("testdev", test_img, self.secrets[0])
+
+        # Add bunch of secrets
+        self.addKeyQmp("testdev", secret = self.secrets[1], slot=4)
+        self.addKeyQmp("testdev", secret = self.secrets[4], slot=2)
+
+        # overwrite an active secret
+        self.addKeyQmp("testdev", secret = self.secrets[5], slot=2)
+        self.addKeyQmp("testdev", secret = self.secrets[5], slot=2, force=True)
+
+        self.addKeyQmp("testdev", secret = self.secrets[0])
+
+        # Now erase all the secrets
+        self.eraseKeyQmp("testdev", secret = self.secrets[5])
+        self.eraseKeyQmp("testdev", slot=4)
+
+        # erase last keyslot
+        self.eraseKeyQmp("testdev", secret = self.secrets[0])
+        self.eraseKeyQmp("testdev", secret = self.secrets[0], force=True)
+
+        self.closeImageQmp("testdev")
+        os.remove(test_img)
+
+
+if __name__ == '__main__':
+    # Encrypted formats support
+    iotests.main(supported_fmts = ['qcow2', 'luks'])
diff --git a/tests/qemu-iotests/302.out b/tests/qemu-iotests/302.out
new file mode 100644
index 0000000000..da4d43d31e
--- /dev/null
+++ b/tests/qemu-iotests/302.out
@@ -0,0 +1,40 @@
+...
+----------------------------------------------------------------------
+Ran 3 tests
+
+OK
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+Job failed: Invalid password, cannot unlock any keyslot
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+Job failed: Refusing to overwrite active slot 2 - please erase it first
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+Job failed: Requested operation will erase all active keyslots which will erase all the data in the image irreversibly - refusing operation
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
diff --git a/tests/qemu-iotests/303 b/tests/qemu-iotests/303
new file mode 100644
index 0000000000..75cdd099cf
--- /dev/null
+++ b/tests/qemu-iotests/303
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+#
+# Test case for encryption key management versus image sharing
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import iotests
+import os
+import time
+import json
+
+test_img = os.path.join(iotests.test_dir, 'test.img')
+
+class Secret:
+    def __init__(self, index):
+        self._id = "keysec" + str(index)
+        # you are not supposed to see the password...
+        self._secret = "hunter" + str(index)
+
+    def id(self):
+        return self._id
+
+    def secret(self):
+        return self._secret
+
+    def to_cmdline_object(self):
+        return  [ "secret,id=" + self._id + ",data=" + self._secret]
+
+    def to_qmp_object(self):
+        return { "qom_type" : "secret", "id": self.id(),
+                 "props": { "data": self.secret() } }
+
+################################################################################
+
+class EncryptionSetupTestCase(iotests.QMPTestCase):
+
+    # test case startup
+    def setUp(self):
+
+        # start the VMs
+        self.vm1 = iotests.VM(path_suffix = 'VM1')
+        self.vm2 = iotests.VM(path_suffix = 'VM2')
+        self.vm1.launch()
+        self.vm2.launch()
+
+        # create the secrets and load 'em into the VMs
+        self.secrets = [ Secret(i) for i in range(0, 4) ]
+        for secret in self.secrets:
+            result = self.vm1.qmp("object-add", **secret.to_qmp_object())
+            self.assert_qmp(result, 'return', {})
+            result = self.vm2.qmp("object-add", **secret.to_qmp_object())
+            self.assert_qmp(result, 'return', {})
+
+    # test case shutdown
+    def tearDown(self):
+        # stop the VM
+        self.vm1.shutdown()
+        self.vm2.shutdown()
+
+    ###########################################################################
+    # create the encrypted block device using qemu-img
+    def createImg(self, file, secret):
+
+        output = iotests.qemu_img_pipe(
+            'create',
+            '--object', *secret.to_cmdline_object(),
+            '-f', iotests.imgfmt,
+            '-o', 'key-secret=' + secret.id(),
+            '-o', 'iter-time=10',
+            file,
+            '1M')
+
+        iotests.log(output, filters=[iotests.filter_test_dir])
+
+    # attempts to add a key using qemu-img
+    def addKey(self, file, secret_open, secret_add):
+
+        image_options = {
+            'key-secret' : secret_open.id(),
+            'driver' : iotests.imgfmt,
+            'file' : {
+                'driver':'file',
+                'filename': file,
+                }
+            }
+
+        output = iotests.qemu_img_pipe(
+            'amend',
+            '--object', *secret_open.to_cmdline_object(),
+            '--object', *secret_add.to_cmdline_object(),
+
+            '-o', 'keys.0.new-secret=' + secret_add.id(),
+            '-o', 'keys.0.iter-time=10',
+
+            "json:" + json.dumps(image_options)
+            )
+
+        iotests.log(output, filters=[iotests.filter_test_dir])
+
+    ###########################################################################
+    # open an encrypted block device
+    def openImageQmp(self, vm, id, file, secret,
+                     readOnly = False, reOpen = False):
+
+        command = 'x-blockdev-reopen' if reOpen else 'blockdev-add'
+
+        result = vm.qmp(command, **
+            {
+                'driver': iotests.imgfmt,
+                'node-name': id,
+                'read-only': readOnly,
+                'key-secret' : secret.id(),
+                'file': {
+                    'driver': 'file',
+                    'filename': test_img,
+                }
+            }
+        )
+        self.assert_qmp(result, 'return', {})
+
+    # close the encrypted block device
+    def closeImageQmp(self, vm, id):
+        result = vm.qmp('blockdev-del', **{ 'node-name': id })
+        self.assert_qmp(result, 'return', {})
+
+    ###########################################################################
+
+    # add a key to an encrypted block device
+    def addKeyQmp(self, vm, id, secret):
+
+        args = {
+            'node-name': id,
+            'job-id' : 'job0',
+            'options' : {
+                    'driver' : iotests.imgfmt,
+                    'keys': [
+                        {
+                            'new-secret' : secret.id(),
+                            'iter-time' : 10
+                        }
+                    ]
+                },
+        }
+
+        result = vm.qmp('x-blockdev-amend', **args)
+        assert result['return'] == {}
+        vm.run_job('job0')
+
+    # test that when the image opened by two qemu processes,
+    # neither of them can update the image
+    def test1(self):
+        self.createImg(test_img, self.secrets[0]);
+
+        # VM1 opens the image and adds a key
+        self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0])
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[1])
+
+
+        # VM2 opens the image
+        self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0])
+
+
+        # neither VMs now should be able to add a key
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2])
+        self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2])
+
+
+        # VM 1 closes the image
+        self.closeImageQmp(self.vm1, "testdev")
+
+
+        # now VM2 can add the key
+        self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2])
+
+
+        # qemu-img should also not be able to add a key
+        self.addKey(test_img, self.secrets[0], self.secrets[2])
+
+        # cleanup
+        self.closeImageQmp(self.vm2, "testdev")
+        os.remove(test_img)
+
+
+    def test2(self):
+        self.createImg(test_img, self.secrets[0]);
+
+        # VM1 opens the image readonly
+        self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0],
+                          readOnly = True)
+
+        # VM2 opens the image
+        self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0])
+
+        # VM1 can't add a key since image is readonly
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2])
+
+        # VM2 can't add a key since VM is has the image opened
+        self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2])
+
+
+        #VM1 reopens the image read-write
+        self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0],
+                          reOpen = True, readOnly = False)
+
+        # VM1 still can't add the key
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2])
+
+        # VM2 gets away
+        self.closeImageQmp(self.vm2, "testdev")
+
+        # VM1 now can add the key
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2])
+
+        self.closeImageQmp(self.vm1, "testdev")
+        os.remove(test_img)
+
+
+if __name__ == '__main__':
+    # support only raw luks since luks encrypted qcow2 is a proper
+    # format driver which doesn't allow any sharing
+    iotests.main(supported_fmts = ['luks'])
diff --git a/tests/qemu-iotests/303.out b/tests/qemu-iotests/303.out
new file mode 100644
index 0000000000..a62bb20b67
--- /dev/null
+++ b/tests/qemu-iotests/303.out
@@ -0,0 +1,33 @@
+..
+----------------------------------------------------------------------
+Ran 2 tests
+
+OK
+Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
+
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+Job failed: Failed to get shared "consistent read" lock
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+Job failed: Failed to get shared "consistent read" lock
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+qemu-img: Failed to get shared "consistent read" lock
+Is another process using the image [TEST_DIR/test.img]?
+
+Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
+
+Job failed: Block node is read-only
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+Job failed: Failed to get shared "consistent read" lock
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+Job failed: Failed to get shared "consistent read" lock
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 34e3139ad7..c92fff2b4a 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -291,3 +291,6 @@
 
 300 rw auto
 301 rw auto quick
+302 rw auto
+303 rw auto
+
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 84+ messages in thread

* Re: [PATCH 00/13] LUKS: encryption slot management using amend interface
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (12 preceding siblings ...)
  2020-01-14 19:33 ` [PATCH 13/13] iotests: add tests for blockdev-amend Maxim Levitsky
@ 2020-01-14 21:16 ` no-reply
  2020-01-16 14:01   ` Maxim Levitsky
  2020-01-14 21:17 ` no-reply
  14 siblings, 1 reply; 84+ messages in thread
From: no-reply @ 2020-01-14 21:16 UTC (permalink / raw)
  To: mlevitsk
  Cc: kwolf, berrange, qemu-block, armbru, qemu-devel, mreitz, mlevitsk, jsnow

Patchew URL: https://patchew.org/QEMU/20200114193350.10830-1-mlevitsk@redhat.com/



Hi,

This series failed the docker-quick@centos7 build test. Please find the testing commands and
their output below. If you have Docker installed, you can probably reproduce it
locally.

=== TEST SCRIPT BEGIN ===
#!/bin/bash
make docker-image-centos7 V=1 NETWORK=1
time make docker-test-quick@centos7 SHOW_ENV=1 J=14 NETWORK=1
=== TEST SCRIPT END ===

Not run: 301 303
Failures: 049 300
Failed 2 of 110 iotests
make: *** [check-tests/check-block.sh] Error 1
Traceback (most recent call last):
  File "./tests/docker/docker.py", line 662, in <module>
    sys.exit(main())
---
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['sudo', '-n', 'docker', 'run', '--label', 'com.qemu.instance.uuid=1fad358ccc2746a4b0095e3abd7d4c78', '-u', '1003', '--security-opt', 'seccomp=unconfined', '--rm', '-e', 'TARGET_LIST=', '-e', 'EXTRA_CONFIGURE_OPTS=', '-e', 'V=', '-e', 'J=14', '-e', 'DEBUG=', '-e', 'SHOW_ENV=1', '-e', 'CCACHE_DIR=/var/tmp/ccache', '-v', '/home/patchew2/.cache/qemu-docker-ccache:/var/tmp/ccache:z', '-v', '/var/tmp/patchew-tester-tmp-lks33yi2/src/docker-src.2020-01-14-16.05.10.20352:/var/tmp/qemu:z,ro', 'qemu:centos7', '/var/tmp/qemu/run', 'test-quick']' returned non-zero exit status 2.
filter=--filter=label=com.qemu.instance.uuid=1fad358ccc2746a4b0095e3abd7d4c78
make[1]: *** [docker-run] Error 1
make[1]: Leaving directory `/var/tmp/patchew-tester-tmp-lks33yi2/src'
make: *** [docker-run-test-quick@centos7] Error 2

real    11m29.420s
user    0m8.596s


The full log is available at
http://patchew.org/logs/20200114193350.10830-1-mlevitsk@redhat.com/testing.docker-quick@centos7/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 00/13] LUKS: encryption slot management using amend interface
  2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (13 preceding siblings ...)
  2020-01-14 21:16 ` [PATCH 00/13] LUKS: encryption slot management using amend interface no-reply
@ 2020-01-14 21:17 ` no-reply
  2020-01-16 14:19   ` Maxim Levitsky
  14 siblings, 1 reply; 84+ messages in thread
From: no-reply @ 2020-01-14 21:17 UTC (permalink / raw)
  To: mlevitsk
  Cc: kwolf, berrange, qemu-block, armbru, qemu-devel, mreitz, mlevitsk, jsnow

Patchew URL: https://patchew.org/QEMU/20200114193350.10830-1-mlevitsk@redhat.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Subject: [PATCH 00/13] LUKS: encryption slot management using amend interface
Type: series
Message-id: 20200114193350.10830-1-mlevitsk@redhat.com

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Switched to a new branch 'test'
c97e00f iotests: add tests for blockdev-amend
005f7d8 block/qcow2: implement blockdev-amend
fcfaaaa block/crypto: implement blockdev-amend
97610b5 block: add generic infrastructure for x-blockdev-amend qmp command
b93e775 qemu-iotests: qemu-img tests for luks key management
9730684 iotests: filter few more luks specific create options
f98c145 qcow2: extend qemu-img amend interface with crypto options
dd5bc1c block/crypto: implement the encryption key management
ad24636 block/crypto: rename two functions
88f372b block: amend: separate amend and create options for qemu-img
e9720f3 block: amend: add 'force' option
d96c666 qcrypto-luks: implement encryption key management
c41fba3 qcrypto: add generic infrastructure for crypto options amendment

=== OUTPUT BEGIN ===
1/13 Checking commit c41fba3b83a1 (qcrypto: add generic infrastructure for crypto options amendment)
2/13 Checking commit d96c6663e39d (qcrypto-luks: implement encryption key management)
3/13 Checking commit e9720f380038 (block: amend: add 'force' option)
4/13 Checking commit 88f372b238fc (block: amend: separate amend and create options for qemu-img)
ERROR: Macros with multiple statements should be enclosed in a do - while loop
#30: FILE: block/qcow2.c:5448:
+#define QCOW_COMMON_OPTIONS                                         \
+    {                                                               \
+        .name = BLOCK_OPT_SIZE,                                     \
+        .type = QEMU_OPT_SIZE,                                      \
+        .help = "Virtual disk size"                                 \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_COMPAT_LEVEL,                             \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Compatibility level (v2 [0.10] or v3 [1.1])"       \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_BACKING_FILE,                             \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "File name of a base image"                         \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_BACKING_FMT,                              \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Image format of the base image"                    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_DATA_FILE,                                \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "File name of an external data file"                \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_DATA_FILE_RAW,                            \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "The external data file must stay valid "           \
+                "as a raw image"                                    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_ENCRYPT,                                  \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "Encrypt the image with format 'aes'. (Deprecated " \
+                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
+    },                                                              \
+    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
+        "ID of secret providing qcow AES key or LUKS passphrase"),  \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
+    {                                                               \
+        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
+        .type = QEMU_OPT_SIZE,                                      \
+        .help = "qcow2 cluster size",                               \
+        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_PREALLOC,                                 \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Preallocation mode (allowed values: off, "         \
+                "metadata, falloc, full)"                           \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "Postpone refcount updates",                        \
+        .def_value_str = "off"                                      \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_REFCOUNT_BITS,                            \
+        .type = QEMU_OPT_NUMBER,                                    \
+        .help = "Width of a reference count entry in bits",         \
+        .def_value_str = "16"                                       \
+    }                                                               \
+

total: 1 errors, 0 warnings, 231 lines checked

Patch 4/13 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

5/13 Checking commit ad24636acdc5 (block/crypto: rename two functions)
6/13 Checking commit dd5bc1cc0be5 (block/crypto: implement the encryption key management)
ERROR: Macros with complex values should be enclosed in parenthesis
#253: FILE: block/crypto.h:116:
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE(prefix)  \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(prefix),            \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(prefix),         \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(prefix),         \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix)           \
+

total: 1 errors, 0 warnings, 213 lines checked

Patch 6/13 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

7/13 Checking commit f98c145c0ebc (qcow2: extend qemu-img amend interface with crypto options)
ERROR: "foo* bar" should be "foo *bar"
#44: FILE: block/qcow2.c:4648:
+    QDict* crypto_opts_dict;

total: 1 errors, 0 warnings, 165 lines checked

Patch 7/13 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

8/13 Checking commit 97306845f3fb (iotests: filter few more luks specific create options)
9/13 Checking commit b93e77524180 (qemu-iotests: qemu-img tests for luks key management)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#14: 
new file mode 100755

total: 0 errors, 1 warnings, 432 lines checked

Patch 9/13 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
10/13 Checking commit 97610b546fea (block: add generic infrastructure for x-blockdev-amend qmp command)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#30: 
new file mode 100644

total: 0 errors, 1 warnings, 215 lines checked

Patch 10/13 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
11/13 Checking commit fcfaaaa88585 (block/crypto: implement blockdev-amend)
12/13 Checking commit 005f7d83f052 (block/qcow2: implement blockdev-amend)
13/13 Checking commit c97e00f6078b (iotests: add tests for blockdev-amend)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#14: 
new file mode 100644

total: 0 errors, 1 warnings, 598 lines checked

Patch 13/13 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20200114193350.10830-1-mlevitsk@redhat.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 00/13] LUKS: encryption slot management using amend interface
  2020-01-14 21:16 ` [PATCH 00/13] LUKS: encryption slot management using amend interface no-reply
@ 2020-01-16 14:01   ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-16 14:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berrange, qemu-block, armbru, mreitz, jsnow

On Tue, 2020-01-14 at 13:16 -0800, no-reply@patchew.org wrote:
> Patchew URL: https://patchew.org/QEMU/20200114193350.10830-1-mlevitsk@redhat.com/
> 
> 
> 
> Hi,
> 
> This series failed the docker-quick@centos7 build test. Please find the testing commands and
> their output below. If you have Docker installed, you can probably reproduce it
> locally.
> 
> === TEST SCRIPT BEGIN ===
> #!/bin/bash
> make docker-image-centos7 V=1 NETWORK=1
> time make docker-test-quick@centos7 SHOW_ENV=1 J=14 NETWORK=1
> === TEST SCRIPT END ===
> 
> Not run: 301 303
> Failures: 049 300
> Failed 2 of 110 iotests
> make: *** [check-tests/check-block.sh] Error 1
> Traceback (most recent call last):
>   File "./tests/docker/docker.py", line 662, in <module>
>     sys.exit(main())
> ---
>     raise CalledProcessError(retcode, cmd)
> subprocess.CalledProcessError: Command '['sudo', '-n', 'docker', 'run', '--label', 'com.qemu.instance.uuid=1fad358ccc2746a4b0095e3abd7d4c78', '-u', '1003', '--security-opt', 'seccomp=unconfined', '-
> -rm', '-e', 'TARGET_LIST=', '-e', 'EXTRA_CONFIGURE_OPTS=', '-e', 'V=', '-e', 'J=14', '-e', 'DEBUG=', '-e', 'SHOW_ENV=1', '-e', 'CCACHE_DIR=/var/tmp/ccache', '-v', '/home/patchew2/.cache/qemu-docker-
> ccache:/var/tmp/ccache:z', '-v', '/var/tmp/patchew-tester-tmp-lks33yi2/src/docker-src.2020-01-14-16.05.10.20352:/var/tmp/qemu:z,ro', 'qemu:centos7', '/var/tmp/qemu/run', 'test-quick']' returned non-
> zero exit status 2.
> filter=--filter=label=com.qemu.instance.uuid=1fad358ccc2746a4b0095e3abd7d4c78
> make[1]: *** [docker-run] Error 1
> make[1]: Leaving directory `/var/tmp/patchew-tester-tmp-lks33yi2/src'
> make: *** [docker-run-test-quick@centos7] Error 2
> 
> real    11m29.420s
> user    0m8.596s
> 
> 
> The full log is available at
> http://patchew.org/logs/20200114193350.10830-1-mlevitsk@redhat.com/testing.docker-quick@centos7/?type=message.
> ---
> Email generated automatically by Patchew [https://patchew.org/].
> Please send your feedback to patchew-devel@redhat.com

Hi, this is my fault. I made tiny change in the error message, and didn't update
the output of 300 test.

I'll fix that in next version of the patches.

Best regards,
	Maxim Levitsky



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 00/13] LUKS: encryption slot management using amend interface
  2020-01-14 21:17 ` no-reply
@ 2020-01-16 14:19   ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-16 14:19 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berrange, qemu-block, armbru, mreitz, jsnow

On Tue, 2020-01-14 at 13:17 -0800, no-reply@patchew.org wrote:
> Patchew URL: https://patchew.org/QEMU/20200114193350.10830-1-mlevitsk@redhat.com/
> 
> 
> 
> Hi,
> 
> This series seems to have some coding style problems. See output below for
> more information:
> 
> Subject: [PATCH 00/13] LUKS: encryption slot management using amend interface
> Type: series
> Message-id: 20200114193350.10830-1-mlevitsk@redhat.com
> 
> === TEST SCRIPT BEGIN ===
> #!/bin/bash
> git rev-parse base > /dev/null || exit 0
> git config --local diff.renamelimit 0
> git config --local diff.renames True
> git config --local diff.algorithm histogram
> ./scripts/checkpatch.pl --mailback base..
> === TEST SCRIPT END ===
> 
> Switched to a new branch 'test'
> c97e00f iotests: add tests for blockdev-amend
> 005f7d8 block/qcow2: implement blockdev-amend
> fcfaaaa block/crypto: implement blockdev-amend
> 97610b5 block: add generic infrastructure for x-blockdev-amend qmp command
> b93e775 qemu-iotests: qemu-img tests for luks key management
> 9730684 iotests: filter few more luks specific create options
> f98c145 qcow2: extend qemu-img amend interface with crypto options
> dd5bc1c block/crypto: implement the encryption key management
> ad24636 block/crypto: rename two functions
> 88f372b block: amend: separate amend and create options for qemu-img
> e9720f3 block: amend: add 'force' option
> d96c666 qcrypto-luks: implement encryption key management
> c41fba3 qcrypto: add generic infrastructure for crypto options amendment
> 
> === OUTPUT BEGIN ===
> 1/13 Checking commit c41fba3b83a1 (qcrypto: add generic infrastructure for crypto options amendment)
> 2/13 Checking commit d96c6663e39d (qcrypto-luks: implement encryption key management)
> 3/13 Checking commit e9720f380038 (block: amend: add 'force' option)
> 4/13 Checking commit 88f372b238fc (block: amend: separate amend and create options for qemu-img)
> ERROR: Macros with multiple statements should be enclosed in a do - while loop
> #30: FILE: block/qcow2.c:5448:
> +#define QCOW_COMMON_OPTIONS                                         \
> +    {                                                               \
> +        .name = BLOCK_OPT_SIZE,                                     \
> +        .type = QEMU_OPT_SIZE,                                      \
> +        .help = "Virtual disk size"                                 \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_COMPAT_LEVEL,                             \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Compatibility level (v2 [0.10] or v3 [1.1])"       \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_BACKING_FILE,                             \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "File name of a base image"                         \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_BACKING_FMT,                              \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Image format of the base image"                    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_DATA_FILE,                                \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "File name of an external data file"                \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_DATA_FILE_RAW,                            \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "The external data file must stay valid "           \
> +                "as a raw image"                                    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_ENCRYPT,                                  \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "Encrypt the image with format 'aes'. (Deprecated " \
> +                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
> +    },                                                              \
> +    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
> +        "ID of secret providing qcow AES key or LUKS passphrase"),  \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
> +    {                                                               \
> +        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
> +        .type = QEMU_OPT_SIZE,                                      \
> +        .help = "qcow2 cluster size",                               \
> +        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_PREALLOC,                                 \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Preallocation mode (allowed values: off, "         \
> +                "metadata, falloc, full)"                           \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "Postpone refcount updates",                        \
> +        .def_value_str = "off"                                      \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_REFCOUNT_BITS,                            \
> +        .type = QEMU_OPT_NUMBER,                                    \
> +        .help = "Width of a reference count entry in bits",         \
> +        .def_value_str = "16"                                       \
> +    }                                                               \
> +
> 
> total: 1 errors, 0 warnings, 231 lines checked
> 
> Patch 4/13 has style problems, please review.  If any of these errors
> are false positives report them to the maintainer, see
> CHECKPATCH in MAINTAINERS.
> 
> 5/13 Checking commit ad24636acdc5 (block/crypto: rename two functions)
> 6/13 Checking commit dd5bc1cc0be5 (block/crypto: implement the encryption key management)
> ERROR: Macros with complex values should be enclosed in parenthesis
> #253: FILE: block/crypto.h:116:
> +#define BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE(prefix)  \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(prefix),            \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(prefix),         \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(prefix),         \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix)           \
> +
> 
> total: 1 errors, 0 warnings, 213 lines checked
> 
> Patch 6/13 has style problems, please review.  If any of these errors
> are false positives report them to the maintainer, see
> CHECKPATCH in MAINTAINERS.
> 
> 7/13 Checking commit f98c145c0ebc (qcow2: extend qemu-img amend interface with crypto options)
> ERROR: "foo* bar" should be "foo *bar"
> #44: FILE: block/qcow2.c:4648:
> +    QDict* crypto_opts_dict;
> 
> total: 1 errors, 0 warnings, 165 lines checked
> 
> Patch 7/13 has style problems, please review.  If any of these errors
> are false positives report them to the maintainer, see
> CHECKPATCH in MAINTAINERS.
> 
> 8/13 Checking commit 97306845f3fb (iotests: filter few more luks specific create options)
> 9/13 Checking commit b93e77524180 (qemu-iotests: qemu-img tests for luks key management)
> WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
> #14: 
> new file mode 100755
> 
> total: 0 errors, 1 warnings, 432 lines checked
> 
> Patch 9/13 has style problems, please review.  If any of these errors
> are false positives report them to the maintainer, see
> CHECKPATCH in MAINTAINERS.
> 10/13 Checking commit 97610b546fea (block: add generic infrastructure for x-blockdev-amend qmp command)
> WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
> #30: 
> new file mode 100644
> 
> total: 0 errors, 1 warnings, 215 lines checked
> 
> Patch 10/13 has style problems, please review.  If any of these errors
> are false positives report them to the maintainer, see
> CHECKPATCH in MAINTAINERS.
> 11/13 Checking commit fcfaaaa88585 (block/crypto: implement blockdev-amend)
> 12/13 Checking commit 005f7d83f052 (block/qcow2: implement blockdev-amend)
> 13/13 Checking commit c97e00f6078b (iotests: add tests for blockdev-amend)
> WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
> #14: 
> new file mode 100644
> 
> total: 0 errors, 1 warnings, 598 lines checked
> 
> Patch 13/13 has style problems, please review.  If any of these errors
> are false positives report them to the maintainer, see
> CHECKPATCH in MAINTAINERS.
> === OUTPUT END ===
> 
> Test command exited with code: 1
> 
> 
> The full log is available at
> http://patchew.org/logs/20200114193350.10830-1-mlevitsk@redhat.com/testing.checkpatch/?type=message.

This time I did throughout manual check of all patches, since checkpatch.pl doesn't catch most
of coding style mistakes I automatically do, but for commit f98c145c0ebc, I somehow missed this.

The warnings about macro style are known to me, I tried to do this similar to rest of the luks code.

Best regards,
	Maxim Levitsky


> ---
> Email generated automatically by Patchew [https://patchew.org/].
> Please send your feedback to patchew-devel@redhat.com




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-14 19:33 ` [PATCH 02/13] qcrypto-luks: implement encryption key management Maxim Levitsky
@ 2020-01-21  7:54   ` Markus Armbruster
  2020-01-21 13:13     ` Maxim Levitsky
  2020-01-28 17:21   ` Daniel P. Berrangé
  2020-02-15 14:51   ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Markus Armbruster
  2 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-01-21  7:54 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

Reviewing just the QAPI schema.

Maxim Levitsky <mlevitsk@redhat.com> writes:

> Next few patches will expose that functionality
> to the user.
>
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  crypto/block-luks.c | 374 +++++++++++++++++++++++++++++++++++++++++++-
>  qapi/crypto.json    |  50 +++++-
>  2 files changed, 421 insertions(+), 3 deletions(-)
>
> diff --git a/crypto/block-luks.c b/crypto/block-luks.c
> index 4861db810c..349e95fed3 100644
> --- a/crypto/block-luks.c
> +++ b/crypto/block-luks.c
> @@ -32,6 +32,7 @@
>  #include "qemu/uuid.h"
>  
>  #include "qemu/coroutine.h"
> +#include "qemu/bitmap.h"
>  
>  /*
>   * Reference for the LUKS format implemented here is
> @@ -70,6 +71,9 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
>  
>  #define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL
>  
> +#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS 2000
> +#define QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS 40
> +
>  static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = {
>      'L', 'U', 'K', 'S', 0xBA, 0xBE
>  };
> @@ -219,6 +223,9 @@ struct QCryptoBlockLUKS {
>  
>      /* Hash algorithm used in pbkdf2 function */
>      QCryptoHashAlgorithm hash_alg;
> +
> +    /* Name of the secret that was used to open the image */
> +    char *secret;
>  };
>  
>  
> @@ -1069,6 +1076,112 @@ qcrypto_block_luks_find_key(QCryptoBlock *block,
>      return -1;
>  }
>  
> +/*
> + * Returns true if a slot i is marked as active
> + * (contains encrypted copy of the master key)
> + */
> +static bool
> +qcrypto_block_luks_slot_active(const QCryptoBlockLUKS *luks,
> +                               unsigned int slot_idx)
> +{
> +    uint32_t val = luks->header.key_slots[slot_idx].active;
> +    return val ==  QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED;
> +}
> +
> +/*
> + * Returns the number of slots that are marked as active
> + * (slots that contain encrypted copy of the master key)
> + */
> +static unsigned int
> +qcrypto_block_luks_count_active_slots(const QCryptoBlockLUKS *luks)
> +{
> +    size_t i = 0;
> +    unsigned int ret = 0;
> +
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        if (qcrypto_block_luks_slot_active(luks, i)) {
> +            ret++;
> +        }
> +    }
> +    return ret;
> +}
> +
> +/*
> + * Finds first key slot which is not active
> + * Returns the key slot index, or -1 if it doesn't exist
> + */
> +static int
> +qcrypto_block_luks_find_free_keyslot(const QCryptoBlockLUKS *luks)
> +{
> +    size_t i;
> +
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        if (!qcrypto_block_luks_slot_active(luks, i)) {
> +            return i;
> +        }
> +    }
> +    return -1;
> +
> +}
> +
> +/*
> + * Erases an keyslot given its index
> + * Returns:
> + *    0 if the keyslot was erased successfully
> + *   -1 if a error occurred while erasing the keyslot
> + *
> + */
> +static int
> +qcrypto_block_luks_erase_key(QCryptoBlock *block,
> +                             unsigned int slot_idx,
> +                             QCryptoBlockWriteFunc writefunc,
> +                             void *opaque,
> +                             Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
> +    g_autofree uint8_t *garbagesplitkey = NULL;
> +    size_t splitkeylen = luks->header.master_key_len * slot->stripes;
> +    size_t i;
> +
> +    assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +    assert(splitkeylen > 0);
> +
> +    garbagesplitkey = g_new0(uint8_t, splitkeylen);
> +
> +    /* Reset the key slot header */
> +    memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN);
> +    slot->iterations = 0;
> +    slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
> +
> +    qcrypto_block_luks_store_header(block,  writefunc, opaque, errp);
> +
> +    /*
> +     * Now try to erase the key material, even if the header
> +     * update failed
> +     */
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) {
> +        if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) {
> +            /*
> +             * If we failed to get the random data, still write
> +             * at least zeros to the key slot at least once
> +             */
> +            if (i > 0) {
> +                return -1;
> +            }
> +        }
> +
> +        if (writefunc(block,
> +                      slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
> +                      garbagesplitkey,
> +                      splitkeylen,
> +                      opaque,
> +                      errp) != splitkeylen) {
> +            return -1;
> +        }
> +    }
> +    return 0;
> +}
>  
>  static int
>  qcrypto_block_luks_open(QCryptoBlock *block,
> @@ -1099,6 +1212,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
>  
>      luks = g_new0(QCryptoBlockLUKS, 1);
>      block->opaque = luks;
> +    luks->secret = g_strdup(options->u.luks.key_secret);
>  
>      if (qcrypto_block_luks_load_header(block, readfunc, opaque, errp) < 0) {
>          goto fail;
> @@ -1164,6 +1278,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
>   fail:
>      qcrypto_block_free_cipher(block);
>      qcrypto_ivgen_free(block->ivgen);
> +    g_free(luks->secret);
>      g_free(luks);
>      return -1;
>  }
> @@ -1204,7 +1319,7 @@ qcrypto_block_luks_create(QCryptoBlock *block,
>  
>      memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts));
>      if (!luks_opts.has_iter_time) {
> -        luks_opts.iter_time = 2000;
> +        luks_opts.iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
>      }
>      if (!luks_opts.has_cipher_alg) {
>          luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
> @@ -1244,6 +1359,8 @@ qcrypto_block_luks_create(QCryptoBlock *block,
>                     optprefix ? optprefix : "");
>          goto error;
>      }
> +    luks->secret = g_strdup(options->u.luks.key_secret);
> +
>      password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
>      if (!password) {
>          goto error;
> @@ -1471,10 +1588,260 @@ qcrypto_block_luks_create(QCryptoBlock *block,
>      qcrypto_block_free_cipher(block);
>      qcrypto_ivgen_free(block->ivgen);
>  
> +    g_free(luks->secret);
>      g_free(luks);
>      return -1;
>  }
>  
> +/*
> + * Given LUKSKeyslotUpdate command, return @slots_bitmap with all slots
> + * that will be updated with new password (or erased)
> + * returns number of affected slots
> + */
> +static int qcrypto_block_luks_get_slots_bitmap(QCryptoBlock *block,
> +                                               QCryptoBlockReadFunc readfunc,
> +                                               void *opaque,
> +                                               const LUKSKeyslotUpdate *command,
> +                                               unsigned long *slots_bitmap,
> +                                               Error **errp)
> +{
> +    const QCryptoBlockLUKS *luks = block->opaque;
> +    size_t i;
> +    int ret = 0;
> +
> +    if (command->has_keyslot) {
> +        /* keyslot set, select only this keyslot */
> +        int keyslot = command->keyslot;
> +
> +        if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
> +            error_setg(errp,
> +                       "Invalid slot %u specified, must be between 0 and %u",
> +                       keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
> +            goto error;
> +        }
> +        bitmap_set(slots_bitmap, keyslot, 1);
> +        ret++;
> +
> +    } else if (command->has_old_secret) {
> +        /* initially select all active keyslots */
> +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +            if (qcrypto_block_luks_slot_active(luks, i)) {
> +                bitmap_set(slots_bitmap, i, 1);
> +                ret++;
> +            }
> +        }
> +    } else {
> +        /* find a free keyslot */
> +        int slot = qcrypto_block_luks_find_free_keyslot(luks);
> +
> +        if (slot == -1) {
> +            error_setg(errp,
> +                       "Can't add a keyslot - all key slots are in use");
> +            goto error;
> +        }
> +        bitmap_set(slots_bitmap, slot, 1);
> +        ret++;
> +    }
> +
> +    if (command->has_old_secret) {
> +        /* now deselect all keyslots that don't contain the password */
> +        g_autofree uint8_t *tmpkey = g_new0(uint8_t,
> +                                            luks->header.master_key_len);
> +
> +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +            g_autofree char *old_password = NULL;
> +            int rv;
> +
> +            if (!test_bit(i, slots_bitmap)) {
> +                continue;
> +            }
> +
> +            old_password = qcrypto_secret_lookup_as_utf8(command->old_secret,
> +                                                         errp);
> +            if (!old_password) {
> +                goto error;
> +            }
> +
> +            rv = qcrypto_block_luks_load_key(block,
> +                                             i,
> +                                             old_password,
> +                                             tmpkey,
> +                                             readfunc,
> +                                             opaque,
> +                                             errp);
> +            if (rv == -1)
> +                goto error;
> +            else if (rv == 0) {
> +                bitmap_clear(slots_bitmap, i, 1);
> +                ret--;
> +            }
> +        }
> +    }
> +    return ret;
> +error:
> +    return -1;
> +}
> +
> +/*
> + * Apply a single keyslot update command as described in @command
> + * Optionally use @unlock_secret to retrieve the master key
> + */
> +static int
> +qcrypto_block_luks_apply_keyslot_update(QCryptoBlock *block,
> +                                        QCryptoBlockReadFunc readfunc,
> +                                        QCryptoBlockWriteFunc writefunc,
> +                                        void *opaque,
> +                                        LUKSKeyslotUpdate *command,
> +                                        const char *unlock_secret,
> +                                        uint8_t **master_key,
> +                                        bool force,
> +                                        Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    g_autofree unsigned long *slots_bitmap = NULL;
> +    int64_t iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
> +    int slot_count;
> +    size_t i;
> +    char *new_password;
> +    bool erasing;
> +
> +    slots_bitmap = bitmap_new(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +    slot_count = qcrypto_block_luks_get_slots_bitmap(block, readfunc, opaque,
> +                                                     command, slots_bitmap,
> +                                                     errp);
> +    if (slot_count == -1) {
> +        goto error;
> +    }
> +    /* no matching slots, so nothing to do */
> +    if (slot_count == 0) {
> +        error_setg(errp, "Requested operation didn't match any slots");
> +        goto error;
> +    }
> +    /*
> +     * slot is erased when the password is set to null, or empty string
> +     * (for compatibility with command line)
> +     */
> +    erasing = command->new_secret->type == QTYPE_QNULL ||
> +              strlen(command->new_secret->u.s) == 0;
> +
> +    /* safety checks */
> +    if (!force) {
> +        if (erasing) {
> +            if (slot_count == qcrypto_block_luks_count_active_slots(luks)) {
> +                error_setg(errp,
> +                           "Requested operation will erase all active keyslots"
> +                           " which will erase all the data in the image"
> +                           " irreversibly - refusing operation");
> +                goto error;
> +            }
> +        } else {
> +            for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +                if (!test_bit(i, slots_bitmap)) {
> +                    continue;
> +                }
> +                if (qcrypto_block_luks_slot_active(luks, i)) {
> +                    error_setg(errp,
> +                               "Refusing to overwrite active slot %zu - "
> +                               "please erase it first", i);
> +                    goto error;
> +                }
> +            }
> +        }
> +    }
> +
> +    /* setup the data needed for storing the new keyslot */
> +    if (!erasing) {
> +        /* Load the master key if it wasn't already loaded */
> +        if (!*master_key) {
> +            g_autofree char *old_password;
> +            old_password = qcrypto_secret_lookup_as_utf8(unlock_secret,  errp);
> +            if (!old_password) {
> +                goto error;
> +            }
> +            *master_key = g_new0(uint8_t, luks->header.master_key_len);
> +
> +            if (qcrypto_block_luks_find_key(block, old_password, *master_key,
> +                                            readfunc, opaque, errp) < 0) {
> +                error_append_hint(errp, "Failed to retrieve the master key");
> +                goto error;
> +            }
> +        }
> +        new_password = qcrypto_secret_lookup_as_utf8(command->new_secret->u.s,
> +                                                     errp);
> +        if (!new_password) {
> +            goto error;
> +        }
> +        if (command->has_iter_time) {
> +            iter_time = command->iter_time;
> +        }
> +    }
> +
> +    /* new apply the update */
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        if (!test_bit(i, slots_bitmap)) {
> +            continue;
> +        }
> +        if (erasing) {
> +            if (qcrypto_block_luks_erase_key(block, i,
> +                                             writefunc,
> +                                             opaque,
> +                                             errp)) {
> +                error_append_hint(errp, "Failed to erase keyslot %zu", i);
> +                goto error;
> +            }
> +        } else {
> +            if (qcrypto_block_luks_store_key(block, i,
> +                                             new_password,
> +                                             *master_key,
> +                                             iter_time,
> +                                             writefunc,
> +                                             opaque,
> +                                             errp)) {
> +                error_append_hint(errp, "Failed to write to keyslot %zu", i);
> +                goto error;
> +            }
> +        }
> +    }
> +    return 0;
> +error:
> +    return -EINVAL;
> +}
> +
> +static int
> +qcrypto_block_luks_amend_options(QCryptoBlock *block,
> +                                 QCryptoBlockReadFunc readfunc,
> +                                 QCryptoBlockWriteFunc writefunc,
> +                                 void *opaque,
> +                                 QCryptoBlockAmendOptions *options,
> +                                 bool force,
> +                                 Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    QCryptoBlockAmendOptionsLUKS *options_luks = &options->u.luks;
> +    LUKSKeyslotUpdateList *ptr;
> +    g_autofree uint8_t *master_key = NULL;
> +    int ret;
> +
> +    char *unlock_secret = options_luks->has_unlock_secret ?
> +                          options_luks->unlock_secret :
> +                          luks->secret;
> +
> +    for (ptr = options_luks->keys; ptr; ptr = ptr->next) {
> +        ret = qcrypto_block_luks_apply_keyslot_update(block, readfunc,
> +                                                      writefunc, opaque,
> +                                                      ptr->value,
> +                                                      unlock_secret,
> +                                                      &master_key,
> +                                                      force, errp);
> +
> +        if (ret != 0) {
> +            goto error;
> +        }
> +    }
> +    return 0;
> +error:
> +    return -1;
> +}
>  
>  static int qcrypto_block_luks_get_info(QCryptoBlock *block,
>                                         QCryptoBlockInfo *info,
> @@ -1523,7 +1890,9 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block,
>  
>  static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
>  {
> -    g_free(block->opaque);
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    g_free(luks->secret);
> +    g_free(luks);
>  }
>  
>  
> @@ -1560,6 +1929,7 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block,
>  const QCryptoBlockDriver qcrypto_block_driver_luks = {
>      .open = qcrypto_block_luks_open,
>      .create = qcrypto_block_luks_create,
> +    .amend = qcrypto_block_luks_amend_options,
>      .get_info = qcrypto_block_luks_get_info,
>      .cleanup = qcrypto_block_luks_cleanup,
>      .decrypt = qcrypto_block_luks_decrypt,
> diff --git a/qapi/crypto.json b/qapi/crypto.json
> index 9faebd03d4..e83847c71e 100644
> --- a/qapi/crypto.json
> +++ b/qapi/crypto.json
> @@ -1,6 +1,8 @@
>  # -*- Mode: Python -*-
>  #
>  
> +{ 'include': 'common.json' }
> +
>  ##
>  # = Cryptography
>  ##
> @@ -310,6 +312,52 @@
>    'discriminator': 'format',
>    'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
>  
> +##
> +# @LUKSKeyslotUpdate:
> +#
> +# @keyslot:         If specified, will update only keyslot with this index
> +#
> +# @old-secret:      If specified, will only update keyslots that
> +#                   can be opened with password which is contained in
> +#                   QCryptoSecret with @old-secret ID
> +#
> +#                   If neither @keyslot nor @old-secret is specified,
> +#                   first empty keyslot is selected for the update
> +#
> +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> +#                   key to place in all matching keyslots.
> +#                   null/empty string erases all matching keyslots

I hate making the empty string do something completely different than a
non-empty string.

What about making @new-secret optional, and have absent @new-secret
erase?

> +#                                                                  unless
> +#                   last valid keyslot is erased.

Leaves me to wonder what happens when I try.  If I read your code
correctly, it's an error.  Suggest "You cannot erase the last valid
keyslot".

Not documented here: "Refusing to overwrite active slot".

> +#
> +# @iter-time:       number of milliseconds to spend in
> +#                   PBKDF passphrase processing

Default?

> +# Since: 5.0
> +##
> +{ 'struct': 'LUKSKeyslotUpdate',
> +  'data': {
> +           '*keyslot': 'int',
> +           '*old-secret': 'str',
> +           'new-secret' : 'StrOrNull',
> +           '*iter-time' : 'int' } }
> +
> +
> +##
> +# @QCryptoBlockAmendOptionsLUKS:
> +#
> +# The options that can be changed on existing luks encrypted device
> +# @keys:           list of keyslot updates to perform
> +#                  (updates are performed in order)
> +# @unlock-secret:  use this secret to retrieve the current master key
> +#                  if not given will use the same secret as one

"as the one"?

> +#                  that was used to open the image
> +#
> +# Since: 5.0
> +##
> +{ 'struct': 'QCryptoBlockAmendOptionsLUKS',
> +  'data' : {
> +            'keys': ['LUKSKeyslotUpdate'],
> +             '*unlock-secret' : 'str' } }
> +
>  
>  
>  ##
> @@ -324,4 +372,4 @@
>    'base': 'QCryptoBlockOptionsBase',
>    'discriminator': 'format',
>    'data': {
> -            } }
> +          'luks': 'QCryptoBlockAmendOptionsLUKS' } }



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 10/13] block: add generic infrastructure for x-blockdev-amend qmp command
  2020-01-14 19:33 ` [PATCH 10/13] block: add generic infrastructure for x-blockdev-amend qmp command Maxim Levitsky
@ 2020-01-21  7:59   ` Markus Armbruster
  2020-01-21 13:58     ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-01-21  7:59 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, qemu-devel, Max Reitz, John Snow

Maxim Levitsky <mlevitsk@redhat.com> writes:

> blockdev-amend will be used similiar to blockdev-create
> to allow on the fly changes of the structure of the format based block devices.
>
> Current plan is to first support encryption keyslot management for luks
> based formats (raw and embedded in qcow2)
>
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
[...]
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 7ff5e5edaf..601f7dc9a4 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -4743,6 +4743,48 @@
>    'data': { 'job-id': 'str',
>              'options': 'BlockdevCreateOptions' } }
>  
> +##
> +# @BlockdevAmendOptions:
> +#
> +# Options for amending an image format
> +#
> +# @driver           block driver that is suitable for the image
> +#
> +# Since: 5.0
> +##
> +{ 'union': 'BlockdevAmendOptions',
> +  'base': {
> +      'driver':         'BlockdevDriver' },
> +  'discriminator': 'driver',
> +  'data': {
> +  } }
> +
> +##
> +# @x-blockdev-amend:
> +#
> +# Starts a job to amend format specific options of an existing open block device
> +# The job is automatically finalized, but a manual job-dismiss is required.
> +#
> +# @job-id:          Identifier for the newly created job.
> +#
> +# @node-name:       Name of the block node to work on
> +#
> +# @options:         Options (driver specific)
> +#
> +# @force:           Allow unsafe operations, format specific
> +#                   For luks that allows erase of the last active keyslot
> +#                   (permanent loss of data),
> +#                   and replacement of an active keyslot
> +#                   (possible loss of data if IO error happens)

PATCH 2 appears to reject that.  What am I missing?

> +#
> +# Since: 5.0
> +##
> +{ 'command': 'x-blockdev-amend',
> +  'data': { 'job-id': 'str',
> +            'node-name': 'str',
> +            'options': 'BlockdevAmendOptions',
> +            '*force': 'bool' } }
> +
>  ##
>  # @blockdev-open-tray:
>  #
> diff --git a/qapi/job.json b/qapi/job.json
> index a121b615fb..362b634ec1 100644
> --- a/qapi/job.json
> +++ b/qapi/job.json
> @@ -19,10 +19,12 @@
>  #
>  # @create: image creation job type, see "blockdev-create" (since 3.0)
>  #
> +# @amend: image options amend job type, see "x-blockdev-amend" (since 5.0)
> +#
>  # Since: 1.7
>  ##
>  { 'enum': 'JobType',
> -  'data': ['commit', 'stream', 'mirror', 'backup', 'create'] }
> +  'data': ['commit', 'stream', 'mirror', 'backup', 'create', 'amend'] }
>  
>  ##
>  # @JobStatus:



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-21  7:54   ` Markus Armbruster
@ 2020-01-21 13:13     ` Maxim Levitsky
  2020-01-28 17:11       ` Daniel P. Berrangé
  0 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-21 13:13 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Daniel P.Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:

<trimmed>

> > +##
> > +# @LUKSKeyslotUpdate:
> > +#
> > +# @keyslot:         If specified, will update only keyslot with this index
> > +#
> > +# @old-secret:      If specified, will only update keyslots that
> > +#                   can be opened with password which is contained in
> > +#                   QCryptoSecret with @old-secret ID
> > +#
> > +#                   If neither @keyslot nor @old-secret is specified,
> > +#                   first empty keyslot is selected for the update
> > +#
> > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > +#                   key to place in all matching keyslots.
> > +#                   null/empty string erases all matching keyslots
> 
> I hate making the empty string do something completely different than a
> non-empty string.
> 
> What about making @new-secret optional, and have absent @new-secret
> erase?

I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
I don't mind personally to do this this way.
empty string though is my addition, since its not possible to pass null on command line.

> 
> > +#                                                                  unless
> > +#                   last valid keyslot is erased.
> 
> Leaves me to wonder what happens when I try.  If I read your code
> correctly, it's an error.  Suggest "You cannot erase the last valid
> keyslot".
> 
> Not documented here: "Refusing to overwrite active slot".

In my current implementation, if all slots are selected for erase,
I just refuse the erase operation. In former versions of my patches,
I would instead erase all but the last one.
IMHO, its unlikely that more that one slot would have the same password,
thus anyway correct usage for replacing the password would be first add
a new slot, then erase all slots that match the old password.
If all slots are active and have the same password, then user still can
use 'force' option to overwrite one of them.

> 
> > +#
> > +# @iter-time:       number of milliseconds to spend in
> > +#                   PBKDF passphrase processing
> 
> Default?
2000, as in QCryptoBlockCreateOptionsLUKS. I forgot to copy this here.

> 
> > +# Since: 5.0
> > +##
> > +{ 'struct': 'LUKSKeyslotUpdate',
> > +  'data': {
> > +           '*keyslot': 'int',
> > +           '*old-secret': 'str',
> > +           'new-secret' : 'StrOrNull',
> > +           '*iter-time' : 'int' } }
> > +
> > +
> > +##
> > +# @QCryptoBlockAmendOptionsLUKS:
> > +#
> > +# The options that can be changed on existing luks encrypted device
> > +# @keys:           list of keyslot updates to perform
> > +#                  (updates are performed in order)
> > +# @unlock-secret:  use this secret to retrieve the current master key
> > +#                  if not given will use the same secret as one
> 
> "as the one"?
Yea, this is wrong wording, I'll drop those words. Thanks.

> 
> > +#                  that was used to open the image
> > +#
> > +# Since: 5.0
> > +##
> > +{ 'struct': 'QCryptoBlockAmendOptionsLUKS',
> > +  'data' : {
> > +            'keys': ['LUKSKeyslotUpdate'],
> > +             '*unlock-secret' : 'str' } }
> > +
> >   
> >   
> >   ##
> > @@ -324,4 +372,4 @@
> >     'base': 'QCryptoBlockOptionsBase',
> >     'discriminator': 'format',
> >     'data': {
> > -            } }
> > +          'luks': 'QCryptoBlockAmendOptionsLUKS' } }


Thanks for review,
	Best regards,
		Maxim Levitsky



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 10/13] block: add generic infrastructure for x-blockdev-amend qmp command
  2020-01-21  7:59   ` Markus Armbruster
@ 2020-01-21 13:58     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-21 13:58 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Daniel P.Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

On Tue, 2020-01-21 at 08:59 +0100, Markus Armbruster wrote:
> Maxim Levitsky <mlevitsk@redhat.com> writes:
> 
> > blockdev-amend will be used similiar to blockdev-create
> > to allow on the fly changes of the structure of the format based block devices.
> > 
> > Current plan is to first support encryption keyslot management for luks
> > based formats (raw and embedded in qcow2)
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> 
> [...]
> > diff --git a/qapi/block-core.json b/qapi/block-core.json
> > index 7ff5e5edaf..601f7dc9a4 100644
> > --- a/qapi/block-core.json
> > +++ b/qapi/block-core.json
> > @@ -4743,6 +4743,48 @@
> >    'data': { 'job-id': 'str',
> >              'options': 'BlockdevCreateOptions' } }
> >  
> > +##
> > +# @BlockdevAmendOptions:
> > +#
> > +# Options for amending an image format
> > +#
> > +# @driver           block driver that is suitable for the image
> > +#
> > +# Since: 5.0
> > +##
> > +{ 'union': 'BlockdevAmendOptions',
> > +  'base': {
> > +      'driver':         'BlockdevDriver' },
> > +  'discriminator': 'driver',
> > +  'data': {
> > +  } }
> > +
> > +##
> > +# @x-blockdev-amend:
> > +#
> > +# Starts a job to amend format specific options of an existing open block device
> > +# The job is automatically finalized, but a manual job-dismiss is required.
> > +#
> > +# @job-id:          Identifier for the newly created job.
> > +#
> > +# @node-name:       Name of the block node to work on
> > +#
> > +# @options:         Options (driver specific)
> > +#
> > +# @force:           Allow unsafe operations, format specific
> > +#                   For luks that allows erase of the last active keyslot
> > +#                   (permanent loss of data),
> > +#                   and replacement of an active keyslot
> > +#                   (possible loss of data if IO error happens)
> 
> PATCH 2 appears to reject that.  What am I missing?

this parameter overrides the safety checks for both operations.
It allows to erase all the keyslots (to allow to destroy the data
in unrecoverable way very fast), and it allows to overwrite an active
keyslot, which is not as dramatic, but in case of IO failure can
also result in bad things happening.

> 
> > +#
> > +# Since: 5.0
> > +##
> > +{ 'command': 'x-blockdev-amend',
> > +  'data': { 'job-id': 'str',
> > +            'node-name': 'str',
> > +            'options': 'BlockdevAmendOptions',
> > +            '*force': 'bool' } }
> > +
> >  ##
> >  # @blockdev-open-tray:
> >  #
> > diff --git a/qapi/job.json b/qapi/job.json
> > index a121b615fb..362b634ec1 100644
> > --- a/qapi/job.json
> > +++ b/qapi/job.json
> > @@ -19,10 +19,12 @@
> >  #
> >  # @create: image creation job type, see "blockdev-create" (since 3.0)
> >  #
> > +# @amend: image options amend job type, see "x-blockdev-amend" (since 5.0)
> > +#
> >  # Since: 1.7
> >  ##
> >  { 'enum': 'JobType',
> > -  'data': ['commit', 'stream', 'mirror', 'backup', 'create'] }
> > +  'data': ['commit', 'stream', 'mirror', 'backup', 'create', 'amend'] }
> >  
> >  ##
> >  # @JobStatus:


Best regards,
	Maxim Levitsky



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment
  2020-01-14 19:33 ` [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment Maxim Levitsky
@ 2020-01-28 16:59   ` Daniel P. Berrangé
  2020-01-29 17:49     ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 16:59 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, Jan 14, 2020 at 09:33:38PM +0200, Maxim Levitsky wrote:
> This will be used first to implement luks keyslot management.
> 
> block_crypto_amend_opts_init will be used to convert
> qemu-img cmdline to QCryptoBlockAmendOptions
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/crypto.c         | 17 +++++++++++++++++
>  block/crypto.h         |  3 +++
>  crypto/block.c         | 31 +++++++++++++++++++++++++++++++
>  crypto/blockpriv.h     |  8 ++++++++
>  include/crypto/block.h | 22 ++++++++++++++++++++++
>  qapi/crypto.json       | 16 ++++++++++++++++
>  6 files changed, 97 insertions(+)

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


> diff --git a/qapi/crypto.json b/qapi/crypto.json
> index b2a4cff683..9faebd03d4 100644
> --- a/qapi/crypto.json
> +++ b/qapi/crypto.json
> @@ -309,3 +309,19 @@
>    'base': 'QCryptoBlockInfoBase',
>    'discriminator': 'format',
>    'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
> +
> +
> +
> +##
> +# @QCryptoBlockAmendOptions:
> +#
> +# The options that are available for all encryption formats
> +# when initializing a new volume

minor point, the comment needs updating

> +#
> +# Since: 5.0
> +##
> +{ 'union': 'QCryptoBlockAmendOptions',
> +  'base': 'QCryptoBlockOptionsBase',
> +  'discriminator': 'format',
> +  'data': {
> +            } }

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-21 13:13     ` Maxim Levitsky
@ 2020-01-28 17:11       ` Daniel P. Berrangé
  2020-01-28 17:32         ` Daniel P. Berrangé
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:11 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> 
> <trimmed>
> 
> > > +##
> > > +# @LUKSKeyslotUpdate:
> > > +#
> > > +# @keyslot:         If specified, will update only keyslot with this index
> > > +#
> > > +# @old-secret:      If specified, will only update keyslots that
> > > +#                   can be opened with password which is contained in
> > > +#                   QCryptoSecret with @old-secret ID
> > > +#
> > > +#                   If neither @keyslot nor @old-secret is specified,
> > > +#                   first empty keyslot is selected for the update
> > > +#
> > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > > +#                   key to place in all matching keyslots.
> > > +#                   null/empty string erases all matching keyslots
> > 
> > I hate making the empty string do something completely different than a
> > non-empty string.
> > 
> > What about making @new-secret optional, and have absent @new-secret
> > erase?
> 
> I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> I don't mind personally to do this this way.
> empty string though is my addition, since its not possible to pass null on command line.

IIUC this a result of using  "StrOrNull" for this one field...


> > > +# Since: 5.0
> > > +##
> > > +{ 'struct': 'LUKSKeyslotUpdate',
> > > +  'data': {
> > > +           '*keyslot': 'int',
> > > +           '*old-secret': 'str',
> > > +           'new-secret' : 'StrOrNull',
> > > +           '*iter-time' : 'int' } }

It looks wierd here to be special casing "new-secret" to "StrOrNull"
instead of just marking it as an optional string field

   "*new-secret": "str"

which would be possible to use from the command line, as you simply
omit the field.

I guess the main danger here is that we're using this as a trigger
to erase keyslots. So simply omitting "new-secret" can result
in damage to the volume by accident which is not an attractive
mode. 


> > > +
> > > +##
> > > +# @QCryptoBlockAmendOptionsLUKS:
> > > +#
> > > +# The options that can be changed on existing luks encrypted device
> > > +# @keys:           list of keyslot updates to perform
> > > +#                  (updates are performed in order)
> > > +# @unlock-secret:  use this secret to retrieve the current master key
> > > +#                  if not given will use the same secret as one
> > 
> > "as the one"?
> Yea, this is wrong wording, I'll drop those words. Thanks.
> 
> > 
> > > +#                  that was used to open the image
> > > +#
> > > +# Since: 5.0
> > > +##
> > > +{ 'struct': 'QCryptoBlockAmendOptionsLUKS',
> > > +  'data' : {
> > > +            'keys': ['LUKSKeyslotUpdate'],
> > > +             '*unlock-secret' : 'str' } }
> > > +
> > >   
> > >   
> > >   ##
> > > @@ -324,4 +372,4 @@
> > >     'base': 'QCryptoBlockOptionsBase',
> > >     'discriminator': 'format',
> > >     'data': {
> > > -            } }
> > > +          'luks': 'QCryptoBlockAmendOptionsLUKS' } }
> 
> 
> Thanks for review,
> 	Best regards,
> 		Maxim Levitsky
> 

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-14 19:33 ` [PATCH 02/13] qcrypto-luks: implement encryption key management Maxim Levitsky
  2020-01-21  7:54   ` Markus Armbruster
@ 2020-01-28 17:21   ` Daniel P. Berrangé
  2020-01-30 12:58     ` Maxim Levitsky
  2020-02-15 14:51   ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Markus Armbruster
  2 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:21 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, Jan 14, 2020 at 09:33:39PM +0200, Maxim Levitsky wrote:
> Next few patches will expose that functionality
> to the user.
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  crypto/block-luks.c | 374 +++++++++++++++++++++++++++++++++++++++++++-
>  qapi/crypto.json    |  50 +++++-
>  2 files changed, 421 insertions(+), 3 deletions(-)
> 
> diff --git a/crypto/block-luks.c b/crypto/block-luks.c
> index 4861db810c..349e95fed3 100644
> --- a/crypto/block-luks.c
> +++ b/crypto/block-luks.c
> @@ -32,6 +32,7 @@
>  #include "qemu/uuid.h"
>  
>  #include "qemu/coroutine.h"
> +#include "qemu/bitmap.h"
>  
>  /*
>   * Reference for the LUKS format implemented here is
> @@ -70,6 +71,9 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
>  
>  #define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL
>  
> +#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS 2000
> +#define QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS 40
> +
>  static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = {
>      'L', 'U', 'K', 'S', 0xBA, 0xBE
>  };
> @@ -219,6 +223,9 @@ struct QCryptoBlockLUKS {
>  
>      /* Hash algorithm used in pbkdf2 function */
>      QCryptoHashAlgorithm hash_alg;
> +
> +    /* Name of the secret that was used to open the image */
> +    char *secret;
>  };
>  
>  
> @@ -1069,6 +1076,112 @@ qcrypto_block_luks_find_key(QCryptoBlock *block,
>      return -1;
>  }
>  
> +/*
> + * Returns true if a slot i is marked as active
> + * (contains encrypted copy of the master key)
> + */
> +static bool
> +qcrypto_block_luks_slot_active(const QCryptoBlockLUKS *luks,
> +                               unsigned int slot_idx)
> +{
> +    uint32_t val = luks->header.key_slots[slot_idx].active;
> +    return val ==  QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED;
> +}
> +
> +/*
> + * Returns the number of slots that are marked as active
> + * (slots that contain encrypted copy of the master key)
> + */
> +static unsigned int
> +qcrypto_block_luks_count_active_slots(const QCryptoBlockLUKS *luks)
> +{
> +    size_t i = 0;
> +    unsigned int ret = 0;
> +
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        if (qcrypto_block_luks_slot_active(luks, i)) {
> +            ret++;
> +        }
> +    }
> +    return ret;
> +}
> +
> +/*
> + * Finds first key slot which is not active
> + * Returns the key slot index, or -1 if it doesn't exist
> + */
> +static int
> +qcrypto_block_luks_find_free_keyslot(const QCryptoBlockLUKS *luks)
> +{
> +    size_t i;
> +
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        if (!qcrypto_block_luks_slot_active(luks, i)) {
> +            return i;
> +        }
> +    }
> +    return -1;
> +
> +}
> +
> +/*
> + * Erases an keyslot given its index
> + * Returns:
> + *    0 if the keyslot was erased successfully
> + *   -1 if a error occurred while erasing the keyslot
> + *
> + */
> +static int
> +qcrypto_block_luks_erase_key(QCryptoBlock *block,
> +                             unsigned int slot_idx,
> +                             QCryptoBlockWriteFunc writefunc,
> +                             void *opaque,
> +                             Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
> +    g_autofree uint8_t *garbagesplitkey = NULL;
> +    size_t splitkeylen = luks->header.master_key_len * slot->stripes;
> +    size_t i;
> +
> +    assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +    assert(splitkeylen > 0);
> +
> +    garbagesplitkey = g_new0(uint8_t, splitkeylen);
> +
> +    /* Reset the key slot header */
> +    memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN);
> +    slot->iterations = 0;
> +    slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
> +
> +    qcrypto_block_luks_store_header(block,  writefunc, opaque, errp);
> +
> +    /*
> +     * Now try to erase the key material, even if the header
> +     * update failed
> +     */
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) {
> +        if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) {
> +            /*
> +             * If we failed to get the random data, still write
> +             * at least zeros to the key slot at least once
> +             */
> +            if (i > 0) {
> +                return -1;
> +            }
> +        }
> +
> +        if (writefunc(block,
> +                      slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
> +                      garbagesplitkey,
> +                      splitkeylen,
> +                      opaque,
> +                      errp) != splitkeylen) {
> +            return -1;
> +        }
> +    }
> +    return 0;
> +}
>  
>  static int
>  qcrypto_block_luks_open(QCryptoBlock *block,
> @@ -1099,6 +1212,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
>  
>      luks = g_new0(QCryptoBlockLUKS, 1);
>      block->opaque = luks;
> +    luks->secret = g_strdup(options->u.luks.key_secret);
>  
>      if (qcrypto_block_luks_load_header(block, readfunc, opaque, errp) < 0) {
>          goto fail;
> @@ -1164,6 +1278,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
>   fail:
>      qcrypto_block_free_cipher(block);
>      qcrypto_ivgen_free(block->ivgen);
> +    g_free(luks->secret);
>      g_free(luks);
>      return -1;
>  }
> @@ -1204,7 +1319,7 @@ qcrypto_block_luks_create(QCryptoBlock *block,
>  
>      memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts));
>      if (!luks_opts.has_iter_time) {
> -        luks_opts.iter_time = 2000;
> +        luks_opts.iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
>      }
>      if (!luks_opts.has_cipher_alg) {
>          luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
> @@ -1244,6 +1359,8 @@ qcrypto_block_luks_create(QCryptoBlock *block,
>                     optprefix ? optprefix : "");
>          goto error;
>      }
> +    luks->secret = g_strdup(options->u.luks.key_secret);
> +
>      password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
>      if (!password) {
>          goto error;
> @@ -1471,10 +1588,260 @@ qcrypto_block_luks_create(QCryptoBlock *block,
>      qcrypto_block_free_cipher(block);
>      qcrypto_ivgen_free(block->ivgen);
>  
> +    g_free(luks->secret);
>      g_free(luks);
>      return -1;
>  }
>  
> +/*
> + * Given LUKSKeyslotUpdate command, return @slots_bitmap with all slots
> + * that will be updated with new password (or erased)
> + * returns number of affected slots
> + */
> +static int qcrypto_block_luks_get_slots_bitmap(QCryptoBlock *block,
> +                                               QCryptoBlockReadFunc readfunc,
> +                                               void *opaque,
> +                                               const LUKSKeyslotUpdate *command,
> +                                               unsigned long *slots_bitmap,
> +                                               Error **errp)
> +{
> +    const QCryptoBlockLUKS *luks = block->opaque;
> +    size_t i;
> +    int ret = 0;
> +
> +    if (command->has_keyslot) {
> +        /* keyslot set, select only this keyslot */
> +        int keyslot = command->keyslot;
> +
> +        if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
> +            error_setg(errp,
> +                       "Invalid slot %u specified, must be between 0 and %u",
> +                       keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
> +            goto error;
> +        }
> +        bitmap_set(slots_bitmap, keyslot, 1);
> +        ret++;
> +
> +    } else if (command->has_old_secret) {
> +        /* initially select all active keyslots */
> +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +            if (qcrypto_block_luks_slot_active(luks, i)) {
> +                bitmap_set(slots_bitmap, i, 1);
> +                ret++;
> +            }
> +        }
> +    } else {
> +        /* find a free keyslot */
> +        int slot = qcrypto_block_luks_find_free_keyslot(luks);
> +
> +        if (slot == -1) {
> +            error_setg(errp,
> +                       "Can't add a keyslot - all key slots are in use");
> +            goto error;
> +        }
> +        bitmap_set(slots_bitmap, slot, 1);
> +        ret++;
> +    }
> +
> +    if (command->has_old_secret) {
> +        /* now deselect all keyslots that don't contain the password */
> +        g_autofree uint8_t *tmpkey = g_new0(uint8_t,
> +                                            luks->header.master_key_len);
> +
> +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +            g_autofree char *old_password = NULL;
> +            int rv;
> +
> +            if (!test_bit(i, slots_bitmap)) {
> +                continue;
> +            }
> +
> +            old_password = qcrypto_secret_lookup_as_utf8(command->old_secret,
> +                                                         errp);
> +            if (!old_password) {
> +                goto error;
> +            }
> +
> +            rv = qcrypto_block_luks_load_key(block,
> +                                             i,
> +                                             old_password,
> +                                             tmpkey,
> +                                             readfunc,
> +                                             opaque,
> +                                             errp);
> +            if (rv == -1)
> +                goto error;
> +            else if (rv == 0) {
> +                bitmap_clear(slots_bitmap, i, 1);
> +                ret--;
> +            }
> +        }
> +    }
> +    return ret;
> +error:
> +    return -1;
> +}
> +
> +/*
> + * Apply a single keyslot update command as described in @command
> + * Optionally use @unlock_secret to retrieve the master key
> + */
> +static int
> +qcrypto_block_luks_apply_keyslot_update(QCryptoBlock *block,
> +                                        QCryptoBlockReadFunc readfunc,
> +                                        QCryptoBlockWriteFunc writefunc,
> +                                        void *opaque,
> +                                        LUKSKeyslotUpdate *command,
> +                                        const char *unlock_secret,
> +                                        uint8_t **master_key,
> +                                        bool force,
> +                                        Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    g_autofree unsigned long *slots_bitmap = NULL;
> +    int64_t iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
> +    int slot_count;
> +    size_t i;
> +    char *new_password;
> +    bool erasing;
> +
> +    slots_bitmap = bitmap_new(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +    slot_count = qcrypto_block_luks_get_slots_bitmap(block, readfunc, opaque,
> +                                                     command, slots_bitmap,
> +                                                     errp);
> +    if (slot_count == -1) {
> +        goto error;
> +    }
> +    /* no matching slots, so nothing to do */
> +    if (slot_count == 0) {
> +        error_setg(errp, "Requested operation didn't match any slots");
> +        goto error;
> +    }
> +    /*
> +     * slot is erased when the password is set to null, or empty string
> +     * (for compatibility with command line)
> +     */
> +    erasing = command->new_secret->type == QTYPE_QNULL ||
> +              strlen(command->new_secret->u.s) == 0;
> +
> +    /* safety checks */
> +    if (!force) {
> +        if (erasing) {
> +            if (slot_count == qcrypto_block_luks_count_active_slots(luks)) {
> +                error_setg(errp,
> +                           "Requested operation will erase all active keyslots"
> +                           " which will erase all the data in the image"
> +                           " irreversibly - refusing operation");
> +                goto error;
> +            }
> +        } else {
> +            for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +                if (!test_bit(i, slots_bitmap)) {
> +                    continue;
> +                }
> +                if (qcrypto_block_luks_slot_active(luks, i)) {
> +                    error_setg(errp,
> +                               "Refusing to overwrite active slot %zu - "
> +                               "please erase it first", i);
> +                    goto error;
> +                }
> +            }
> +        }
> +    }
> +
> +    /* setup the data needed for storing the new keyslot */
> +    if (!erasing) {
> +        /* Load the master key if it wasn't already loaded */
> +        if (!*master_key) {
> +            g_autofree char *old_password;
> +            old_password = qcrypto_secret_lookup_as_utf8(unlock_secret,  errp);
> +            if (!old_password) {
> +                goto error;
> +            }
> +            *master_key = g_new0(uint8_t, luks->header.master_key_len);
> +
> +            if (qcrypto_block_luks_find_key(block, old_password, *master_key,
> +                                            readfunc, opaque, errp) < 0) {
> +                error_append_hint(errp, "Failed to retrieve the master key");
> +                goto error;
> +            }
> +        }
> +        new_password = qcrypto_secret_lookup_as_utf8(command->new_secret->u.s,
> +                                                     errp);
> +        if (!new_password) {
> +            goto error;
> +        }
> +        if (command->has_iter_time) {
> +            iter_time = command->iter_time;
> +        }
> +    }
> +
> +    /* new apply the update */
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        if (!test_bit(i, slots_bitmap)) {
> +            continue;
> +        }
> +        if (erasing) {
> +            if (qcrypto_block_luks_erase_key(block, i,
> +                                             writefunc,
> +                                             opaque,
> +                                             errp)) {
> +                error_append_hint(errp, "Failed to erase keyslot %zu", i);
> +                goto error;
> +            }
> +        } else {
> +            if (qcrypto_block_luks_store_key(block, i,
> +                                             new_password,
> +                                             *master_key,
> +                                             iter_time,
> +                                             writefunc,
> +                                             opaque,
> +                                             errp)) {
> +                error_append_hint(errp, "Failed to write to keyslot %zu", i);
> +                goto error;
> +            }
> +        }
> +    }
> +    return 0;
> +error:
> +    return -EINVAL;
> +}

I feel the this method is confusing from trying to handle both
adding and erasing keyslots....


> +
> +static int
> +qcrypto_block_luks_amend_options(QCryptoBlock *block,
> +                                 QCryptoBlockReadFunc readfunc,
> +                                 QCryptoBlockWriteFunc writefunc,
> +                                 void *opaque,
> +                                 QCryptoBlockAmendOptions *options,
> +                                 bool force,
> +                                 Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    QCryptoBlockAmendOptionsLUKS *options_luks = &options->u.luks;
> +    LUKSKeyslotUpdateList *ptr;
> +    g_autofree uint8_t *master_key = NULL;
> +    int ret;
> +
> +    char *unlock_secret = options_luks->has_unlock_secret ?
> +                          options_luks->unlock_secret :
> +                          luks->secret;
> +
> +    for (ptr = options_luks->keys; ptr; ptr = ptr->next) {
> +        ret = qcrypto_block_luks_apply_keyslot_update(block, readfunc,
> +                                                      writefunc, opaque,
> +                                                      ptr->value,
> +                                                      unlock_secret,
> +                                                      &master_key,
> +                                                      force, errp);

.... imho we sould do

    bool  erasing = command->new_secret->type == QTYPE_QNULL;

    if (erasing) {
         ret = qcrypto_block_luks_disable_keyslot(block, readfunc,
                                                       writefunc, opaque,
                                                       ptr->value,
                                                       unlock_secret,
                                                       &master_key,
                                                       force, errp);
    } else {
        ret = qcrypto_block_luks_enable_keyslot(block, readfunc,
                                                       writefunc, opaque,
                                                       ptr->value,
                                                       unlock_secret,
                                                       &master_key,
                                                       force, errp);
    }

> +
> +        if (ret != 0) {
> +            goto error;
> +        }
> +    }
> +    return 0;
> +error:
> +    return -1;
> +}

If there's no code to run in the 'error' label, then we should just
get rid of it and 'return -1' instead of "goto error"

>  
>  static int qcrypto_block_luks_get_info(QCryptoBlock *block,
>                                         QCryptoBlockInfo *info,
> @@ -1523,7 +1890,9 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block,
>  
>  static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
>  {
> -    g_free(block->opaque);
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    g_free(luks->secret);

Check if "luks" is non-NULL for robustness in early failure scenarios

> +    g_free(luks);
>  }
>  

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 04/13] block: amend: separate amend and create options for qemu-img
  2020-01-14 19:33 ` [PATCH 04/13] block: amend: separate amend and create options for qemu-img Maxim Levitsky
@ 2020-01-28 17:23   ` Daniel P. Berrangé
  2020-01-30 15:54     ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:23 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, Jan 14, 2020 at 09:33:41PM +0200, Maxim Levitsky wrote:
> Some options are only useful for creation
> (or hard to be amended, like cluster size for qcow2), while some other
> options are only useful for amend, like upcoming keyslot management
> options for luks
> 
> Since currently only qcow2 supports amend, move all its options
> to a common macro and then include it in each action option list.
> 
> In future it might be useful to remove some options which are
> not supported anyway from amend list, which currently
> cause an error message if amended.

I think I would have done that in this commit. At least the
encrypt.* options shouldn't be added to the amend_opts list,
since they're being removed from it again a few patches later.

> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/qcow2.c             | 160 +++++++++++++++++++++-----------------
>  include/block/block_int.h |   4 +
>  qemu-img.c                |  18 ++---
>  3 files changed, 100 insertions(+), 82 deletions(-)
> 
> diff --git a/block/qcow2.c b/block/qcow2.c
> index 6bcf4a5fc4..c6c2deee75 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -5445,83 +5445,96 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
>      s->signaled_corruption = true;
>  }
>  
> +#define QCOW_COMMON_OPTIONS                                         \
> +    {                                                               \
> +        .name = BLOCK_OPT_SIZE,                                     \
> +        .type = QEMU_OPT_SIZE,                                      \
> +        .help = "Virtual disk size"                                 \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_COMPAT_LEVEL,                             \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Compatibility level (v2 [0.10] or v3 [1.1])"       \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_BACKING_FILE,                             \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "File name of a base image"                         \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_BACKING_FMT,                              \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Image format of the base image"                    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_DATA_FILE,                                \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "File name of an external data file"                \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_DATA_FILE_RAW,                            \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "The external data file must stay valid "           \
> +                "as a raw image"                                    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_ENCRYPT,                                  \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "Encrypt the image with format 'aes'. (Deprecated " \
> +                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
> +    },                                                              \
> +    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
> +        "ID of secret providing qcow AES key or LUKS passphrase"),  \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
> +    {                                                               \
> +        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
> +        .type = QEMU_OPT_SIZE,                                      \
> +        .help = "qcow2 cluster size",                               \
> +        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_PREALLOC,                                 \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Preallocation mode (allowed values: off, "         \
> +                "metadata, falloc, full)"                           \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "Postpone refcount updates",                        \
> +        .def_value_str = "off"                                      \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_REFCOUNT_BITS,                            \
> +        .type = QEMU_OPT_NUMBER,                                    \
> +        .help = "Width of a reference count entry in bits",         \
> +        .def_value_str = "16"                                       \
> +    }                                                               \
> +
>  static QemuOptsList qcow2_create_opts = {
>      .name = "qcow2-create-opts",
>      .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
>      .desc = {
> -        {
> -            .name = BLOCK_OPT_SIZE,
> -            .type = QEMU_OPT_SIZE,
> -            .help = "Virtual disk size"
> -        },
> -        {
> -            .name = BLOCK_OPT_COMPAT_LEVEL,
> -            .type = QEMU_OPT_STRING,
> -            .help = "Compatibility level (v2 [0.10] or v3 [1.1])"
> -        },
> -        {
> -            .name = BLOCK_OPT_BACKING_FILE,
> -            .type = QEMU_OPT_STRING,
> -            .help = "File name of a base image"
> -        },
> -        {
> -            .name = BLOCK_OPT_BACKING_FMT,
> -            .type = QEMU_OPT_STRING,
> -            .help = "Image format of the base image"
> -        },
> -        {
> -            .name = BLOCK_OPT_DATA_FILE,
> -            .type = QEMU_OPT_STRING,
> -            .help = "File name of an external data file"
> -        },
> -        {
> -            .name = BLOCK_OPT_DATA_FILE_RAW,
> -            .type = QEMU_OPT_BOOL,
> -            .help = "The external data file must stay valid as a raw image"
> -        },
> -        {
> -            .name = BLOCK_OPT_ENCRYPT,
> -            .type = QEMU_OPT_BOOL,
> -            .help = "Encrypt the image with format 'aes'. (Deprecated "
> -                    "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",
> -        },
> -        {
> -            .name = BLOCK_OPT_ENCRYPT_FORMAT,
> -            .type = QEMU_OPT_STRING,
> -            .help = "Encrypt the image, format choices: 'aes', 'luks'",
> -        },
> -        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
> -            "ID of secret providing qcow AES key or LUKS passphrase"),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
> -        {
> -            .name = BLOCK_OPT_CLUSTER_SIZE,
> -            .type = QEMU_OPT_SIZE,
> -            .help = "qcow2 cluster size",
> -            .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)
> -        },
> -        {
> -            .name = BLOCK_OPT_PREALLOC,
> -            .type = QEMU_OPT_STRING,
> -            .help = "Preallocation mode (allowed values: off, metadata, "
> -                    "falloc, full)"
> -        },
> -        {
> -            .name = BLOCK_OPT_LAZY_REFCOUNTS,
> -            .type = QEMU_OPT_BOOL,
> -            .help = "Postpone refcount updates",
> -            .def_value_str = "off"
> -        },
> -        {
> -            .name = BLOCK_OPT_REFCOUNT_BITS,
> -            .type = QEMU_OPT_NUMBER,
> -            .help = "Width of a reference count entry in bits",
> -            .def_value_str = "16"
> -        },
> +        QCOW_COMMON_OPTIONS,
> +        { /* end of list */ }
> +    }
> +};
> +
> +static QemuOptsList qcow2_amend_opts = {
> +    .name = "qcow2-amend-opts",
> +    .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
> +    .desc = {
> +        QCOW_COMMON_OPTIONS,
>          { /* end of list */ }
>      }
>  };
> @@ -5581,6 +5594,7 @@ BlockDriver bdrv_qcow2 = {
>      .bdrv_inactivate            = qcow2_inactivate,
>  
>      .create_opts         = &qcow2_create_opts,
> +    .amend_opts          = &qcow2_amend_opts,
>      .strong_runtime_opts = qcow2_strong_runtime_opts,
>      .mutable_opts        = mutable_opts,
>      .bdrv_co_check       = qcow2_co_check,
> diff --git a/include/block/block_int.h b/include/block/block_int.h
> index 810a9ecb86..6f0abf8544 100644
> --- a/include/block/block_int.h
> +++ b/include/block/block_int.h
> @@ -407,6 +407,10 @@ struct BlockDriver {
>  
>      /* List of options for creating images, terminated by name == NULL */
>      QemuOptsList *create_opts;
> +
> +    /* List of options for image amend*/
> +    QemuOptsList *amend_opts;
> +
>      /*
>       * If this driver supports reopening images this contains a
>       * NULL-terminated list of the runtime options that can be
> diff --git a/qemu-img.c b/qemu-img.c
> index a79f3904db..befd53943b 100644
> --- a/qemu-img.c
> +++ b/qemu-img.c
> @@ -3878,11 +3878,11 @@ static int print_amend_option_help(const char *format)
>          return 1;
>      }
>  
> -    /* Every driver supporting amendment must have create_opts */
> -    assert(drv->create_opts);
> +    /* Every driver supporting amendment must have amend_opts */
> +    assert(drv->amend_opts);
>  
>      printf("Creation options for '%s':\n", format);
> -    qemu_opts_print_help(drv->create_opts, false);
> +    qemu_opts_print_help(drv->amend_opts, false);
>      printf("\nNote that not all of these options may be amendable.\n");
>      return 0;
>  }
> @@ -3892,7 +3892,7 @@ static int img_amend(int argc, char **argv)
>      Error *err = NULL;
>      int c, ret = 0;
>      char *options = NULL;
> -    QemuOptsList *create_opts = NULL;
> +    QemuOptsList *amend_opts = NULL;
>      QemuOpts *opts = NULL;
>      const char *fmt = NULL, *filename, *cache;
>      int flags;
> @@ -4031,11 +4031,11 @@ static int img_amend(int argc, char **argv)
>          goto out;
>      }
>  
> -    /* Every driver supporting amendment must have create_opts */
> -    assert(bs->drv->create_opts);
> +    /* Every driver supporting amendment must have amend_opts */
> +    assert(bs->drv->amend_opts);
>  
> -    create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
> -    opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
> +    amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
> +    opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
>      qemu_opts_do_parse(opts, options, NULL, &err);
>      if (err) {
>          error_report_err(err);
> @@ -4058,7 +4058,7 @@ out:
>  out_no_progress:
>      blk_unref(blk);
>      qemu_opts_del(opts);
> -    qemu_opts_free(create_opts);
> +    qemu_opts_free(amend_opts);
>      g_free(options);
>  
>      if (ret) {
> -- 
> 2.17.2
> 

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 06/13] block/crypto: implement the encryption key management
  2020-01-14 19:33 ` [PATCH 06/13] block/crypto: implement the encryption key management Maxim Levitsky
@ 2020-01-28 17:27   ` Daniel P. Berrangé
  2020-01-30 16:08     ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:27 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, Jan 14, 2020 at 09:33:43PM +0200, Maxim Levitsky wrote:
> This implements the encryption key management using the generic code in
> qcrypto layer and exposes it to the user via qemu-img
> 
> This code adds another 'write_func' because the initialization
> write_func works directly on the underlying file, and amend
> works on instance of luks device.
> 
> This commit also adds a 'hack/workaround' I and Kevin Wolf (thanks)
> made to make the driver both support write sharing (to avoid breaking the users),
> and be safe against concurrent  metadata update (the keyslots)
> 
> Eventually the write sharing for luks driver will be deprecated
> and removed together with this hack.
> 
> The hack is that we ask (as a format driver) for BLK_PERM_CONSISTENT_READ
> and then when we want to update the keys, we unshare that permission.
> So if someone else has the image open, even readonly, encryption
> key update will fail gracefully.
> 
> Also thanks to Daniel Berrange for the idea of
> unsharing read, rather that write permission which allows
> to avoid cases when the other user had opened the image read-only.
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/crypto.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++--
>  block/crypto.h |  31 ++++++++++++
>  2 files changed, 158 insertions(+), 3 deletions(-)
> 

> @@ -148,6 +167,22 @@ static QemuOptsList block_crypto_create_opts_luks = {
>  };
>  
>  
> +static QemuOptsList block_crypto_amend_opts_luks = {
> +    .name = "crypto",
> +    .head = QTAILQ_HEAD_INITIALIZER(block_crypto_create_opts_luks.head),
> +    .desc = {
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.0."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.1."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.2."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.3."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.4."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.5."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.6."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.7."),

I'd probably suggest  "key.0" or "keyslot.0" as a name.

> +        { /* end of list */ }
> +    },
> +};
> +


> @@ -661,6 +696,95 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp)
>      return spec_info;
>  }
>  
> +static int
> +block_crypto_amend_options(BlockDriverState *bs,
> +                           QemuOpts *opts,
> +                           BlockDriverAmendStatusCB *status_cb,
> +                           void *cb_opaque,
> +                           bool force,
> +                           Error **errp)

This method should have a "_luks" suffix since...

> +{
> +    BlockCrypto *crypto = bs->opaque;
> +    QDict *cryptoopts = NULL;
> +    QCryptoBlockAmendOptions *amend_options = NULL;
> +    int ret;
> +
> +    assert(crypto);
> +    assert(crypto->block);
> +    crypto->updating_keys = true;
> +
> +    ret = bdrv_child_refresh_perms(bs, bs->file, errp);
> +    if (ret < 0) {
> +        goto cleanup;
> +    }
> +
> +    cryptoopts = qemu_opts_to_qdict(opts, NULL);
> +    qdict_put_str(cryptoopts, "format", "luks");

...it is hardcoded here to assume luks.

> +    amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
> +    if (!amend_options) {
> +        ret = -EINVAL;
> +        goto cleanup;
> +    }
> +
> +    ret = qcrypto_block_amend_options(crypto->block,
> +                                      block_crypto_read_func,
> +                                      block_crypto_write_func,
> +                                      bs,
> +                                      amend_options,
> +                                      force,
> +                                      errp);
> +cleanup:
> +    crypto->updating_keys = false;
> +    bdrv_child_refresh_perms(bs, bs->file, errp);
> +    qapi_free_QCryptoBlockAmendOptions(amend_options);
> +    qobject_unref(cryptoopts);
> +    return ret;
> +}

With the minor changes above

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 07/13] qcow2: extend qemu-img amend interface with crypto options
  2020-01-14 19:33 ` [PATCH 07/13] qcow2: extend qemu-img amend interface with crypto options Maxim Levitsky
@ 2020-01-28 17:30   ` Daniel P. Berrangé
  2020-01-30 16:09     ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:30 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, Jan 14, 2020 at 09:33:44PM +0200, Maxim Levitsky wrote:
> Now that we have all the infrastructure in place,
> wire it in the qcow2 driver and expose this to the user.
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/qcow2.c | 101 +++++++++++++++++++++++++++++++++++++++-----------
>  1 file changed, 79 insertions(+), 22 deletions(-)
> 
> diff --git a/block/qcow2.c b/block/qcow2.c
> index c6c2deee75..1b01174aed 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -173,6 +173,19 @@ static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
>      return ret;
>  }
>  
> +static QDict*
> +qcow2_extract_crypto_opts(QemuOpts *opts, const char *fmt, Error **errp)
> +{
> +    QDict *cryptoopts_qdict;
> +    QDict *opts_qdict;
> +
> +    /* Extract "encrypt." options into a qdict */
> +    opts_qdict = qemu_opts_to_qdict(opts, NULL);
> +    qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
> +    qobject_unref(opts_qdict);
> +    qdict_put_str(cryptoopts_qdict, "format", "luks");
> +    return cryptoopts_qdict;
> +}
>  
>  /* 
>   * read qcow2 extension and fill bs
> @@ -4631,20 +4644,18 @@ static ssize_t qcow2_measure_crypto_hdr_write_func(QCryptoBlock *block,
>  static bool qcow2_measure_luks_headerlen(QemuOpts *opts, size_t *len,
>                                           Error **errp)
>  {
> -    QDict *opts_qdict;
> -    QDict *cryptoopts_qdict;
>      QCryptoBlockCreateOptions *cryptoopts;
> +    QDict* crypto_opts_dict;
>      QCryptoBlock *crypto;
>  
> -    /* Extract "encrypt." options into a qdict */
> -    opts_qdict = qemu_opts_to_qdict(opts, NULL);
> -    qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
> -    qobject_unref(opts_qdict);
> +    crypto_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp);
> +    if (!crypto_opts_dict) {
> +        return false;
> +    }
> +
> +    cryptoopts = block_crypto_create_opts_init(crypto_opts_dict, errp);
> +    qobject_unref(crypto_opts_dict);
>  
> -    /* Build QCryptoBlockCreateOptions object from qdict */
> -    qdict_put_str(cryptoopts_qdict, "format", "luks");
> -    cryptoopts = block_crypto_create_opts_init(cryptoopts_qdict, errp);
> -    qobject_unref(cryptoopts_qdict);
>      if (!cryptoopts) {
>          return false;
>      }
> @@ -5083,6 +5094,7 @@ typedef enum Qcow2AmendOperation {
>      QCOW2_NO_OPERATION = 0,
>  
>      QCOW2_UPGRADING,
> +    QCOW2_UPDATING_ENCRYPTION,
>      QCOW2_CHANGING_REFCOUNT_ORDER,
>      QCOW2_DOWNGRADING,
>  } Qcow2AmendOperation;
> @@ -5167,6 +5179,7 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
>      int ret;
>      QemuOptDesc *desc = opts->list->desc;
>      Qcow2AmendHelperCBInfo helper_cb_info;
> +    bool encryption_update = false;
>  
>      while (desc && desc->name) {
>          if (!qemu_opt_find(opts, desc->name)) {
> @@ -5215,9 +5228,17 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
>                  return -ENOTSUP;
>              }
>          } else if (g_str_has_prefix(desc->name, "encrypt.")) {
> -            error_setg(errp,
> -                       "Changing the encryption parameters is not supported");
> -            return -ENOTSUP;
> +            if (!s->crypto) {
> +                error_setg(errp,
> +                           "Can't amend encryption options - encryption not present");
> +                return -EINVAL;
> +            }
> +            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
> +                error_setg(errp,
> +                           "Only LUKS encryption options can be amended");
> +                return -ENOTSUP;
> +            }
> +            encryption_update = true;
>          } else if (!strcmp(desc->name, BLOCK_OPT_CLUSTER_SIZE)) {
>              cluster_size = qemu_opt_get_size(opts, BLOCK_OPT_CLUSTER_SIZE,
>                                               cluster_size);
> @@ -5267,7 +5288,8 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
>          .original_status_cb = status_cb,
>          .original_cb_opaque = cb_opaque,
>          .total_operations = (new_version != old_version)
> -                          + (s->refcount_bits != refcount_bits)
> +                          + (s->refcount_bits != refcount_bits) +
> +                            (encryption_update == true)
>      };
>  
>      /* Upgrade first (some features may require compat=1.1) */
> @@ -5280,6 +5302,33 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
>          }
>      }
>  
> +    if (encryption_update) {
> +        QDict *amend_opts_dict;
> +        QCryptoBlockAmendOptions *amend_opts;
> +
> +        helper_cb_info.current_operation = QCOW2_UPDATING_ENCRYPTION;
> +        amend_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp);
> +        if (!amend_opts_dict) {
> +            return -EINVAL;
> +        }
> +        amend_opts = block_crypto_amend_opts_init(amend_opts_dict, errp);
> +        qobject_unref(amend_opts_dict);
> +        if (!amend_opts) {
> +            return -EINVAL;
> +        }
> +        ret = qcrypto_block_amend_options(s->crypto,
> +                                          qcow2_crypto_hdr_read_func,
> +                                          qcow2_crypto_hdr_write_func,
> +                                          bs,
> +                                          amend_opts,
> +                                          force,
> +                                          errp);
> +        qapi_free_QCryptoBlockAmendOptions(amend_opts);
> +        if (ret < 0) {
> +            return ret;
> +        }
> +    }
> +
>      if (s->refcount_bits != refcount_bits) {
>          int refcount_order = ctz32(refcount_bits);
>  
> @@ -5488,14 +5537,6 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
>          .type = QEMU_OPT_STRING,                                    \
>          .help = "Encrypt the image, format choices: 'aes', 'luks'", \
>      },                                                              \
> -    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
> -        "ID of secret providing qcow AES key or LUKS passphrase"),  \
> -    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
> -    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
> -    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
> -    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
> -    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
> -    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
>      {                                                               \
>          .name = BLOCK_OPT_CLUSTER_SIZE,                             \
>          .type = QEMU_OPT_SIZE,                                      \
> @@ -5526,6 +5567,14 @@ static QemuOptsList qcow2_create_opts = {
>      .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
>      .desc = {
>          QCOW_COMMON_OPTIONS,
> +        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
> +            "ID of secret providing qcow AES key or LUKS passphrase"),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
>          { /* end of list */ }
>      }

These two chunks should habe been in the earlier patch IMHO.

>  };
> @@ -5535,6 +5584,14 @@ static QemuOptsList qcow2_amend_opts = {
>      .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
>      .desc = {
>          QCOW_COMMON_OPTIONS,
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.0."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.1."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.2."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.3."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.4."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.5."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.6."),
> +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.7."),
>          { /* end of list */ }

Same naming idea about  "encrypt.key.0" or "encrypt.keyslot.0"

>      }
>  };
> -- 
> 2.17.2
> 

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-28 17:11       ` Daniel P. Berrangé
@ 2020-01-28 17:32         ` Daniel P. Berrangé
  2020-01-29 17:54           ` Maxim Levitsky
  2020-01-30 12:38           ` Kevin Wolf
  0 siblings, 2 replies; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:32 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, Markus Armbruster, qemu-devel, Max Reitz,
	John Snow

On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> > 
> > <trimmed>
> > 
> > > > +##
> > > > +# @LUKSKeyslotUpdate:
> > > > +#
> > > > +# @keyslot:         If specified, will update only keyslot with this index
> > > > +#
> > > > +# @old-secret:      If specified, will only update keyslots that
> > > > +#                   can be opened with password which is contained in
> > > > +#                   QCryptoSecret with @old-secret ID
> > > > +#
> > > > +#                   If neither @keyslot nor @old-secret is specified,
> > > > +#                   first empty keyslot is selected for the update
> > > > +#
> > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > > > +#                   key to place in all matching keyslots.
> > > > +#                   null/empty string erases all matching keyslots
> > > 
> > > I hate making the empty string do something completely different than a
> > > non-empty string.
> > > 
> > > What about making @new-secret optional, and have absent @new-secret
> > > erase?
> > 
> > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> > I don't mind personally to do this this way.
> > empty string though is my addition, since its not possible to pass null on command line.
> 
> IIUC this a result of using  "StrOrNull" for this one field...
> 
> 
> > > > +# Since: 5.0
> > > > +##
> > > > +{ 'struct': 'LUKSKeyslotUpdate',
> > > > +  'data': {
> > > > +           '*keyslot': 'int',
> > > > +           '*old-secret': 'str',
> > > > +           'new-secret' : 'StrOrNull',
> > > > +           '*iter-time' : 'int' } }
> 
> It looks wierd here to be special casing "new-secret" to "StrOrNull"
> instead of just marking it as an optional string field
> 
>    "*new-secret": "str"
> 
> which would be possible to use from the command line, as you simply
> omit the field.
> 
> I guess the main danger here is that we're using this as a trigger
> to erase keyslots. So simply omitting "new-secret" can result
> in damage to the volume by accident which is not an attractive
> mode.

Thinking about this again, I really believe we ought to be moire
explicit about disabling the keyslot by having the "active" field.
eg

{ 'struct': 'LUKSKeyslotUpdate',
  'data': {
          'active': 'bool',
          '*keyslot': 'int',
          '*old-secret': 'str',
          '*new-secret' : 'str',
          '*iter-time' : 'int' } }

"new-secret" is thus only needed when "active" == true.

This avoids the problem with being unable to specify a
null for StrOrNull on the command line too.


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 08/13] iotests: filter few more luks specific create options
  2020-01-14 19:33 ` [PATCH 08/13] iotests: filter few more luks specific create options Maxim Levitsky
@ 2020-01-28 17:36   ` Daniel P. Berrangé
  2020-01-30 16:12     ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:36 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, Markus Armbruster, qemu-devel, Max Reitz,
	John Snow

On Tue, Jan 14, 2020 at 09:33:45PM +0200, Maxim Levitsky wrote:
> This allows more tests to be able to have same output on both qcow2 luks encrypted images
> and raw luks images
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  tests/qemu-iotests/087.out       | 6 +++---
>  tests/qemu-iotests/134.out       | 2 +-
>  tests/qemu-iotests/158.out       | 4 ++--
>  tests/qemu-iotests/188.out       | 2 +-
>  tests/qemu-iotests/189.out       | 4 ++--
>  tests/qemu-iotests/198.out       | 4 ++--
>  tests/qemu-iotests/common.filter | 6 ++++--
>  7 files changed, 15 insertions(+), 13 deletions(-)
> 
> diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
> index 2d92ea847b..b61ba638af 100644
> --- a/tests/qemu-iotests/087.out
> +++ b/tests/qemu-iotests/087.out
> @@ -34,7 +34,7 @@ QMP_VERSION
1>  
>  === Encrypted image QCow ===
>  
> -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
> +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on

I'm not convinced about this - it feels like this is throwing
away relevant info to be validated about the test scenario

Can you give more info about the scenario this benefits us in ?

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 11/13] block/crypto: implement blockdev-amend
  2020-01-14 19:33 ` [PATCH 11/13] block/crypto: implement blockdev-amend Maxim Levitsky
@ 2020-01-28 17:40   ` Daniel P. Berrangé
  2020-01-30 16:24     ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:40 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, Jan 14, 2020 at 09:33:48PM +0200, Maxim Levitsky wrote:
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/crypto.c       | 70 ++++++++++++++++++++++++++++++++------------
>  qapi/block-core.json | 14 ++++++++-
>  2 files changed, 64 insertions(+), 20 deletions(-)
> 
> diff --git a/block/crypto.c b/block/crypto.c
> index 081880bced..6836337863 100644
> --- a/block/crypto.c
> +++ b/block/crypto.c


>  
> +static int
> +coroutine_fn block_crypto_co_amend(BlockDriverState *bs,
> +                                   BlockdevAmendOptions *opts,
> +                                   bool force,
> +                                   Error **errp)

This should have a _luks suffix given...

> +{
> +    QCryptoBlockAmendOptions amend_opts;
> +
> +    amend_opts = (QCryptoBlockAmendOptions) {
> +        .format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
> +        .u.luks = *qapi_BlockdevAmendOptionsLUKS_base(&opts->u.luks),

...this is hardcoded to luks

> +    };
> +    return block_crypto_amend_options_generic(bs, &amend_opts, force, errp);
> +}
>  
>  static void
>  block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c,
> @@ -812,6 +843,7 @@ static BlockDriver bdrv_crypto_luks = {
>      .bdrv_get_info      = block_crypto_get_info_luks,
>      .bdrv_get_specific_info = block_crypto_get_specific_info_luks,
>      .bdrv_amend_options = block_crypto_amend_options,
> +    .bdrv_co_amend      = block_crypto_co_amend,
>  
>      .strong_runtime_opts = block_crypto_strong_runtime_opts,
>  };

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>



Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 12/13] block/qcow2: implement blockdev-amend
  2020-01-14 19:33 ` [PATCH 12/13] block/qcow2: " Maxim Levitsky
@ 2020-01-28 17:41   ` Daniel P. Berrangé
  0 siblings, 0 replies; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-28 17:41 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, Jan 14, 2020 at 09:33:49PM +0200, Maxim Levitsky wrote:
> Currently the implementation only supports amending the encryption
> options, unlike the qemu-img version
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/qcow2.c        | 39 +++++++++++++++++++++++++++++++++++++++
>  qapi/block-core.json | 16 +++++++++++++++-
>  2 files changed, 54 insertions(+), 1 deletion(-)

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment
  2020-01-28 16:59   ` Daniel P. Berrangé
@ 2020-01-29 17:49     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-29 17:49 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, 2020-01-28 at 16:59 +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 14, 2020 at 09:33:38PM +0200, Maxim Levitsky wrote:
> > This will be used first to implement luks keyslot management.
> > 
> > block_crypto_amend_opts_init will be used to convert
> > qemu-img cmdline to QCryptoBlockAmendOptions
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  block/crypto.c         | 17 +++++++++++++++++
> >  block/crypto.h         |  3 +++
> >  crypto/block.c         | 31 +++++++++++++++++++++++++++++++
> >  crypto/blockpriv.h     |  8 ++++++++
> >  include/crypto/block.h | 22 ++++++++++++++++++++++
> >  qapi/crypto.json       | 16 ++++++++++++++++
> >  6 files changed, 97 insertions(+)
> 
> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
> 
> 
> > diff --git a/qapi/crypto.json b/qapi/crypto.json
> > index b2a4cff683..9faebd03d4 100644
> > --- a/qapi/crypto.json
> > +++ b/qapi/crypto.json
> > @@ -309,3 +309,19 @@
> >    'base': 'QCryptoBlockInfoBase',
> >    'discriminator': 'format',
> >    'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
> > +
> > +
> > +
> > +##
> > +# @QCryptoBlockAmendOptions:
> > +#
> > +# The options that are available for all encryption formats
> > +# when initializing a new volume
> 
> minor point, the comment needs updating

Done.
Thanks for the review!

Best regards,
	Maxim Levitsky

> 
> > +#
> > +# Since: 5.0
> > +##
> > +{ 'union': 'QCryptoBlockAmendOptions',
> > +  'base': 'QCryptoBlockOptionsBase',
> > +  'discriminator': 'format',
> > +  'data': {
> > +            } }
> 
> Regards,
> Daniel




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-28 17:32         ` Daniel P. Berrangé
@ 2020-01-29 17:54           ` Maxim Levitsky
  2020-01-30 12:38           ` Kevin Wolf
  1 sibling, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-29 17:54 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, Markus Armbruster, qemu-devel, Max Reitz,
	John Snow

On Tue, 2020-01-28 at 17:32 +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
> > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> > > 
> > > <trimmed>
> > > 
> > > > > +##
> > > > > +# @LUKSKeyslotUpdate:
> > > > > +#
> > > > > +# @keyslot:         If specified, will update only keyslot with this index
> > > > > +#
> > > > > +# @old-secret:      If specified, will only update keyslots that
> > > > > +#                   can be opened with password which is contained in
> > > > > +#                   QCryptoSecret with @old-secret ID
> > > > > +#
> > > > > +#                   If neither @keyslot nor @old-secret is specified,
> > > > > +#                   first empty keyslot is selected for the update
> > > > > +#
> > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > > > > +#                   key to place in all matching keyslots.
> > > > > +#                   null/empty string erases all matching keyslots
> > > > 
> > > > I hate making the empty string do something completely different than a
> > > > non-empty string.
> > > > 
> > > > What about making @new-secret optional, and have absent @new-secret
> > > > erase?
> > > 
> > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> > > I don't mind personally to do this this way.
> > > empty string though is my addition, since its not possible to pass null on command line.
> > 
> > IIUC this a result of using  "StrOrNull" for this one field...
> > 
> > 
> > > > > +# Since: 5.0
> > > > > +##
> > > > > +{ 'struct': 'LUKSKeyslotUpdate',
> > > > > +  'data': {
> > > > > +           '*keyslot': 'int',
> > > > > +           '*old-secret': 'str',
> > > > > +           'new-secret' : 'StrOrNull',
> > > > > +           '*iter-time' : 'int' } }
> > 
> > It looks wierd here to be special casing "new-secret" to "StrOrNull"
> > instead of just marking it as an optional string field
> > 
> >    "*new-secret": "str"
> > 
> > which would be possible to use from the command line, as you simply
> > omit the field.
> > 
> > I guess the main danger here is that we're using this as a trigger
> > to erase keyslots. So simply omitting "new-secret" can result
> > in damage to the volume by accident which is not an attractive
> > mode.
> 
> Thinking about this again, I really believe we ought to be moire
> explicit about disabling the keyslot by having the "active" field.
> eg
> 
> { 'struct': 'LUKSKeyslotUpdate',
>   'data': {
>           'active': 'bool',
>           '*keyslot': 'int',
>           '*old-secret': 'str',
>           '*new-secret' : 'str',
>           '*iter-time' : 'int' } }
> 
> "new-secret" is thus only needed when "active" == true.
> 
> This avoids the problem with being unable to specify a
> null for StrOrNull on the command line too.
I fully support this idea.
If no objections from anybody else, I'll do it this way.

Best regards,
	Maxim Levitsky

> 
> Regards,
> Daniel



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-28 17:32         ` Daniel P. Berrangé
  2020-01-29 17:54           ` Maxim Levitsky
@ 2020-01-30 12:38           ` Kevin Wolf
  2020-01-30 12:53             ` Daniel P. Berrangé
  1 sibling, 1 reply; 84+ messages in thread
From: Kevin Wolf @ 2020-01-30 12:38 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	Maxim Levitsky, John Snow

Am 28.01.2020 um 18:32 hat Daniel P. Berrangé geschrieben:
> On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
> > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> > > 
> > > <trimmed>
> > > 
> > > > > +##
> > > > > +# @LUKSKeyslotUpdate:
> > > > > +#
> > > > > +# @keyslot:         If specified, will update only keyslot with this index
> > > > > +#
> > > > > +# @old-secret:      If specified, will only update keyslots that
> > > > > +#                   can be opened with password which is contained in
> > > > > +#                   QCryptoSecret with @old-secret ID
> > > > > +#
> > > > > +#                   If neither @keyslot nor @old-secret is specified,
> > > > > +#                   first empty keyslot is selected for the update
> > > > > +#
> > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > > > > +#                   key to place in all matching keyslots.
> > > > > +#                   null/empty string erases all matching keyslots
> > > > 
> > > > I hate making the empty string do something completely different than a
> > > > non-empty string.
> > > > 
> > > > What about making @new-secret optional, and have absent @new-secret
> > > > erase?
> > > 
> > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> > > I don't mind personally to do this this way.
> > > empty string though is my addition, since its not possible to pass null on command line.
> > 
> > IIUC this a result of using  "StrOrNull" for this one field...
> > 
> > 
> > > > > +# Since: 5.0
> > > > > +##
> > > > > +{ 'struct': 'LUKSKeyslotUpdate',
> > > > > +  'data': {
> > > > > +           '*keyslot': 'int',
> > > > > +           '*old-secret': 'str',
> > > > > +           'new-secret' : 'StrOrNull',
> > > > > +           '*iter-time' : 'int' } }
> > 
> > It looks wierd here to be special casing "new-secret" to "StrOrNull"
> > instead of just marking it as an optional string field
> > 
> >    "*new-secret": "str"
> > 
> > which would be possible to use from the command line, as you simply
> > omit the field.
> > 
> > I guess the main danger here is that we're using this as a trigger
> > to erase keyslots. So simply omitting "new-secret" can result
> > in damage to the volume by accident which is not an attractive
> > mode.

Right. It's been a while since I discussed this with Maxim, but I think
this was the motivation for me to suggest an explicit null value.

As long as we don't support passing null from the command line, I see
the problem with it, though. Empty string (which I think we didn't
discuss before) looks like a reasonable enough workaround to me, but if
you think this is too much magic, then maybe not.

> Thinking about this again, I really believe we ought to be moire
> explicit about disabling the keyslot by having the "active" field.
> eg
> 
> { 'struct': 'LUKSKeyslotUpdate',
>   'data': {
>           'active': 'bool',
>           '*keyslot': 'int',
>           '*old-secret': 'str',
>           '*new-secret' : 'str',
>           '*iter-time' : 'int' } }
> 
> "new-secret" is thus only needed when "active" == true.

Hm. At the very least, I would make 'active' optional and default to
true, so that for adding or updating you must only specify 'new-secret'
and for deleting only 'active'.

> This avoids the problem with being unable to specify a null for
> StrOrNull on the command line too.

If we ever get a way to pass null on the command line, how would we
think about a struct like this? Will it still feel right, or will it
feel like we feel about simple unions today (they exist, we would like
to get rid of them, but we can't because compatibility)?

Instead of keeping talking about potential future extensions, would it
make more sense to just extend the grammar of the keyval parser now so
that you can specify a type, including null?

We already wanted to use an alternate for keyslot (int) and old-secret
(str) initially, which makes it clear on the schema level that you can
only specify one of both. It would have worked fine for QMP, but not on
the command line because we can't tell integers from strings there. If
we can distinguish them as foo:int=2 and foo:str=2 then that wouldn't be
a problem any more either.

Kevin



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 12:38           ` Kevin Wolf
@ 2020-01-30 12:53             ` Daniel P. Berrangé
  2020-01-30 14:23               ` Kevin Wolf
  2020-01-30 14:47               ` Markus Armbruster
  0 siblings, 2 replies; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-30 12:53 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-block, Markus Armbruster, qemu-devel, Maxim Levitsky,
	Max Reitz, John Snow

On Thu, Jan 30, 2020 at 01:38:47PM +0100, Kevin Wolf wrote:
> Am 28.01.2020 um 18:32 hat Daniel P. Berrangé geschrieben:
> > On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
> > > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> > > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> > > > 
> > > > <trimmed>
> > > > 
> > > > > > +##
> > > > > > +# @LUKSKeyslotUpdate:
> > > > > > +#
> > > > > > +# @keyslot:         If specified, will update only keyslot with this index
> > > > > > +#
> > > > > > +# @old-secret:      If specified, will only update keyslots that
> > > > > > +#                   can be opened with password which is contained in
> > > > > > +#                   QCryptoSecret with @old-secret ID
> > > > > > +#
> > > > > > +#                   If neither @keyslot nor @old-secret is specified,
> > > > > > +#                   first empty keyslot is selected for the update
> > > > > > +#
> > > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > > > > > +#                   key to place in all matching keyslots.
> > > > > > +#                   null/empty string erases all matching keyslots
> > > > > 
> > > > > I hate making the empty string do something completely different than a
> > > > > non-empty string.
> > > > > 
> > > > > What about making @new-secret optional, and have absent @new-secret
> > > > > erase?
> > > > 
> > > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> > > > I don't mind personally to do this this way.
> > > > empty string though is my addition, since its not possible to pass null on command line.
> > > 
> > > IIUC this a result of using  "StrOrNull" for this one field...
> > > 
> > > 
> > > > > > +# Since: 5.0
> > > > > > +##
> > > > > > +{ 'struct': 'LUKSKeyslotUpdate',
> > > > > > +  'data': {
> > > > > > +           '*keyslot': 'int',
> > > > > > +           '*old-secret': 'str',
> > > > > > +           'new-secret' : 'StrOrNull',
> > > > > > +           '*iter-time' : 'int' } }
> > > 
> > > It looks wierd here to be special casing "new-secret" to "StrOrNull"
> > > instead of just marking it as an optional string field
> > > 
> > >    "*new-secret": "str"
> > > 
> > > which would be possible to use from the command line, as you simply
> > > omit the field.
> > > 
> > > I guess the main danger here is that we're using this as a trigger
> > > to erase keyslots. So simply omitting "new-secret" can result
> > > in damage to the volume by accident which is not an attractive
> > > mode.
> 
> Right. It's been a while since I discussed this with Maxim, but I think
> this was the motivation for me to suggest an explicit null value.
> 
> As long as we don't support passing null from the command line, I see
> the problem with it, though. Empty string (which I think we didn't
> discuss before) looks like a reasonable enough workaround to me, but if
> you think this is too much magic, then maybe not.
> 
> > Thinking about this again, I really believe we ought to be moire
> > explicit about disabling the keyslot by having the "active" field.
> > eg
> > 
> > { 'struct': 'LUKSKeyslotUpdate',
> >   'data': {
> >           'active': 'bool',
> >           '*keyslot': 'int',
> >           '*old-secret': 'str',
> >           '*new-secret' : 'str',
> >           '*iter-time' : 'int' } }
> > 
> > "new-secret" is thus only needed when "active" == true.
> 
> Hm. At the very least, I would make 'active' optional and default to
> true, so that for adding or updating you must only specify 'new-secret'
> and for deleting only 'active'.

Is that asymmetry really worth while ? It merely saves a few
characters of typing by omitting "active: true", so I'm not
really convinced.

> 
> > This avoids the problem with being unable to specify a null for
> > StrOrNull on the command line too.
> 
> If we ever get a way to pass null on the command line, how would we
> think about a struct like this? Will it still feel right, or will it
> feel like we feel about simple unions today (they exist, we would like
> to get rid of them, but we can't because compatibility)?

Personally I really don't like the idea of using "new-secret:null"
as a way to request deletion of a keyslot. That's too magical
for an action that is so dangerous to data IMhO.

I think of these operations as activating & deactivating keyslots,
hence my suggestion to use an explicit "active: true|false" to
associate the core action being performed, instead of inferring
the action indirectly from the secret.

I think this could lend itself better to future extensions too.
eg currently we're just activating or deactivating a keyslot.
it is conceivable in future (LUKS2) we might want to modify an
existing keyslot in some way. In that scenario, "active" can
be updated to be allowed to be optional such that:

 - active: true ->  activate a currently inactive keyslot
 - active: false -> deactivate a currently active keyslot
 - active omitted -> modify a currently active keyslot

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-28 17:21   ` Daniel P. Berrangé
@ 2020-01-30 12:58     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-30 12:58 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, 2020-01-28 at 17:21 +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 14, 2020 at 09:33:39PM +0200, Maxim Levitsky wrote:
> > Next few patches will expose that functionality
> > to the user.
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  crypto/block-luks.c | 374 +++++++++++++++++++++++++++++++++++++++++++-
> >  qapi/crypto.json    |  50 +++++-
> >  2 files changed, 421 insertions(+), 3 deletions(-)
> > 
> > diff --git a/crypto/block-luks.c b/crypto/block-luks.c
> > index 4861db810c..349e95fed3 100644
> > --- a/crypto/block-luks.c
> > +++ b/crypto/block-luks.c
> > @@ -32,6 +32,7 @@
> >  #include "qemu/uuid.h"
> >  
> >  #include "qemu/coroutine.h"
> > +#include "qemu/bitmap.h"
> >  
> >  /*
> >   * Reference for the LUKS format implemented here is
> > @@ -70,6 +71,9 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
> >  
> >  #define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL
> >  
> > +#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS 2000
> > +#define QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS 40
> > +
> >  static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = {
> >      'L', 'U', 'K', 'S', 0xBA, 0xBE
> >  };
> > @@ -219,6 +223,9 @@ struct QCryptoBlockLUKS {
> >  
> >      /* Hash algorithm used in pbkdf2 function */
> >      QCryptoHashAlgorithm hash_alg;
> > +
> > +    /* Name of the secret that was used to open the image */
> > +    char *secret;
> >  };
> >  
> >  
> > @@ -1069,6 +1076,112 @@ qcrypto_block_luks_find_key(QCryptoBlock *block,
> >      return -1;
> >  }
> >  
> > +/*
> > + * Returns true if a slot i is marked as active
> > + * (contains encrypted copy of the master key)
> > + */
> > +static bool
> > +qcrypto_block_luks_slot_active(const QCryptoBlockLUKS *luks,
> > +                               unsigned int slot_idx)
> > +{
> > +    uint32_t val = luks->header.key_slots[slot_idx].active;
> > +    return val ==  QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED;
> > +}
> > +
> > +/*
> > + * Returns the number of slots that are marked as active
> > + * (slots that contain encrypted copy of the master key)
> > + */
> > +static unsigned int
> > +qcrypto_block_luks_count_active_slots(const QCryptoBlockLUKS *luks)
> > +{
> > +    size_t i = 0;
> > +    unsigned int ret = 0;
> > +
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +        if (qcrypto_block_luks_slot_active(luks, i)) {
> > +            ret++;
> > +        }
> > +    }
> > +    return ret;
> > +}
> > +
> > +/*
> > + * Finds first key slot which is not active
> > + * Returns the key slot index, or -1 if it doesn't exist
> > + */
> > +static int
> > +qcrypto_block_luks_find_free_keyslot(const QCryptoBlockLUKS *luks)
> > +{
> > +    size_t i;
> > +
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +        if (!qcrypto_block_luks_slot_active(luks, i)) {
> > +            return i;
> > +        }
> > +    }
> > +    return -1;
> > +
> > +}
> > +
> > +/*
> > + * Erases an keyslot given its index
> > + * Returns:
> > + *    0 if the keyslot was erased successfully
> > + *   -1 if a error occurred while erasing the keyslot
> > + *
> > + */
> > +static int
> > +qcrypto_block_luks_erase_key(QCryptoBlock *block,
> > +                             unsigned int slot_idx,
> > +                             QCryptoBlockWriteFunc writefunc,
> > +                             void *opaque,
> > +                             Error **errp)
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
> > +    g_autofree uint8_t *garbagesplitkey = NULL;
> > +    size_t splitkeylen = luks->header.master_key_len * slot->stripes;
> > +    size_t i;
> > +
> > +    assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +    assert(splitkeylen > 0);
> > +
> > +    garbagesplitkey = g_new0(uint8_t, splitkeylen);
> > +
> > +    /* Reset the key slot header */
> > +    memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN);
> > +    slot->iterations = 0;
> > +    slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
> > +
> > +    qcrypto_block_luks_store_header(block,  writefunc, opaque, errp);
> > +
> > +    /*
> > +     * Now try to erase the key material, even if the header
> > +     * update failed
> > +     */
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) {
> > +        if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) {
> > +            /*
> > +             * If we failed to get the random data, still write
> > +             * at least zeros to the key slot at least once
> > +             */
> > +            if (i > 0) {
> > +                return -1;
> > +            }
> > +        }
> > +
> > +        if (writefunc(block,
> > +                      slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
> > +                      garbagesplitkey,
> > +                      splitkeylen,
> > +                      opaque,
> > +                      errp) != splitkeylen) {
> > +            return -1;
> > +        }
> > +    }
> > +    return 0;
> > +}
> >  
> >  static int
> >  qcrypto_block_luks_open(QCryptoBlock *block,
> > @@ -1099,6 +1212,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
> >  
> >      luks = g_new0(QCryptoBlockLUKS, 1);
> >      block->opaque = luks;
> > +    luks->secret = g_strdup(options->u.luks.key_secret);
> >  
> >      if (qcrypto_block_luks_load_header(block, readfunc, opaque, errp) < 0) {
> >          goto fail;
> > @@ -1164,6 +1278,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
> >   fail:
> >      qcrypto_block_free_cipher(block);
> >      qcrypto_ivgen_free(block->ivgen);
> > +    g_free(luks->secret);
> >      g_free(luks);
> >      return -1;
> >  }
> > @@ -1204,7 +1319,7 @@ qcrypto_block_luks_create(QCryptoBlock *block,
> >  
> >      memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts));
> >      if (!luks_opts.has_iter_time) {
> > -        luks_opts.iter_time = 2000;
> > +        luks_opts.iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
> >      }
> >      if (!luks_opts.has_cipher_alg) {
> >          luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
> > @@ -1244,6 +1359,8 @@ qcrypto_block_luks_create(QCryptoBlock *block,
> >                     optprefix ? optprefix : "");
> >          goto error;
> >      }
> > +    luks->secret = g_strdup(options->u.luks.key_secret);
> > +
> >      password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
> >      if (!password) {
> >          goto error;
> > @@ -1471,10 +1588,260 @@ qcrypto_block_luks_create(QCryptoBlock *block,
> >      qcrypto_block_free_cipher(block);
> >      qcrypto_ivgen_free(block->ivgen);
> >  
> > +    g_free(luks->secret);
> >      g_free(luks);
> >      return -1;
> >  }
> >  
> > +/*
> > + * Given LUKSKeyslotUpdate command, return @slots_bitmap with all slots
> > + * that will be updated with new password (or erased)
> > + * returns number of affected slots
> > + */
> > +static int qcrypto_block_luks_get_slots_bitmap(QCryptoBlock *block,
> > +                                               QCryptoBlockReadFunc readfunc,
> > +                                               void *opaque,
> > +                                               const LUKSKeyslotUpdate *command,
> > +                                               unsigned long *slots_bitmap,
> > +                                               Error **errp)
> > +{
> > +    const QCryptoBlockLUKS *luks = block->opaque;
> > +    size_t i;
> > +    int ret = 0;
> > +
> > +    if (command->has_keyslot) {
> > +        /* keyslot set, select only this keyslot */
> > +        int keyslot = command->keyslot;
> > +
> > +        if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
> > +            error_setg(errp,
> > +                       "Invalid slot %u specified, must be between 0 and %u",
> > +                       keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
> > +            goto error;
> > +        }
> > +        bitmap_set(slots_bitmap, keyslot, 1);
> > +        ret++;
> > +
> > +    } else if (command->has_old_secret) {
> > +        /* initially select all active keyslots */
> > +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +            if (qcrypto_block_luks_slot_active(luks, i)) {
> > +                bitmap_set(slots_bitmap, i, 1);
> > +                ret++;
> > +            }
> > +        }
> > +    } else {
> > +        /* find a free keyslot */
> > +        int slot = qcrypto_block_luks_find_free_keyslot(luks);
> > +
> > +        if (slot == -1) {
> > +            error_setg(errp,
> > +                       "Can't add a keyslot - all key slots are in use");
> > +            goto error;
> > +        }
> > +        bitmap_set(slots_bitmap, slot, 1);
> > +        ret++;
> > +    }
> > +
> > +    if (command->has_old_secret) {
> > +        /* now deselect all keyslots that don't contain the password */
> > +        g_autofree uint8_t *tmpkey = g_new0(uint8_t,
> > +                                            luks->header.master_key_len);
> > +
> > +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +            g_autofree char *old_password = NULL;
> > +            int rv;
> > +
> > +            if (!test_bit(i, slots_bitmap)) {
> > +                continue;
> > +            }
> > +
> > +            old_password = qcrypto_secret_lookup_as_utf8(command->old_secret,
> > +                                                         errp);
> > +            if (!old_password) {
> > +                goto error;
> > +            }
> > +
> > +            rv = qcrypto_block_luks_load_key(block,
> > +                                             i,
> > +                                             old_password,
> > +                                             tmpkey,
> > +                                             readfunc,
> > +                                             opaque,
> > +                                             errp);
> > +            if (rv == -1)
> > +                goto error;
> > +            else if (rv == 0) {
> > +                bitmap_clear(slots_bitmap, i, 1);
> > +                ret--;
> > +            }
> > +        }
> > +    }
> > +    return ret;
> > +error:
> > +    return -1;
> > +}
> > +
> > +/*
> > + * Apply a single keyslot update command as described in @command
> > + * Optionally use @unlock_secret to retrieve the master key
> > + */
> > +static int
> > +qcrypto_block_luks_apply_keyslot_update(QCryptoBlock *block,
> > +                                        QCryptoBlockReadFunc readfunc,
> > +                                        QCryptoBlockWriteFunc writefunc,
> > +                                        void *opaque,
> > +                                        LUKSKeyslotUpdate *command,
> > +                                        const char *unlock_secret,
> > +                                        uint8_t **master_key,
> > +                                        bool force,
> > +                                        Error **errp)
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    g_autofree unsigned long *slots_bitmap = NULL;
> > +    int64_t iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
> > +    int slot_count;
> > +    size_t i;
> > +    char *new_password;
> > +    bool erasing;
> > +
> > +    slots_bitmap = bitmap_new(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +    slot_count = qcrypto_block_luks_get_slots_bitmap(block, readfunc, opaque,
> > +                                                     command, slots_bitmap,
> > +                                                     errp);
> > +    if (slot_count == -1) {
> > +        goto error;
> > +    }
> > +    /* no matching slots, so nothing to do */
> > +    if (slot_count == 0) {
> > +        error_setg(errp, "Requested operation didn't match any slots");
> > +        goto error;
> > +    }
> > +    /*
> > +     * slot is erased when the password is set to null, or empty string
> > +     * (for compatibility with command line)
> > +     */
> > +    erasing = command->new_secret->type == QTYPE_QNULL ||
> > +              strlen(command->new_secret->u.s) == 0;
> > +
> > +    /* safety checks */
> > +    if (!force) {
> > +        if (erasing) {
> > +            if (slot_count == qcrypto_block_luks_count_active_slots(luks)) {
> > +                error_setg(errp,
> > +                           "Requested operation will erase all active keyslots"
> > +                           " which will erase all the data in the image"
> > +                           " irreversibly - refusing operation");
> > +                goto error;
> > +            }
> > +        } else {
> > +            for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +                if (!test_bit(i, slots_bitmap)) {
> > +                    continue;
> > +                }
> > +                if (qcrypto_block_luks_slot_active(luks, i)) {
> > +                    error_setg(errp,
> > +                               "Refusing to overwrite active slot %zu - "
> > +                               "please erase it first", i);
> > +                    goto error;
> > +                }
> > +            }
> > +        }
> > +    }
> > +
> > +    /* setup the data needed for storing the new keyslot */
> > +    if (!erasing) {
> > +        /* Load the master key if it wasn't already loaded */
> > +        if (!*master_key) {
> > +            g_autofree char *old_password;
> > +            old_password = qcrypto_secret_lookup_as_utf8(unlock_secret,  errp);
> > +            if (!old_password) {
> > +                goto error;
> > +            }
> > +            *master_key = g_new0(uint8_t, luks->header.master_key_len);
> > +
> > +            if (qcrypto_block_luks_find_key(block, old_password, *master_key,
> > +                                            readfunc, opaque, errp) < 0) {
> > +                error_append_hint(errp, "Failed to retrieve the master key");
> > +                goto error;
> > +            }
> > +        }
> > +        new_password = qcrypto_secret_lookup_as_utf8(command->new_secret->u.s,
> > +                                                     errp);
> > +        if (!new_password) {
> > +            goto error;
> > +        }
> > +        if (command->has_iter_time) {
> > +            iter_time = command->iter_time;
> > +        }
> > +    }
> > +
> > +    /* new apply the update */
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +        if (!test_bit(i, slots_bitmap)) {
> > +            continue;
> > +        }
> > +        if (erasing) {
> > +            if (qcrypto_block_luks_erase_key(block, i,
> > +                                             writefunc,
> > +                                             opaque,
> > +                                             errp)) {
> > +                error_append_hint(errp, "Failed to erase keyslot %zu", i);
> > +                goto error;
> > +            }
> > +        } else {
> > +            if (qcrypto_block_luks_store_key(block, i,
> > +                                             new_password,
> > +                                             *master_key,
> > +                                             iter_time,
> > +                                             writefunc,
> > +                                             opaque,
> > +                                             errp)) {
> > +                error_append_hint(errp, "Failed to write to keyslot %zu", i);
> > +                goto error;
> > +            }
> > +        }
> > +    }
> > +    return 0;
> > +error:
> > +    return -EINVAL;
> > +}
> 
> I feel the this method is confusing from trying to handle both
> adding and erasing keyslots....
> 
> 
> > +
> > +static int
> > +qcrypto_block_luks_amend_options(QCryptoBlock *block,
> > +                                 QCryptoBlockReadFunc readfunc,
> > +                                 QCryptoBlockWriteFunc writefunc,
> > +                                 void *opaque,
> > +                                 QCryptoBlockAmendOptions *options,
> > +                                 bool force,
> > +                                 Error **errp)
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    QCryptoBlockAmendOptionsLUKS *options_luks = &options->u.luks;
> > +    LUKSKeyslotUpdateList *ptr;
> > +    g_autofree uint8_t *master_key = NULL;
> > +    int ret;
> > +
> > +    char *unlock_secret = options_luks->has_unlock_secret ?
> > +                          options_luks->unlock_secret :
> > +                          luks->secret;
> > +
> > +    for (ptr = options_luks->keys; ptr; ptr = ptr->next) {
> > +        ret = qcrypto_block_luks_apply_keyslot_update(block, readfunc,
> > +                                                      writefunc, opaque,
> > +                                                      ptr->value,
> > +                                                      unlock_secret,
> > +                                                      &master_key,
> > +                                                      force, errp);
> 
> .... imho we sould do
> 
>     bool  erasing = command->new_secret->type == QTYPE_QNULL;
> 
>     if (erasing) {
>          ret = qcrypto_block_luks_disable_keyslot(block, readfunc,
>                                                        writefunc, opaque,
>                                                        ptr->value,
>                                                        unlock_secret,
>                                                        &master_key,
>                                                        force, errp);
>     } else {
>         ret = qcrypto_block_luks_enable_keyslot(block, readfunc,
>                                                        writefunc, opaque,
>                                                        ptr->value,
>                                                        unlock_secret,
>                                                        &master_key,
>                                                        force, errp);
>     }

I implemented something like that now, I'll send this in next version of the patches.

> 
> > +
> > +        if (ret != 0) {
> > +            goto error;
> > +        }
> > +    }
> > +    return 0;
> > +error:
> > +    return -1;
> > +}
> 
> If there's no code to run in the 'error' label, then we should just
> get rid of it and 'return -1' instead of "goto error"
Old habit. Fixed now.

> 
> >  
> >  static int qcrypto_block_luks_get_info(QCryptoBlock *block,
> >                                         QCryptoBlockInfo *info,
> > @@ -1523,7 +1890,9 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block,
> >  
> >  static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
> >  {
> > -    g_free(block->opaque);
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    g_free(luks->secret);
> 
> Check if "luks" is non-NULL for robustness in early failure scenarios
100% agree. Fixed.

> 
> > +    g_free(luks);
> >  }
> >  
> 
> Regards,
> Daniel

Thanks for the review,
	Best regards,
		Maxim Levitsky




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 12:53             ` Daniel P. Berrangé
@ 2020-01-30 14:23               ` Kevin Wolf
  2020-01-30 14:30                 ` Daniel P. Berrangé
  2020-01-30 14:53                 ` Markus Armbruster
  2020-01-30 14:47               ` Markus Armbruster
  1 sibling, 2 replies; 84+ messages in thread
From: Kevin Wolf @ 2020-01-30 14:23 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-block, Markus Armbruster, qemu-devel, Maxim Levitsky,
	Max Reitz, John Snow

Am 30.01.2020 um 13:53 hat Daniel P. Berrangé geschrieben:
> On Thu, Jan 30, 2020 at 01:38:47PM +0100, Kevin Wolf wrote:
> > Am 28.01.2020 um 18:32 hat Daniel P. Berrangé geschrieben:
> > > On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
> > > > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> > > > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> > > > > 
> > > > > <trimmed>
> > > > > 
> > > > > > > +##
> > > > > > > +# @LUKSKeyslotUpdate:
> > > > > > > +#
> > > > > > > +# @keyslot:         If specified, will update only keyslot with this index
> > > > > > > +#
> > > > > > > +# @old-secret:      If specified, will only update keyslots that
> > > > > > > +#                   can be opened with password which is contained in
> > > > > > > +#                   QCryptoSecret with @old-secret ID
> > > > > > > +#
> > > > > > > +#                   If neither @keyslot nor @old-secret is specified,
> > > > > > > +#                   first empty keyslot is selected for the update
> > > > > > > +#
> > > > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > > > > > > +#                   key to place in all matching keyslots.
> > > > > > > +#                   null/empty string erases all matching keyslots
> > > > > > 
> > > > > > I hate making the empty string do something completely different than a
> > > > > > non-empty string.
> > > > > > 
> > > > > > What about making @new-secret optional, and have absent @new-secret
> > > > > > erase?
> > > > > 
> > > > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> > > > > I don't mind personally to do this this way.
> > > > > empty string though is my addition, since its not possible to pass null on command line.
> > > > 
> > > > IIUC this a result of using  "StrOrNull" for this one field...
> > > > 
> > > > 
> > > > > > > +# Since: 5.0
> > > > > > > +##
> > > > > > > +{ 'struct': 'LUKSKeyslotUpdate',
> > > > > > > +  'data': {
> > > > > > > +           '*keyslot': 'int',
> > > > > > > +           '*old-secret': 'str',
> > > > > > > +           'new-secret' : 'StrOrNull',
> > > > > > > +           '*iter-time' : 'int' } }
> > > > 
> > > > It looks wierd here to be special casing "new-secret" to "StrOrNull"
> > > > instead of just marking it as an optional string field
> > > > 
> > > >    "*new-secret": "str"
> > > > 
> > > > which would be possible to use from the command line, as you simply
> > > > omit the field.
> > > > 
> > > > I guess the main danger here is that we're using this as a trigger
> > > > to erase keyslots. So simply omitting "new-secret" can result
> > > > in damage to the volume by accident which is not an attractive
> > > > mode.
> > 
> > Right. It's been a while since I discussed this with Maxim, but I think
> > this was the motivation for me to suggest an explicit null value.
> > 
> > As long as we don't support passing null from the command line, I see
> > the problem with it, though. Empty string (which I think we didn't
> > discuss before) looks like a reasonable enough workaround to me, but if
> > you think this is too much magic, then maybe not.
> > 
> > > Thinking about this again, I really believe we ought to be moire
> > > explicit about disabling the keyslot by having the "active" field.
> > > eg
> > > 
> > > { 'struct': 'LUKSKeyslotUpdate',
> > >   'data': {
> > >           'active': 'bool',
> > >           '*keyslot': 'int',
> > >           '*old-secret': 'str',
> > >           '*new-secret' : 'str',
> > >           '*iter-time' : 'int' } }
> > > 
> > > "new-secret" is thus only needed when "active" == true.
> > 
> > Hm. At the very least, I would make 'active' optional and default to
> > true, so that for adding or updating you must only specify 'new-secret'
> > and for deleting only 'active'.
> 
> Is that asymmetry really worth while ? It merely saves a few
> characters of typing by omitting "active: true", so I'm not
> really convinced.
> 
> > 
> > > This avoids the problem with being unable to specify a null for
> > > StrOrNull on the command line too.
> > 
> > If we ever get a way to pass null on the command line, how would we
> > think about a struct like this? Will it still feel right, or will it
> > feel like we feel about simple unions today (they exist, we would like
> > to get rid of them, but we can't because compatibility)?
> 
> Personally I really don't like the idea of using "new-secret:null"
> as a way to request deletion of a keyslot. That's too magical
> for an action that is so dangerous to data IMhO.
> 
> I think of these operations as activating & deactivating keyslots,
> hence my suggestion to use an explicit "active: true|false" to
> associate the core action being performed, instead of inferring
> the action indirectly from the secret.

The general idea of the amend interface is more that you describe a
desired state rather than operations to achieve it.

> I think this could lend itself better to future extensions too.
> eg currently we're just activating or deactivating a keyslot.
> it is conceivable in future (LUKS2) we might want to modify an
> existing keyslot in some way. In that scenario, "active" can
> be updated to be allowed to be optional such that:
> 
>  - active: true ->  activate a currently inactive keyslot
>  - active: false -> deactivate a currently active keyslot
>  - active omitted -> modify a currently active keyslot

This distinction feels artificial to me. All three operations just
change the content of a keyslot. Whether it contained a key or not in
the old state shouldn't make a difference for how to get a new value
(which could be a new key or just an empty keyslot) written to it.

Making an omitted key mean something different from the other options so
that it's not just defaulting to one of them is problematic, too. We
have at least one place where it works like this (backing files) and it
tends to give us headaches.

Kevin



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 14:23               ` Kevin Wolf
@ 2020-01-30 14:30                 ` Daniel P. Berrangé
  2020-01-30 14:53                 ` Markus Armbruster
  1 sibling, 0 replies; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-30 14:30 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-block, Markus Armbruster, qemu-devel, Maxim Levitsky,
	Max Reitz, John Snow

On Thu, Jan 30, 2020 at 03:23:10PM +0100, Kevin Wolf wrote:
> Am 30.01.2020 um 13:53 hat Daniel P. Berrangé geschrieben:
> > On Thu, Jan 30, 2020 at 01:38:47PM +0100, Kevin Wolf wrote:
> > > Am 28.01.2020 um 18:32 hat Daniel P. Berrangé geschrieben:
> > > > On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
> > > > > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> > > > > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> > > > > > 
> > > > > > <trimmed>
> > > > > > 
> > > > > > > > +##
> > > > > > > > +# @LUKSKeyslotUpdate:
> > > > > > > > +#
> > > > > > > > +# @keyslot:         If specified, will update only keyslot with this index
> > > > > > > > +#
> > > > > > > > +# @old-secret:      If specified, will only update keyslots that
> > > > > > > > +#                   can be opened with password which is contained in
> > > > > > > > +#                   QCryptoSecret with @old-secret ID
> > > > > > > > +#
> > > > > > > > +#                   If neither @keyslot nor @old-secret is specified,
> > > > > > > > +#                   first empty keyslot is selected for the update
> > > > > > > > +#
> > > > > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > > > > > > > +#                   key to place in all matching keyslots.
> > > > > > > > +#                   null/empty string erases all matching keyslots
> > > > > > > 
> > > > > > > I hate making the empty string do something completely different than a
> > > > > > > non-empty string.
> > > > > > > 
> > > > > > > What about making @new-secret optional, and have absent @new-secret
> > > > > > > erase?
> > > > > > 
> > > > > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> > > > > > I don't mind personally to do this this way.
> > > > > > empty string though is my addition, since its not possible to pass null on command line.
> > > > > 
> > > > > IIUC this a result of using  "StrOrNull" for this one field...
> > > > > 
> > > > > 
> > > > > > > > +# Since: 5.0
> > > > > > > > +##
> > > > > > > > +{ 'struct': 'LUKSKeyslotUpdate',
> > > > > > > > +  'data': {
> > > > > > > > +           '*keyslot': 'int',
> > > > > > > > +           '*old-secret': 'str',
> > > > > > > > +           'new-secret' : 'StrOrNull',
> > > > > > > > +           '*iter-time' : 'int' } }
> > > > > 
> > > > > It looks wierd here to be special casing "new-secret" to "StrOrNull"
> > > > > instead of just marking it as an optional string field
> > > > > 
> > > > >    "*new-secret": "str"
> > > > > 
> > > > > which would be possible to use from the command line, as you simply
> > > > > omit the field.
> > > > > 
> > > > > I guess the main danger here is that we're using this as a trigger
> > > > > to erase keyslots. So simply omitting "new-secret" can result
> > > > > in damage to the volume by accident which is not an attractive
> > > > > mode.
> > > 
> > > Right. It's been a while since I discussed this with Maxim, but I think
> > > this was the motivation for me to suggest an explicit null value.
> > > 
> > > As long as we don't support passing null from the command line, I see
> > > the problem with it, though. Empty string (which I think we didn't
> > > discuss before) looks like a reasonable enough workaround to me, but if
> > > you think this is too much magic, then maybe not.
> > > 
> > > > Thinking about this again, I really believe we ought to be moire
> > > > explicit about disabling the keyslot by having the "active" field.
> > > > eg
> > > > 
> > > > { 'struct': 'LUKSKeyslotUpdate',
> > > >   'data': {
> > > >           'active': 'bool',
> > > >           '*keyslot': 'int',
> > > >           '*old-secret': 'str',
> > > >           '*new-secret' : 'str',
> > > >           '*iter-time' : 'int' } }
> > > > 
> > > > "new-secret" is thus only needed when "active" == true.
> > > 
> > > Hm. At the very least, I would make 'active' optional and default to
> > > true, so that for adding or updating you must only specify 'new-secret'
> > > and for deleting only 'active'.
> > 
> > Is that asymmetry really worth while ? It merely saves a few
> > characters of typing by omitting "active: true", so I'm not
> > really convinced.
> > 
> > > 
> > > > This avoids the problem with being unable to specify a null for
> > > > StrOrNull on the command line too.
> > > 
> > > If we ever get a way to pass null on the command line, how would we
> > > think about a struct like this? Will it still feel right, or will it
> > > feel like we feel about simple unions today (they exist, we would like
> > > to get rid of them, but we can't because compatibility)?
> > 
> > Personally I really don't like the idea of using "new-secret:null"
> > as a way to request deletion of a keyslot. That's too magical
> > for an action that is so dangerous to data IMhO.
> > 
> > I think of these operations as activating & deactivating keyslots,
> > hence my suggestion to use an explicit "active: true|false" to
> > associate the core action being performed, instead of inferring
> > the action indirectly from the secret.
> 
> The general idea of the amend interface is more that you describe a
> desired state rather than operations to achieve it.
> 
> > I think this could lend itself better to future extensions too.
> > eg currently we're just activating or deactivating a keyslot.
> > it is conceivable in future (LUKS2) we might want to modify an
> > existing keyslot in some way. In that scenario, "active" can
> > be updated to be allowed to be optional such that:
> > 
> >  - active: true ->  activate a currently inactive keyslot
> >  - active: false -> deactivate a currently active keyslot
> >  - active omitted -> modify a currently active keyslot
> 
> This distinction feels artificial to me. All three operations just
> change the content of a keyslot. Whether it contained a key or not in
> the old state shouldn't make a difference for how to get a new value
> (which could be a new key or just an empty keyslot) written to it.

There is an explicit "active" state associated with keyslots on disk
and in the LUKS crypto data structures in QEMU. So this is simply
exposing "active" as a field in the amend interface, directly.

This also matches up with the "qemu-img info" output for a luks
volume which reports the "active" state of each key slot.

> Making an omitted key mean something different from the other options so
> that it's not just defaulting to one of them is problematic, too. We
> have at least one place where it works like this (backing files) and it
> tends to give us headaches.

Omitting "active" in my example above, just means that we're doing
something that is not changing the "active" state in the keyslot
on disk. 

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 12:53             ` Daniel P. Berrangé
  2020-01-30 14:23               ` Kevin Wolf
@ 2020-01-30 14:47               ` Markus Armbruster
  2020-01-30 15:01                 ` Daniel P. Berrangé
  2020-01-30 15:45                 ` Maxim Levitsky
  1 sibling, 2 replies; 84+ messages in thread
From: Markus Armbruster @ 2020-01-30 14:47 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, Maxim Levitsky, John Snow

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Thu, Jan 30, 2020 at 01:38:47PM +0100, Kevin Wolf wrote:
>> Am 28.01.2020 um 18:32 hat Daniel P. Berrangé geschrieben:
>> > On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
>> > > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
>> > > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
>> > > > 
>> > > > <trimmed>
>> > > > 
>> > > > > > +##
>> > > > > > +# @LUKSKeyslotUpdate:
>> > > > > > +#
>> > > > > > +# @keyslot:         If specified, will update only keyslot with this index
>> > > > > > +#
>> > > > > > +# @old-secret:      If specified, will only update keyslots that
>> > > > > > +#                   can be opened with password which is contained in
>> > > > > > +#                   QCryptoSecret with @old-secret ID
>> > > > > > +#
>> > > > > > +#                   If neither @keyslot nor @old-secret is specified,
>> > > > > > +#                   first empty keyslot is selected for the update
>> > > > > > +#
>> > > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
>> > > > > > +#                   key to place in all matching keyslots.
>> > > > > > +#                   null/empty string erases all matching keyslots
>> > > > > 
>> > > > > I hate making the empty string do something completely different than a
>> > > > > non-empty string.
>> > > > > 
>> > > > > What about making @new-secret optional, and have absent @new-secret
>> > > > > erase?
>> > > > 
>> > > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
>> > > > I don't mind personally to do this this way.
>> > > > empty string though is my addition, since its not possible to pass null on command line.
>> > > 
>> > > IIUC this a result of using  "StrOrNull" for this one field...
>> > > 
>> > > 
>> > > > > > +# Since: 5.0
>> > > > > > +##
>> > > > > > +{ 'struct': 'LUKSKeyslotUpdate',
>> > > > > > +  'data': {
>> > > > > > +           '*keyslot': 'int',
>> > > > > > +           '*old-secret': 'str',
>> > > > > > +           'new-secret' : 'StrOrNull',
>> > > > > > +           '*iter-time' : 'int' } }
>> > > 
>> > > It looks wierd here to be special casing "new-secret" to "StrOrNull"
>> > > instead of just marking it as an optional string field
>> > > 
>> > >    "*new-secret": "str"
>> > > 
>> > > which would be possible to use from the command line, as you simply
>> > > omit the field.
>> > > 
>> > > I guess the main danger here is that we're using this as a trigger
>> > > to erase keyslots. So simply omitting "new-secret" can result
>> > > in damage to the volume by accident which is not an attractive
>> > > mode.
>> 
>> Right. It's been a while since I discussed this with Maxim, but I think
>> this was the motivation for me to suggest an explicit null value.

A bit of redundancy to guard against catastrophic accidents makes sense.
We can discuss its shape.

>> As long as we don't support passing null from the command line, I see
>> the problem with it, though. Empty string (which I think we didn't
>> discuss before) looks like a reasonable enough workaround to me, but if
>> you think this is too much magic, then maybe not.
>> 
>> > Thinking about this again, I really believe we ought to be moire
>> > explicit about disabling the keyslot by having the "active" field.
>> > eg
>> > 
>> > { 'struct': 'LUKSKeyslotUpdate',
>> >   'data': {
>> >           'active': 'bool',
>> >           '*keyslot': 'int',
>> >           '*old-secret': 'str',
>> >           '*new-secret' : 'str',
>> >           '*iter-time' : 'int' } }
>> > 
>> > "new-secret" is thus only needed when "active" == true.

I figure @iter-time, too.

>> Hm. At the very least, I would make 'active' optional and default to
>> true, so that for adding or updating you must only specify 'new-secret'
>> and for deleting only 'active'.
>
> Is that asymmetry really worth while ? It merely saves a few
> characters of typing by omitting "active: true", so I'm not
> really convinced.
>
>> > This avoids the problem with being unable to specify a null for
>> > StrOrNull on the command line too.
>> 
>> If we ever get a way to pass null on the command line, how would we
>> think about a struct like this? Will it still feel right, or will it
>> feel like we feel about simple unions today (they exist, we would like
>> to get rid of them, but we can't because compatibility)?
>
> Personally I really don't like the idea of using "new-secret:null"
> as a way to request deletion of a keyslot. That's too magical
> for an action that is so dangerous to data IMhO.

I tend to agree.  Expressing "zap the secret" as '"new-secret": null' is
clever and kind of cute, but "clever" and "cute" have no place next to
"irrevocably destroy data".

> I think of these operations as activating & deactivating keyslots,
> hence my suggestion to use an explicit "active: true|false" to
> associate the core action being performed, instead of inferring
> the action indirectly from the secret.
>
> I think this could lend itself better to future extensions too.
> eg currently we're just activating or deactivating a keyslot.
> it is conceivable in future (LUKS2) we might want to modify an
> existing keyslot in some way. In that scenario, "active" can
> be updated to be allowed to be optional such that:
>
>  - active: true ->  activate a currently inactive keyslot
>  - active: false -> deactivate a currently active keyslot
>  - active omitted -> modify a currently active keyslot

A boolean provides two actions.  By making it optional, we can squeeze
out a third, at the price of making the interface unintuitive: how would
you know what "@active absent" means without looking it up?

Why not have an @action of enum type instead?  Values "add" and "delete"
now (or "activate" and "deactivate", whatever makes the most sense when
writing the docs), leaving us room to later add whatever comes up.

This also lets us turn LUKSKeyslotUpdate into a union.

Brief detour before I sketch that: update safety.

Unless writing a keyslot is atomic, i.e. always either succeeds
completely, or fails without changing anything, updating a slot in place
is dangerous: you may destroy the old key without getting your new one
in place.

To safely replace an existing secret, you first write the new secret to
a free slot, and only when that succeeded, you delete the old one.

This leads to the following safe operations:

* "Activate": write a secret to a free keyslot (chosen by the system)

* "Deactivate": delete an existing secret from all keyslots containing
  it (commonly just one)

Dangerous and unwanted:

* Replace existing secret in place

Low-level operations we may or may not want to support:

* Write a secret to specific keyslot (dangerous unless it's free)

* Zap a specific keyslot (hope it contains the secret you think it does)

Now let me sketch LUKSKeyslotUpdate as union.  First without support for
the low-level operations:

    { state: 'LUKSKeyslotUpdateAction',
      'data': [ 'add', 'delete' ] }
    { 'struct': 'LUKSKeyslotAdd',
      'data': { 'secret': 'str',
                '*iter-time': 'int' } }
    { 'struct': 'LUKSKeyslotDelete',
      'data': { 'secret': 'str' }
    { 'union: 'LUKSKeyslotUpdate',
      'base': { 'action': 'LUKSKeyslotUpdateAction' }
      'discriminator': 'action',
      'data': { 'add': 'LUKSKeyslotAdd' },
              { 'delete': 'LUKSKeyslotDelete' } }

Since @secret occurs in all variants, we could also put it in @base
instead.  Matter of taste.  I think this way is clearer.  Lets us easily
add a variant that doesn't want @secret later on (although moving it
from @base to variants then would be possible).

Compare:

* Activate free keyslot to hold a secret

  { "new-secret": "CIA/GRU/MI6" }

  vs.

  { "active": true, "new-secret": "CIA/GRU/MI6" }

  vs.

  { "action": "add", "secret": "CIA/GRU/MI6" }

* Deactivate keyslots holding a secret

  { "old-secret": "CIA/GRU/MI6", "new-secret": null }

  vs.

  { "active": false, "old-secret": "CIA/GRU/MI6" }

  vs.

  { "action": "delete", "secret": "CIA/GRU/MI6" }

* Replace existing secret in-place (unsafe!)

  { "old-secret": "OSS/NKVD/MI6", "new-secret": "CIA/GRU/MI6" }

  vs.

  Can't do.

Now let's add support for the low-level operations.  To "write specific
slot" to "add", we add optional LUKSKeyslotAdd member @keyslot to direct
the write to that keyslot instead of the first free one:

    { 'struct': 'LUKSKeyslotAdd',
      'data': { 'secret': 'str',
                '*keyslot': 'int',
                '*iter-time': 'int' } }

To add "zap specific slot" to delete, we need to pass a slot number
instead of the old secret.  We could add member @keyslot, make both
optional, and require users to specify exactly one of them:

    { 'struct': 'LUKSKeyslotDelete',
      'data': { '*secret': 'str',       # must pass either @secret
                '*keyslot': 'int' } }   # or @keyslot

Or we use an alternate:

    { 'alternate': 'LUKSKeyslotMatch',
      'data': { 'secret': 'str',
                'keyslot': 'int' } }
    { 'struct': 'LUKSKeyslotDelete',
      'data': { 'match': 'LUKSKeyslotMatch' } }

Hmm, that gets us into trouble with dotted keys, because match=1 could
be keyslot#1 or the (truly bad) secret "1".  Nevermind.

Or we add a separate "zap" action:

    { state: 'LUKSKeyslotUpdateAction',
      'data': [ 'add', 'delete', 'zap' ] }
    { 'struct': 'LUKSKeyslotZap',
      'data': { 'keyslot': 'int' } }
    { 'union: 'LUKSKeyslotUpdate',
      'base': { 'action': 'LUKSKeyslotUpdateAction' }
      'discriminator': 'action',
      'data': { 'add': 'LUKSKeyslotAdd' },
              { 'delete': 'LUKSKeyslotDelete' },
              { 'zap': 'LUKSKeyslotZap' } }

Compare:

* Write to keyslot#1

  { "new-secret": "CIA/GRU/MI6", "keyslot": 1 }

  vs.

  { "active": true, "new-secret": "CIA/GRU/MI6", "keyslot": 1 }

  vs.

  { "action": "add", "secret": "CIA/GRU/MI6", "keyslot": 1 }

* Zap keyslot#1

  { "keyslot": 1, "new-secret": null }

  vs.

  { "active": false, "keyslot": 1 }

  vs.

  { "action": "delete", "keyslot": 1 }

  vs.

  { "action": "zap", "keyslot": 1 }



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 14:23               ` Kevin Wolf
  2020-01-30 14:30                 ` Daniel P. Berrangé
@ 2020-01-30 14:53                 ` Markus Armbruster
  1 sibling, 0 replies; 84+ messages in thread
From: Markus Armbruster @ 2020-01-30 14:53 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Max Reitz, Maxim Levitsky, John Snow

Kevin Wolf <kwolf@redhat.com> writes:

> Am 30.01.2020 um 13:53 hat Daniel P. Berrangé geschrieben:
[...]
>> Personally I really don't like the idea of using "new-secret:null"
>> as a way to request deletion of a keyslot. That's too magical
>> for an action that is so dangerous to data IMhO.
>> 
>> I think of these operations as activating & deactivating keyslots,
>> hence my suggestion to use an explicit "active: true|false" to
>> associate the core action being performed, instead of inferring
>> the action indirectly from the secret.
>
> The general idea of the amend interface is more that you describe a
> desired state rather than operations to achieve it.

Point taken.

>> I think this could lend itself better to future extensions too.
>> eg currently we're just activating or deactivating a keyslot.
>> it is conceivable in future (LUKS2) we might want to modify an
>> existing keyslot in some way. In that scenario, "active" can
>> be updated to be allowed to be optional such that:
>> 
>>  - active: true ->  activate a currently inactive keyslot
>>  - active: false -> deactivate a currently active keyslot
>>  - active omitted -> modify a currently active keyslot
>
> This distinction feels artificial to me. All three operations just
> change the content of a keyslot. Whether it contained a key or not in
> the old state shouldn't make a difference for how to get a new value
> (which could be a new key or just an empty keyslot) written to it.

*If* you can get it to fail only safely.  Can you?

> Making an omitted key mean something different from the other options so
> that it's not just defaulting to one of them is problematic, too. We
> have at least one place where it works like this (backing files) and it
> tends to give us headaches.

Seconded.



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 14:47               ` Markus Armbruster
@ 2020-01-30 15:01                 ` Daniel P. Berrangé
  2020-01-30 16:37                   ` Markus Armbruster
  2020-01-30 15:45                 ` Maxim Levitsky
  1 sibling, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-01-30 15:01 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, Maxim Levitsky, John Snow

On Thu, Jan 30, 2020 at 03:47:00PM +0100, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > On Thu, Jan 30, 2020 at 01:38:47PM +0100, Kevin Wolf wrote:
> >> Am 28.01.2020 um 18:32 hat Daniel P. Berrangé geschrieben:
> >> > On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
> >> > > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> >> > > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> >> > > > 
> >> > > > <trimmed>
> >> > > > 
> >> > > > > > +##
> >> > > > > > +# @LUKSKeyslotUpdate:
> >> > > > > > +#
> >> > > > > > +# @keyslot:         If specified, will update only keyslot with this index
> >> > > > > > +#
> >> > > > > > +# @old-secret:      If specified, will only update keyslots that
> >> > > > > > +#                   can be opened with password which is contained in
> >> > > > > > +#                   QCryptoSecret with @old-secret ID
> >> > > > > > +#
> >> > > > > > +#                   If neither @keyslot nor @old-secret is specified,
> >> > > > > > +#                   first empty keyslot is selected for the update
> >> > > > > > +#
> >> > > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> >> > > > > > +#                   key to place in all matching keyslots.
> >> > > > > > +#                   null/empty string erases all matching keyslots
> >> > > > > 
> >> > > > > I hate making the empty string do something completely different than a
> >> > > > > non-empty string.
> >> > > > > 
> >> > > > > What about making @new-secret optional, and have absent @new-secret
> >> > > > > erase?
> >> > > > 
> >> > > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> >> > > > I don't mind personally to do this this way.
> >> > > > empty string though is my addition, since its not possible to pass null on command line.
> >> > > 
> >> > > IIUC this a result of using  "StrOrNull" for this one field...
> >> > > 
> >> > > 
> >> > > > > > +# Since: 5.0
> >> > > > > > +##
> >> > > > > > +{ 'struct': 'LUKSKeyslotUpdate',
> >> > > > > > +  'data': {
> >> > > > > > +           '*keyslot': 'int',
> >> > > > > > +           '*old-secret': 'str',
> >> > > > > > +           'new-secret' : 'StrOrNull',
> >> > > > > > +           '*iter-time' : 'int' } }
> >> > > 
> >> > > It looks wierd here to be special casing "new-secret" to "StrOrNull"
> >> > > instead of just marking it as an optional string field
> >> > > 
> >> > >    "*new-secret": "str"
> >> > > 
> >> > > which would be possible to use from the command line, as you simply
> >> > > omit the field.
> >> > > 
> >> > > I guess the main danger here is that we're using this as a trigger
> >> > > to erase keyslots. So simply omitting "new-secret" can result
> >> > > in damage to the volume by accident which is not an attractive
> >> > > mode.
> >> 
> >> Right. It's been a while since I discussed this with Maxim, but I think
> >> this was the motivation for me to suggest an explicit null value.
> 
> A bit of redundancy to guard against catastrophic accidents makes sense.
> We can discuss its shape.
> 
> >> As long as we don't support passing null from the command line, I see
> >> the problem with it, though. Empty string (which I think we didn't
> >> discuss before) looks like a reasonable enough workaround to me, but if
> >> you think this is too much magic, then maybe not.
> >> 
> >> > Thinking about this again, I really believe we ought to be moire
> >> > explicit about disabling the keyslot by having the "active" field.
> >> > eg
> >> > 
> >> > { 'struct': 'LUKSKeyslotUpdate',
> >> >   'data': {
> >> >           'active': 'bool',
> >> >           '*keyslot': 'int',
> >> >           '*old-secret': 'str',
> >> >           '*new-secret' : 'str',
> >> >           '*iter-time' : 'int' } }
> >> > 
> >> > "new-secret" is thus only needed when "active" == true.
> 
> I figure @iter-time, too.
> 
> >> Hm. At the very least, I would make 'active' optional and default to
> >> true, so that for adding or updating you must only specify 'new-secret'
> >> and for deleting only 'active'.
> >
> > Is that asymmetry really worth while ? It merely saves a few
> > characters of typing by omitting "active: true", so I'm not
> > really convinced.
> >
> >> > This avoids the problem with being unable to specify a null for
> >> > StrOrNull on the command line too.
> >> 
> >> If we ever get a way to pass null on the command line, how would we
> >> think about a struct like this? Will it still feel right, or will it
> >> feel like we feel about simple unions today (they exist, we would like
> >> to get rid of them, but we can't because compatibility)?
> >
> > Personally I really don't like the idea of using "new-secret:null"
> > as a way to request deletion of a keyslot. That's too magical
> > for an action that is so dangerous to data IMhO.
> 
> I tend to agree.  Expressing "zap the secret" as '"new-secret": null' is
> clever and kind of cute, but "clever" and "cute" have no place next to
> "irrevocably destroy data".
> 
> > I think of these operations as activating & deactivating keyslots,
> > hence my suggestion to use an explicit "active: true|false" to
> > associate the core action being performed, instead of inferring
> > the action indirectly from the secret.
> >
> > I think this could lend itself better to future extensions too.
> > eg currently we're just activating or deactivating a keyslot.
> > it is conceivable in future (LUKS2) we might want to modify an
> > existing keyslot in some way. In that scenario, "active" can
> > be updated to be allowed to be optional such that:
> >
> >  - active: true ->  activate a currently inactive keyslot
> >  - active: false -> deactivate a currently active keyslot
> >  - active omitted -> modify a currently active keyslot
> 
> A boolean provides two actions.  By making it optional, we can squeeze
> out a third, at the price of making the interface unintuitive: how would
> you know what "@active absent" means without looking it up?
> 
> Why not have an @action of enum type instead?  Values "add" and "delete"
> now (or "activate" and "deactivate", whatever makes the most sense when
> writing the docs), leaving us room to later add whatever comes up.

I probably worded my suggestion badly - "active" should not be
thought of as expressing an operation type; it should be considered
a direct reflection of the "active" metadat field in a LUKS keyslot
on disk.

So I should have described it as:

 - active: true|false ->  set the keyslot active state to this value
 - active omitted -> don't change the keyslot active state

The three possible states of the "active" field then happen to
provide the way to express the desired operations.

> 
> This also lets us turn LUKSKeyslotUpdate into a union.
> 
> Brief detour before I sketch that: update safety.
> 
> Unless writing a keyslot is atomic, i.e. always either succeeds
> completely, or fails without changing anything, updating a slot in place
> is dangerous: you may destroy the old key without getting your new one
> in place.
> 
> To safely replace an existing secret, you first write the new secret to
> a free slot, and only when that succeeded, you delete the old one.
> 
> This leads to the following safe operations:
> 
> * "Activate": write a secret to a free keyslot (chosen by the system)
> 
> * "Deactivate": delete an existing secret from all keyslots containing
>   it (commonly just one)
> 
> Dangerous and unwanted:
> 
> * Replace existing secret in place
> 
> Low-level operations we may or may not want to support:
> 
> * Write a secret to specific keyslot (dangerous unless it's free)
> 
> * Zap a specific keyslot (hope it contains the secret you think it does)
> 
> Now let me sketch LUKSKeyslotUpdate as union.  First without support for
> the low-level operations:
> 
>     { state: 'LUKSKeyslotUpdateAction',
>       'data': [ 'add', 'delete' ] }
>     { 'struct': 'LUKSKeyslotAdd',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int' } }
>     { 'struct': 'LUKSKeyslotDelete',
>       'data': { 'secret': 'str' }
>     { 'union: 'LUKSKeyslotUpdate',
>       'base': { 'action': 'LUKSKeyslotUpdateAction' }
>       'discriminator': 'action',
>       'data': { 'add': 'LUKSKeyslotAdd' },
>               { 'delete': 'LUKSKeyslotDelete' } }
> 
> Since @secret occurs in all variants, we could also put it in @base
> instead.  Matter of taste.  I think this way is clearer.  Lets us easily
> add a variant that doesn't want @secret later on (although moving it
> from @base to variants then would be possible).


This kind of approach is what I originally believed we
should do, but it is contrary to the design expectations
of the "amend" operation. That is not supposed to be
expressing operations, rather expressing the desired
state of the resulting disk.


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 14:47               ` Markus Armbruster
  2020-01-30 15:01                 ` Daniel P. Berrangé
@ 2020-01-30 15:45                 ` Maxim Levitsky
  1 sibling, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-30 15:45 UTC (permalink / raw)
  To: Markus Armbruster, Daniel P.Berrangé
  Cc: Kevin Wolf, John Snow, qemu-devel, qemu-block, Max Reitz

On Thu, 2020-01-30 at 15:47 +0100, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > On Thu, Jan 30, 2020 at 01:38:47PM +0100, Kevin Wolf wrote:
> > > Am 28.01.2020 um 18:32 hat Daniel P. Berrangé geschrieben:
> > > > On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
> > > > > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
> > > > > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
> > > > > > 
> > > > > > <trimmed>
> > > > > > 
> > > > > > > > +##
> > > > > > > > +# @LUKSKeyslotUpdate:
> > > > > > > > +#
> > > > > > > > +# @keyslot:         If specified, will update only keyslot with this index
> > > > > > > > +#
> > > > > > > > +# @old-secret:      If specified, will only update keyslots that
> > > > > > > > +#                   can be opened with password which is contained in
> > > > > > > > +#                   QCryptoSecret with @old-secret ID
> > > > > > > > +#
> > > > > > > > +#                   If neither @keyslot nor @old-secret is specified,
> > > > > > > > +#                   first empty keyslot is selected for the update
> > > > > > > > +#
> > > > > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
> > > > > > > > +#                   key to place in all matching keyslots.
> > > > > > > > +#                   null/empty string erases all matching keyslots
> > > > > > > 
> > > > > > > I hate making the empty string do something completely different than a
> > > > > > > non-empty string.
> > > > > > > 
> > > > > > > What about making @new-secret optional, and have absent @new-secret
> > > > > > > erase?
> > > > > > 
> > > > > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
> > > > > > I don't mind personally to do this this way.
> > > > > > empty string though is my addition, since its not possible to pass null on command line.
> > > > > 
> > > > > IIUC this a result of using  "StrOrNull" for this one field...
> > > > > 
> > > > > 
> > > > > > > > +# Since: 5.0
> > > > > > > > +##
> > > > > > > > +{ 'struct': 'LUKSKeyslotUpdate',
> > > > > > > > +  'data': {
> > > > > > > > +           '*keyslot': 'int',
> > > > > > > > +           '*old-secret': 'str',
> > > > > > > > +           'new-secret' : 'StrOrNull',
> > > > > > > > +           '*iter-time' : 'int' } }
> > > > > 
> > > > > It looks wierd here to be special casing "new-secret" to "StrOrNull"
> > > > > instead of just marking it as an optional string field
> > > > > 
> > > > >    "*new-secret": "str"
> > > > > 
> > > > > which would be possible to use from the command line, as you simply
> > > > > omit the field.
> > > > > 
> > > > > I guess the main danger here is that we're using this as a trigger
> > > > > to erase keyslots. So simply omitting "new-secret" can result
> > > > > in damage to the volume by accident which is not an attractive
> > > > > mode.
> > > 
> > > Right. It's been a while since I discussed this with Maxim, but I think
> > > this was the motivation for me to suggest an explicit null value.
> 
> A bit of redundancy to guard against catastrophic accidents makes sense.
> We can discuss its shape.
> 
> > > As long as we don't support passing null from the command line, I see
> > > the problem with it, though. Empty string (which I think we didn't
> > > discuss before) looks like a reasonable enough workaround to me, but if
> > > you think this is too much magic, then maybe not.
> > > 
> > > > Thinking about this again, I really believe we ought to be moire
> > > > explicit about disabling the keyslot by having the "active" field.
> > > > eg
> > > > 
> > > > { 'struct': 'LUKSKeyslotUpdate',
> > > >   'data': {
> > > >           'active': 'bool',
> > > >           '*keyslot': 'int',
> > > >           '*old-secret': 'str',
> > > >           '*new-secret' : 'str',
> > > >           '*iter-time' : 'int' } }
> > > > 
> > > > "new-secret" is thus only needed when "active" == true.
> 
> I figure @iter-time, too.
> 
> > > Hm. At the very least, I would make 'active' optional and default to
> > > true, so that for adding or updating you must only specify 'new-secret'
> > > and for deleting only 'active'.
> > 
> > Is that asymmetry really worth while ? It merely saves a few
> > characters of typing by omitting "active: true", so I'm not
> > really convinced.
> > 
> > > > This avoids the problem with being unable to specify a null for
> > > > StrOrNull on the command line too.
> > > 
> > > If we ever get a way to pass null on the command line, how would we
> > > think about a struct like this? Will it still feel right, or will it
> > > feel like we feel about simple unions today (they exist, we would like
> > > to get rid of them, but we can't because compatibility)?
> > 
> > Personally I really don't like the idea of using "new-secret:null"
> > as a way to request deletion of a keyslot. That's too magical
> > for an action that is so dangerous to data IMhO.
> 
> I tend to agree.  Expressing "zap the secret" as '"new-secret": null' is
> clever and kind of cute, but "clever" and "cute" have no place next to
> "irrevocably destroy data".
> 
> > I think of these operations as activating & deactivating keyslots,
> > hence my suggestion to use an explicit "active: true|false" to
> > associate the core action being performed, instead of inferring
> > the action indirectly from the secret.
> > 
> > I think this could lend itself better to future extensions too.
> > eg currently we're just activating or deactivating a keyslot.
> > it is conceivable in future (LUKS2) we might want to modify an
> > existing keyslot in some way. In that scenario, "active" can
> > be updated to be allowed to be optional such that:
> > 
> >  - active: true ->  activate a currently inactive keyslot
> >  - active: false -> deactivate a currently active keyslot
> >  - active omitted -> modify a currently active keyslot
> 
> A boolean provides two actions.  By making it optional, we can squeeze
> out a third, at the price of making the interface unintuitive: how would
> you know what "@active absent" means without looking it up?

Note that modifying a currently active keyslot is potentially dangerous
operation and thus not allowed at all by default unless user passes 'force'
parameter.

The right safe usage is always to add a new keyslot and then erase the old
one(s).

> 
> Why not have an @action of enum type instead?  Values "add" and "delete"
> now (or "activate" and "deactivate", whatever makes the most sense when
> writing the docs), leaving us room to later add whatever comes up.
> 
> This also lets us turn LUKSKeyslotUpdate into a union.
> 
> Brief detour before I sketch that: update safety.
> 
> Unless writing a keyslot is atomic, i.e. always either succeeds
> completely, or fails without changing anything, updating a slot in place
> is dangerous: you may destroy the old key without getting your new one
> in place.
Exactly. The keyslot is scattered on the disk. It partially resides
in 1st 512 bytes sector as part of 8 keyslot header table,
and partially resides in area after that header, where the encrypted
master key is stored. Due to anti-forensic algorithm used, that
area for each keyslot even takes itself several sectors.

Write can fail partially/fully and leave us with broken keyslot.
In theory you can restore the old values, but since write failure
usually means media error (e.g. bad disk sector), this won't help much.
In theory the code can try to write a other keyslot, but that is even uglier.

Its best to leave this to user and thus user indeed is supposed to write first to a free keyslot,
check that write succeeded and only then erase the old keyslot.


> 
> To safely replace an existing secret, you first write the new secret to
> a free slot, and only when that succeeded, you delete the old one.
> 
> This leads to the following safe operations:
> 
> * "Activate": write a secret to a free keyslot (chosen by the system)
> 
> * "Deactivate": delete an existing secret from all keyslots containing
>   it (commonly just one)
This can be dangerous too if last keyslot is erased since that basically
guarantees data loss, and therefore also needs 'force' option in my patchset.


Exactly
> 
> Dangerous and unwanted:
> 
> * Replace existing secret in place
> 
> Low-level operations we may or may not want to support:
> 
> * Write a secret to specific keyslot (dangerous unless it's free)
> 
> * Zap a specific keyslot (hope it contains the secret you think it does)

^^ and the above is especially bad if erasing last keyslot as explained above.

> 
> Now let me sketch LUKSKeyslotUpdate as union.  First without support for
> the low-level operations:
> 
>     { state: 'LUKSKeyslotUpdateAction',
>       'data': [ 'add', 'delete' ] }
>     { 'struct': 'LUKSKeyslotAdd',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int' } }
>     { 'struct': 'LUKSKeyslotDelete',
>       'data': { 'secret': 'str' }
>     { 'union: 'LUKSKeyslotUpdate',
>       'base': { 'action': 'LUKSKeyslotUpdateAction' }
>       'discriminator': 'action',
>       'data': { 'add': 'LUKSKeyslotAdd' },
>               { 'delete': 'LUKSKeyslotDelete' } }
> 
> Since @secret occurs in all variants, we could also put it in @base
> instead.  Matter of taste.  I think this way is clearer.  Lets us easily
> add a variant that doesn't want @secret later on (although moving it
> from @base to variants then would be possible).
> 
> Compare:
> 
> * Activate free keyslot to hold a secret
> 
>   { "new-secret": "CIA/GRU/MI6" }
> 
>   vs.
> 
>   { "active": true, "new-secret": "CIA/GRU/MI6" }
> 
>   vs.
> 
>   { "action": "add", "secret": "CIA/GRU/MI6" }
> 
> * Deactivate keyslots holding a secret
> 
>   { "old-secret": "CIA/GRU/MI6", "new-secret": null }
> 
>   vs.
> 
>   { "active": false, "old-secret": "CIA/GRU/MI6" }
> 
>   vs.
> 
>   { "action": "delete", "secret": "CIA/GRU/MI6" }
> 
> * Replace existing secret in-place (unsafe!)
> 
>   { "old-secret": "OSS/NKVD/MI6", "new-secret": "CIA/GRU/MI6" }
Note that my code doesn't support this currently, and user can do this
by first adding a secret and then erasing old one.
I can add this just for fun (but only when 'force' is used).

> 
>   vs.
> 
>   Can't do.
> 
> Now let's add support for the low-level operations.  To "write specific
> slot" to "add", we add optional LUKSKeyslotAdd member @keyslot to direct
> the write to that keyslot instead of the first free one:
> 
>     { 'struct': 'LUKSKeyslotAdd',
>       'data': { 'secret': 'str',
>                 '*keyslot': 'int',
>                 '*iter-time': 'int' } }
OK.

> 
> To add "zap specific slot" to delete, we need to pass a slot number
> instead of the old secret.  We could add member @keyslot, make both
> optional, and require users to specify exactly one of them:
> 
>     { 'struct': 'LUKSKeyslotDelete',
>       'data': { '*secret': 'str',       # must pass either @secret
>                 '*keyslot': 'int' } }   # or @keyslot
No problem with that.

> 
> Or we use an alternate:
> 
>     { 'alternate': 'LUKSKeyslotMatch',
>       'data': { 'secret': 'str',
>                 'keyslot': 'int' } }
>     { 'struct': 'LUKSKeyslotDelete',
>       'data': { 'match': 'LUKSKeyslotMatch' } }
> 
> Hmm, that gets us into trouble with dotted keys, because match=1 could
> be keyslot#1 or the (truly bad) secret "1".  Nevermind.
> 
> Or we add a separate "zap" action:
> 
>     { state: 'LUKSKeyslotUpdateAction',
>       'data': [ 'add', 'delete', 'zap' ] }
>     { 'struct': 'LUKSKeyslotZap',
>       'data': { 'keyslot': 'int' } }
>     { 'union: 'LUKSKeyslotUpdate',
>       'base': { 'action': 'LUKSKeyslotUpdateAction' }
>       'discriminator': 'action',
>       'data': { 'add': 'LUKSKeyslotAdd' },
>               { 'delete': 'LUKSKeyslotDelete' },
>               { 'zap': 'LUKSKeyslotZap' } }

I am not sure I like the 'zap' action, but if this is agreed upon,
I won't argue about this.


> 
> Compare:
> 
> * Write to keyslot#1
> 
>   { "new-secret": "CIA/GRU/MI6", "keyslot": 1 }
> 
>   vs.
> 
>   { "active": true, "new-secret": "CIA/GRU/MI6", "keyslot": 1 }
> 
>   vs.
> 
>   { "action": "add", "secret": "CIA/GRU/MI6", "keyslot": 1 }
> 
> * Zap keyslot#1
> 
>   { "keyslot": 1, "new-secret": null }
> 
>   vs.
> 
>   { "active": false, "keyslot": 1 }
> 
>   vs.
> 
>   { "action": "delete", "keyslot": 1 }
> 
>   vs.
> 
>   { "action": "zap", "keyslot": 1 }


Best regards,
	Maxim Levitsky




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 04/13] block: amend: separate amend and create options for qemu-img
  2020-01-28 17:23   ` Daniel P. Berrangé
@ 2020-01-30 15:54     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-30 15:54 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, 2020-01-28 at 17:23 +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 14, 2020 at 09:33:41PM +0200, Maxim Levitsky wrote:
> > Some options are only useful for creation
> > (or hard to be amended, like cluster size for qcow2), while some other
> > options are only useful for amend, like upcoming keyslot management
> > options for luks
> > 
> > Since currently only qcow2 supports amend, move all its options
> > to a common macro and then include it in each action option list.
> > 
> > In future it might be useful to remove some options which are
> > not supported anyway from amend list, which currently
> > cause an error message if amended.
> 
> I think I would have done that in this commit. At least the
> encrypt.* options shouldn't be added to the amend_opts list,
> since they're being removed from it again a few patches later.

After taking on IRC, I understood the point.

I indeed will send another patch after this one that will
only cleanup qcow2 amend/create options and then patch that
adds luks amend to qcow2, thus 3 patches in total, this one,
the patch that cleans the create/amend separation and then
patch that adds luks amend to qcow2.




[...]

Thanks for the review,
Best regards,
	Maxim Levitsky



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 06/13] block/crypto: implement the encryption key management
  2020-01-28 17:27   ` Daniel P. Berrangé
@ 2020-01-30 16:08     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-30 16:08 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, 2020-01-28 at 17:27 +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 14, 2020 at 09:33:43PM +0200, Maxim Levitsky wrote:
> > This implements the encryption key management using the generic code in
> > qcrypto layer and exposes it to the user via qemu-img
> > 
> > This code adds another 'write_func' because the initialization
> > write_func works directly on the underlying file, and amend
> > works on instance of luks device.
> > 
> > This commit also adds a 'hack/workaround' I and Kevin Wolf (thanks)
> > made to make the driver both support write sharing (to avoid breaking the users),
> > and be safe against concurrent  metadata update (the keyslots)
> > 
> > Eventually the write sharing for luks driver will be deprecated
> > and removed together with this hack.
> > 
> > The hack is that we ask (as a format driver) for BLK_PERM_CONSISTENT_READ
> > and then when we want to update the keys, we unshare that permission.
> > So if someone else has the image open, even readonly, encryption
> > key update will fail gracefully.
> > 
> > Also thanks to Daniel Berrange for the idea of
> > unsharing read, rather that write permission which allows
> > to avoid cases when the other user had opened the image read-only.
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  block/crypto.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++--
> >  block/crypto.h |  31 ++++++++++++
> >  2 files changed, 158 insertions(+), 3 deletions(-)
> > 
> > @@ -148,6 +167,22 @@ static QemuOptsList block_crypto_create_opts_luks = {
> >  };
> >  
> >  
> > +static QemuOptsList block_crypto_amend_opts_luks = {
> > +    .name = "crypto",
> > +    .head = QTAILQ_HEAD_INITIALIZER(block_crypto_create_opts_luks.head),
> > +    .desc = {
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.0."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.1."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.2."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.3."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.4."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.5."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.6."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.7."),
> 
> I'd probably suggest  "key.0" or "keyslot.0" as a name.
To be honest, I don't like either of these. 

I don't like the 'keys'
array, because it is a bit misleading, as basically each 'key' is a command
that can add/erase an arbitrary keyslot.

I would call this 'command' or cmd at least.

Also note that the 'keys' here is passed as is to qmp parser so if I change it here, I will have probably
to update the qmp version as well and there the 'keys' name is more or less agreed upon.
Thoughts?


> 
> > +        { /* end of list */ }
> > +    },
> > +};
> > +
> 
> 
> > @@ -661,6 +696,95 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp)
> >      return spec_info;
> >  }
> >  
> > +static int
> > +block_crypto_amend_options(BlockDriverState *bs,
> > +                           QemuOpts *opts,
> > +                           BlockDriverAmendStatusCB *status_cb,
> > +                           void *cb_opaque,
> > +                           bool force,
> > +                           Error **errp)
> 
> This method should have a "_luks" suffix since...

Oops, thanks!
> 
> > +{
> > +    BlockCrypto *crypto = bs->opaque;
> > +    QDict *cryptoopts = NULL;
> > +    QCryptoBlockAmendOptions *amend_options = NULL;
> > +    int ret;
> > +
> > +    assert(crypto);
> > +    assert(crypto->block);
> > +    crypto->updating_keys = true;
> > +
> > +    ret = bdrv_child_refresh_perms(bs, bs->file, errp);
> > +    if (ret < 0) {
> > +        goto cleanup;
> > +    }
> > +
> > +    cryptoopts = qemu_opts_to_qdict(opts, NULL);
> > +    qdict_put_str(cryptoopts, "format", "luks");
> 
> ...it is hardcoded here to assume luks.
> 
> > +    amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
> > +    if (!amend_options) {
> > +        ret = -EINVAL;
> > +        goto cleanup;
> > +    }
> > +
> > +    ret = qcrypto_block_amend_options(crypto->block,
> > +                                      block_crypto_read_func,
> > +                                      block_crypto_write_func,
> > +                                      bs,
> > +                                      amend_options,
> > +                                      force,
> > +                                      errp);
> > +cleanup:
> > +    crypto->updating_keys = false;
> > +    bdrv_child_refresh_perms(bs, bs->file, errp);
> > +    qapi_free_QCryptoBlockAmendOptions(amend_options);
> > +    qobject_unref(cryptoopts);
> > +    return ret;
> > +}
> 
> With the minor changes above
> 
> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>

Best regards,
	Thanks for the review,
		Maxim Levitsky


> 
> 
> Regards,
> Daniel




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 07/13] qcow2: extend qemu-img amend interface with crypto options
  2020-01-28 17:30   ` Daniel P. Berrangé
@ 2020-01-30 16:09     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-30 16:09 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, 2020-01-28 at 17:30 +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 14, 2020 at 09:33:44PM +0200, Maxim Levitsky wrote:
> > Now that we have all the infrastructure in place,
> > wire it in the qcow2 driver and expose this to the user.
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  block/qcow2.c | 101 +++++++++++++++++++++++++++++++++++++++-----------
> >  1 file changed, 79 insertions(+), 22 deletions(-)
> > 
> > diff --git a/block/qcow2.c b/block/qcow2.c
> > index c6c2deee75..1b01174aed 100644
> > --- a/block/qcow2.c
> > +++ b/block/qcow2.c
> > @@ -173,6 +173,19 @@ static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
> >      return ret;
> >  }
> >  
> > +static QDict*
> > +qcow2_extract_crypto_opts(QemuOpts *opts, const char *fmt, Error **errp)
> > +{
> > +    QDict *cryptoopts_qdict;
> > +    QDict *opts_qdict;
> > +
> > +    /* Extract "encrypt." options into a qdict */
> > +    opts_qdict = qemu_opts_to_qdict(opts, NULL);
> > +    qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
> > +    qobject_unref(opts_qdict);
> > +    qdict_put_str(cryptoopts_qdict, "format", "luks");
> > +    return cryptoopts_qdict;
> > +}
> >  
> >  /* 
> >   * read qcow2 extension and fill bs
> > @@ -4631,20 +4644,18 @@ static ssize_t qcow2_measure_crypto_hdr_write_func(QCryptoBlock *block,
> >  static bool qcow2_measure_luks_headerlen(QemuOpts *opts, size_t *len,
> >                                           Error **errp)
> >  {
> > -    QDict *opts_qdict;
> > -    QDict *cryptoopts_qdict;
> >      QCryptoBlockCreateOptions *cryptoopts;
> > +    QDict* crypto_opts_dict;
> >      QCryptoBlock *crypto;
> >  
> > -    /* Extract "encrypt." options into a qdict */
> > -    opts_qdict = qemu_opts_to_qdict(opts, NULL);
> > -    qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
> > -    qobject_unref(opts_qdict);
> > +    crypto_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp);
> > +    if (!crypto_opts_dict) {
> > +        return false;
> > +    }
> > +
> > +    cryptoopts = block_crypto_create_opts_init(crypto_opts_dict, errp);
> > +    qobject_unref(crypto_opts_dict);
> >  
> > -    /* Build QCryptoBlockCreateOptions object from qdict */
> > -    qdict_put_str(cryptoopts_qdict, "format", "luks");
> > -    cryptoopts = block_crypto_create_opts_init(cryptoopts_qdict, errp);
> > -    qobject_unref(cryptoopts_qdict);
> >      if (!cryptoopts) {
> >          return false;
> >      }
> > @@ -5083,6 +5094,7 @@ typedef enum Qcow2AmendOperation {
> >      QCOW2_NO_OPERATION = 0,
> >  
> >      QCOW2_UPGRADING,
> > +    QCOW2_UPDATING_ENCRYPTION,
> >      QCOW2_CHANGING_REFCOUNT_ORDER,
> >      QCOW2_DOWNGRADING,
> >  } Qcow2AmendOperation;
> > @@ -5167,6 +5179,7 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
> >      int ret;
> >      QemuOptDesc *desc = opts->list->desc;
> >      Qcow2AmendHelperCBInfo helper_cb_info;
> > +    bool encryption_update = false;
> >  
> >      while (desc && desc->name) {
> >          if (!qemu_opt_find(opts, desc->name)) {
> > @@ -5215,9 +5228,17 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
> >                  return -ENOTSUP;
> >              }
> >          } else if (g_str_has_prefix(desc->name, "encrypt.")) {
> > -            error_setg(errp,
> > -                       "Changing the encryption parameters is not supported");
> > -            return -ENOTSUP;
> > +            if (!s->crypto) {
> > +                error_setg(errp,
> > +                           "Can't amend encryption options - encryption not present");
> > +                return -EINVAL;
> > +            }
> > +            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
> > +                error_setg(errp,
> > +                           "Only LUKS encryption options can be amended");
> > +                return -ENOTSUP;
> > +            }
> > +            encryption_update = true;
> >          } else if (!strcmp(desc->name, BLOCK_OPT_CLUSTER_SIZE)) {
> >              cluster_size = qemu_opt_get_size(opts, BLOCK_OPT_CLUSTER_SIZE,
> >                                               cluster_size);
> > @@ -5267,7 +5288,8 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
> >          .original_status_cb = status_cb,
> >          .original_cb_opaque = cb_opaque,
> >          .total_operations = (new_version != old_version)
> > -                          + (s->refcount_bits != refcount_bits)
> > +                          + (s->refcount_bits != refcount_bits) +
> > +                            (encryption_update == true)
> >      };
> >  
> >      /* Upgrade first (some features may require compat=1.1) */
> > @@ -5280,6 +5302,33 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
> >          }
> >      }
> >  
> > +    if (encryption_update) {
> > +        QDict *amend_opts_dict;
> > +        QCryptoBlockAmendOptions *amend_opts;
> > +
> > +        helper_cb_info.current_operation = QCOW2_UPDATING_ENCRYPTION;
> > +        amend_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp);
> > +        if (!amend_opts_dict) {
> > +            return -EINVAL;
> > +        }
> > +        amend_opts = block_crypto_amend_opts_init(amend_opts_dict, errp);
> > +        qobject_unref(amend_opts_dict);
> > +        if (!amend_opts) {
> > +            return -EINVAL;
> > +        }
> > +        ret = qcrypto_block_amend_options(s->crypto,
> > +                                          qcow2_crypto_hdr_read_func,
> > +                                          qcow2_crypto_hdr_write_func,
> > +                                          bs,
> > +                                          amend_opts,
> > +                                          force,
> > +                                          errp);
> > +        qapi_free_QCryptoBlockAmendOptions(amend_opts);
> > +        if (ret < 0) {
> > +            return ret;
> > +        }
> > +    }
> > +
> >      if (s->refcount_bits != refcount_bits) {
> >          int refcount_order = ctz32(refcount_bits);
> >  
> > @@ -5488,14 +5537,6 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
> >          .type = QEMU_OPT_STRING,                                    \
> >          .help = "Encrypt the image, format choices: 'aes', 'luks'", \
> >      },                                                              \
> > -    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
> > -        "ID of secret providing qcow AES key or LUKS passphrase"),  \
> > -    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
> > -    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
> > -    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
> > -    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
> > -    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
> > -    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
> >      {                                                               \
> >          .name = BLOCK_OPT_CLUSTER_SIZE,                             \
> >          .type = QEMU_OPT_SIZE,                                      \
> > @@ -5526,6 +5567,14 @@ static QemuOptsList qcow2_create_opts = {
> >      .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
> >      .desc = {
> >          QCOW_COMMON_OPTIONS,
> > +        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
> > +            "ID of secret providing qcow AES key or LUKS passphrase"),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
> >          { /* end of list */ }
> >      }
> 
> These two chunks should habe been in the earlier patch IMHO.
Yep, these are gone from this patch in the next version I'll post soon.

> 
> >  };
> > @@ -5535,6 +5584,14 @@ static QemuOptsList qcow2_amend_opts = {
> >      .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
> >      .desc = {
> >          QCOW_COMMON_OPTIONS,
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.0."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.1."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.2."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.3."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.4."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.5."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.6."),
> > +        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.7."),
> >          { /* end of list */ }
> 
> Same naming idea about  "encrypt.key.0" or "encrypt.keyslot.0"
Yep, same applies as I said in comment in the other patch.

> 
> >      }
> >  };
> > -- 
> > 2.17.2
> > 
> 
> Regards,
> Daniel

Thanks for the review,
Best regards,
	Maxim Levitsky




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 08/13] iotests: filter few more luks specific create options
  2020-01-28 17:36   ` Daniel P. Berrangé
@ 2020-01-30 16:12     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-30 16:12 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, Markus Armbruster, qemu-devel, Max Reitz,
	John Snow

On Tue, 2020-01-28 at 17:36 +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 14, 2020 at 09:33:45PM +0200, Maxim Levitsky wrote:
> > This allows more tests to be able to have same output on both qcow2 luks encrypted images
> > and raw luks images
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  tests/qemu-iotests/087.out       | 6 +++---
> >  tests/qemu-iotests/134.out       | 2 +-
> >  tests/qemu-iotests/158.out       | 4 ++--
> >  tests/qemu-iotests/188.out       | 2 +-
> >  tests/qemu-iotests/189.out       | 4 ++--
> >  tests/qemu-iotests/198.out       | 4 ++--
> >  tests/qemu-iotests/common.filter | 6 ++++--
> >  7 files changed, 15 insertions(+), 13 deletions(-)
> > 
> > diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
> > index 2d92ea847b..b61ba638af 100644
> > --- a/tests/qemu-iotests/087.out
> > +++ b/tests/qemu-iotests/087.out
> > @@ -34,7 +34,7 @@ QMP_VERSION
> 
> 1>  
> >  === Encrypted image QCow ===
> >  
> > -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
> > +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
> 
> I'm not convinced about this - it feels like this is throwing
> away relevant info to be validated about the test scenario
This is mostly echo of the input info so it is only nice to have to debug the test IMHO.

> 
> Can you give more info about the scenario this benefits us in ?

This allows me to have same test output for raw luks and qcow2, and thus
use mostly the same test (sans adding the encrypt.* prefix everywhere) for both
luks and qcow2.

Best regards,
	Maxim Levitsky

> 
> Regards,
> Daniel




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 11/13] block/crypto: implement blockdev-amend
  2020-01-28 17:40   ` Daniel P. Berrangé
@ 2020-01-30 16:24     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-01-30 16:24 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, John Snow,
	Markus Armbruster

On Tue, 2020-01-28 at 17:40 +0000, Daniel P. Berrangé wrote:
> On Tue, Jan 14, 2020 at 09:33:48PM +0200, Maxim Levitsky wrote:
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  block/crypto.c       | 70 ++++++++++++++++++++++++++++++++------------
> >  qapi/block-core.json | 14 ++++++++-
> >  2 files changed, 64 insertions(+), 20 deletions(-)
> > 
> > diff --git a/block/crypto.c b/block/crypto.c
> > index 081880bced..6836337863 100644
> > --- a/block/crypto.c
> > +++ b/block/crypto.c
> 
> 
> >  
> > +static int
> > +coroutine_fn block_crypto_co_amend(BlockDriverState *bs,
> > +                                   BlockdevAmendOptions *opts,
> > +                                   bool force,
> > +                                   Error **errp)
> 
> This should have a _luks suffix given...

100% agree. Fixed now.

> > +{
> > +    QCryptoBlockAmendOptions amend_opts;
> > +
> > +    amend_opts = (QCryptoBlockAmendOptions) {
> > +        .format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
> > +        .u.luks = *qapi_BlockdevAmendOptionsLUKS_base(&opts->u.luks),
> 
> ...this is hardcoded to luks
> 
> > +    };
> > +    return block_crypto_amend_options_generic(bs, &amend_opts, force, errp);
> > +}
> >  
> >  static void
> >  block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c,
> > @@ -812,6 +843,7 @@ static BlockDriver bdrv_crypto_luks = {
> >      .bdrv_get_info      = block_crypto_get_info_luks,
> >      .bdrv_get_specific_info = block_crypto_get_specific_info_luks,
> >      .bdrv_amend_options = block_crypto_amend_options,
> > +    .bdrv_co_amend      = block_crypto_co_amend,
> >  
> >      .strong_runtime_opts = block_crypto_strong_runtime_opts,
> >  };
> 
> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>

Thanks for the review,
	Best regards,
		Maxim Levitsky
> 
> 
> 
> Regards,
> Daniel




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 15:01                 ` Daniel P. Berrangé
@ 2020-01-30 16:37                   ` Markus Armbruster
  2020-02-05  8:24                     ` Markus Armbruster
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-01-30 16:37 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Thu, Jan 30, 2020 at 03:47:00PM +0100, Markus Armbruster wrote:
>> Daniel P. Berrangé <berrange@redhat.com> writes:
>> 
>> > On Thu, Jan 30, 2020 at 01:38:47PM +0100, Kevin Wolf wrote:
>> >> Am 28.01.2020 um 18:32 hat Daniel P. Berrangé geschrieben:
>> >> > On Tue, Jan 28, 2020 at 05:11:16PM +0000, Daniel P. Berrangé wrote:
>> >> > > On Tue, Jan 21, 2020 at 03:13:01PM +0200, Maxim Levitsky wrote:
>> >> > > > On Tue, 2020-01-21 at 08:54 +0100, Markus Armbruster wrote:
>> >> > > > 
>> >> > > > <trimmed>
>> >> > > > 
>> >> > > > > > +##
>> >> > > > > > +# @LUKSKeyslotUpdate:
>> >> > > > > > +#
>> >> > > > > > +# @keyslot:         If specified, will update only keyslot with this index
>> >> > > > > > +#
>> >> > > > > > +# @old-secret:      If specified, will only update keyslots that
>> >> > > > > > +#                   can be opened with password which is contained in
>> >> > > > > > +#                   QCryptoSecret with @old-secret ID
>> >> > > > > > +#
>> >> > > > > > +#                   If neither @keyslot nor @old-secret is specified,
>> >> > > > > > +#                   first empty keyslot is selected for the update
>> >> > > > > > +#
>> >> > > > > > +# @new-secret:      The ID of a QCryptoSecret object providing a new decryption
>> >> > > > > > +#                   key to place in all matching keyslots.
>> >> > > > > > +#                   null/empty string erases all matching keyslots
>> >> > > > > 
>> >> > > > > I hate making the empty string do something completely different than a
>> >> > > > > non-empty string.
>> >> > > > > 
>> >> > > > > What about making @new-secret optional, and have absent @new-secret
>> >> > > > > erase?
>> >> > > > 
>> >> > > > I don't remember already why I and Keven Wolf decided to do this this way, but I think that you are right here.
>> >> > > > I don't mind personally to do this this way.
>> >> > > > empty string though is my addition, since its not possible to pass null on command line.
>> >> > > 
>> >> > > IIUC this a result of using  "StrOrNull" for this one field...
>> >> > > 
>> >> > > 
>> >> > > > > > +# Since: 5.0
>> >> > > > > > +##
>> >> > > > > > +{ 'struct': 'LUKSKeyslotUpdate',
>> >> > > > > > +  'data': {
>> >> > > > > > +           '*keyslot': 'int',
>> >> > > > > > +           '*old-secret': 'str',
>> >> > > > > > +           'new-secret' : 'StrOrNull',
>> >> > > > > > +           '*iter-time' : 'int' } }
>> >> > > 
>> >> > > It looks wierd here to be special casing "new-secret" to "StrOrNull"
>> >> > > instead of just marking it as an optional string field
>> >> > > 
>> >> > >    "*new-secret": "str"
>> >> > > 
>> >> > > which would be possible to use from the command line, as you simply
>> >> > > omit the field.
>> >> > > 
>> >> > > I guess the main danger here is that we're using this as a trigger
>> >> > > to erase keyslots. So simply omitting "new-secret" can result
>> >> > > in damage to the volume by accident which is not an attractive
>> >> > > mode.
>> >> 
>> >> Right. It's been a while since I discussed this with Maxim, but I think
>> >> this was the motivation for me to suggest an explicit null value.
>> 
>> A bit of redundancy to guard against catastrophic accidents makes sense.
>> We can discuss its shape.
>> 
>> >> As long as we don't support passing null from the command line, I see
>> >> the problem with it, though. Empty string (which I think we didn't
>> >> discuss before) looks like a reasonable enough workaround to me, but if
>> >> you think this is too much magic, then maybe not.
>> >> 
>> >> > Thinking about this again, I really believe we ought to be moire
>> >> > explicit about disabling the keyslot by having the "active" field.
>> >> > eg
>> >> > 
>> >> > { 'struct': 'LUKSKeyslotUpdate',
>> >> >   'data': {
>> >> >           'active': 'bool',
>> >> >           '*keyslot': 'int',
>> >> >           '*old-secret': 'str',
>> >> >           '*new-secret' : 'str',
>> >> >           '*iter-time' : 'int' } }
>> >> > 
>> >> > "new-secret" is thus only needed when "active" == true.
>> 
>> I figure @iter-time, too.
>> 
>> >> Hm. At the very least, I would make 'active' optional and default to
>> >> true, so that for adding or updating you must only specify 'new-secret'
>> >> and for deleting only 'active'.
>> >
>> > Is that asymmetry really worth while ? It merely saves a few
>> > characters of typing by omitting "active: true", so I'm not
>> > really convinced.
>> >
>> >> > This avoids the problem with being unable to specify a null for
>> >> > StrOrNull on the command line too.
>> >> 
>> >> If we ever get a way to pass null on the command line, how would we
>> >> think about a struct like this? Will it still feel right, or will it
>> >> feel like we feel about simple unions today (they exist, we would like
>> >> to get rid of them, but we can't because compatibility)?
>> >
>> > Personally I really don't like the idea of using "new-secret:null"
>> > as a way to request deletion of a keyslot. That's too magical
>> > for an action that is so dangerous to data IMhO.
>> 
>> I tend to agree.  Expressing "zap the secret" as '"new-secret": null' is
>> clever and kind of cute, but "clever" and "cute" have no place next to
>> "irrevocably destroy data".
>> 
>> > I think of these operations as activating & deactivating keyslots,
>> > hence my suggestion to use an explicit "active: true|false" to
>> > associate the core action being performed, instead of inferring
>> > the action indirectly from the secret.
>> >
>> > I think this could lend itself better to future extensions too.
>> > eg currently we're just activating or deactivating a keyslot.
>> > it is conceivable in future (LUKS2) we might want to modify an
>> > existing keyslot in some way. In that scenario, "active" can
>> > be updated to be allowed to be optional such that:
>> >
>> >  - active: true ->  activate a currently inactive keyslot
>> >  - active: false -> deactivate a currently active keyslot
>> >  - active omitted -> modify a currently active keyslot
>> 
>> A boolean provides two actions.  By making it optional, we can squeeze
>> out a third, at the price of making the interface unintuitive: how would
>> you know what "@active absent" means without looking it up?
>> 
>> Why not have an @action of enum type instead?  Values "add" and "delete"
>> now (or "activate" and "deactivate", whatever makes the most sense when
>> writing the docs), leaving us room to later add whatever comes up.
>
> I probably worded my suggestion badly - "active" should not be
> thought of as expressing an operation type; it should be considered
> a direct reflection of the "active" metadat field in a LUKS keyslot
> on disk.
>
> So I should have described it as:
>
>  - active: true|false ->  set the keyslot active state to this value
>  - active omitted -> don't change the keyslot active state
>
> The three possible states of the "active" field then happen to
> provide the way to express the desired operations.
>
>> 
>> This also lets us turn LUKSKeyslotUpdate into a union.
>> 
>> Brief detour before I sketch that: update safety.
>> 
>> Unless writing a keyslot is atomic, i.e. always either succeeds
>> completely, or fails without changing anything, updating a slot in place
>> is dangerous: you may destroy the old key without getting your new one
>> in place.
>> 
>> To safely replace an existing secret, you first write the new secret to
>> a free slot, and only when that succeeded, you delete the old one.
>> 
>> This leads to the following safe operations:
>> 
>> * "Activate": write a secret to a free keyslot (chosen by the system)
>> 
>> * "Deactivate": delete an existing secret from all keyslots containing
>>   it (commonly just one)
>> 
>> Dangerous and unwanted:
>> 
>> * Replace existing secret in place
>> 
>> Low-level operations we may or may not want to support:
>> 
>> * Write a secret to specific keyslot (dangerous unless it's free)
>> 
>> * Zap a specific keyslot (hope it contains the secret you think it does)
>> 
>> Now let me sketch LUKSKeyslotUpdate as union.  First without support for
>> the low-level operations:
>> 
>>     { state: 'LUKSKeyslotUpdateAction',
>>       'data': [ 'add', 'delete' ] }
>>     { 'struct': 'LUKSKeyslotAdd',
>>       'data': { 'secret': 'str',
>>                 '*iter-time': 'int' } }
>>     { 'struct': 'LUKSKeyslotDelete',
>>       'data': { 'secret': 'str' }
>>     { 'union: 'LUKSKeyslotUpdate',
>>       'base': { 'action': 'LUKSKeyslotUpdateAction' }
>>       'discriminator': 'action',
>>       'data': { 'add': 'LUKSKeyslotAdd' },
>>               { 'delete': 'LUKSKeyslotDelete' } }
>> 
>> Since @secret occurs in all variants, we could also put it in @base
>> instead.  Matter of taste.  I think this way is clearer.  Lets us easily
>> add a variant that doesn't want @secret later on (although moving it
>> from @base to variants then would be possible).
>
>
> This kind of approach is what I originally believed we
> should do, but it is contrary to the design expectations
> of the "amend" operation. That is not supposed to be
> expressing operations, rather expressing the desired
> state of the resulting disk.

I got that now, so let's talk state.

A keyslot can be either inactive or active.

Let's start low-level, i.e. we specify the slot by slot#:

    state       new state   action
    inactive    inactive    nop
    inactive    active      put secret, iter-time, mark active
    active      inactive    mark inactive (effectively deletes secret)
    active      active      in general, error (unsafe update in place)
                            we can make it a nop when secret, iter-time
                                remain unchanged
                            we can allow unsafe update with force: true

As struct:

    { 'struct': 'LUKSKeyslotUpdate',
      'data': { 'active': 'bool',       # could do enum instead
                'keyslot': 'int',
                '*secret': 'str',       # present if @active is true
                '*iter-time': 'int' } } # absent if @active is false

As union:

    { 'enum': 'LUKSKeyslotState',
      'data': [ 'active', 'inactive' ] }
    { 'struct': 'LUKSKeyslotActive',
      'data': { 'secret': 'str',
                '*iter-time': 'int } }
    { 'union': 'LUKSKeyslotAmend',
      'base': { 'state': 'LUKSKeyslotState' }   # must do enum
      'discriminator': 'state',
      'data': { 'active': 'LUKSKeyslotActive' } }

When we don't specify the slot#, then "new state active" selects an
inactive slot (chosen by the system, and "new state inactive selects
slots by secret (commonly just one slot).

New state active:

    state       new state   action
    inactive    active      put secret, iter-time, mark active
    active      active      N/A (system choses inactive slot)

New state inactive, for each slot holding the specified secret:

    state       new state   action
    inactive    inactive    N/A (inactive slot holds no secret)
    active      inactive    mark inactive (effectively deletes secret)

As struct:

    { 'struct': 'LUKSKeyslotUpdate',
      'data': { 'active': 'bool',       # could do enum instead
                '*keyslot': 'int',
                '*secret': 'str',       # present if @active is true
                '*iter-time': 'int' } } # absent if @active is false

As union:

    { 'enum': 'LUKSKeyslotState',
      'data': [ 'active', 'inactive' ] }
    { 'struct': 'LUKSKeyslotActive',
      'data': { 'secret': 'str',
                '*iter-time': 'int } }
    { 'union': 'LUKSKeyslotAmend',
      'base': { '*keyslot': 'int',
                'state': 'LUKSKeyslotState' }
      'discriminator': 'state',
      'data': { 'active': 'LUKSKeyslotActive' } }

Union looks more complicated because our union notation sucks[*].  I
like it anyway, because you don't have to explain when which optional
members aren't actually optional.

Regardless of struct vs. union, this supports an active -> active
transition only with an explicit keyslot.  Feels fine to me.  If we want
to support it without keyslot as well, we need a way to specify both old
and new secret.  Do we?


[*] I hope to fix that one day.  It's not even hard.



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-01-30 16:37                   ` Markus Armbruster
@ 2020-02-05  8:24                     ` Markus Armbruster
  2020-02-05  9:30                       ` Kevin Wolf
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-05  8:24 UTC (permalink / raw)
  To: Daniel P. Berrangé, Kevin Wolf
  Cc: Maxim Levitsky, John Snow, qemu-devel, qemu-block, Max Reitz

Daniel, Kevin, any comments or objections to the QAPI schema design
sketch developed below?

For your convenience, here's the result again:

    { 'enum': 'LUKSKeyslotState',
      'data': [ 'active', 'inactive' ] }
    { 'struct': 'LUKSKeyslotActive',
      'data': { 'secret': 'str',
                '*iter-time': 'int } }
    { 'union': 'LUKSKeyslotAmend',
      'base': { '*keyslot': 'int',
                'state': 'LUKSKeyslotState' }
      'discriminator': 'state',
      'data': { 'active': 'LUKSKeyslotActive' } }

Markus Armbruster <armbru@redhat.com> writes:

[...]
> A keyslot can be either inactive or active.
>
> Let's start low-level, i.e. we specify the slot by slot#:
>
>     state       new state   action
>     inactive    inactive    nop
>     inactive    active      put secret, iter-time, mark active
>     active      inactive    mark inactive (effectively deletes secret)
>     active      active      in general, error (unsafe update in place)
>                             we can make it a nop when secret, iter-time
>                                 remain unchanged
>                             we can allow unsafe update with force: true
>
> As struct:
>
>     { 'struct': 'LUKSKeyslotUpdate',
>       'data': { 'active': 'bool',       # could do enum instead
>                 'keyslot': 'int',
>                 '*secret': 'str',       # present if @active is true
>                 '*iter-time': 'int' } } # absent if @active is false
>
> As union:
>
>     { 'enum': 'LUKSKeyslotState',
>       'data': [ 'active', 'inactive' ] }
>     { 'struct': 'LUKSKeyslotActive',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int } }
>     { 'union': 'LUKSKeyslotAmend',
>       'base': { 'state': 'LUKSKeyslotState' }   # must do enum
>       'discriminator': 'state',
>       'data': { 'active': 'LUKSKeyslotActive' } }
>
> When we don't specify the slot#, then "new state active" selects an
> inactive slot (chosen by the system, and "new state inactive selects
> slots by secret (commonly just one slot).
>
> New state active:
>
>     state       new state   action
>     inactive    active      put secret, iter-time, mark active
>     active      active      N/A (system choses inactive slot)
>
> New state inactive, for each slot holding the specified secret:
>
>     state       new state   action
>     inactive    inactive    N/A (inactive slot holds no secret)
>     active      inactive    mark inactive (effectively deletes secret)
>
> As struct:
>
>     { 'struct': 'LUKSKeyslotUpdate',
>       'data': { 'active': 'bool',       # could do enum instead
>                 '*keyslot': 'int',
>                 '*secret': 'str',       # present if @active is true
>                 '*iter-time': 'int' } } # absent if @active is false
>
> As union:
>
>     { 'enum': 'LUKSKeyslotState',
>       'data': [ 'active', 'inactive' ] }
>     { 'struct': 'LUKSKeyslotActive',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int } }
>     { 'union': 'LUKSKeyslotAmend',
>       'base': { '*keyslot': 'int',
>                 'state': 'LUKSKeyslotState' }
>       'discriminator': 'state',
>       'data': { 'active': 'LUKSKeyslotActive' } }
>
> Union looks more complicated because our union notation sucks[*].  I
> like it anyway, because you don't have to explain when which optional
> members aren't actually optional.
>
> Regardless of struct vs. union, this supports an active -> active
> transition only with an explicit keyslot.  Feels fine to me.  If we want
> to support it without keyslot as well, we need a way to specify both old
> and new secret.  Do we?
>
>
> [*] I hope to fix that one day.  It's not even hard.



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-05  8:24                     ` Markus Armbruster
@ 2020-02-05  9:30                       ` Kevin Wolf
  2020-02-05 10:03                         ` Markus Armbruster
  2020-02-05 10:23                         ` Daniel P. Berrangé
  0 siblings, 2 replies; 84+ messages in thread
From: Kevin Wolf @ 2020-02-05  9:30 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

Am 05.02.2020 um 09:24 hat Markus Armbruster geschrieben:
> Daniel, Kevin, any comments or objections to the QAPI schema design
> sketch developed below?
> 
> For your convenience, here's the result again:
> 
>     { 'enum': 'LUKSKeyslotState',
>       'data': [ 'active', 'inactive' ] }
>     { 'struct': 'LUKSKeyslotActive',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int } }
>     { 'union': 'LUKSKeyslotAmend',
>       'base': { '*keyslot': 'int',
>                 'state': 'LUKSKeyslotState' }
>       'discriminator': 'state',
>       'data': { 'active': 'LUKSKeyslotActive' } }

I think one of the requirements was that you can specify the keyslot not
only by using its number, but also by specifying the old secret. Trivial
extension, you just get another optional field that can be specified
instead of 'keyslot'.

Resulting commands:

    Adding a key:
    qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2

    Deleting a key:
    qemu-img amend -o encrypt.keys.0.state=inactive,encrypt.keys.0.keyslot=2 test.qcow2

Previous version (if this series is applied unchanged):

    Adding a key:
    qemu-img amend -o encrypt.keys.0.new-secret=sec0 test.qcow2

    Deleting a key:
    qemu-img amend -o encrypt.keys.0.new-secret=,encrypt.keys.0.keyslot=2 test.qcow2

Adding a key gets more complicated with your proposed interface because
state must be set explicitly now whereas before it was derived
automatically from the fact that if you give a key, only active makes
sense.

Deleting stays more or less the same, you just change the state instead
of clearing the secret.

Kevin

> Markus Armbruster <armbru@redhat.com> writes:
> 
> [...]
> > A keyslot can be either inactive or active.
> >
> > Let's start low-level, i.e. we specify the slot by slot#:
> >
> >     state       new state   action
> >     inactive    inactive    nop
> >     inactive    active      put secret, iter-time, mark active
> >     active      inactive    mark inactive (effectively deletes secret)
> >     active      active      in general, error (unsafe update in place)
> >                             we can make it a nop when secret, iter-time
> >                                 remain unchanged
> >                             we can allow unsafe update with force: true
> >
> > As struct:
> >
> >     { 'struct': 'LUKSKeyslotUpdate',
> >       'data': { 'active': 'bool',       # could do enum instead
> >                 'keyslot': 'int',
> >                 '*secret': 'str',       # present if @active is true
> >                 '*iter-time': 'int' } } # absent if @active is false
> >
> > As union:
> >
> >     { 'enum': 'LUKSKeyslotState',
> >       'data': [ 'active', 'inactive' ] }
> >     { 'struct': 'LUKSKeyslotActive',
> >       'data': { 'secret': 'str',
> >                 '*iter-time': 'int } }
> >     { 'union': 'LUKSKeyslotAmend',
> >       'base': { 'state': 'LUKSKeyslotState' }   # must do enum
> >       'discriminator': 'state',
> >       'data': { 'active': 'LUKSKeyslotActive' } }
> >
> > When we don't specify the slot#, then "new state active" selects an
> > inactive slot (chosen by the system, and "new state inactive selects
> > slots by secret (commonly just one slot).
> >
> > New state active:
> >
> >     state       new state   action
> >     inactive    active      put secret, iter-time, mark active
> >     active      active      N/A (system choses inactive slot)
> >
> > New state inactive, for each slot holding the specified secret:
> >
> >     state       new state   action
> >     inactive    inactive    N/A (inactive slot holds no secret)
> >     active      inactive    mark inactive (effectively deletes secret)
> >
> > As struct:
> >
> >     { 'struct': 'LUKSKeyslotUpdate',
> >       'data': { 'active': 'bool',       # could do enum instead
> >                 '*keyslot': 'int',
> >                 '*secret': 'str',       # present if @active is true
> >                 '*iter-time': 'int' } } # absent if @active is false
> >
> > As union:
> >
> >     { 'enum': 'LUKSKeyslotState',
> >       'data': [ 'active', 'inactive' ] }
> >     { 'struct': 'LUKSKeyslotActive',
> >       'data': { 'secret': 'str',
> >                 '*iter-time': 'int } }
> >     { 'union': 'LUKSKeyslotAmend',
> >       'base': { '*keyslot': 'int',
> >                 'state': 'LUKSKeyslotState' }
> >       'discriminator': 'state',
> >       'data': { 'active': 'LUKSKeyslotActive' } }
> >
> > Union looks more complicated because our union notation sucks[*].  I
> > like it anyway, because you don't have to explain when which optional
> > members aren't actually optional.
> >
> > Regardless of struct vs. union, this supports an active -> active
> > transition only with an explicit keyslot.  Feels fine to me.  If we want
> > to support it without keyslot as well, we need a way to specify both old
> > and new secret.  Do we?
> >
> >
> > [*] I hope to fix that one day.  It's not even hard.



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-05  9:30                       ` Kevin Wolf
@ 2020-02-05 10:03                         ` Markus Armbruster
  2020-02-05 11:02                           ` Kevin Wolf
  2020-02-05 10:23                         ` Daniel P. Berrangé
  1 sibling, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-05 10:03 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	Maxim Levitsky, John Snow

Kevin Wolf <kwolf@redhat.com> writes:

> Am 05.02.2020 um 09:24 hat Markus Armbruster geschrieben:
>> Daniel, Kevin, any comments or objections to the QAPI schema design
>> sketch developed below?
>> 
>> For your convenience, here's the result again:
>> 
>>     { 'enum': 'LUKSKeyslotState',
>>       'data': [ 'active', 'inactive' ] }
>>     { 'struct': 'LUKSKeyslotActive',
>>       'data': { 'secret': 'str',
>>                 '*iter-time': 'int } }
>>     { 'union': 'LUKSKeyslotAmend',
>>       'base': { '*keyslot': 'int',
>>                 'state': 'LUKSKeyslotState' }
>>       'discriminator': 'state',
>>       'data': { 'active': 'LUKSKeyslotActive' } }
>
> I think one of the requirements was that you can specify the keyslot not
> only by using its number, but also by specifying the old secret.

Quoting myself:

  When we don't specify the slot#, then "new state active" selects an
  inactive slot (chosen by the system, and "new state inactive selects
  slots by secret (commonly just one slot).

This takes care of selecting (active) slots by old secret with "new
state inactive".

I intentionally did not provide for selecting (active) slots by old
secret with "new state active", because that's unsafe update in place.

We want to update secrets, of course.  But the safe way to do that is to
put the new secret into a free slot, and if that succeeds, deactivate
the old secret.  If deactivation fails, you're left with both old and
new secret, which beats being left with no secret when update in place
fails.

>                                                                  Trivial
> extension, you just get another optional field that can be specified
> instead of 'keyslot'.
>
> Resulting commands:
>
>     Adding a key:
>     qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2

This activates an inactive slot chosen by the sysem.

You can activate a specific keyslot N by throwing in
encrypt.keys.0.keyslot=N.

>     Deleting a key:
>     qemu-img amend -o encrypt.keys.0.state=inactive,encrypt.keys.0.keyslot=2 test.qcow2

This deactivates keyslot#2.

You can deactivate slots holding a specific secret S by replacing
encrypt.keys.0.keyslot=2 by encrypt.keys.0.secret=S.

> Previous version (if this series is applied unchanged):
>
>     Adding a key:
>     qemu-img amend -o encrypt.keys.0.new-secret=sec0 test.qcow2
>
>     Deleting a key:
>     qemu-img amend -o encrypt.keys.0.new-secret=,encrypt.keys.0.keyslot=2 test.qcow2
>
> Adding a key gets more complicated with your proposed interface because
> state must be set explicitly now whereas before it was derived
> automatically from the fact that if you give a key, only active makes
> sense.

The explicitness could be viewed as an improvement :)

If you'd prefer implicit here: Max has patches for making union tags
optional with a default.  They'd let you default active to true.

> Deleting stays more or less the same, you just change the state instead
> of clearing the secret.

Thanks for your input!



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-05  9:30                       ` Kevin Wolf
  2020-02-05 10:03                         ` Markus Armbruster
@ 2020-02-05 10:23                         ` Daniel P. Berrangé
  2020-02-05 14:31                           ` Markus Armbruster
  1 sibling, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-02-05 10:23 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-block, qemu-devel, Markus Armbruster, Maxim Levitsky,
	Max Reitz, John Snow

On Wed, Feb 05, 2020 at 10:30:11AM +0100, Kevin Wolf wrote:
> Am 05.02.2020 um 09:24 hat Markus Armbruster geschrieben:
> > Daniel, Kevin, any comments or objections to the QAPI schema design
> > sketch developed below?
> > 
> > For your convenience, here's the result again:
> > 
> >     { 'enum': 'LUKSKeyslotState',
> >       'data': [ 'active', 'inactive' ] }
> >     { 'struct': 'LUKSKeyslotActive',
> >       'data': { 'secret': 'str',
> >                 '*iter-time': 'int } }
> >     { 'union': 'LUKSKeyslotAmend',
> >       'base': { '*keyslot': 'int',
> >                 'state': 'LUKSKeyslotState' }
> >       'discriminator': 'state',
> >       'data': { 'active': 'LUKSKeyslotActive' } }

We need 'secret' in the 'inactive' case too

> 
> I think one of the requirements was that you can specify the keyslot not
> only by using its number, but also by specifying the old secret. Trivial
> extension, you just get another optional field that can be specified
> instead of 'keyslot'.
> 
> Resulting commands:
> 
>     Adding a key:
>     qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2
> 
>     Deleting a key:
>     qemu-img amend -o encrypt.keys.0.state=inactive,encrypt.keys.0.keyslot=2 test.qcow2

I think this is good as a design.

Expanding the examples to cover all scenarios we've discussed


  - Activating a new keyslot, auto-picking slot

     qemu-img amend -o encrypt.keys.0.state=active,\
                       encrypt.keys.0.secret=sec0 \
		    test.qcow2

    Must raise an error if no free slots


  - Activating a new keyslot, picking a specific slot

     qemu-img amend -o encrypt.keys.0.state=active,\
                       encrypt.keys.0.secret=sec0 \
		       encrypt.keys.0.keyslot=3 \
		    test.qcow2

    Must raise an error if slot is already active


  - Deactivating a old keyslot, auto-picking slot(s) from existing password

     qemu-img amend -o encrypt.keys.0.state=inactive,\
                       encrypt.keys.0.secret=sec0 \
		    test.qcow2

    Must raise an error if this would leave zero keyslots
    after processing.


  - Deactivating a old keyslot, picking a specific slot

     qemu-img amend -o encrypt.keys.0.state=inactive,\
                       encrypt.keys.0.keyslot=2 \
		    test.qcow2

    Always succeeds even if zero keyslots left.

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-05 10:03                         ` Markus Armbruster
@ 2020-02-05 11:02                           ` Kevin Wolf
  2020-02-05 14:31                             ` Markus Armbruster
  0 siblings, 1 reply; 84+ messages in thread
From: Kevin Wolf @ 2020-02-05 11:02 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Max Reitz, Maxim Levitsky, John Snow

Am 05.02.2020 um 11:03 hat Markus Armbruster geschrieben:
> Kevin Wolf <kwolf@redhat.com> writes:
> 
> > Am 05.02.2020 um 09:24 hat Markus Armbruster geschrieben:
> >> Daniel, Kevin, any comments or objections to the QAPI schema design
> >> sketch developed below?
> >> 
> >> For your convenience, here's the result again:
> >> 
> >>     { 'enum': 'LUKSKeyslotState',
> >>       'data': [ 'active', 'inactive' ] }
> >>     { 'struct': 'LUKSKeyslotActive',
> >>       'data': { 'secret': 'str',
> >>                 '*iter-time': 'int } }
> >>     { 'union': 'LUKSKeyslotAmend',
> >>       'base': { '*keyslot': 'int',
> >>                 'state': 'LUKSKeyslotState' }
> >>       'discriminator': 'state',
> >>       'data': { 'active': 'LUKSKeyslotActive' } }
> >
> > I think one of the requirements was that you can specify the keyslot not
> > only by using its number, but also by specifying the old secret.
> 
> Quoting myself:
> 
>   When we don't specify the slot#, then "new state active" selects an
>   inactive slot (chosen by the system, and "new state inactive selects
>   slots by secret (commonly just one slot).
> 
> This takes care of selecting (active) slots by old secret with "new
> state inactive".

"new secret inactive" can't select a slot by secret because 'secret'
doesn't even exist for inactive.

> I intentionally did not provide for selecting (active) slots by old
> secret with "new state active", because that's unsafe update in place.
> 
> We want to update secrets, of course.  But the safe way to do that is to
> put the new secret into a free slot, and if that succeeds, deactivate
> the old secret.  If deactivation fails, you're left with both old and
> new secret, which beats being left with no secret when update in place
> fails.

Right. I wonder if qemu-img wants support for that specifically
(possibly with allowing to enter the key interactively) rather than
requiring the user to call qemu-img amend twice.

> >                                                                  Trivial
> > extension, you just get another optional field that can be specified
> > instead of 'keyslot'.
> >
> > Resulting commands:
> >
> >     Adding a key:
> >     qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2
> 
> This activates an inactive slot chosen by the sysem.
> 
> You can activate a specific keyslot N by throwing in
> encrypt.keys.0.keyslot=N.

Yes. The usual case is that you just want to add a new key somwhere.

> >     Deleting a key:
> >     qemu-img amend -o encrypt.keys.0.state=inactive,encrypt.keys.0.keyslot=2 test.qcow2
> 
> This deactivates keyslot#2.
> 
> You can deactivate slots holding a specific secret S by replacing
> encrypt.keys.0.keyslot=2 by encrypt.keys.0.secret=S.

Not with your definition above, but with the appropriate changes, this
makes sense.

> > Previous version (if this series is applied unchanged):
> >
> >     Adding a key:
> >     qemu-img amend -o encrypt.keys.0.new-secret=sec0 test.qcow2
> >
> >     Deleting a key:
> >     qemu-img amend -o encrypt.keys.0.new-secret=,encrypt.keys.0.keyslot=2 test.qcow2
> >
> > Adding a key gets more complicated with your proposed interface because
> > state must be set explicitly now whereas before it was derived
> > automatically from the fact that if you give a key, only active makes
> > sense.
> 
> The explicitness could be viewed as an improvement :)

Not really. I mean, I really know to appreciate the advantages of
-blockdev where needed, but usually I don't want to type all that stuff
for the most common tasks. qemu-img amend is similar.

For deleting, I might actually agree that explicitness is an
improvement, but for creating it's just unnecessary verbosity.

> If you'd prefer implicit here: Max has patches for making union tags
> optional with a default.  They'd let you default active to true.

I guess this would improve the usability in this case.

Kevin



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-05 11:02                           ` Kevin Wolf
@ 2020-02-05 14:31                             ` Markus Armbruster
  2020-02-06 13:44                               ` Markus Armbruster
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-05 14:31 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

Kevin Wolf <kwolf@redhat.com> writes:

> Am 05.02.2020 um 11:03 hat Markus Armbruster geschrieben:
>> Kevin Wolf <kwolf@redhat.com> writes:
>> 
>> > Am 05.02.2020 um 09:24 hat Markus Armbruster geschrieben:
>> >> Daniel, Kevin, any comments or objections to the QAPI schema design
>> >> sketch developed below?
>> >> 
>> >> For your convenience, here's the result again:
>> >> 
>> >>     { 'enum': 'LUKSKeyslotState',
>> >>       'data': [ 'active', 'inactive' ] }
>> >>     { 'struct': 'LUKSKeyslotActive',
>> >>       'data': { 'secret': 'str',
>> >>                 '*iter-time': 'int } }
>> >>     { 'union': 'LUKSKeyslotAmend',
>> >>       'base': { '*keyslot': 'int',
>> >>                 'state': 'LUKSKeyslotState' }
>> >>       'discriminator': 'state',
>> >>       'data': { 'active': 'LUKSKeyslotActive' } }
>> >
>> > I think one of the requirements was that you can specify the keyslot not
>> > only by using its number, but also by specifying the old secret.
>> 
>> Quoting myself:
>> 
>>   When we don't specify the slot#, then "new state active" selects an
>>   inactive slot (chosen by the system, and "new state inactive selects
>>   slots by secret (commonly just one slot).
>> 
>> This takes care of selecting (active) slots by old secret with "new
>> state inactive".
>
> "new secret inactive" can't select a slot by secret because 'secret'
> doesn't even exist for inactive.

My mistake.  My text leading up to my schema has it, but the schema
itself doesn't.  Obvious fix:

As struct:

    { 'struct': 'LUKSKeyslotUpdate',
      'data': { 'active': 'bool',       # could do enum instead
                '*keyslot': 'int',
                '*secret': 'str',       # present if @active is true
                                        #   or @keyslot is absent
                '*iter-time': 'int' } } # absent if @active is false

As union:

    { 'enum': 'LUKSKeyslotState',
      'data': [ 'active', 'inactive' ] }
    { 'struct': 'LUKSKeyslotActive',
      'data': { 'secret': 'str',
                '*iter-time': 'int } }
    { 'struct': 'LUKSKeyslotInactive',
      'data': { '*secret': 'str' } }    # either @secret or @keyslot present
                                        # might want to name this @old-secret
    { 'union': 'LUKSKeyslotAmend',
      'base': { '*keyslot': 'int',
                'state': 'LUKSKeyslotState' }
      'discriminator': 'state',
      'data': { 'active': 'LUKSKeyslotActive',
                'inactive': 'LUKSKeyslotInactive' } }

The "deactivate secret" operation needs a bit of force to fit into the
amend interface's "describe desired state" mold: the desired state is
(state=inactive, secret=S).  In other words, the inactive slot keeps its
secret, you just can't use it for anything.

Sadly, even with a union, we now have optional members that aren't
really optional: "either @secret or @keyslot present".  To avoid that,
we'd have to come up with sane semantics for "neither" and "both".  Let
me try.

The basic idea is to have @keyslot and @secret each select a set of
slots, and take the intersection.

If @keyslot is present: { @keyslot }
               absent: all slots
If @secret is present: the set of slots holding @secret
              absent: all slots

Neither present: select all slots.
Both present: slot @keyslot if it holds @secret, else no slots

The ability to specify @keyslot and @secret might actually be
appreciated by some users.  Belt *and* suspenders.

Selecting no slots could be a no-op or an error.  As a user, I don't
care as long as I can tell what the command actually changed.

Selecting all slots is an error because deactivating the last slot is.
No different from selecting all slots with a particular secret when no
active slots with different secrets exist.

I'm not sure this is much of an improvement.

>> I intentionally did not provide for selecting (active) slots by old
>> secret with "new state active", because that's unsafe update in place.
>> 
>> We want to update secrets, of course.  But the safe way to do that is to
>> put the new secret into a free slot, and if that succeeds, deactivate
>> the old secret.  If deactivation fails, you're left with both old and
>> new secret, which beats being left with no secret when update in place
>> fails.
>
> Right. I wonder if qemu-img wants support for that specifically
> (possibly with allowing to enter the key interactively) rather than
> requiring the user to call qemu-img amend twice.

Human users may well appreciate such a "replace secret" operation.  As
so often with high-level operations, the difficulty is its failure
modes:

* Activation fails: no change (old secret still active)

* Deactivate fails: both secrets are active

Humans should be able to deal with both failure modes, provided the
error reporting is sane.

If I'd have to program a machine, however, I'd rather use the primitive
operations, because each either succeeds completely or fails completely,
which means I don't have to figure out *how* something failed.

Note that such a high-level "replace secret" doesn't quite fit into the
amend interface's "describe desired state" mold: the old secret is not
part of the desired state.

>> >                                                                  Trivial
>> > extension, you just get another optional field that can be specified
>> > instead of 'keyslot'.
>> >
>> > Resulting commands:
>> >
>> >     Adding a key:
>> >     qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2
>> 
>> This activates an inactive slot chosen by the sysem.
>> 
>> You can activate a specific keyslot N by throwing in
>> encrypt.keys.0.keyslot=N.
>
> Yes. The usual case is that you just want to add a new key somwhere.

Sure.

>> >     Deleting a key:
>> >     qemu-img amend -o encrypt.keys.0.state=inactive,encrypt.keys.0.keyslot=2 test.qcow2
>> 
>> This deactivates keyslot#2.
>> 
>> You can deactivate slots holding a specific secret S by replacing
>> encrypt.keys.0.keyslot=2 by encrypt.keys.0.secret=S.
>
> Not with your definition above, but with the appropriate changes, this
> makes sense.

Appropriately corrected, I hope.

>> > Previous version (if this series is applied unchanged):
>> >
>> >     Adding a key:
>> >     qemu-img amend -o encrypt.keys.0.new-secret=sec0 test.qcow2
>> >
>> >     Deleting a key:
>> >     qemu-img amend -o encrypt.keys.0.new-secret=,encrypt.keys.0.keyslot=2 test.qcow2
>> >
>> > Adding a key gets more complicated with your proposed interface because
>> > state must be set explicitly now whereas before it was derived
>> > automatically from the fact that if you give a key, only active makes
>> > sense.
>> 
>> The explicitness could be viewed as an improvement :)
>
> Not really. I mean, I really know to appreciate the advantages of
> -blockdev where needed, but usually I don't want to type all that stuff
> for the most common tasks. qemu-img amend is similar.
>
> For deleting, I might actually agree that explicitness is an
> improvement, but for creating it's just unnecessary verbosity.
>
>> If you'd prefer implicit here: Max has patches for making union tags
>> optional with a default.  They'd let you default active to true.
>
> I guess this would improve the usability in this case.
>
> Kevin



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-05 10:23                         ` Daniel P. Berrangé
@ 2020-02-05 14:31                           ` Markus Armbruster
  2020-02-06 13:20                             ` Markus Armbruster
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-05 14:31 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, Maxim Levitsky, John Snow

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Wed, Feb 05, 2020 at 10:30:11AM +0100, Kevin Wolf wrote:
>> Am 05.02.2020 um 09:24 hat Markus Armbruster geschrieben:
>> > Daniel, Kevin, any comments or objections to the QAPI schema design
>> > sketch developed below?
>> > 
>> > For your convenience, here's the result again:
>> > 
>> >     { 'enum': 'LUKSKeyslotState',
>> >       'data': [ 'active', 'inactive' ] }
>> >     { 'struct': 'LUKSKeyslotActive',
>> >       'data': { 'secret': 'str',
>> >                 '*iter-time': 'int } }
>> >     { 'union': 'LUKSKeyslotAmend',
>> >       'base': { '*keyslot': 'int',
>> >                 'state': 'LUKSKeyslotState' }
>> >       'discriminator': 'state',
>> >       'data': { 'active': 'LUKSKeyslotActive' } }
>
> We need 'secret' in the 'inactive' case too

Yes, my mistake.

>> I think one of the requirements was that you can specify the keyslot not
>> only by using its number, but also by specifying the old secret. Trivial
>> extension, you just get another optional field that can be specified
>> instead of 'keyslot'.
>> 
>> Resulting commands:
>> 
>>     Adding a key:
>>     qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2
>> 
>>     Deleting a key:
>>     qemu-img amend -o encrypt.keys.0.state=inactive,encrypt.keys.0.keyslot=2 test.qcow2
>
> I think this is good as a design.
>
> Expanding the examples to cover all scenarios we've discussed
>
>
>   - Activating a new keyslot, auto-picking slot
>
>      qemu-img amend -o encrypt.keys.0.state=active,\
>                        encrypt.keys.0.secret=sec0 \
> 		    test.qcow2
>
>     Must raise an error if no free slots
>
>
>   - Activating a new keyslot, picking a specific slot
>
>      qemu-img amend -o encrypt.keys.0.state=active,\
>                        encrypt.keys.0.secret=sec0 \
> 		       encrypt.keys.0.keyslot=3 \
> 		    test.qcow2
>
>     Must raise an error if slot is already active

From the "describe desired state" point of view:

* Always suceeds when slot is inactive

* No-op when active and its secret is already the desired secret

* Must raise "in place update refused" error otherwise

>   - Deactivating a old keyslot, auto-picking slot(s) from existing password
>
>      qemu-img amend -o encrypt.keys.0.state=inactive,\
>                        encrypt.keys.0.secret=sec0 \
> 		    test.qcow2
>
>     Must raise an error if this would leave zero keyslots
>     after processing.
>
>
>   - Deactivating a old keyslot, picking a specific slot
>
>      qemu-img amend -o encrypt.keys.0.state=inactive,\
>                        encrypt.keys.0.keyslot=2 \
> 		    test.qcow2
>
>     Always succeeds even if zero keyslots left.

This one's dangerous.

Here's a variation: permit operations that may or will lose data only
with 'force': true.

When @keyslot is absent, using force has no effect.

When @keyslot is present, using force permits update in place and
deactivating the last slot.



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-05 14:31                           ` Markus Armbruster
@ 2020-02-06 13:20                             ` Markus Armbruster
  2020-02-06 13:36                               ` Daniel P. Berrangé
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-06 13:20 UTC (permalink / raw)
  To: Daniel P. Berrangé, Kevin Wolf
  Cc: Max Reitz, John Snow, qemu-devel, qemu-block, Maxim Levitsky

One more question regarding the array in

    { 'struct': 'QCryptoBlockAmendOptionsLUKS',
      'data' : {
                'keys': ['LUKSKeyslotUpdate'],
                 '*unlock-secret' : 'str' } }

Why an array?  Do we really need multiple keyslot updates in one amend
operation?



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-06 13:20                             ` Markus Armbruster
@ 2020-02-06 13:36                               ` Daniel P. Berrangé
  2020-02-06 14:25                                 ` Kevin Wolf
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-02-06 13:36 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, Maxim Levitsky, John Snow

On Thu, Feb 06, 2020 at 02:20:11PM +0100, Markus Armbruster wrote:
> One more question regarding the array in
> 
>     { 'struct': 'QCryptoBlockAmendOptionsLUKS',
>       'data' : {
>                 'keys': ['LUKSKeyslotUpdate'],
>                  '*unlock-secret' : 'str' } }
> 
> Why an array?  Do we really need multiple keyslot updates in one amend
> operation?

I think it it is unlikely we'd use this in libvirt. In the case of wanting
to *change* a key, it is safer to do a sequence of "add key" and then
"remove key". If you combine them into the same operation, and you get
an error back, it is hard to know /where/ it failed ? was the new key
added or not ?

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-05 14:31                             ` Markus Armbruster
@ 2020-02-06 13:44                               ` Markus Armbruster
  2020-02-06 13:49                                 ` Daniel P. Berrangé
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-06 13:44 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

Markus Armbruster <armbru@redhat.com> writes:

> Kevin Wolf <kwolf@redhat.com> writes:
>
>> Am 05.02.2020 um 11:03 hat Markus Armbruster geschrieben:
>>> Kevin Wolf <kwolf@redhat.com> writes:
[...]
>>> > Adding a key gets more complicated with your proposed interface because
>>> > state must be set explicitly now whereas before it was derived
>>> > automatically from the fact that if you give a key, only active makes
>>> > sense.
>>> 
>>> The explicitness could be viewed as an improvement :)
>>
>> Not really. I mean, I really know to appreciate the advantages of
>> -blockdev where needed, but usually I don't want to type all that stuff
>> for the most common tasks. qemu-img amend is similar.
>>
>> For deleting, I might actually agree that explicitness is an
>> improvement, but for creating it's just unnecessary verbosity.
>>
>>> If you'd prefer implicit here: Max has patches for making union tags
>>> optional with a default.  They'd let you default active to true.
>>
>> I guess this would improve the usability in this case.

Thinking and writing in the "Making QEMU easier for management tools and
applications" monster thread have made me realize we're mixing up two
aspects that ought to be kept separate: machine-friendly QMP and
human-friendly CLI.

You argue that

    $ qemu-img amend -o encrypt.keys.0.new-secret=sec0 test.qcow2

is nicer than

    $ qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2

and you do have a point: humans want their CLI terse.  Redundancy is
unwanted, except perhaps to protect users from dangerous accidents.  In
this example, state=active is redundant when a secret is given, because
anything else would be an error.

In QMP, however, we like things simple and explicit, and we eschew
magic.

This particular magic might just be simple enough to be acceptable in
QMP.  We'd "merely" have to support explicit defaults in the schema (a
clear improvement if you ask me), and optional union tags (tolerable as
long as the default comes from the schema, I guess).

My point is: QAPI schema design *must* focus on QMP and nothing else.
If we try to serve both QMP and human-friendly CLI, we'll likely botch
both.

I believe a truly human-friendly CLI requires more than just
human-friendly concrete syntax for QMP.  Same as HMP, really.



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-06 13:44                               ` Markus Armbruster
@ 2020-02-06 13:49                                 ` Daniel P. Berrangé
  2020-02-06 14:20                                   ` Max Reitz
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-02-06 13:49 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

On Thu, Feb 06, 2020 at 02:44:45PM +0100, Markus Armbruster wrote:
> Markus Armbruster <armbru@redhat.com> writes:
> 
> > Kevin Wolf <kwolf@redhat.com> writes:
> >
> >> Am 05.02.2020 um 11:03 hat Markus Armbruster geschrieben:
> >>> Kevin Wolf <kwolf@redhat.com> writes:
> [...]
> >>> > Adding a key gets more complicated with your proposed interface because
> >>> > state must be set explicitly now whereas before it was derived
> >>> > automatically from the fact that if you give a key, only active makes
> >>> > sense.
> >>> 
> >>> The explicitness could be viewed as an improvement :)
> >>
> >> Not really. I mean, I really know to appreciate the advantages of
> >> -blockdev where needed, but usually I don't want to type all that stuff
> >> for the most common tasks. qemu-img amend is similar.
> >>
> >> For deleting, I might actually agree that explicitness is an
> >> improvement, but for creating it's just unnecessary verbosity.
> >>
> >>> If you'd prefer implicit here: Max has patches for making union tags
> >>> optional with a default.  They'd let you default active to true.
> >>
> >> I guess this would improve the usability in this case.
> 
> Thinking and writing in the "Making QEMU easier for management tools and
> applications" monster thread have made me realize we're mixing up two
> aspects that ought to be kept separate: machine-friendly QMP and
> human-friendly CLI.
> 
> You argue that
> 
>     $ qemu-img amend -o encrypt.keys.0.new-secret=sec0 test.qcow2
> 
> is nicer than
> 
>     $ qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2
> 
> and you do have a point: humans want their CLI terse.  Redundancy is
> unwanted, except perhaps to protect users from dangerous accidents.  In
> this example, state=active is redundant when a secret is given, because
> anything else would be an error.
> 
> In QMP, however, we like things simple and explicit, and we eschew
> magic.
> 
> This particular magic might just be simple enough to be acceptable in
> QMP.  We'd "merely" have to support explicit defaults in the schema (a
> clear improvement if you ask me), and optional union tags (tolerable as
> long as the default comes from the schema, I guess).
> 
> My point is: QAPI schema design *must* focus on QMP and nothing else.
> If we try to serve both QMP and human-friendly CLI, we'll likely botch
> both.
> 
> I believe a truly human-friendly CLI requires more than just
> human-friendly concrete syntax for QMP.  Same as HMP, really.

A human-friendly approach to this problem would never even
have the generic "amend" design IMHO. Friendly would be to
have a CLI that is approx the same as "cryptsetup" provides
eg

    $ qemu-img add-key /path/to/disk
    enter key>..
    re-enter key>...

or

    qemu-img add-key --keyfile /some/file.txt /path/to/disk


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-06 13:49                                 ` Daniel P. Berrangé
@ 2020-02-06 14:20                                   ` Max Reitz
  0 siblings, 0 replies; 84+ messages in thread
From: Max Reitz @ 2020-02-06 14:20 UTC (permalink / raw)
  To: Daniel P. Berrangé, Markus Armbruster
  Cc: Kevin Wolf, John Snow, qemu-devel, qemu-block, Maxim Levitsky


[-- Attachment #1.1: Type: text/plain, Size: 3675 bytes --]

On 06.02.20 14:49, Daniel P. Berrangé wrote:
> On Thu, Feb 06, 2020 at 02:44:45PM +0100, Markus Armbruster wrote:
>> Markus Armbruster <armbru@redhat.com> writes:
>>
>>> Kevin Wolf <kwolf@redhat.com> writes:
>>>
>>>> Am 05.02.2020 um 11:03 hat Markus Armbruster geschrieben:
>>>>> Kevin Wolf <kwolf@redhat.com> writes:
>> [...]
>>>>>> Adding a key gets more complicated with your proposed interface because
>>>>>> state must be set explicitly now whereas before it was derived
>>>>>> automatically from the fact that if you give a key, only active makes
>>>>>> sense.
>>>>>
>>>>> The explicitness could be viewed as an improvement :)
>>>>
>>>> Not really. I mean, I really know to appreciate the advantages of
>>>> -blockdev where needed, but usually I don't want to type all that stuff
>>>> for the most common tasks. qemu-img amend is similar.
>>>>
>>>> For deleting, I might actually agree that explicitness is an
>>>> improvement, but for creating it's just unnecessary verbosity.
>>>>
>>>>> If you'd prefer implicit here: Max has patches for making union tags
>>>>> optional with a default.  They'd let you default active to true.
>>>>
>>>> I guess this would improve the usability in this case.
>>
>> Thinking and writing in the "Making QEMU easier for management tools and
>> applications" monster thread have made me realize we're mixing up two
>> aspects that ought to be kept separate: machine-friendly QMP and
>> human-friendly CLI.
>>
>> You argue that
>>
>>     $ qemu-img amend -o encrypt.keys.0.new-secret=sec0 test.qcow2
>>
>> is nicer than
>>
>>     $ qemu-img amend -o encrypt.keys.0.state=active,encrypt.keys.0.secret=sec0 test.qcow2
>>
>> and you do have a point: humans want their CLI terse.  Redundancy is
>> unwanted, except perhaps to protect users from dangerous accidents.  In
>> this example, state=active is redundant when a secret is given, because
>> anything else would be an error.
>>
>> In QMP, however, we like things simple and explicit, and we eschew
>> magic.
>>
>> This particular magic might just be simple enough to be acceptable in
>> QMP.  We'd "merely" have to support explicit defaults in the schema (a
>> clear improvement if you ask me), and optional union tags (tolerable as
>> long as the default comes from the schema, I guess).
>>
>> My point is: QAPI schema design *must* focus on QMP and nothing else.
>> If we try to serve both QMP and human-friendly CLI, we'll likely botch
>> both.
>>
>> I believe a truly human-friendly CLI requires more than just
>> human-friendly concrete syntax for QMP.  Same as HMP, really.
> 
> A human-friendly approach to this problem would never even
> have the generic "amend" design IMHO. Friendly would be to
> have a CLI that is approx the same as "cryptsetup" provides
> eg
> 
>     $ qemu-img add-key /path/to/disk
>     enter key>..
>     re-enter key>...
> 
> or
> 
>     qemu-img add-key --keyfile /some/file.txt /path/to/disk

I have only scanned through the discussion up until this point, but I
agree that amend doesn’t need to be human-friendly at all cost.

If we really want a human-friendly keyslot modification interface, we
can always add a specific qemu-img subcommand that provides high-level
succinct operations based on a low-level and more verbose amend interface.

(Or just a script that isn’t even built into qemu-img, because I suppose
such a operation “translation” would be easier to implement in a
scripting language.  Maybe qemu-img could be extended to invoke external
scripts for specific subcommands?  But anyway, those would all be ideas
for the future.)

Max


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-06 13:36                               ` Daniel P. Berrangé
@ 2020-02-06 14:25                                 ` Kevin Wolf
  2020-02-06 15:19                                   ` Markus Armbruster
  0 siblings, 1 reply; 84+ messages in thread
From: Kevin Wolf @ 2020-02-06 14:25 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	Maxim Levitsky, John Snow

Am 06.02.2020 um 14:36 hat Daniel P. Berrangé geschrieben:
> On Thu, Feb 06, 2020 at 02:20:11PM +0100, Markus Armbruster wrote:
> > One more question regarding the array in
> > 
> >     { 'struct': 'QCryptoBlockAmendOptionsLUKS',
> >       'data' : {
> >                 'keys': ['LUKSKeyslotUpdate'],
> >                  '*unlock-secret' : 'str' } }
> > 
> > Why an array?  Do we really need multiple keyslot updates in one amend
> > operation?
> 
> I think it it is unlikely we'd use this in libvirt. In the case of wanting
> to *change* a key, it is safer to do a sequence of "add key" and then
> "remove key". If you combine them into the same operation, and you get
> an error back, it is hard to know /where/ it failed ? was the new key
> added or not ?

I think the array came in because of the "describe the new state"
approach. The state has eight keyslots, so in order to fully describe
the new state, you would have to be able to pass multiple slots at once.

Kevin



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-06 14:25                                 ` Kevin Wolf
@ 2020-02-06 15:19                                   ` Markus Armbruster
  2020-02-06 15:23                                     ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-06 15:19 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

Kevin Wolf <kwolf@redhat.com> writes:

> Am 06.02.2020 um 14:36 hat Daniel P. Berrangé geschrieben:
>> On Thu, Feb 06, 2020 at 02:20:11PM +0100, Markus Armbruster wrote:
>> > One more question regarding the array in
>> > 
>> >     { 'struct': 'QCryptoBlockAmendOptionsLUKS',
>> >       'data' : {
>> >                 'keys': ['LUKSKeyslotUpdate'],
>> >                  '*unlock-secret' : 'str' } }
>> > 
>> > Why an array?  Do we really need multiple keyslot updates in one amend
>> > operation?
>> 
>> I think it it is unlikely we'd use this in libvirt. In the case of wanting
>> to *change* a key, it is safer to do a sequence of "add key" and then
>> "remove key". If you combine them into the same operation, and you get
>> an error back, it is hard to know /where/ it failed ? was the new key
>> added or not ?
>
> I think the array came in because of the "describe the new state"
> approach. The state has eight keyslots, so in order to fully describe
> the new state, you would have to be able to pass multiple slots at once.

I see.

Of course, it can also describe multiple new states for the same slot.

Example:

    [{'state': 'active', 'keyslot': 0, 'secret': 'sec0'},
     {'state': 'active', 'keyslot': 0, 'secret': 'sec1'}]

    where slot 0's old state is 'inactive'.

Which one is the new state?

If we execute the array elements one by one, this first makes slot 0
active with secret 'sec0', then tries to make it active with secret
'sec1', which fails.  Simple enough, but it's not really "describe the
new state", it's still "specify a series of state transitions".

If we merge the array elements into a description of the new state of
all eight slots, where a slot's description can be "same as old state",
then this makes slot 0 active with either secret 'sec0' or 'sec1',
depending on how we resolve the conflict.  We could even make conflicts
an error, and then this would fail without changing anything.

What do we want?

Is this worth the trouble?



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: [PATCH 02/13] qcrypto-luks: implement encryption key management
  2020-02-06 15:19                                   ` Markus Armbruster
@ 2020-02-06 15:23                                     ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-02-06 15:23 UTC (permalink / raw)
  To: Markus Armbruster, Kevin Wolf
  Cc: John Snow, Daniel P.Berrangé, qemu-devel, qemu-block, Max Reitz

On Thu, 2020-02-06 at 16:19 +0100, Markus Armbruster wrote:
> Kevin Wolf <kwolf@redhat.com> writes:
> 
> > Am 06.02.2020 um 14:36 hat Daniel P. Berrangé geschrieben:
> > > On Thu, Feb 06, 2020 at 02:20:11PM +0100, Markus Armbruster wrote:
> > > > One more question regarding the array in
> > > > 
> > > >     { 'struct': 'QCryptoBlockAmendOptionsLUKS',
> > > >       'data' : {
> > > >                 'keys': ['LUKSKeyslotUpdate'],
> > > >                  '*unlock-secret' : 'str' } }
> > > > 
> > > > Why an array?  Do we really need multiple keyslot updates in one amend
> > > > operation?
> > > 
> > > I think it it is unlikely we'd use this in libvirt. In the case of wanting
> > > to *change* a key, it is safer to do a sequence of "add key" and then
> > > "remove key". If you combine them into the same operation, and you get
> > > an error back, it is hard to know /where/ it failed ? was the new key
> > > added or not ?
> > 
> > I think the array came in because of the "describe the new state"
> > approach. The state has eight keyslots, so in order to fully describe
> > the new state, you would have to be able to pass multiple slots at once.
> 
> I see.
> 
> Of course, it can also describe multiple new states for the same slot.
> 
> Example:
> 
>     [{'state': 'active', 'keyslot': 0, 'secret': 'sec0'},
>      {'state': 'active', 'keyslot': 0, 'secret': 'sec1'}]
> 
>     where slot 0's old state is 'inactive'.
> 
> Which one is the new state?
> 
> If we execute the array elements one by one, this first makes slot 0
> active with secret 'sec0', then tries to make it active with secret
> 'sec1', which fails.  Simple enough, but it's not really "describe the
> new state", it's still "specify a series of state transitions".
> 
> If we merge the array elements into a description of the new state of
> all eight slots, where a slot's description can be "same as old state",
> then this makes slot 0 active with either secret 'sec0' or 'sec1',
> depending on how we resolve the conflict.  We could even make conflicts
> an error, and then this would fail without changing anything.
> 
> What do we want?
> 
> Is this worth the trouble?

Yes, that is my thoughts on this as well.

Best regards,
	Maxim Levitsky




^ permalink raw reply	[flat|nested] 84+ messages in thread

* QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-01-14 19:33 ` [PATCH 02/13] qcrypto-luks: implement encryption key management Maxim Levitsky
  2020-01-21  7:54   ` Markus Armbruster
  2020-01-28 17:21   ` Daniel P. Berrangé
@ 2020-02-15 14:51   ` Markus Armbruster
  2020-02-16  8:05     ` Maxim Levitsky
                       ` (4 more replies)
  2 siblings, 5 replies; 84+ messages in thread
From: Markus Armbruster @ 2020-02-15 14:51 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

Review of this patch led to a lengthy QAPI schema design discussion.
Let me try to condense it into a concrete proposal.

This is about the QAPI schema, and therefore about QMP.  The
human-friendly interface is out of scope.  Not because it's not
important (it clearly is!), only because we need to *focus* to have a
chance at success.

I'm going to include a few design options.  I'll mark them "Option:".

The proposed "amend" interface takes a specification of desired state,
and figures out how to get from here to there by itself.  LUKS keyslots
are one part of desired state.

We commonly have eight LUKS keyslots.  Each keyslot is either active or
inactive.  An active keyslot holds a secret.

Goal: a QAPI type for specifying desired state of LUKS keyslots.

Proposal:

    { 'enum': 'LUKSKeyslotState',
      'data': [ 'active', 'inactive' ] }

    { 'struct': 'LUKSKeyslotActive',
      'data': { 'secret': 'str',
                '*iter-time': 'int } }

    { 'struct': 'LUKSKeyslotInactive',
      'data': { '*old-secret': 'str' } }

    { 'union': 'LUKSKeyslotAmend',
      'base': { '*keyslot': 'int',
                'state': 'LUKSKeyslotState' }
      'discriminator': 'state',
      'data': { 'active': 'LUKSKeyslotActive',
                'inactive': 'LUKSKeyslotInactive' } }

LUKSKeyslotAmend specifies desired state for a set of keyslots.

Four cases:

* @state is "active"

  Desired state is active holding the secret given by @secret.  Optional
  @iter-time tweaks key stretching.

  The keyslot is chosen either by the user or by the system, as follows:

  - @keyslot absent

    One inactive keyslot chosen by the system.  If none exists, error.

  - @keyslot present

    The keyslot given by @keyslot.

    If it's already active holding @secret, no-op.  Rationale: the
    current state is the desired state.

    If it's already active holding another secret, error.  Rationale:
    update in place is unsafe.

    Option: delete the "already active holding @secret" case.  Feels
    inelegant to me.  Okay if it makes things substantially simpler.

* @state is "inactive"

  Desired state is inactive.

  Error if the current state has active keyslots, but the desired state
  has none.

  The user choses the keyslot by number and/or by the secret it holds,
  as follows:

  - @keyslot absent, @old-secret present

    All active keyslots holding @old-secret.  If none exists, error.

  - @keyslot present, @old-secret absent

    The keyslot given by @keyslot.

    If it's already inactive, no-op.  Rationale: the current state is
    the desired state.

  - both @keyslot and @old-secret present

    The keyslot given by keyslot.

    If it's inactive or holds a secret other than @old-secret, error.

    Option: error regardless of @old-secret, if that makes things
    simpler.

  - neither @keyslot not @old-secret present

    All keyslots.  Note that this will error out due to "desired state
    has no active keyslots" unless the current state has none, either.

    Option: error out unconditionally.

Note that LUKSKeyslotAmend can specify only one desired state for
commonly just one keyslot.  Rationale: this satisfies practical needs.
An array of LUKSKeyslotAmend could specify desired state for all
keyslots.  However, multiple array elements could then apply to the same
slot.  We'd have to specify how to resolve such conflicts, and we'd have
to code up conflict detection.  Not worth it.

Examples:

* Add a secret to some free keyslot:

  { "state": "active", "secret": "CIA/GRU/MI6" }

* Deactivate all keyslots holding a secret:

  { "state": "inactive", "old-secret": "CIA/GRU/MI6" }

* Add a secret to a specific keyslot:

  { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }

* Deactivate a specific keyslot:

  { "state": "inactive", "keyslot": 0 }

  Possibly less dangerous:

  { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }

Option: Make use of Max's patches to support optional union tag with
default value to let us default @state to "active".  I doubt this makes
much of a difference in QMP.  A human-friendly interface should probably
be higher level anyway (Daniel pointed to cryptsetup).

Option: LUKSKeyslotInactive member @old-secret could also be named
@secret.  I don't care.

Option: delete @keyslot.  It provides low-level slot access.
Complicates the interface.  Fine if we need lov-level slot access.  Do
we?

I apologize for the time it has taken me to write this.

Comments?



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-02-15 14:51   ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Markus Armbruster
@ 2020-02-16  8:05     ` Maxim Levitsky
  2020-02-17  6:45       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
  2020-02-17 10:37     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Kevin Wolf
                       ` (3 subsequent siblings)
  4 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-02-16  8:05 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Daniel P.Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

On Sat, 2020-02-15 at 15:51 +0100, Markus Armbruster wrote:
> Review of this patch led to a lengthy QAPI schema design discussion.
> Let me try to condense it into a concrete proposal.
> 
> This is about the QAPI schema, and therefore about QMP.  The
> human-friendly interface is out of scope.  Not because it's not
> important (it clearly is!), only because we need to *focus* to have a
> chance at success.
100% agree.
> 
> I'm going to include a few design options.  I'll mark them "Option:".
> 
> The proposed "amend" interface takes a specification of desired state,
> and figures out how to get from here to there by itself.  LUKS keyslots
> are one part of desired state.
> 
> We commonly have eight LUKS keyslots.  Each keyslot is either active or
> inactive.  An active keyslot holds a secret.
> 
> Goal: a QAPI type for specifying desired state of LUKS keyslots.
> 
> Proposal:
> 
>     { 'enum': 'LUKSKeyslotState',
>       'data': [ 'active', 'inactive' ] }
> 
>     { 'struct': 'LUKSKeyslotActive',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int } }
> 
>     { 'struct': 'LUKSKeyslotInactive',
>       'data': { '*old-secret': 'str' } }
> 
>     { 'union': 'LUKSKeyslotAmend',
>       'base': { '*keyslot': 'int',
>                 'state': 'LUKSKeyslotState' }
>       'discriminator': 'state',
>       'data': { 'active': 'LUKSKeyslotActive',
>                 'inactive': 'LUKSKeyslotInactive' } }
> 
> LUKSKeyslotAmend specifies desired state for a set of keyslots.
> 
> Four cases:
> 
> * @state is "active"
> 
>   Desired state is active holding the secret given by @secret.  Optional
>   @iter-time tweaks key stretching.
> 
>   The keyslot is chosen either by the user or by the system, as follows:
> 
>   - @keyslot absent
> 
>     One inactive keyslot chosen by the system.  If none exists, error.
> 
>   - @keyslot present
> 
>     The keyslot given by @keyslot.
> 
>     If it's already active holding @secret, no-op.  Rationale: the
>     current state is the desired state.
> 
>     If it's already active holding another secret, error.  Rationale:
>     update in place is unsafe.
> 
>     Option: delete the "already active holding @secret" case.  Feels
>     inelegant to me.  Okay if it makes things substantially simpler.
I didn't really understand this, since in state=active we shouldn't
delete anything. Looks OK otherwise.

> 
> * @state is "inactive"
> 
>   Desired state is inactive.
> 
>   Error if the current state has active keyslots, but the desired state
>   has none.
> 
>   The user choses the keyslot by number and/or by the secret it holds,
>   as follows:
> 
>   - @keyslot absent, @old-secret present
> 
>     All active keyslots holding @old-secret.  If none exists, error.
> 
>   - @keyslot present, @old-secret absent
> 
>     The keyslot given by @keyslot.
> 
>     If it's already inactive, no-op.  Rationale: the current state is
>     the desired state.
> 
>   - both @keyslot and @old-secret present
> 
>     The keyslot given by keyslot.
> 
>     If it's inactive or holds a secret other than @old-secret, error.
Yea, that would be very nice to have.
> 
>     Option: error regardless of @old-secret, if that makes things
>     simpler.
> 
>   - neither @keyslot not @old-secret present
> 
>     All keyslots.  Note that this will error out due to "desired state
>     has no active keyslots" unless the current state has none, either.
> 
>     Option: error out unconditionally.
Yep, that the best IMHO.
> 
> Note that LUKSKeyslotAmend can specify only one desired state for
> commonly just one keyslot.  Rationale: this satisfies practical needs.
> An array of LUKSKeyslotAmend could specify desired state for all
> keyslots.  However, multiple array elements could then apply to the same
> slot.  We'd have to specify how to resolve such conflicts, and we'd have
> to code up conflict detection.  Not worth it.
110% agree (that is not a typo :-) )
> 
> Examples:
> 
> * Add a secret to some free keyslot:
> 
>   { "state": "active", "secret": "CIA/GRU/MI6" }
> 
> * Deactivate all keyslots holding a secret:
> 
>   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
> 
> * Add a secret to a specific keyslot:
> 
>   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
> 
> * Deactivate a specific keyslot:
> 
>   { "state": "inactive", "keyslot": 0 }
> 
>   Possibly less dangerous:
> 
>   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
> 
> Option: Make use of Max's patches to support optional union tag with
> default value to let us default @state to "active".  I doubt this makes
> much of a difference in QMP.  A human-friendly interface should probably
> be higher level anyway (Daniel pointed to cryptsetup).
Also agree.
> 
> Option: LUKSKeyslotInactive member @old-secret could also be named
> @secret.  I don't care.
I prefer old-secret.
> 
> Option: delete @keyslot.  It provides low-level slot access.
> Complicates the interface.  Fine if we need lov-level slot access.  Do
> we?
I don't have strong opinion on that. I'll probably would like to keep
this for tests/debugging/etc.

> 
> I apologize for the time it has taken me to write this.
Thank you very much for doing this.

> 
> Comments?

Looks good to me.

Best regards,
	Maxim Levitsky




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-16  8:05     ` Maxim Levitsky
@ 2020-02-17  6:45       ` Markus Armbruster
  2020-02-17  8:19         ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-17  6:45 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, Daniel P.Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

Maxim Levitsky <mlevitsk@redhat.com> writes:

> On Sat, 2020-02-15 at 15:51 +0100, Markus Armbruster wrote:
>> Review of this patch led to a lengthy QAPI schema design discussion.
>> Let me try to condense it into a concrete proposal.
>> 
>> This is about the QAPI schema, and therefore about QMP.  The
>> human-friendly interface is out of scope.  Not because it's not
>> important (it clearly is!), only because we need to *focus* to have a
>> chance at success.
> 100% agree.
>> 
>> I'm going to include a few design options.  I'll mark them "Option:".
>> 
>> The proposed "amend" interface takes a specification of desired state,
>> and figures out how to get from here to there by itself.  LUKS keyslots
>> are one part of desired state.
>> 
>> We commonly have eight LUKS keyslots.  Each keyslot is either active or
>> inactive.  An active keyslot holds a secret.
>> 
>> Goal: a QAPI type for specifying desired state of LUKS keyslots.
>> 
>> Proposal:
>> 
>>     { 'enum': 'LUKSKeyslotState',
>>       'data': [ 'active', 'inactive' ] }
>> 
>>     { 'struct': 'LUKSKeyslotActive',
>>       'data': { 'secret': 'str',
>>                 '*iter-time': 'int } }
>> 
>>     { 'struct': 'LUKSKeyslotInactive',
>>       'data': { '*old-secret': 'str' } }
>> 
>>     { 'union': 'LUKSKeyslotAmend',
>>       'base': { '*keyslot': 'int',
>>                 'state': 'LUKSKeyslotState' }
>>       'discriminator': 'state',
>>       'data': { 'active': 'LUKSKeyslotActive',
>>                 'inactive': 'LUKSKeyslotInactive' } }
>> 
>> LUKSKeyslotAmend specifies desired state for a set of keyslots.
>> 
>> Four cases:
>> 
>> * @state is "active"
>> 
>>   Desired state is active holding the secret given by @secret.  Optional
>>   @iter-time tweaks key stretching.
>> 
>>   The keyslot is chosen either by the user or by the system, as follows:
>> 
>>   - @keyslot absent
>> 
>>     One inactive keyslot chosen by the system.  If none exists, error.
>> 
>>   - @keyslot present
>> 
>>     The keyslot given by @keyslot.
>> 
>>     If it's already active holding @secret, no-op.  Rationale: the
>>     current state is the desired state.
>> 
>>     If it's already active holding another secret, error.  Rationale:
>>     update in place is unsafe.
>> 
>>     Option: delete the "already active holding @secret" case.  Feels
>>     inelegant to me.  Okay if it makes things substantially simpler.
> I didn't really understand this, since in state=active we shouldn't
> delete anything. Looks OK otherwise.

Let me try to clarify.

Option: make the "already active holding @secret" case an error like the
"already active holding another secret" case.  In longhand:

     - @keyslot present

       The keyslot given by @keyslot.

       If it's already active, error.

It feels inelegant to me, because it deviates from "specify desired
state" paradigm: the specified desired state is fine, the way to get
there from current state is obvious (do nothing), yet it's still an
error.

>> * @state is "inactive"
>> 
>>   Desired state is inactive.
>> 
>>   Error if the current state has active keyslots, but the desired state
>>   has none.
>> 
>>   The user choses the keyslot by number and/or by the secret it holds,
>>   as follows:
>> 
>>   - @keyslot absent, @old-secret present
>> 
>>     All active keyslots holding @old-secret.  If none exists, error.
>> 
>>   - @keyslot present, @old-secret absent
>> 
>>     The keyslot given by @keyslot.
>> 
>>     If it's already inactive, no-op.  Rationale: the current state is
>>     the desired state.
>> 
>>   - both @keyslot and @old-secret present
>> 
>>     The keyslot given by keyslot.
>> 
>>     If it's inactive or holds a secret other than @old-secret, error.
> Yea, that would be very nice to have.
>> 
>>     Option: error regardless of @old-secret, if that makes things
>>     simpler.
>> 
>>   - neither @keyslot not @old-secret present
>> 
>>     All keyslots.  Note that this will error out due to "desired state
>>     has no active keyslots" unless the current state has none, either.
>> 
>>     Option: error out unconditionally.
> Yep, that the best IMHO.

It's a matter of taste.

If we interpret "both absent" as "all keyslots", then all cases flow out
of the following simple spec:

    0. Start with the set of all keyslots

    1. If @old-secret is present, interset it with the set of slots
       holding that secret.

    2. If @keyslots is present, intersect it with the set of slots with
       that slot number.

The order of steps 1 and 2 doesn't matter.

To error out unconditionally, we have to make "both absent" a special
case.

A good way to resolve such matters of taste is to try writing doc
comments for all proposals.  If you find you hate one of them much less,
you have a winner :)

[...]



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-17  6:45       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
@ 2020-02-17  8:19         ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-02-17  8:19 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Daniel P.Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

On Mon, 2020-02-17 at 07:45 +0100, Markus Armbruster wrote:
> Maxim Levitsky <mlevitsk@redhat.com> writes:
> 
> > On Sat, 2020-02-15 at 15:51 +0100, Markus Armbruster wrote:
> > > Review of this patch led to a lengthy QAPI schema design discussion.
> > > Let me try to condense it into a concrete proposal.
> > > 
> > > This is about the QAPI schema, and therefore about QMP.  The
> > > human-friendly interface is out of scope.  Not because it's not
> > > important (it clearly is!), only because we need to *focus* to have a
> > > chance at success.
> > 
> > 100% agree.
> > > 
> > > I'm going to include a few design options.  I'll mark them "Option:".
> > > 
> > > The proposed "amend" interface takes a specification of desired state,
> > > and figures out how to get from here to there by itself.  LUKS keyslots
> > > are one part of desired state.
> > > 
> > > We commonly have eight LUKS keyslots.  Each keyslot is either active or
> > > inactive.  An active keyslot holds a secret.
> > > 
> > > Goal: a QAPI type for specifying desired state of LUKS keyslots.
> > > 
> > > Proposal:
> > > 
> > >     { 'enum': 'LUKSKeyslotState',
> > >       'data': [ 'active', 'inactive' ] }
> > > 
> > >     { 'struct': 'LUKSKeyslotActive',
> > >       'data': { 'secret': 'str',
> > >                 '*iter-time': 'int } }
> > > 
> > >     { 'struct': 'LUKSKeyslotInactive',
> > >       'data': { '*old-secret': 'str' } }
> > > 
> > >     { 'union': 'LUKSKeyslotAmend',
> > >       'base': { '*keyslot': 'int',
> > >                 'state': 'LUKSKeyslotState' }
> > >       'discriminator': 'state',
> > >       'data': { 'active': 'LUKSKeyslotActive',
> > >                 'inactive': 'LUKSKeyslotInactive' } }
> > > 
> > > LUKSKeyslotAmend specifies desired state for a set of keyslots.
> > > 
> > > Four cases:
> > > 
> > > * @state is "active"
> > > 
> > >   Desired state is active holding the secret given by @secret.  Optional
> > >   @iter-time tweaks key stretching.
> > > 
> > >   The keyslot is chosen either by the user or by the system, as follows:
> > > 
> > >   - @keyslot absent
> > > 
> > >     One inactive keyslot chosen by the system.  If none exists, error.
> > > 
> > >   - @keyslot present
> > > 
> > >     The keyslot given by @keyslot.
> > > 
> > >     If it's already active holding @secret, no-op.  Rationale: the
> > >     current state is the desired state.
> > > 
> > >     If it's already active holding another secret, error.  Rationale:
> > >     update in place is unsafe.
> > > 
> > >     Option: delete the "already active holding @secret" case.  Feels
> > >     inelegant to me.  Okay if it makes things substantially simpler.
> > 
> > I didn't really understand this, since in state=active we shouldn't
> > delete anything. Looks OK otherwise.
> 
> Let me try to clarify.
> 
> Option: make the "already active holding @secret" case an error like the
> "already active holding another secret" case.  In longhand:
> 
>      - @keyslot present
> 
>        The keyslot given by @keyslot.
> 
>        If it's already active, error.
> 
> It feels inelegant to me, because it deviates from "specify desired
> state" paradigm: the specified desired state is fine, the way to get
> there from current state is obvious (do nothing), yet it's still an
> error.
Yep, although in theory we also specify that iteration count, which might not
match (and it will never exactly match since it is benchmark based), thus
if user specified it, we might err out, and otherwise indeed ignore this.
This is of course very minor issue.

> 
> > > * @state is "inactive"
> > > 
> > >   Desired state is inactive.
> > > 
> > >   Error if the current state has active keyslots, but the desired state
> > >   has none.
> > > 
> > >   The user choses the keyslot by number and/or by the secret it holds,
> > >   as follows:
> > > 
> > >   - @keyslot absent, @old-secret present
> > > 
> > >     All active keyslots holding @old-secret.  If none exists, error.
> > > 
> > >   - @keyslot present, @old-secret absent
> > > 
> > >     The keyslot given by @keyslot.
> > > 
> > >     If it's already inactive, no-op.  Rationale: the current state is
> > >     the desired state.
> > > 
> > >   - both @keyslot and @old-secret present
> > > 
> > >     The keyslot given by keyslot.
> > > 
> > >     If it's inactive or holds a secret other than @old-secret, error.
> > 
> > Yea, that would be very nice to have.
> > > 
> > >     Option: error regardless of @old-secret, if that makes things
> > >     simpler.
> > > 
> > >   - neither @keyslot not @old-secret present
> > > 
> > >     All keyslots.  Note that this will error out due to "desired state
> > >     has no active keyslots" unless the current state has none, either.
> > > 
> > >     Option: error out unconditionally.
> > 
> > Yep, that the best IMHO.
> 
> It's a matter of taste.
> 
> If we interpret "both absent" as "all keyslots", then all cases flow out
> of the following simple spec:
> 
>     0. Start with the set of all keyslots
> 
>     1. If @old-secret is present, interset it with the set of slots
>        holding that secret.
> 
>     2. If @keyslots is present, intersect it with the set of slots with
>        that slot number.
> 
> The order of steps 1 and 2 doesn't matter.
> 
> To error out unconditionally, we have to make "both absent" a special
> case.
Yes but to be honest it is few lines of code, and gets you a better
error message in the process. I don't have a strong opinion on this though.

> 
> A good way to resolve such matters of taste is to try writing doc
> comments for all proposals.  If you find you hate one of them much less,
> you have a winner :)

I am already out of this game for some time, I myself won't argue
over this interface (in addition to that that current interface is very good IMHO),
and I am just waiting for others to accept it.


Best regards and thanks for help,
	Maxim Levitsky

> 
> [...]




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-02-15 14:51   ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Markus Armbruster
  2020-02-16  8:05     ` Maxim Levitsky
@ 2020-02-17 10:37     ` Kevin Wolf
  2020-02-17 11:07       ` Maxim Levitsky
  2020-02-17 12:28       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
  2020-02-24 14:45     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Daniel P. Berrangé
                       ` (2 subsequent siblings)
  4 siblings, 2 replies; 84+ messages in thread
From: Kevin Wolf @ 2020-02-17 10:37 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Max Reitz, Maxim Levitsky, John Snow

Am 15.02.2020 um 15:51 hat Markus Armbruster geschrieben:
> Review of this patch led to a lengthy QAPI schema design discussion.
> Let me try to condense it into a concrete proposal.
> 
> This is about the QAPI schema, and therefore about QMP.  The
> human-friendly interface is out of scope.  Not because it's not
> important (it clearly is!), only because we need to *focus* to have a
> chance at success.
> 
> I'm going to include a few design options.  I'll mark them "Option:".
> 
> The proposed "amend" interface takes a specification of desired state,
> and figures out how to get from here to there by itself.  LUKS keyslots
> are one part of desired state.
> 
> We commonly have eight LUKS keyslots.  Each keyslot is either active or
> inactive.  An active keyslot holds a secret.
> 
> Goal: a QAPI type for specifying desired state of LUKS keyslots.
> 
> Proposal:
> 
>     { 'enum': 'LUKSKeyslotState',
>       'data': [ 'active', 'inactive' ] }
> 
>     { 'struct': 'LUKSKeyslotActive',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int } }
> 
>     { 'struct': 'LUKSKeyslotInactive',
>       'data': { '*old-secret': 'str' } }
> 
>     { 'union': 'LUKSKeyslotAmend',
>       'base': { '*keyslot': 'int',
>                 'state': 'LUKSKeyslotState' }
>       'discriminator': 'state',
>       'data': { 'active': 'LUKSKeyslotActive',
>                 'inactive': 'LUKSKeyslotInactive' } }
> 
> LUKSKeyslotAmend specifies desired state for a set of keyslots.

Though not arbitrary sets of keyslots, it's only a single keyslot or
multiple keyslots containing the same secret. Might be good enough in
practice, though it means that you may have to issue multiple amend
commands to get to the final state that you really want (even if doing
everything at once would be safe).

> Four cases:
> 
> * @state is "active"
> 
>   Desired state is active holding the secret given by @secret.  Optional
>   @iter-time tweaks key stretching.
> 
>   The keyslot is chosen either by the user or by the system, as follows:
> 
>   - @keyslot absent
> 
>     One inactive keyslot chosen by the system.  If none exists, error.
> 
>   - @keyslot present
> 
>     The keyslot given by @keyslot.
> 
>     If it's already active holding @secret, no-op.  Rationale: the
>     current state is the desired state.
> 
>     If it's already active holding another secret, error.  Rationale:
>     update in place is unsafe.
> 
>     Option: delete the "already active holding @secret" case.  Feels
>     inelegant to me.  Okay if it makes things substantially simpler.
> 
> * @state is "inactive"
> 
>   Desired state is inactive.
> 
>   Error if the current state has active keyslots, but the desired state
>   has none.
> 
>   The user choses the keyslot by number and/or by the secret it holds,
>   as follows:
> 
>   - @keyslot absent, @old-secret present
> 
>     All active keyslots holding @old-secret.  If none exists, error.
> 
>   - @keyslot present, @old-secret absent
> 
>     The keyslot given by @keyslot.
> 
>     If it's already inactive, no-op.  Rationale: the current state is
>     the desired state.
> 
>   - both @keyslot and @old-secret present
> 
>     The keyslot given by keyslot.
> 
>     If it's inactive or holds a secret other than @old-secret, error.
> 
>     Option: error regardless of @old-secret, if that makes things
>     simpler.
> 
>   - neither @keyslot not @old-secret present
> 
>     All keyslots.  Note that this will error out due to "desired state
>     has no active keyslots" unless the current state has none, either.
> 
>     Option: error out unconditionally.
> 
> Note that LUKSKeyslotAmend can specify only one desired state for
> commonly just one keyslot.  Rationale: this satisfies practical needs.
> An array of LUKSKeyslotAmend could specify desired state for all
> keyslots.  However, multiple array elements could then apply to the same
> slot.  We'd have to specify how to resolve such conflicts, and we'd have
> to code up conflict detection.  Not worth it.
> 
> Examples:
> 
> * Add a secret to some free keyslot:
> 
>   { "state": "active", "secret": "CIA/GRU/MI6" }
> 
> * Deactivate all keyslots holding a secret:
> 
>   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
> 
> * Add a secret to a specific keyslot:
> 
>   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
> 
> * Deactivate a specific keyslot:
> 
>   { "state": "inactive", "keyslot": 0 }
> 
>   Possibly less dangerous:
> 
>   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
> 
> Option: Make use of Max's patches to support optional union tag with
> default value to let us default @state to "active".  I doubt this makes
> much of a difference in QMP.  A human-friendly interface should probably
> be higher level anyway (Daniel pointed to cryptsetup).
> 
> Option: LUKSKeyslotInactive member @old-secret could also be named
> @secret.  I don't care.
> 
> Option: delete @keyslot.  It provides low-level slot access.
> Complicates the interface.  Fine if we need lov-level slot access.  Do
> we?
> 
> I apologize for the time it has taken me to write this.
> 
> Comments?

Works for me (without taking any of the options).

The unclear part is what the human-friendly interface should look like
and where it should live. I'm afraid doing only the QMP part and calling
the feature completed like we do so often won't work in this case.

Kevin



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-02-17 10:37     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Kevin Wolf
@ 2020-02-17 11:07       ` Maxim Levitsky
  2020-02-24 14:46         ` Daniel P. Berrangé
  2020-02-17 12:28       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
  1 sibling, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-02-17 11:07 UTC (permalink / raw)
  To: Kevin Wolf, Markus Armbruster
  Cc: John Snow, Daniel P. Berrangé, qemu-devel, qemu-block, Max Reitz

On Mon, 2020-02-17 at 11:37 +0100, Kevin Wolf wrote:
> Am 15.02.2020 um 15:51 hat Markus Armbruster geschrieben:
> > Review of this patch led to a lengthy QAPI schema design discussion.
> > Let me try to condense it into a concrete proposal.
> > 
> > This is about the QAPI schema, and therefore about QMP.  The
> > human-friendly interface is out of scope.  Not because it's not
> > important (it clearly is!), only because we need to *focus* to have a
> > chance at success.
> > 
> > I'm going to include a few design options.  I'll mark them "Option:".
> > 
> > The proposed "amend" interface takes a specification of desired state,
> > and figures out how to get from here to there by itself.  LUKS keyslots
> > are one part of desired state.
> > 
> > We commonly have eight LUKS keyslots.  Each keyslot is either active or
> > inactive.  An active keyslot holds a secret.
> > 
> > Goal: a QAPI type for specifying desired state of LUKS keyslots.
> > 
> > Proposal:
> > 
> >     { 'enum': 'LUKSKeyslotState',
> >       'data': [ 'active', 'inactive' ] }
> > 
> >     { 'struct': 'LUKSKeyslotActive',
> >       'data': { 'secret': 'str',
> >                 '*iter-time': 'int } }
> > 
> >     { 'struct': 'LUKSKeyslotInactive',
> >       'data': { '*old-secret': 'str' } }
> > 
> >     { 'union': 'LUKSKeyslotAmend',
> >       'base': { '*keyslot': 'int',
> >                 'state': 'LUKSKeyslotState' }
> >       'discriminator': 'state',
> >       'data': { 'active': 'LUKSKeyslotActive',
> >                 'inactive': 'LUKSKeyslotInactive' } }
> > 
> > LUKSKeyslotAmend specifies desired state for a set of keyslots.
> 
> Though not arbitrary sets of keyslots, it's only a single keyslot or
> multiple keyslots containing the same secret. Might be good enough in
> practice, though it means that you may have to issue multiple amend
> commands to get to the final state that you really want (even if doing
> everything at once would be safe).
> 
> > Four cases:
> > 
> > * @state is "active"
> > 
> >   Desired state is active holding the secret given by @secret.  Optional
> >   @iter-time tweaks key stretching.
> > 
> >   The keyslot is chosen either by the user or by the system, as follows:
> > 
> >   - @keyslot absent
> > 
> >     One inactive keyslot chosen by the system.  If none exists, error.
> > 
> >   - @keyslot present
> > 
> >     The keyslot given by @keyslot.
> > 
> >     If it's already active holding @secret, no-op.  Rationale: the
> >     current state is the desired state.
> > 
> >     If it's already active holding another secret, error.  Rationale:
> >     update in place is unsafe.
> > 
> >     Option: delete the "already active holding @secret" case.  Feels
> >     inelegant to me.  Okay if it makes things substantially simpler.
> > 
> > * @state is "inactive"
> > 
> >   Desired state is inactive.
> > 
> >   Error if the current state has active keyslots, but the desired state
> >   has none.
> > 
> >   The user choses the keyslot by number and/or by the secret it holds,
> >   as follows:
> > 
> >   - @keyslot absent, @old-secret present
> > 
> >     All active keyslots holding @old-secret.  If none exists, error.
> > 
> >   - @keyslot present, @old-secret absent
> > 
> >     The keyslot given by @keyslot.
> > 
> >     If it's already inactive, no-op.  Rationale: the current state is
> >     the desired state.
> > 
> >   - both @keyslot and @old-secret present
> > 
> >     The keyslot given by keyslot.
> > 
> >     If it's inactive or holds a secret other than @old-secret, error.
> > 
> >     Option: error regardless of @old-secret, if that makes things
> >     simpler.
> > 
> >   - neither @keyslot not @old-secret present
> > 
> >     All keyslots.  Note that this will error out due to "desired state
> >     has no active keyslots" unless the current state has none, either.
> > 
> >     Option: error out unconditionally.
> > 
> > Note that LUKSKeyslotAmend can specify only one desired state for
> > commonly just one keyslot.  Rationale: this satisfies practical needs.
> > An array of LUKSKeyslotAmend could specify desired state for all
> > keyslots.  However, multiple array elements could then apply to the same
> > slot.  We'd have to specify how to resolve such conflicts, and we'd have
> > to code up conflict detection.  Not worth it.
> > 
> > Examples:
> > 
> > * Add a secret to some free keyslot:
> > 
> >   { "state": "active", "secret": "CIA/GRU/MI6" }
> > 
> > * Deactivate all keyslots holding a secret:
> > 
> >   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
> > 
> > * Add a secret to a specific keyslot:
> > 
> >   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
> > 
> > * Deactivate a specific keyslot:
> > 
> >   { "state": "inactive", "keyslot": 0 }
> > 
> >   Possibly less dangerous:
> > 
> >   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
> > 
> > Option: Make use of Max's patches to support optional union tag with
> > default value to let us default @state to "active".  I doubt this makes
> > much of a difference in QMP.  A human-friendly interface should probably
> > be higher level anyway (Daniel pointed to cryptsetup).
> > 
> > Option: LUKSKeyslotInactive member @old-secret could also be named
> > @secret.  I don't care.
> > 
> > Option: delete @keyslot.  It provides low-level slot access.
> > Complicates the interface.  Fine if we need lov-level slot access.  Do
> > we?
> > 
> > I apologize for the time it has taken me to write this.
> > 
> > Comments?
> 
> Works for me (without taking any of the options).
> 
> The unclear part is what the human-friendly interface should look like
> and where it should live. I'm afraid doing only the QMP part and calling
> the feature completed like we do so often won't work in this case.

IMHO, I think that the best way to create human friendly part is to implement
luks specific commands for qemu-img and use interface very similar
to what cryptsetup does.

Best regards,
	Maxim Levitsky
> 
> Kevin




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-17 10:37     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Kevin Wolf
  2020-02-17 11:07       ` Maxim Levitsky
@ 2020-02-17 12:28       ` Markus Armbruster
  2020-02-17 12:44         ` Eric Blake
  2020-02-24 14:43         ` Daniel P. Berrangé
  1 sibling, 2 replies; 84+ messages in thread
From: Markus Armbruster @ 2020-02-17 12:28 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: Daniel P. Berrangé,
	qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

Kevin Wolf <kwolf@redhat.com> writes:

> Am 15.02.2020 um 15:51 hat Markus Armbruster geschrieben:
>> Review of this patch led to a lengthy QAPI schema design discussion.
>> Let me try to condense it into a concrete proposal.
>> 
>> This is about the QAPI schema, and therefore about QMP.  The
>> human-friendly interface is out of scope.  Not because it's not
>> important (it clearly is!), only because we need to *focus* to have a
>> chance at success.
>> 
>> I'm going to include a few design options.  I'll mark them "Option:".
>> 
>> The proposed "amend" interface takes a specification of desired state,
>> and figures out how to get from here to there by itself.  LUKS keyslots
>> are one part of desired state.
>> 
>> We commonly have eight LUKS keyslots.  Each keyslot is either active or
>> inactive.  An active keyslot holds a secret.
>> 
>> Goal: a QAPI type for specifying desired state of LUKS keyslots.
>> 
>> Proposal:
>> 
>>     { 'enum': 'LUKSKeyslotState',
>>       'data': [ 'active', 'inactive' ] }
>> 
>>     { 'struct': 'LUKSKeyslotActive',
>>       'data': { 'secret': 'str',
>>                 '*iter-time': 'int } }
>> 
>>     { 'struct': 'LUKSKeyslotInactive',
>>       'data': { '*old-secret': 'str' } }
>> 
>>     { 'union': 'LUKSKeyslotAmend',
>>       'base': { '*keyslot': 'int',
>>                 'state': 'LUKSKeyslotState' }
>>       'discriminator': 'state',
>>       'data': { 'active': 'LUKSKeyslotActive',
>>                 'inactive': 'LUKSKeyslotInactive' } }
>> 
>> LUKSKeyslotAmend specifies desired state for a set of keyslots.
>
> Though not arbitrary sets of keyslots, it's only a single keyslot or
> multiple keyslots containing the same secret. Might be good enough in
> practice, though it means that you may have to issue multiple amend
> commands to get to the final state that you really want (even if doing
> everything at once would be safe).

True.  I traded expressiveness for simplicity.

Here's the only practical case I can think of where the lack of
expressiveness may hurt: replace secrets.

With this interface, you need two operations: activate a free slot with
the new secret, deactivate the slot(s) with the old secret.  There is an
intermediate state with both secrets active.

A more expressive interface could let you do both in one step.  Relevant
only if the implementation actually provides atomicity.  Can it?

>> Four cases:
>> 
>> * @state is "active"
>> 
>>   Desired state is active holding the secret given by @secret.  Optional
>>   @iter-time tweaks key stretching.
>> 
>>   The keyslot is chosen either by the user or by the system, as follows:
>> 
>>   - @keyslot absent
>> 
>>     One inactive keyslot chosen by the system.  If none exists, error.
>> 
>>   - @keyslot present
>> 
>>     The keyslot given by @keyslot.
>> 
>>     If it's already active holding @secret, no-op.  Rationale: the
>>     current state is the desired state.
>> 
>>     If it's already active holding another secret, error.  Rationale:
>>     update in place is unsafe.
>> 
>>     Option: delete the "already active holding @secret" case.  Feels
>>     inelegant to me.  Okay if it makes things substantially simpler.
>> 
>> * @state is "inactive"
>> 
>>   Desired state is inactive.
>> 
>>   Error if the current state has active keyslots, but the desired state
>>   has none.
>> 
>>   The user choses the keyslot by number and/or by the secret it holds,
>>   as follows:
>> 
>>   - @keyslot absent, @old-secret present
>> 
>>     All active keyslots holding @old-secret.  If none exists, error.
>> 
>>   - @keyslot present, @old-secret absent
>> 
>>     The keyslot given by @keyslot.
>> 
>>     If it's already inactive, no-op.  Rationale: the current state is
>>     the desired state.
>> 
>>   - both @keyslot and @old-secret present
>> 
>>     The keyslot given by keyslot.
>> 
>>     If it's inactive or holds a secret other than @old-secret, error.
>> 
>>     Option: error regardless of @old-secret, if that makes things
>>     simpler.
>> 
>>   - neither @keyslot not @old-secret present
>> 
>>     All keyslots.  Note that this will error out due to "desired state
>>     has no active keyslots" unless the current state has none, either.
>> 
>>     Option: error out unconditionally.
>> 
>> Note that LUKSKeyslotAmend can specify only one desired state for
>> commonly just one keyslot.  Rationale: this satisfies practical needs.
>> An array of LUKSKeyslotAmend could specify desired state for all
>> keyslots.  However, multiple array elements could then apply to the same
>> slot.  We'd have to specify how to resolve such conflicts, and we'd have
>> to code up conflict detection.  Not worth it.
>> 
>> Examples:
>> 
>> * Add a secret to some free keyslot:
>> 
>>   { "state": "active", "secret": "CIA/GRU/MI6" }
>> 
>> * Deactivate all keyslots holding a secret:
>> 
>>   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
>> 
>> * Add a secret to a specific keyslot:
>> 
>>   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
>> 
>> * Deactivate a specific keyslot:
>> 
>>   { "state": "inactive", "keyslot": 0 }
>> 
>>   Possibly less dangerous:
>> 
>>   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
>> 
>> Option: Make use of Max's patches to support optional union tag with
>> default value to let us default @state to "active".  I doubt this makes
>> much of a difference in QMP.  A human-friendly interface should probably
>> be higher level anyway (Daniel pointed to cryptsetup).
>> 
>> Option: LUKSKeyslotInactive member @old-secret could also be named
>> @secret.  I don't care.
>> 
>> Option: delete @keyslot.  It provides low-level slot access.
>> Complicates the interface.  Fine if we need lov-level slot access.  Do
>> we?
>> 
>> I apologize for the time it has taken me to write this.
>> 
>> Comments?
>
> Works for me (without taking any of the options).
>
> The unclear part is what the human-friendly interface should look like
> and where it should live. I'm afraid doing only the QMP part and calling
> the feature completed like we do so often won't work in this case.

No argument.  Perhaps Daniel can help with designing a human-friendly
high-level interface.



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-17 12:28       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
@ 2020-02-17 12:44         ` Eric Blake
  2020-02-24 14:43         ` Daniel P. Berrangé
  1 sibling, 0 replies; 84+ messages in thread
From: Eric Blake @ 2020-02-17 12:44 UTC (permalink / raw)
  To: Markus Armbruster, Kevin Wolf
  Cc: Daniel P. Berrangé, qemu-devel, qemu-block, Max Reitz

On 2/17/20 6:28 AM, Markus Armbruster wrote:

>>> Proposal:
>>>
>>>      { 'enum': 'LUKSKeyslotState',
>>>        'data': [ 'active', 'inactive' ] }
>>>
>>>      { 'struct': 'LUKSKeyslotActive',
>>>        'data': { 'secret': 'str',
>>>                  '*iter-time': 'int } }
>>>
>>>      { 'struct': 'LUKSKeyslotInactive',
>>>        'data': { '*old-secret': 'str' } }
>>>
>>>      { 'union': 'LUKSKeyslotAmend',
>>>        'base': { '*keyslot': 'int',
>>>                  'state': 'LUKSKeyslotState' }
>>>        'discriminator': 'state',
>>>        'data': { 'active': 'LUKSKeyslotActive',
>>>                  'inactive': 'LUKSKeyslotInactive' } }
>>>
>>> LUKSKeyslotAmend specifies desired state for a set of keyslots.
>>
>> Though not arbitrary sets of keyslots, it's only a single keyslot or
>> multiple keyslots containing the same secret. Might be good enough in
>> practice, though it means that you may have to issue multiple amend
>> commands to get to the final state that you really want (even if doing
>> everything at once would be safe).
> 
> True.  I traded expressiveness for simplicity.
> 
> Here's the only practical case I can think of where the lack of
> expressiveness may hurt: replace secrets.
> 
> With this interface, you need two operations: activate a free slot with
> the new secret, deactivate the slot(s) with the old secret.  There is an
> intermediate state with both secrets active.
> 
> A more expressive interface could let you do both in one step.  Relevant
> only if the implementation actually provides atomicity.  Can it?

Or put another way, can atomicity be added via 'transaction' later?  If 
so, reusing one common interface to provide atomicity is nicer than 
making every interface reimplement atomicity on an ad hoc basis.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-17 12:28       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
  2020-02-17 12:44         ` Eric Blake
@ 2020-02-24 14:43         ` Daniel P. Berrangé
  1 sibling, 0 replies; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-02-24 14:43 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

On Mon, Feb 17, 2020 at 01:28:51PM +0100, Markus Armbruster wrote:
> Kevin Wolf <kwolf@redhat.com> writes:
> 
> > Am 15.02.2020 um 15:51 hat Markus Armbruster geschrieben:
> >> Review of this patch led to a lengthy QAPI schema design discussion.
> >> Let me try to condense it into a concrete proposal.
> >> 
> >> This is about the QAPI schema, and therefore about QMP.  The
> >> human-friendly interface is out of scope.  Not because it's not
> >> important (it clearly is!), only because we need to *focus* to have a
> >> chance at success.
> >> 
> >> I'm going to include a few design options.  I'll mark them "Option:".
> >> 
> >> The proposed "amend" interface takes a specification of desired state,
> >> and figures out how to get from here to there by itself.  LUKS keyslots
> >> are one part of desired state.
> >> 
> >> We commonly have eight LUKS keyslots.  Each keyslot is either active or
> >> inactive.  An active keyslot holds a secret.
> >> 
> >> Goal: a QAPI type for specifying desired state of LUKS keyslots.
> >> 
> >> Proposal:
> >> 
> >>     { 'enum': 'LUKSKeyslotState',
> >>       'data': [ 'active', 'inactive' ] }
> >> 
> >>     { 'struct': 'LUKSKeyslotActive',
> >>       'data': { 'secret': 'str',
> >>                 '*iter-time': 'int } }
> >> 
> >>     { 'struct': 'LUKSKeyslotInactive',
> >>       'data': { '*old-secret': 'str' } }
> >> 
> >>     { 'union': 'LUKSKeyslotAmend',
> >>       'base': { '*keyslot': 'int',
> >>                 'state': 'LUKSKeyslotState' }
> >>       'discriminator': 'state',
> >>       'data': { 'active': 'LUKSKeyslotActive',
> >>                 'inactive': 'LUKSKeyslotInactive' } }
> >> 
> >> LUKSKeyslotAmend specifies desired state for a set of keyslots.
> >
> > Though not arbitrary sets of keyslots, it's only a single keyslot or
> > multiple keyslots containing the same secret. Might be good enough in
> > practice, though it means that you may have to issue multiple amend
> > commands to get to the final state that you really want (even if doing
> > everything at once would be safe).
> 
> True.  I traded expressiveness for simplicity.
> 
> Here's the only practical case I can think of where the lack of
> expressiveness may hurt: replace secrets.
> 
> With this interface, you need two operations: activate a free slot with
> the new secret, deactivate the slot(s) with the old secret.  There is an
> intermediate state with both secrets active.
> 
> A more expressive interface could let you do both in one step.  Relevant
> only if the implementation actually provides atomicity.  Can it?

This restriction is already present in the the long standing
cryptsetup command, so I don't think it is a big deal. Or to
put it another way I don't see a compelling justification for
why QEMU needs to be special and do it in op.


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-02-15 14:51   ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Markus Armbruster
  2020-02-16  8:05     ` Maxim Levitsky
  2020-02-17 10:37     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Kevin Wolf
@ 2020-02-24 14:45     ` Daniel P. Berrangé
  2020-02-25 12:15     ` Max Reitz
  2020-03-03  9:18     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Maxim Levitsky
  4 siblings, 0 replies; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-02-24 14:45 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, qemu-block, qemu-devel, Max Reitz, Maxim Levitsky, John Snow

On Sat, Feb 15, 2020 at 03:51:46PM +0100, Markus Armbruster wrote:
> Review of this patch led to a lengthy QAPI schema design discussion.
> Let me try to condense it into a concrete proposal.
> 
> This is about the QAPI schema, and therefore about QMP.  The
> human-friendly interface is out of scope.  Not because it's not
> important (it clearly is!), only because we need to *focus* to have a
> chance at success.

OK

> I'm going to include a few design options.  I'll mark them "Option:".
> 
> The proposed "amend" interface takes a specification of desired state,
> and figures out how to get from here to there by itself.  LUKS keyslots
> are one part of desired state.
> 
> We commonly have eight LUKS keyslots.  Each keyslot is either active or
> inactive.  An active keyslot holds a secret.
> 
> Goal: a QAPI type for specifying desired state of LUKS keyslots.
> 
> Proposal:
> 
>     { 'enum': 'LUKSKeyslotState',
>       'data': [ 'active', 'inactive' ] }
> 
>     { 'struct': 'LUKSKeyslotActive',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int } }
> 
>     { 'struct': 'LUKSKeyslotInactive',
>       'data': { '*old-secret': 'str' } }
> 
>     { 'union': 'LUKSKeyslotAmend',
>       'base': { '*keyslot': 'int',
>                 'state': 'LUKSKeyslotState' }
>       'discriminator': 'state',
>       'data': { 'active': 'LUKSKeyslotActive',
>                 'inactive': 'LUKSKeyslotInactive' } }
> 
> LUKSKeyslotAmend specifies desired state for a set of keyslots.
> 
> Four cases:
> 
> * @state is "active"
> 
>   Desired state is active holding the secret given by @secret.  Optional
>   @iter-time tweaks key stretching.
> 
>   The keyslot is chosen either by the user or by the system, as follows:
> 
>   - @keyslot absent
> 
>     One inactive keyslot chosen by the system.  If none exists, error.
> 
>   - @keyslot present
> 
>     The keyslot given by @keyslot.
> 
>     If it's already active holding @secret, no-op.  Rationale: the
>     current state is the desired state.
> 
>     If it's already active holding another secret, error.  Rationale:
>     update in place is unsafe.
> 
>     Option: delete the "already active holding @secret" case.  Feels
>     inelegant to me.  Okay if it makes things substantially simpler.
> 
> * @state is "inactive"
> 
>   Desired state is inactive.
> 
>   Error if the current state has active keyslots, but the desired state
>   has none.
> 
>   The user choses the keyslot by number and/or by the secret it holds,
>   as follows:
> 
>   - @keyslot absent, @old-secret present
> 
>     All active keyslots holding @old-secret.  If none exists, error.
> 
>   - @keyslot present, @old-secret absent
> 
>     The keyslot given by @keyslot.
> 
>     If it's already inactive, no-op.  Rationale: the current state is
>     the desired state.
> 
>   - both @keyslot and @old-secret present
> 
>     The keyslot given by keyslot.
> 
>     If it's inactive or holds a secret other than @old-secret, error.
> 
>     Option: error regardless of @old-secret, if that makes things
>     simpler.
> 
>   - neither @keyslot not @old-secret present
> 
>     All keyslots.  Note that this will error out due to "desired state
>     has no active keyslots" unless the current state has none, either.
> 
>     Option: error out unconditionally.
> 
> Note that LUKSKeyslotAmend can specify only one desired state for
> commonly just one keyslot.  Rationale: this satisfies practical needs.
> An array of LUKSKeyslotAmend could specify desired state for all
> keyslots.  However, multiple array elements could then apply to the same
> slot.  We'd have to specify how to resolve such conflicts, and we'd have
> to code up conflict detection.  Not worth it.
> 
> Examples:
> 
> * Add a secret to some free keyslot:
> 
>   { "state": "active", "secret": "CIA/GRU/MI6" }
> 
> * Deactivate all keyslots holding a secret:
> 
>   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
> 
> * Add a secret to a specific keyslot:
> 
>   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
> 
> * Deactivate a specific keyslot:
> 
>   { "state": "inactive", "keyslot": 0 }
> 
>   Possibly less dangerous:
> 
>   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
> 
> Option: Make use of Max's patches to support optional union tag with
> default value to let us default @state to "active".  I doubt this makes
> much of a difference in QMP.  A human-friendly interface should probably
> be higher level anyway (Daniel pointed to cryptsetup).
> 
> Option: LUKSKeyslotInactive member @old-secret could also be named
> @secret.  I don't care.
> 
> Option: delete @keyslot.  It provides low-level slot access.
> Complicates the interface.  Fine if we need lov-level slot access.  Do
> we?
> 
> I apologize for the time it has taken me to write this.
> 
> Comments?

This is all fine with me. I have no strong opinion on the handful of
options listed above, so fine with any choices out of them.

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-02-17 11:07       ` Maxim Levitsky
@ 2020-02-24 14:46         ` Daniel P. Berrangé
  2020-02-24 14:50           ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-02-24 14:46 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Mon, Feb 17, 2020 at 01:07:23PM +0200, Maxim Levitsky wrote:
> On Mon, 2020-02-17 at 11:37 +0100, Kevin Wolf wrote:
> > Am 15.02.2020 um 15:51 hat Markus Armbruster geschrieben:
> > > Review of this patch led to a lengthy QAPI schema design discussion.
> > > Let me try to condense it into a concrete proposal.
> > > 
> > > This is about the QAPI schema, and therefore about QMP.  The
> > > human-friendly interface is out of scope.  Not because it's not
> > > important (it clearly is!), only because we need to *focus* to have a
> > > chance at success.
> > > 
> > > I'm going to include a few design options.  I'll mark them "Option:".
> > > 
> > > The proposed "amend" interface takes a specification of desired state,
> > > and figures out how to get from here to there by itself.  LUKS keyslots
> > > are one part of desired state.
> > > 
> > > We commonly have eight LUKS keyslots.  Each keyslot is either active or
> > > inactive.  An active keyslot holds a secret.
> > > 
> > > Goal: a QAPI type for specifying desired state of LUKS keyslots.
> > > 
> > > Proposal:
> > > 
> > >     { 'enum': 'LUKSKeyslotState',
> > >       'data': [ 'active', 'inactive' ] }
> > > 
> > >     { 'struct': 'LUKSKeyslotActive',
> > >       'data': { 'secret': 'str',
> > >                 '*iter-time': 'int } }
> > > 
> > >     { 'struct': 'LUKSKeyslotInactive',
> > >       'data': { '*old-secret': 'str' } }
> > > 
> > >     { 'union': 'LUKSKeyslotAmend',
> > >       'base': { '*keyslot': 'int',
> > >                 'state': 'LUKSKeyslotState' }
> > >       'discriminator': 'state',
> > >       'data': { 'active': 'LUKSKeyslotActive',
> > >                 'inactive': 'LUKSKeyslotInactive' } }
> > > 
> > > LUKSKeyslotAmend specifies desired state for a set of keyslots.
> > 
> > Though not arbitrary sets of keyslots, it's only a single keyslot or
> > multiple keyslots containing the same secret. Might be good enough in
> > practice, though it means that you may have to issue multiple amend
> > commands to get to the final state that you really want (even if doing
> > everything at once would be safe).
> > 
> > > Four cases:
> > > 
> > > * @state is "active"
> > > 
> > >   Desired state is active holding the secret given by @secret.  Optional
> > >   @iter-time tweaks key stretching.
> > > 
> > >   The keyslot is chosen either by the user or by the system, as follows:
> > > 
> > >   - @keyslot absent
> > > 
> > >     One inactive keyslot chosen by the system.  If none exists, error.
> > > 
> > >   - @keyslot present
> > > 
> > >     The keyslot given by @keyslot.
> > > 
> > >     If it's already active holding @secret, no-op.  Rationale: the
> > >     current state is the desired state.
> > > 
> > >     If it's already active holding another secret, error.  Rationale:
> > >     update in place is unsafe.
> > > 
> > >     Option: delete the "already active holding @secret" case.  Feels
> > >     inelegant to me.  Okay if it makes things substantially simpler.
> > > 
> > > * @state is "inactive"
> > > 
> > >   Desired state is inactive.
> > > 
> > >   Error if the current state has active keyslots, but the desired state
> > >   has none.
> > > 
> > >   The user choses the keyslot by number and/or by the secret it holds,
> > >   as follows:
> > > 
> > >   - @keyslot absent, @old-secret present
> > > 
> > >     All active keyslots holding @old-secret.  If none exists, error.
> > > 
> > >   - @keyslot present, @old-secret absent
> > > 
> > >     The keyslot given by @keyslot.
> > > 
> > >     If it's already inactive, no-op.  Rationale: the current state is
> > >     the desired state.
> > > 
> > >   - both @keyslot and @old-secret present
> > > 
> > >     The keyslot given by keyslot.
> > > 
> > >     If it's inactive or holds a secret other than @old-secret, error.
> > > 
> > >     Option: error regardless of @old-secret, if that makes things
> > >     simpler.
> > > 
> > >   - neither @keyslot not @old-secret present
> > > 
> > >     All keyslots.  Note that this will error out due to "desired state
> > >     has no active keyslots" unless the current state has none, either.
> > > 
> > >     Option: error out unconditionally.
> > > 
> > > Note that LUKSKeyslotAmend can specify only one desired state for
> > > commonly just one keyslot.  Rationale: this satisfies practical needs.
> > > An array of LUKSKeyslotAmend could specify desired state for all
> > > keyslots.  However, multiple array elements could then apply to the same
> > > slot.  We'd have to specify how to resolve such conflicts, and we'd have
> > > to code up conflict detection.  Not worth it.
> > > 
> > > Examples:
> > > 
> > > * Add a secret to some free keyslot:
> > > 
> > >   { "state": "active", "secret": "CIA/GRU/MI6" }
> > > 
> > > * Deactivate all keyslots holding a secret:
> > > 
> > >   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
> > > 
> > > * Add a secret to a specific keyslot:
> > > 
> > >   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
> > > 
> > > * Deactivate a specific keyslot:
> > > 
> > >   { "state": "inactive", "keyslot": 0 }
> > > 
> > >   Possibly less dangerous:
> > > 
> > >   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
> > > 
> > > Option: Make use of Max's patches to support optional union tag with
> > > default value to let us default @state to "active".  I doubt this makes
> > > much of a difference in QMP.  A human-friendly interface should probably
> > > be higher level anyway (Daniel pointed to cryptsetup).
> > > 
> > > Option: LUKSKeyslotInactive member @old-secret could also be named
> > > @secret.  I don't care.
> > > 
> > > Option: delete @keyslot.  It provides low-level slot access.
> > > Complicates the interface.  Fine if we need lov-level slot access.  Do
> > > we?
> > > 
> > > I apologize for the time it has taken me to write this.
> > > 
> > > Comments?
> > 
> > Works for me (without taking any of the options).
> > 
> > The unclear part is what the human-friendly interface should look like
> > and where it should live. I'm afraid doing only the QMP part and calling
> > the feature completed like we do so often won't work in this case.
> 
> IMHO, I think that the best way to create human friendly part is to implement
> luks specific commands for qemu-img and use interface very similar
> to what cryptsetup does.

I think we can have a generic 'qemu-img amend' for machine type, with the
complex dotted syntax.

And then have two human friendly commands 'qemu-img crypt-add-key' and
'qemu-img crypt-del-key' similarish to cryptsetup.

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-02-24 14:46         ` Daniel P. Berrangé
@ 2020-02-24 14:50           ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-02-24 14:50 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Mon, 2020-02-24 at 14:46 +0000, Daniel P. Berrangé wrote:
> On Mon, Feb 17, 2020 at 01:07:23PM +0200, Maxim Levitsky wrote:
> > On Mon, 2020-02-17 at 11:37 +0100, Kevin Wolf wrote:
> > > Am 15.02.2020 um 15:51 hat Markus Armbruster geschrieben:
> > > > Review of this patch led to a lengthy QAPI schema design discussion.
> > > > Let me try to condense it into a concrete proposal.
> > > > 
> > > > This is about the QAPI schema, and therefore about QMP.  The
> > > > human-friendly interface is out of scope.  Not because it's not
> > > > important (it clearly is!), only because we need to *focus* to have a
> > > > chance at success.
> > > > 
> > > > I'm going to include a few design options.  I'll mark them "Option:".
> > > > 
> > > > The proposed "amend" interface takes a specification of desired state,
> > > > and figures out how to get from here to there by itself.  LUKS keyslots
> > > > are one part of desired state.
> > > > 
> > > > We commonly have eight LUKS keyslots.  Each keyslot is either active or
> > > > inactive.  An active keyslot holds a secret.
> > > > 
> > > > Goal: a QAPI type for specifying desired state of LUKS keyslots.
> > > > 
> > > > Proposal:
> > > > 
> > > >     { 'enum': 'LUKSKeyslotState',
> > > >       'data': [ 'active', 'inactive' ] }
> > > > 
> > > >     { 'struct': 'LUKSKeyslotActive',
> > > >       'data': { 'secret': 'str',
> > > >                 '*iter-time': 'int } }
> > > > 
> > > >     { 'struct': 'LUKSKeyslotInactive',
> > > >       'data': { '*old-secret': 'str' } }
> > > > 
> > > >     { 'union': 'LUKSKeyslotAmend',
> > > >       'base': { '*keyslot': 'int',
> > > >                 'state': 'LUKSKeyslotState' }
> > > >       'discriminator': 'state',
> > > >       'data': { 'active': 'LUKSKeyslotActive',
> > > >                 'inactive': 'LUKSKeyslotInactive' } }
> > > > 
> > > > LUKSKeyslotAmend specifies desired state for a set of keyslots.
> > > 
> > > Though not arbitrary sets of keyslots, it's only a single keyslot or
> > > multiple keyslots containing the same secret. Might be good enough in
> > > practice, though it means that you may have to issue multiple amend
> > > commands to get to the final state that you really want (even if doing
> > > everything at once would be safe).
> > > 
> > > > Four cases:
> > > > 
> > > > * @state is "active"
> > > > 
> > > >   Desired state is active holding the secret given by @secret.  Optional
> > > >   @iter-time tweaks key stretching.
> > > > 
> > > >   The keyslot is chosen either by the user or by the system, as follows:
> > > > 
> > > >   - @keyslot absent
> > > > 
> > > >     One inactive keyslot chosen by the system.  If none exists, error.
> > > > 
> > > >   - @keyslot present
> > > > 
> > > >     The keyslot given by @keyslot.
> > > > 
> > > >     If it's already active holding @secret, no-op.  Rationale: the
> > > >     current state is the desired state.
> > > > 
> > > >     If it's already active holding another secret, error.  Rationale:
> > > >     update in place is unsafe.
> > > > 
> > > >     Option: delete the "already active holding @secret" case.  Feels
> > > >     inelegant to me.  Okay if it makes things substantially simpler.
> > > > 
> > > > * @state is "inactive"
> > > > 
> > > >   Desired state is inactive.
> > > > 
> > > >   Error if the current state has active keyslots, but the desired state
> > > >   has none.
> > > > 
> > > >   The user choses the keyslot by number and/or by the secret it holds,
> > > >   as follows:
> > > > 
> > > >   - @keyslot absent, @old-secret present
> > > > 
> > > >     All active keyslots holding @old-secret.  If none exists, error.
> > > > 
> > > >   - @keyslot present, @old-secret absent
> > > > 
> > > >     The keyslot given by @keyslot.
> > > > 
> > > >     If it's already inactive, no-op.  Rationale: the current state is
> > > >     the desired state.
> > > > 
> > > >   - both @keyslot and @old-secret present
> > > > 
> > > >     The keyslot given by keyslot.
> > > > 
> > > >     If it's inactive or holds a secret other than @old-secret, error.
> > > > 
> > > >     Option: error regardless of @old-secret, if that makes things
> > > >     simpler.
> > > > 
> > > >   - neither @keyslot not @old-secret present
> > > > 
> > > >     All keyslots.  Note that this will error out due to "desired state
> > > >     has no active keyslots" unless the current state has none, either.
> > > > 
> > > >     Option: error out unconditionally.
> > > > 
> > > > Note that LUKSKeyslotAmend can specify only one desired state for
> > > > commonly just one keyslot.  Rationale: this satisfies practical needs.
> > > > An array of LUKSKeyslotAmend could specify desired state for all
> > > > keyslots.  However, multiple array elements could then apply to the same
> > > > slot.  We'd have to specify how to resolve such conflicts, and we'd have
> > > > to code up conflict detection.  Not worth it.
> > > > 
> > > > Examples:
> > > > 
> > > > * Add a secret to some free keyslot:
> > > > 
> > > >   { "state": "active", "secret": "CIA/GRU/MI6" }
> > > > 
> > > > * Deactivate all keyslots holding a secret:
> > > > 
> > > >   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
> > > > 
> > > > * Add a secret to a specific keyslot:
> > > > 
> > > >   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
> > > > 
> > > > * Deactivate a specific keyslot:
> > > > 
> > > >   { "state": "inactive", "keyslot": 0 }
> > > > 
> > > >   Possibly less dangerous:
> > > > 
> > > >   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
> > > > 
> > > > Option: Make use of Max's patches to support optional union tag with
> > > > default value to let us default @state to "active".  I doubt this makes
> > > > much of a difference in QMP.  A human-friendly interface should probably
> > > > be higher level anyway (Daniel pointed to cryptsetup).
> > > > 
> > > > Option: LUKSKeyslotInactive member @old-secret could also be named
> > > > @secret.  I don't care.
> > > > 
> > > > Option: delete @keyslot.  It provides low-level slot access.
> > > > Complicates the interface.  Fine if we need lov-level slot access.  Do
> > > > we?
> > > > 
> > > > I apologize for the time it has taken me to write this.
> > > > 
> > > > Comments?
> > > 
> > > Works for me (without taking any of the options).
> > > 
> > > The unclear part is what the human-friendly interface should look like
> > > and where it should live. I'm afraid doing only the QMP part and calling
> > > the feature completed like we do so often won't work in this case.
> > 
> > IMHO, I think that the best way to create human friendly part is to implement
> > luks specific commands for qemu-img and use interface very similar
> > to what cryptsetup does.
> 
> I think we can have a generic 'qemu-img amend' for machine type, with the
> complex dotted syntax.
> 
> And then have two human friendly commands 'qemu-img crypt-add-key' and
> 'qemu-img crypt-del-key' similarish to cryptsetup.

Yep, this is exactly what I was thinking about this as well!

Best regards,
	Maxim Levitsky
> 
> Regards,
> Daniel




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-02-15 14:51   ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Markus Armbruster
                       ` (2 preceding siblings ...)
  2020-02-24 14:45     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Daniel P. Berrangé
@ 2020-02-25 12:15     ` Max Reitz
  2020-02-25 16:48       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
  2020-03-03  9:18     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Maxim Levitsky
  4 siblings, 1 reply; 84+ messages in thread
From: Max Reitz @ 2020-02-25 12:15 UTC (permalink / raw)
  To: Markus Armbruster, Maxim Levitsky
  Cc: Kevin Wolf, John Snow, Daniel P. Berrangé, qemu-devel, qemu-block


[-- Attachment #1.1: Type: text/plain, Size: 2002 bytes --]

On 15.02.20 15:51, Markus Armbruster wrote:
> Review of this patch led to a lengthy QAPI schema design discussion.
> Let me try to condense it into a concrete proposal.
> 
> This is about the QAPI schema, and therefore about QMP.  The
> human-friendly interface is out of scope.  Not because it's not
> important (it clearly is!), only because we need to *focus* to have a
> chance at success.
> 
> I'm going to include a few design options.  I'll mark them "Option:".
> 
> The proposed "amend" interface takes a specification of desired state,
> and figures out how to get from here to there by itself.  LUKS keyslots
> are one part of desired state.
> 
> We commonly have eight LUKS keyslots.  Each keyslot is either active or
> inactive.  An active keyslot holds a secret.
> 
> Goal: a QAPI type for specifying desired state of LUKS keyslots.
> 
> Proposal:
> 
>     { 'enum': 'LUKSKeyslotState',
>       'data': [ 'active', 'inactive' ] }
> 
>     { 'struct': 'LUKSKeyslotActive',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int } }
> 
>     { 'struct': 'LUKSKeyslotInactive',
>       'data': { '*old-secret': 'str' } }
> 
>     { 'union': 'LUKSKeyslotAmend',
>       'base': { '*keyslot': 'int',
>                 'state': 'LUKSKeyslotState' }
>       'discriminator': 'state',
>       'data': { 'active': 'LUKSKeyslotActive',
>                 'inactive': 'LUKSKeyslotInactive' } }

Looks OK to me.  The only thing is that @old-secret kind of works as an
address, just like @keyslot, so it might also make sense to me to put
@keyslot/@old-secret into a union in the base structure.

(One of the problems that come to mind with that approach is that not
specifying either of @old-secret or @keyslot has different meanings for
activating/inactivating a keyslot: When activating it, it means “The
first unused one”; when deactivating it, it’s just an error because it
doesn’t really mean anything.)

*shrug*

Max


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-25 12:15     ` Max Reitz
@ 2020-02-25 16:48       ` Markus Armbruster
  2020-02-25 17:00         ` Max Reitz
  2020-02-25 17:18         ` Daniel P. Berrangé
  0 siblings, 2 replies; 84+ messages in thread
From: Markus Armbruster @ 2020-02-25 16:48 UTC (permalink / raw)
  To: Max Reitz
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Maxim Levitsky, John Snow

Max Reitz <mreitz@redhat.com> writes:

> On 15.02.20 15:51, Markus Armbruster wrote:
>> Review of this patch led to a lengthy QAPI schema design discussion.
>> Let me try to condense it into a concrete proposal.
>> 
>> This is about the QAPI schema, and therefore about QMP.  The
>> human-friendly interface is out of scope.  Not because it's not
>> important (it clearly is!), only because we need to *focus* to have a
>> chance at success.
>> 
>> I'm going to include a few design options.  I'll mark them "Option:".
>> 
>> The proposed "amend" interface takes a specification of desired state,
>> and figures out how to get from here to there by itself.  LUKS keyslots
>> are one part of desired state.
>> 
>> We commonly have eight LUKS keyslots.  Each keyslot is either active or
>> inactive.  An active keyslot holds a secret.
>> 
>> Goal: a QAPI type for specifying desired state of LUKS keyslots.
>> 
>> Proposal:
>> 
>>     { 'enum': 'LUKSKeyslotState',
>>       'data': [ 'active', 'inactive' ] }
>> 
>>     { 'struct': 'LUKSKeyslotActive',
>>       'data': { 'secret': 'str',
>>                 '*iter-time': 'int } }
>> 
>>     { 'struct': 'LUKSKeyslotInactive',
>>       'data': { '*old-secret': 'str' } }
>> 
>>     { 'union': 'LUKSKeyslotAmend',
>>       'base': { '*keyslot': 'int',
>>                 'state': 'LUKSKeyslotState' }
>>       'discriminator': 'state',
>>       'data': { 'active': 'LUKSKeyslotActive',
>>                 'inactive': 'LUKSKeyslotInactive' } }
>
> Looks OK to me.  The only thing is that @old-secret kind of works as an
> address, just like @keyslot,

It does.

>                              so it might also make sense to me to put
> @keyslot/@old-secret into a union in the base structure.

I'm fine with state-specific extra adressing modes (I better be, I
proposed them).

I'd also be fine with a single state-independent addressing mode, as
long as we can come up with sane semantics.  Less flexible when adding
states, but we almost certainly won't.

Let's see how we could merge my two addressing modes into one.

The two are

* active

  keyslot     old-secret      slot(s) selected
  absent      N/A             one inactive slot if exist, else error
  present     N/A             the slot given by @keyslot

* inactive

  keyslot     old-secret      slot(s) selected
  absent      absent          all keyslots
  present     absent          the slot given by @keyslot
  absent      present         all active slots holding @old-secret
  present     present         the slot given by @keyslot, error unless
                              it's active holding @old-secret

They conflict:

> (One of the problems that come to mind with that approach is that not
> specifying either of @old-secret or @keyslot has different meanings for
> activating/inactivating a keyslot: When activating it, it means “The
> first unused one”; when deactivating it, it’s just an error because it
> doesn’t really mean anything.)
>
> *shrug*

Note we we don't really care what "inactive, both absent" does.  My
proposed semantics are just the most regular I could find.  We can
therefore resolve the conflict by picking "active, both absent":

  keyslot     old-secret      slot(s) selected
  absent      absent          one inactive slot if exist, else error
  present     absent          the slot given by @keyslot
  absent      present         all active slots holding @old-secret
  present     present         the slot given by @keyslot, error unless
                              it's active holding @old-secret

Changes:

* inactive, both absent: changed; we select "one inactive slot" instead of
  "all slots".

  "All slots" is a no-op when the current state has no active keyslots,
  else error.

  "One inactive slot" is a no-op when the current state has one, else
  error.  Thus, we no-op rather than error in some states.

* active, keyslot absent or present, old-secret present: new; selects
  active slot(s) holding @old-secret, no-op when old-secret == secret,
  else error (no in place update)

Can do.  It's differently irregular, and has a few more combinations
that are basically useless, which I find unappealing.  Matter of taste,
I guess.

Anyone got strong feelings here?



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-25 16:48       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
@ 2020-02-25 17:00         ` Max Reitz
  2020-02-26  7:28           ` Markus Armbruster
  2020-02-25 17:18         ` Daniel P. Berrangé
  1 sibling, 1 reply; 84+ messages in thread
From: Max Reitz @ 2020-02-25 17:00 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Maxim Levitsky, John Snow


[-- Attachment #1.1: Type: text/plain, Size: 4565 bytes --]

On 25.02.20 17:48, Markus Armbruster wrote:
> Max Reitz <mreitz@redhat.com> writes:
> 
>> On 15.02.20 15:51, Markus Armbruster wrote:
>>> Review of this patch led to a lengthy QAPI schema design discussion.
>>> Let me try to condense it into a concrete proposal.
>>>
>>> This is about the QAPI schema, and therefore about QMP.  The
>>> human-friendly interface is out of scope.  Not because it's not
>>> important (it clearly is!), only because we need to *focus* to have a
>>> chance at success.
>>>
>>> I'm going to include a few design options.  I'll mark them "Option:".
>>>
>>> The proposed "amend" interface takes a specification of desired state,
>>> and figures out how to get from here to there by itself.  LUKS keyslots
>>> are one part of desired state.
>>>
>>> We commonly have eight LUKS keyslots.  Each keyslot is either active or
>>> inactive.  An active keyslot holds a secret.
>>>
>>> Goal: a QAPI type for specifying desired state of LUKS keyslots.
>>>
>>> Proposal:
>>>
>>>     { 'enum': 'LUKSKeyslotState',
>>>       'data': [ 'active', 'inactive' ] }
>>>
>>>     { 'struct': 'LUKSKeyslotActive',
>>>       'data': { 'secret': 'str',
>>>                 '*iter-time': 'int } }
>>>
>>>     { 'struct': 'LUKSKeyslotInactive',
>>>       'data': { '*old-secret': 'str' } }
>>>
>>>     { 'union': 'LUKSKeyslotAmend',
>>>       'base': { '*keyslot': 'int',
>>>                 'state': 'LUKSKeyslotState' }
>>>       'discriminator': 'state',
>>>       'data': { 'active': 'LUKSKeyslotActive',
>>>                 'inactive': 'LUKSKeyslotInactive' } }
>>
>> Looks OK to me.  The only thing is that @old-secret kind of works as an
>> address, just like @keyslot,
> 
> It does.
> 
>>                              so it might also make sense to me to put
>> @keyslot/@old-secret into a union in the base structure.
> 
> I'm fine with state-specific extra adressing modes (I better be, I
> proposed them).
> 
> I'd also be fine with a single state-independent addressing mode, as
> long as we can come up with sane semantics.  Less flexible when adding
> states, but we almost certainly won't.
> 
> Let's see how we could merge my two addressing modes into one.
> 
> The two are
> 
> * active
> 
>   keyslot     old-secret      slot(s) selected
>   absent      N/A             one inactive slot if exist, else error
>   present     N/A             the slot given by @keyslot

Oh, I thought that maybe we could use old-secret here, too, for
modifying the iter-time.  But if old-secret makes no sense for
to-be-active slots, then there’s little point in putting old-secret in
the base.

(OTOH, specifying old-secret for to-be-active slots does have a sensible
meaning; it’s just that we won’t support changing anything about
already-active slots, except making them inactive.  So that might be an
argument for not making it a syntactic error, but just a semantic error.)

[...]

> Note we we don't really care what "inactive, both absent" does.  My
> proposed semantics are just the most regular I could find.  We can
> therefore resolve the conflict by picking "active, both absent":
> 
>   keyslot     old-secret      slot(s) selected
>   absent      absent          one inactive slot if exist, else error
>   present     absent          the slot given by @keyslot
>   absent      present         all active slots holding @old-secret
>   present     present         the slot given by @keyslot, error unless
>                               it's active holding @old-secret
> 
> Changes:
> 
> * inactive, both absent: changed; we select "one inactive slot" instead of
>   "all slots".
> 
>   "All slots" is a no-op when the current state has no active keyslots,
>   else error.
> 
>   "One inactive slot" is a no-op when the current state has one, else
>   error.  Thus, we no-op rather than error in some states.
> 
> * active, keyslot absent or present, old-secret present: new; selects
>   active slot(s) holding @old-secret, no-op when old-secret == secret,
>   else error (no in place update)
> 
> Can do.  It's differently irregular, and has a few more combinations
> that are basically useless, which I find unappealing.  Matter of taste,
> I guess.
> 
> Anyone got strong feelings here?

The only strong feeling I have is that I absolutely don’t have a strong
feeling about this. :)

As such, I think we should just treat my rambling as such and stick to
your proposal, since we’ve already gathered support for it.

Max


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-25 16:48       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
  2020-02-25 17:00         ` Max Reitz
@ 2020-02-25 17:18         ` Daniel P. Berrangé
  1 sibling, 0 replies; 84+ messages in thread
From: Daniel P. Berrangé @ 2020-02-25 17:18 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, qemu-block, qemu-devel, Maxim Levitsky, Max Reitz, John Snow

On Tue, Feb 25, 2020 at 05:48:02PM +0100, Markus Armbruster wrote:
> Max Reitz <mreitz@redhat.com> writes:
> 
> > On 15.02.20 15:51, Markus Armbruster wrote:
> >> Review of this patch led to a lengthy QAPI schema design discussion.
> >> Let me try to condense it into a concrete proposal.
> >> 
> >> This is about the QAPI schema, and therefore about QMP.  The
> >> human-friendly interface is out of scope.  Not because it's not
> >> important (it clearly is!), only because we need to *focus* to have a
> >> chance at success.
> >> 
> >> I'm going to include a few design options.  I'll mark them "Option:".
> >> 
> >> The proposed "amend" interface takes a specification of desired state,
> >> and figures out how to get from here to there by itself.  LUKS keyslots
> >> are one part of desired state.
> >> 
> >> We commonly have eight LUKS keyslots.  Each keyslot is either active or
> >> inactive.  An active keyslot holds a secret.
> >> 
> >> Goal: a QAPI type for specifying desired state of LUKS keyslots.
> >> 
> >> Proposal:
> >> 
> >>     { 'enum': 'LUKSKeyslotState',
> >>       'data': [ 'active', 'inactive' ] }
> >> 
> >>     { 'struct': 'LUKSKeyslotActive',
> >>       'data': { 'secret': 'str',
> >>                 '*iter-time': 'int } }
> >> 
> >>     { 'struct': 'LUKSKeyslotInactive',
> >>       'data': { '*old-secret': 'str' } }
> >> 
> >>     { 'union': 'LUKSKeyslotAmend',
> >>       'base': { '*keyslot': 'int',
> >>                 'state': 'LUKSKeyslotState' }
> >>       'discriminator': 'state',
> >>       'data': { 'active': 'LUKSKeyslotActive',
> >>                 'inactive': 'LUKSKeyslotInactive' } }
> >
> > Looks OK to me.  The only thing is that @old-secret kind of works as an
> > address, just like @keyslot,
> 
> It does.
> 
> >                              so it might also make sense to me to put
> > @keyslot/@old-secret into a union in the base structure.
> 
> I'm fine with state-specific extra adressing modes (I better be, I
> proposed them).
> 
> I'd also be fine with a single state-independent addressing mode, as
> long as we can come up with sane semantics.  Less flexible when adding
> states, but we almost certainly won't.
> 
> Let's see how we could merge my two addressing modes into one.
> 
> The two are
> 
> * active
> 
>   keyslot     old-secret      slot(s) selected
>   absent      N/A             one inactive slot if exist, else error
>   present     N/A             the slot given by @keyslot
> 
> * inactive
> 
>   keyslot     old-secret      slot(s) selected
>   absent      absent          all keyslots
>   present     absent          the slot given by @keyslot
>   absent      present         all active slots holding @old-secret
>   present     present         the slot given by @keyslot, error unless
>                               it's active holding @old-secret
> 
> They conflict:
> 
> > (One of the problems that come to mind with that approach is that not
> > specifying either of @old-secret or @keyslot has different meanings for
> > activating/inactivating a keyslot: When activating it, it means “The
> > first unused one”; when deactivating it, it’s just an error because it
> > doesn’t really mean anything.)
> >
> > *shrug*
> 
> Note we we don't really care what "inactive, both absent" does.  My
> proposed semantics are just the most regular I could find.  We can
> therefore resolve the conflict by picking "active, both absent":
> 
>   keyslot     old-secret      slot(s) selected
>   absent      absent          one inactive slot if exist, else error
>   present     absent          the slot given by @keyslot
>   absent      present         all active slots holding @old-secret
>   present     present         the slot given by @keyslot, error unless
>                               it's active holding @old-secret
> 
> Changes:
> 
> * inactive, both absent: changed; we select "one inactive slot" instead of
>   "all slots".
> 
>   "All slots" is a no-op when the current state has no active keyslots,
>   else error.
> 
>   "One inactive slot" is a no-op when the current state has one, else
>   error.  Thus, we no-op rather than error in some states.
> 
> * active, keyslot absent or present, old-secret present: new; selects
>   active slot(s) holding @old-secret, no-op when old-secret == secret,
>   else error (no in place update)
> 
> Can do.  It's differently irregular, and has a few more combinations
> that are basically useless, which I find unappealing.  Matter of taste,
> I guess.
> 
> Anyone got strong feelings here?

I don't feel like the changes give us any real world benefit, and
especially think deleting one arbitrary slot is just wierd.

IMHO, inactive with both keyslot & old-secret missing should just
be an error condition.


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-25 17:00         ` Max Reitz
@ 2020-02-26  7:28           ` Markus Armbruster
  2020-02-26  9:18             ` Maxim Levitsky
  0 siblings, 1 reply; 84+ messages in thread
From: Markus Armbruster @ 2020-02-26  7:28 UTC (permalink / raw)
  To: Max Reitz
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Maxim Levitsky, John Snow

Max Reitz <mreitz@redhat.com> writes:

> On 25.02.20 17:48, Markus Armbruster wrote:
>> Max Reitz <mreitz@redhat.com> writes:
>> 
>>> On 15.02.20 15:51, Markus Armbruster wrote:
>>>> Review of this patch led to a lengthy QAPI schema design discussion.
>>>> Let me try to condense it into a concrete proposal.
>>>>
>>>> This is about the QAPI schema, and therefore about QMP.  The
>>>> human-friendly interface is out of scope.  Not because it's not
>>>> important (it clearly is!), only because we need to *focus* to have a
>>>> chance at success.
>>>>
>>>> I'm going to include a few design options.  I'll mark them "Option:".
>>>>
>>>> The proposed "amend" interface takes a specification of desired state,
>>>> and figures out how to get from here to there by itself.  LUKS keyslots
>>>> are one part of desired state.
>>>>
>>>> We commonly have eight LUKS keyslots.  Each keyslot is either active or
>>>> inactive.  An active keyslot holds a secret.
>>>>
>>>> Goal: a QAPI type for specifying desired state of LUKS keyslots.
>>>>
>>>> Proposal:
>>>>
>>>>     { 'enum': 'LUKSKeyslotState',
>>>>       'data': [ 'active', 'inactive' ] }
>>>>
>>>>     { 'struct': 'LUKSKeyslotActive',
>>>>       'data': { 'secret': 'str',
>>>>                 '*iter-time': 'int } }
>>>>
>>>>     { 'struct': 'LUKSKeyslotInactive',
>>>>       'data': { '*old-secret': 'str' } }
>>>>
>>>>     { 'union': 'LUKSKeyslotAmend',
>>>>       'base': { '*keyslot': 'int',
>>>>                 'state': 'LUKSKeyslotState' }
>>>>       'discriminator': 'state',
>>>>       'data': { 'active': 'LUKSKeyslotActive',
>>>>                 'inactive': 'LUKSKeyslotInactive' } }
>>>
>>> Looks OK to me.  The only thing is that @old-secret kind of works as an
>>> address, just like @keyslot,
>> 
>> It does.
>> 
>>>                              so it might also make sense to me to put
>>> @keyslot/@old-secret into a union in the base structure.
>> 
>> I'm fine with state-specific extra adressing modes (I better be, I
>> proposed them).
>> 
>> I'd also be fine with a single state-independent addressing mode, as
>> long as we can come up with sane semantics.  Less flexible when adding
>> states, but we almost certainly won't.
>> 
>> Let's see how we could merge my two addressing modes into one.
>> 
>> The two are
>> 
>> * active
>> 
>>   keyslot     old-secret      slot(s) selected
>>   absent      N/A             one inactive slot if exist, else error
>>   present     N/A             the slot given by @keyslot
>
> Oh, I thought that maybe we could use old-secret here, too, for
> modifying the iter-time.

Update in place is unsafe.

>                           But if old-secret makes no sense for
> to-be-active slots, then there’s little point in putting old-secret in
> the base.
>
> (OTOH, specifying old-secret for to-be-active slots does have a sensible
> meaning; it’s just that we won’t support changing anything about
> already-active slots, except making them inactive.  So that might be an
> argument for not making it a syntactic error, but just a semantic error.)

Matter of taste.  I like to keep simple things syntactic, and thus
visible in introspection.

> [...]
>
>> Note we we don't really care what "inactive, both absent" does.  My
>> proposed semantics are just the most regular I could find.  We can
>> therefore resolve the conflict by picking "active, both absent":
>> 
>>   keyslot     old-secret      slot(s) selected
>>   absent      absent          one inactive slot if exist, else error
>>   present     absent          the slot given by @keyslot
>>   absent      present         all active slots holding @old-secret
>>   present     present         the slot given by @keyslot, error unless
>>                               it's active holding @old-secret
>> 
>> Changes:
>> 
>> * inactive, both absent: changed; we select "one inactive slot" instead of
>>   "all slots".
>> 
>>   "All slots" is a no-op when the current state has no active keyslots,
>>   else error.
>> 
>>   "One inactive slot" is a no-op when the current state has one, else
>>   error.  Thus, we no-op rather than error in some states.
>> 
>> * active, keyslot absent or present, old-secret present: new; selects
>>   active slot(s) holding @old-secret, no-op when old-secret == secret,
>>   else error (no in place update)
>> 
>> Can do.  It's differently irregular, and has a few more combinations
>> that are basically useless, which I find unappealing.  Matter of taste,
>> I guess.
>> 
>> Anyone got strong feelings here?
>
> The only strong feeling I have is that I absolutely don’t have a strong
> feeling about this. :)
>
> As such, I think we should just treat my rambling as such and stick to
> your proposal, since we’ve already gathered support for it.

Thanks!



^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots
  2020-02-26  7:28           ` Markus Armbruster
@ 2020-02-26  9:18             ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-02-26  9:18 UTC (permalink / raw)
  To: Markus Armbruster, Max Reitz
  Cc: Kevin Wolf, John Snow, Daniel P.Berrangé, qemu-devel, qemu-block

On Wed, 2020-02-26 at 08:28 +0100, Markus Armbruster wrote:
> Max Reitz <mreitz@redhat.com> writes:
> 
> > On 25.02.20 17:48, Markus Armbruster wrote:
> > > Max Reitz <mreitz@redhat.com> writes:
> > > 
> > > > On 15.02.20 15:51, Markus Armbruster wrote:
> > > > > Review of this patch led to a lengthy QAPI schema design discussion.
> > > > > Let me try to condense it into a concrete proposal.
> > > > > 
> > > > > This is about the QAPI schema, and therefore about QMP.  The
> > > > > human-friendly interface is out of scope.  Not because it's not
> > > > > important (it clearly is!), only because we need to *focus* to have a
> > > > > chance at success.
> > > > > 
> > > > > I'm going to include a few design options.  I'll mark them "Option:".
> > > > > 
> > > > > The proposed "amend" interface takes a specification of desired state,
> > > > > and figures out how to get from here to there by itself.  LUKS keyslots
> > > > > are one part of desired state.
> > > > > 
> > > > > We commonly have eight LUKS keyslots.  Each keyslot is either active or
> > > > > inactive.  An active keyslot holds a secret.
> > > > > 
> > > > > Goal: a QAPI type for specifying desired state of LUKS keyslots.
> > > > > 
> > > > > Proposal:
> > > > > 
> > > > >     { 'enum': 'LUKSKeyslotState',
> > > > >       'data': [ 'active', 'inactive' ] }
> > > > > 
> > > > >     { 'struct': 'LUKSKeyslotActive',
> > > > >       'data': { 'secret': 'str',
> > > > >                 '*iter-time': 'int } }
> > > > > 
> > > > >     { 'struct': 'LUKSKeyslotInactive',
> > > > >       'data': { '*old-secret': 'str' } }
> > > > > 
> > > > >     { 'union': 'LUKSKeyslotAmend',
> > > > >       'base': { '*keyslot': 'int',
> > > > >                 'state': 'LUKSKeyslotState' }
> > > > >       'discriminator': 'state',
> > > > >       'data': { 'active': 'LUKSKeyslotActive',
> > > > >                 'inactive': 'LUKSKeyslotInactive' } }
> > > > 
> > > > Looks OK to me.  The only thing is that @old-secret kind of works as an
> > > > address, just like @keyslot,
> > > 
> > > It does.
> > > 
> > > >                              so it might also make sense to me to put
> > > > @keyslot/@old-secret into a union in the base structure.
> > > 
> > > I'm fine with state-specific extra adressing modes (I better be, I
> > > proposed them).
> > > 
> > > I'd also be fine with a single state-independent addressing mode, as
> > > long as we can come up with sane semantics.  Less flexible when adding
> > > states, but we almost certainly won't.
> > > 
> > > Let's see how we could merge my two addressing modes into one.
> > > 
> > > The two are
> > > 
> > > * active
> > > 
> > >   keyslot     old-secret      slot(s) selected
> > >   absent      N/A             one inactive slot if exist, else error
> > >   present     N/A             the slot given by @keyslot
> > 
> > Oh, I thought that maybe we could use old-secret here, too, for
> > modifying the iter-time.
> 
> Update in place is unsafe.
> 
> >                           But if old-secret makes no sense for
> > to-be-active slots, then there’s little point in putting old-secret in
> > the base.
> > 
> > (OTOH, specifying old-secret for to-be-active slots does have a sensible
> > meaning; it’s just that we won’t support changing anything about
> > already-active slots, except making them inactive.  So that might be an
> > argument for not making it a syntactic error, but just a semantic error.)
> 
> Matter of taste.  I like to keep simple things syntactic, and thus
> visible in introspection.
> 
> > [...]
> > 
> > > Note we we don't really care what "inactive, both absent" does.  My
> > > proposed semantics are just the most regular I could find.  We can
> > > therefore resolve the conflict by picking "active, both absent":
> > > 
> > >   keyslot     old-secret      slot(s) selected
> > >   absent      absent          one inactive slot if exist, else error
> > >   present     absent          the slot given by @keyslot
> > >   absent      present         all active slots holding @old-secret
> > >   present     present         the slot given by @keyslot, error unless
> > >                               it's active holding @old-secret
> > > 
> > > Changes:
> > > 
> > > * inactive, both absent: changed; we select "one inactive slot" instead of
> > >   "all slots".
> > > 
> > >   "All slots" is a no-op when the current state has no active keyslots,
> > >   else error.
> > > 
> > >   "One inactive slot" is a no-op when the current state has one, else
> > >   error.  Thus, we no-op rather than error in some states.
> > > 
> > > * active, keyslot absent or present, old-secret present: new; selects
> > >   active slot(s) holding @old-secret, no-op when old-secret == secret,
> > >   else error (no in place update)
> > > 
> > > Can do.  It's differently irregular, and has a few more combinations
> > > that are basically useless, which I find unappealing.  Matter of taste,
> > > I guess.
> > > 
> > > Anyone got strong feelings here?
> > 
> > The only strong feeling I have is that I absolutely don’t have a strong
> > feeling about this. :)
> > 
> > As such, I think we should just treat my rambling as such and stick to
> > your proposal, since we’ve already gathered support for it.
> 
> Thanks!

So in summary, do I have the green light to implement the Markus's proposal as is?

Best regards,
	Maxim Levitsky




^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-02-15 14:51   ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Markus Armbruster
                       ` (3 preceding siblings ...)
  2020-02-25 12:15     ` Max Reitz
@ 2020-03-03  9:18     ` Maxim Levitsky
  2020-03-05 12:15       ` Maxim Levitsky
  4 siblings, 1 reply; 84+ messages in thread
From: Maxim Levitsky @ 2020-03-03  9:18 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Daniel P.Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

On Sat, 2020-02-15 at 15:51 +0100, Markus Armbruster wrote:
> Review of this patch led to a lengthy QAPI schema design discussion.
> Let me try to condense it into a concrete proposal.
> 
> This is about the QAPI schema, and therefore about QMP.  The
> human-friendly interface is out of scope.  Not because it's not
> important (it clearly is!), only because we need to *focus* to have a
> chance at success.
> 
> I'm going to include a few design options.  I'll mark them "Option:".
> 
> The proposed "amend" interface takes a specification of desired state,
> and figures out how to get from here to there by itself.  LUKS keyslots
> are one part of desired state.
> 
> We commonly have eight LUKS keyslots.  Each keyslot is either active or
> inactive.  An active keyslot holds a secret.
> 
> Goal: a QAPI type for specifying desired state of LUKS keyslots.
> 
> Proposal:
> 
>     { 'enum': 'LUKSKeyslotState',
>       'data': [ 'active', 'inactive' ] }
> 
>     { 'struct': 'LUKSKeyslotActive',
>       'data': { 'secret': 'str',
>                 '*iter-time': 'int } }
> 
>     { 'struct': 'LUKSKeyslotInactive',
>       'data': { '*old-secret': 'str' } }
> 
>     { 'union': 'LUKSKeyslotAmend',
>       'base': { '*keyslot': 'int',
>                 'state': 'LUKSKeyslotState' }
>       'discriminator': 'state',
>       'data': { 'active': 'LUKSKeyslotActive',
>                 'inactive': 'LUKSKeyslotInactive' } }
> 
> LUKSKeyslotAmend specifies desired state for a set of keyslots.
> 
> Four cases:
> 
> * @state is "active"
> 
>   Desired state is active holding the secret given by @secret.  Optional
>   @iter-time tweaks key stretching.
> 
>   The keyslot is chosen either by the user or by the system, as follows:
> 
>   - @keyslot absent
> 
>     One inactive keyslot chosen by the system.  If none exists, error.
> 
>   - @keyslot present
> 
>     The keyslot given by @keyslot.
> 
>     If it's already active holding @secret, no-op.  Rationale: the
>     current state is the desired state.
> 
>     If it's already active holding another secret, error.  Rationale:
>     update in place is unsafe.
> 
>     Option: delete the "already active holding @secret" case.  Feels
>     inelegant to me.  Okay if it makes things substantially simpler.
> 
> * @state is "inactive"
> 
>   Desired state is inactive.
> 
>   Error if the current state has active keyslots, but the desired state
>   has none.
> 
>   The user choses the keyslot by number and/or by the secret it holds,
>   as follows:
> 
>   - @keyslot absent, @old-secret present
> 
>     All active keyslots holding @old-secret.  If none exists, error.
> 
>   - @keyslot present, @old-secret absent
> 
>     The keyslot given by @keyslot.
> 
>     If it's already inactive, no-op.  Rationale: the current state is
>     the desired state.
> 
>   - both @keyslot and @old-secret present
> 
>     The keyslot given by keyslot.
> 
>     If it's inactive or holds a secret other than @old-secret, error.
> 
>     Option: error regardless of @old-secret, if that makes things
>     simpler.
> 
>   - neither @keyslot not @old-secret present
> 
>     All keyslots.  Note that this will error out due to "desired state
>     has no active keyslots" unless the current state has none, either.
> 
>     Option: error out unconditionally.
> 
> Note that LUKSKeyslotAmend can specify only one desired state for
> commonly just one keyslot.  Rationale: this satisfies practical needs.
> An array of LUKSKeyslotAmend could specify desired state for all
> keyslots.  However, multiple array elements could then apply to the same
> slot.  We'd have to specify how to resolve such conflicts, and we'd have
> to code up conflict detection.  Not worth it.
> 
> Examples:
> 
> * Add a secret to some free keyslot:
> 
>   { "state": "active", "secret": "CIA/GRU/MI6" }
> 
> * Deactivate all keyslots holding a secret:
> 
>   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
> 
> * Add a secret to a specific keyslot:
> 
>   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
> 
> * Deactivate a specific keyslot:
> 
>   { "state": "inactive", "keyslot": 0 }
> 
>   Possibly less dangerous:
> 
>   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
> 
> Option: Make use of Max's patches to support optional union tag with
> default value to let us default @state to "active".  I doubt this makes
> much of a difference in QMP.  A human-friendly interface should probably
> be higher level anyway (Daniel pointed to cryptsetup).
> 
> Option: LUKSKeyslotInactive member @old-secret could also be named
> @secret.  I don't care.
> 
> Option: delete @keyslot.  It provides low-level slot access.
> Complicates the interface.  Fine if we need lov-level slot access.  Do
> we?
> 
> I apologize for the time it has taken me to write this.
> 
> Comments?

I tried today to implement this but I hit a very unpleasant roadblock:

Since QCrypto is generic (even though it only implements currently luks for raw/qcow2 usage,
and legacy qcow2 aes encryption), I still can't assume that this is always the case.
Thus I implemented the Qcrypto amend API in this way:

##
# @QCryptoBlockAmendOptions:
#
# The options that are available for all encryption formats
# when amending encryption settings
#
# Since: 5.0
##
{ 'union': 'QCryptoBlockAmendOptions',
  'base': 'QCryptoBlockOptionsBase',
  'discriminator': 'format',
  'data': {
          'luks': 'QCryptoBlockAmendOptionsLUKS' } }

However the QCryptoBlockAmendOptionsLUKS is a union too to be in line with the API proposal,
but that is not supported on QAPI level and after I and Markus talked about we are not sure
that it is worth it to implement this support only for this case.

So far I see the following solutions


1. Drop the QCryptoBlockAmendOptionsLUKS union for now.
This will bring the schema pretty much to be the same as my original proposal,
however the API will be the same thus once nested unions are implemented this union
can always be introduced again.

2. Drop the QCryptoBlockAmendOptions union. Strictly speaking this union is not needed
since it only has one member anyway, however this union is used both by qcow2 QAPI scheme,
so that it doesn't hardcode an encryption format for amend just like it doesn't for creation,
(this can be hardcoded for now as well for now as long as we don't have more amendable encryption formats).
However I also use the QCryptoBlockAmendOptions in C code in QCrypto API thus it will be ugly to use the 
QCryptoBlockAmendOptionsLUKS instead.


3. Make QCryptoBlockAmendOptionsLUKS a struct and add to it a nested member with new union type 
(say QCryptoBlockAmendOptionsLUKS1) which will be exactly as QCryptoBlockAmendOptionsLUKS was.

This IMHO is even uglier since it changes the API (which we can't later fix) and adds both a dummy struct
field and a dummy struct name.

I personally vote 1.

Best regards,
	Maxim Levitsky






^ permalink raw reply	[flat|nested] 84+ messages in thread

* Re: QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management)
  2020-03-03  9:18     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Maxim Levitsky
@ 2020-03-05 12:15       ` Maxim Levitsky
  0 siblings, 0 replies; 84+ messages in thread
From: Maxim Levitsky @ 2020-03-05 12:15 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Daniel P.Berrangé,
	qemu-block, qemu-devel, Max Reitz, John Snow

On Tue, 2020-03-03 at 11:18 +0200, Maxim Levitsky wrote:
> On Sat, 2020-02-15 at 15:51 +0100, Markus Armbruster wrote:
> > Review of this patch led to a lengthy QAPI schema design discussion.
> > Let me try to condense it into a concrete proposal.
> > 
> > This is about the QAPI schema, and therefore about QMP.  The
> > human-friendly interface is out of scope.  Not because it's not
> > important (it clearly is!), only because we need to *focus* to have a
> > chance at success.
> > 
> > I'm going to include a few design options.  I'll mark them "Option:".
> > 
> > The proposed "amend" interface takes a specification of desired state,
> > and figures out how to get from here to there by itself.  LUKS keyslots
> > are one part of desired state.
> > 
> > We commonly have eight LUKS keyslots.  Each keyslot is either active or
> > inactive.  An active keyslot holds a secret.
> > 
> > Goal: a QAPI type for specifying desired state of LUKS keyslots.
> > 
> > Proposal:
> > 
> >     { 'enum': 'LUKSKeyslotState',
> >       'data': [ 'active', 'inactive' ] }
> > 
> >     { 'struct': 'LUKSKeyslotActive',
> >       'data': { 'secret': 'str',
> >                 '*iter-time': 'int } }
> > 
> >     { 'struct': 'LUKSKeyslotInactive',
> >       'data': { '*old-secret': 'str' } }
> > 
> >     { 'union': 'LUKSKeyslotAmend',
> >       'base': { '*keyslot': 'int',
> >                 'state': 'LUKSKeyslotState' }
> >       'discriminator': 'state',
> >       'data': { 'active': 'LUKSKeyslotActive',
> >                 'inactive': 'LUKSKeyslotInactive' } }
> > 
> > LUKSKeyslotAmend specifies desired state for a set of keyslots.
> > 
> > Four cases:
> > 
> > * @state is "active"
> > 
> >   Desired state is active holding the secret given by @secret.  Optional
> >   @iter-time tweaks key stretching.
> > 
> >   The keyslot is chosen either by the user or by the system, as follows:
> > 
> >   - @keyslot absent
> > 
> >     One inactive keyslot chosen by the system.  If none exists, error.
> > 
> >   - @keyslot present
> > 
> >     The keyslot given by @keyslot.
> > 
> >     If it's already active holding @secret, no-op.  Rationale: the
> >     current state is the desired state.
> > 
> >     If it's already active holding another secret, error.  Rationale:
> >     update in place is unsafe.
> > 
> >     Option: delete the "already active holding @secret" case.  Feels
> >     inelegant to me.  Okay if it makes things substantially simpler.
> > 
> > * @state is "inactive"
> > 
> >   Desired state is inactive.
> > 
> >   Error if the current state has active keyslots, but the desired state
> >   has none.
> > 
> >   The user choses the keyslot by number and/or by the secret it holds,
> >   as follows:
> > 
> >   - @keyslot absent, @old-secret present
> > 
> >     All active keyslots holding @old-secret.  If none exists, error.
> > 
> >   - @keyslot present, @old-secret absent
> > 
> >     The keyslot given by @keyslot.
> > 
> >     If it's already inactive, no-op.  Rationale: the current state is
> >     the desired state.
> > 
> >   - both @keyslot and @old-secret present
> > 
> >     The keyslot given by keyslot.
> > 
> >     If it's inactive or holds a secret other than @old-secret, error.
> > 
> >     Option: error regardless of @old-secret, if that makes things
> >     simpler.
> > 
> >   - neither @keyslot not @old-secret present
> > 
> >     All keyslots.  Note that this will error out due to "desired state
> >     has no active keyslots" unless the current state has none, either.
> > 
> >     Option: error out unconditionally.
> > 
> > Note that LUKSKeyslotAmend can specify only one desired state for
> > commonly just one keyslot.  Rationale: this satisfies practical needs.
> > An array of LUKSKeyslotAmend could specify desired state for all
> > keyslots.  However, multiple array elements could then apply to the same
> > slot.  We'd have to specify how to resolve such conflicts, and we'd have
> > to code up conflict detection.  Not worth it.
> > 
> > Examples:
> > 
> > * Add a secret to some free keyslot:
> > 
> >   { "state": "active", "secret": "CIA/GRU/MI6" }
> > 
> > * Deactivate all keyslots holding a secret:
> > 
> >   { "state": "inactive", "old-secret": "CIA/GRU/MI6" }
> > 
> > * Add a secret to a specific keyslot:
> > 
> >   { "state": "active", "secret": "CIA/GRU/MI6", "keyslot": 0 }
> > 
> > * Deactivate a specific keyslot:
> > 
> >   { "state": "inactive", "keyslot": 0 }
> > 
> >   Possibly less dangerous:
> > 
> >   { "state": "inactive", "keyslot": 0, "old-secret": "CIA/GRU/MI6" }
> > 
> > Option: Make use of Max's patches to support optional union tag with
> > default value to let us default @state to "active".  I doubt this makes
> > much of a difference in QMP.  A human-friendly interface should probably
> > be higher level anyway (Daniel pointed to cryptsetup).
> > 
> > Option: LUKSKeyslotInactive member @old-secret could also be named
> > @secret.  I don't care.
> > 
> > Option: delete @keyslot.  It provides low-level slot access.
> > Complicates the interface.  Fine if we need lov-level slot access.  Do
> > we?
> > 
> > I apologize for the time it has taken me to write this.
> > 
> > Comments?
> 
> I tried today to implement this but I hit a very unpleasant roadblock:
> 
> Since QCrypto is generic (even though it only implements currently luks for raw/qcow2 usage,
> and legacy qcow2 aes encryption), I still can't assume that this is always the case.
> Thus I implemented the Qcrypto amend API in this way:
> 
> ##
> # @QCryptoBlockAmendOptions:
> #
> # The options that are available for all encryption formats
> # when amending encryption settings
> #
> # Since: 5.0
> ##
> { 'union': 'QCryptoBlockAmendOptions',
>   'base': 'QCryptoBlockOptionsBase',
>   'discriminator': 'format',
>   'data': {
>           'luks': 'QCryptoBlockAmendOptionsLUKS' } }
> 
> However the QCryptoBlockAmendOptionsLUKS is a union too to be in line with the API proposal,
> but that is not supported on QAPI level and after I and Markus talked about we are not sure
> that it is worth it to implement this support only for this case.
> 
> So far I see the following solutions
> 
> 
> 1. Drop the QCryptoBlockAmendOptionsLUKS union for now.
> This will bring the schema pretty much to be the same as my original proposal,
> however the API will be the same thus once nested unions are implemented this union
> can always be introduced again.
> 
> 2. Drop the QCryptoBlockAmendOptions union. Strictly speaking this union is not needed
> since it only has one member anyway, however this union is used both by qcow2 QAPI scheme,
> so that it doesn't hardcode an encryption format for amend just like it doesn't for creation,
> (this can be hardcoded for now as well for now as long as we don't have more amendable encryption formats).
> However I also use the QCryptoBlockAmendOptions in C code in QCrypto API thus it will be ugly to use the 
> QCryptoBlockAmendOptionsLUKS instead.
> 
> 
> 3. Make QCryptoBlockAmendOptionsLUKS a struct and add to it a nested member with new union type 
> (say QCryptoBlockAmendOptionsLUKS1) which will be exactly as QCryptoBlockAmendOptionsLUKS was.
> 
> This IMHO is even uglier since it changes the API (which we can't later fix) and adds both a dummy struct
> field and a dummy struct name.
> 
> I personally vote 1.

Any update?

> 
> Best regards,
> 	Maxim Levitsky
> 
> 
> 




^ permalink raw reply	[flat|nested] 84+ messages in thread

end of thread, other threads:[~2020-03-05 12:17 UTC | newest]

Thread overview: 84+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-01-14 19:33 [PATCH 00/13] LUKS: encryption slot management using amend interface Maxim Levitsky
2020-01-14 19:33 ` [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment Maxim Levitsky
2020-01-28 16:59   ` Daniel P. Berrangé
2020-01-29 17:49     ` Maxim Levitsky
2020-01-14 19:33 ` [PATCH 02/13] qcrypto-luks: implement encryption key management Maxim Levitsky
2020-01-21  7:54   ` Markus Armbruster
2020-01-21 13:13     ` Maxim Levitsky
2020-01-28 17:11       ` Daniel P. Berrangé
2020-01-28 17:32         ` Daniel P. Berrangé
2020-01-29 17:54           ` Maxim Levitsky
2020-01-30 12:38           ` Kevin Wolf
2020-01-30 12:53             ` Daniel P. Berrangé
2020-01-30 14:23               ` Kevin Wolf
2020-01-30 14:30                 ` Daniel P. Berrangé
2020-01-30 14:53                 ` Markus Armbruster
2020-01-30 14:47               ` Markus Armbruster
2020-01-30 15:01                 ` Daniel P. Berrangé
2020-01-30 16:37                   ` Markus Armbruster
2020-02-05  8:24                     ` Markus Armbruster
2020-02-05  9:30                       ` Kevin Wolf
2020-02-05 10:03                         ` Markus Armbruster
2020-02-05 11:02                           ` Kevin Wolf
2020-02-05 14:31                             ` Markus Armbruster
2020-02-06 13:44                               ` Markus Armbruster
2020-02-06 13:49                                 ` Daniel P. Berrangé
2020-02-06 14:20                                   ` Max Reitz
2020-02-05 10:23                         ` Daniel P. Berrangé
2020-02-05 14:31                           ` Markus Armbruster
2020-02-06 13:20                             ` Markus Armbruster
2020-02-06 13:36                               ` Daniel P. Berrangé
2020-02-06 14:25                                 ` Kevin Wolf
2020-02-06 15:19                                   ` Markus Armbruster
2020-02-06 15:23                                     ` Maxim Levitsky
2020-01-30 15:45                 ` Maxim Levitsky
2020-01-28 17:21   ` Daniel P. Berrangé
2020-01-30 12:58     ` Maxim Levitsky
2020-02-15 14:51   ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Markus Armbruster
2020-02-16  8:05     ` Maxim Levitsky
2020-02-17  6:45       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
2020-02-17  8:19         ` Maxim Levitsky
2020-02-17 10:37     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Kevin Wolf
2020-02-17 11:07       ` Maxim Levitsky
2020-02-24 14:46         ` Daniel P. Berrangé
2020-02-24 14:50           ` Maxim Levitsky
2020-02-17 12:28       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
2020-02-17 12:44         ` Eric Blake
2020-02-24 14:43         ` Daniel P. Berrangé
2020-02-24 14:45     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Daniel P. Berrangé
2020-02-25 12:15     ` Max Reitz
2020-02-25 16:48       ` QAPI schema for desired state of LUKS keyslots Markus Armbruster
2020-02-25 17:00         ` Max Reitz
2020-02-26  7:28           ` Markus Armbruster
2020-02-26  9:18             ` Maxim Levitsky
2020-02-25 17:18         ` Daniel P. Berrangé
2020-03-03  9:18     ` QAPI schema for desired state of LUKS keyslots (was: [PATCH 02/13] qcrypto-luks: implement encryption key management) Maxim Levitsky
2020-03-05 12:15       ` Maxim Levitsky
2020-01-14 19:33 ` [PATCH 03/13] block: amend: add 'force' option Maxim Levitsky
2020-01-14 19:33 ` [PATCH 04/13] block: amend: separate amend and create options for qemu-img Maxim Levitsky
2020-01-28 17:23   ` Daniel P. Berrangé
2020-01-30 15:54     ` Maxim Levitsky
2020-01-14 19:33 ` [PATCH 05/13] block/crypto: rename two functions Maxim Levitsky
2020-01-14 19:33 ` [PATCH 06/13] block/crypto: implement the encryption key management Maxim Levitsky
2020-01-28 17:27   ` Daniel P. Berrangé
2020-01-30 16:08     ` Maxim Levitsky
2020-01-14 19:33 ` [PATCH 07/13] qcow2: extend qemu-img amend interface with crypto options Maxim Levitsky
2020-01-28 17:30   ` Daniel P. Berrangé
2020-01-30 16:09     ` Maxim Levitsky
2020-01-14 19:33 ` [PATCH 08/13] iotests: filter few more luks specific create options Maxim Levitsky
2020-01-28 17:36   ` Daniel P. Berrangé
2020-01-30 16:12     ` Maxim Levitsky
2020-01-14 19:33 ` [PATCH 09/13] qemu-iotests: qemu-img tests for luks key management Maxim Levitsky
2020-01-14 19:33 ` [PATCH 10/13] block: add generic infrastructure for x-blockdev-amend qmp command Maxim Levitsky
2020-01-21  7:59   ` Markus Armbruster
2020-01-21 13:58     ` Maxim Levitsky
2020-01-14 19:33 ` [PATCH 11/13] block/crypto: implement blockdev-amend Maxim Levitsky
2020-01-28 17:40   ` Daniel P. Berrangé
2020-01-30 16:24     ` Maxim Levitsky
2020-01-14 19:33 ` [PATCH 12/13] block/qcow2: " Maxim Levitsky
2020-01-28 17:41   ` Daniel P. Berrangé
2020-01-14 19:33 ` [PATCH 13/13] iotests: add tests for blockdev-amend Maxim Levitsky
2020-01-14 21:16 ` [PATCH 00/13] LUKS: encryption slot management using amend interface no-reply
2020-01-16 14:01   ` Maxim Levitsky
2020-01-14 21:17 ` no-reply
2020-01-16 14:19   ` Maxim Levitsky

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).