On 06/30/22 18:10, Richard W.M. Jones wrote:
On Thu, Jun 30, 2022 at 02:20:22PM +0200, Laszlo Ersek wrote:
> Extend the "matching_key" structure with a new boolean field,
"clevis".
> "clevis" is mutually exclusive with a non-NULL "passphrase"
field. If
> "clevis" is set, open the LUKS device with guestfs_clevis_luks_unlock() --
> which requires no explicit passphrase --; otherwise, continue calling
> guestfs_cryptsetup_open().
>
> This patch introduces no change in observable behavior; there is no user
> interface yet for setting the "clevis" field to "true".
>
> Bugzilla:
https://bugzilla.redhat.com/show_bug.cgi?id=1809453
> Signed-off-by: Laszlo Ersek <lersek(a)redhat.com>
> ---
>
> Notes:
> v2:
>
> - check GUESTFS_HAVE_CLEVIS_LUKS_UNLOCK; error out if unavailable [Rich]
>
> v1 notes were:
>
> This patch introduces a call to guestfs_clevis_luks_unlock(), which is a
> new libguestfs API (introduced in the sibling libguestfs series).
>
> Assuming we still care about building guestfs-tools and virt-v2v against
> an independent libguestfs (library and appliance), we need to do one of
> the following things:
>
> (1) Make the call dependent on the GUESTFS_HAVE_CLEVIS_LUKS_UNLOCK
> feature test macro. If it is missing, the library is too old, just set
> "r = -1", and (possibly) print a warning to stderr.
>
> (2) Cut a new libguestfs release, and bump the minimum version in
> "m4/guestfs-libraries.m4" (in both guestfs-tools and virt-v2v) to the
> new version.
>
> Both of these have drawbacks.
>
> Disadvantages of (1):
>
> - Printing a raw warning to stderr is OK for the C-language tools, but
> the code is used by OCaml tools as well, which have pretty-printed
> warnings.
>
> - Simply skipping the guestfs_clevis_luks_unlock() call -- i.e.,
> pretending it fails -- is not user-friendly. In particular, once we
> add the new selector type for "--key" later in this series, and
> document it in "options/key-option.pod", all the tools' docs will
pick
> it up at once, even if the library is too old to provide the
> interface.
>
> Disadvantages of (2):
>
> - We may not want to leap over a large version distance in
> "m4/guestfs-libraries.m4" (in both guestfs-tools and virt-v2v), for
> whatever reason.
>
> - Even if we make the guestfs_clevis_luks_unlock() API mandatory in the
> library, the daemon may not implement it -- it's ultimately appliance
> (host distro) dependent, which is why the API belongs to a new option
> group called "clevisluks". That is, the actual behavior of
> guestfs_clevis_luks_unlock() may still differ from what the user
> expects based on "options/key-option.pod".
>
> This drawback is mitigated however: the documentation of the new
> selector in "options/key-option.pod" references "ENCRYPTED
DISKS" in
> guestfs(3), which in turn references "guestfs_clevis_luks_unlock",
> which does highlight the "clevisluks" option group.
>
> I vote (2) -- cut a new libguestfs release, then bump the
> "m4/guestfs-libraries.m4" requirements. I'm not doing that just yet
in
> those projects (in the sibling series here): if I did that, those series
> would not compile; the libguestfs version number would have to be
> increased first! And I don't know if we want *something else* in the new
> libguestfs release, additionally.
>
> options/options.h | 6 ++++++
> options/decrypt.c | 13 ++++++++++++-
> options/keys.c | 7 ++++++-
> 3 files changed, 24 insertions(+), 2 deletions(-)
>
> diff --git a/options/options.h b/options/options.h
> index 9bd812525d8a..61a385da13ae 100644
> --- a/options/options.h
> +++ b/options/options.h
> @@ -136,10 +136,16 @@ struct key_store {
>
> /* A key matching a particular ID (pathname of the libguestfs device node that
> * stands for the encrypted block device, or LUKS UUID).
> */
> struct matching_key {
> + /* True iff the passphrase should be reconstructed using Clevis, talking to
> + * Tang servers over the network.
> + */
> + bool clevis;
> +
> + /* Explicit passphrase, otherwise. */
> char *passphrase;
> };
>
> /* in config.c */
> extern void parse_config (void);
> diff --git a/options/decrypt.c b/options/decrypt.c
> index 421a38c2a11f..97c8b88d16aa 100644
> --- a/options/decrypt.c
> +++ b/options/decrypt.c
> @@ -155,11 +155,22 @@ decrypt_mountables (guestfs_h *g, const char * const
*mountables,
> for (scan = 0; scan < nr_matches; ++scan) {
> struct matching_key *key = keys + scan;
> int r;
>
> guestfs_push_error_handler (g, NULL, NULL);
> - r = guestfs_cryptsetup_open (g, mountable, key->passphrase, mapname, -1);
> + assert (key->clevis == (key->passphrase == NULL));
> + if (key->clevis)
> +#ifdef GUESTFS_HAVE_CLEVIS_LUKS_UNLOCK
> + r = guestfs_clevis_luks_unlock (g, mountable, mapname);
> +#else
> + error (EXIT_FAILURE, 0,
> + _("'clevis_luks_unlock', needed for decrypting %s, is
"
> + "unavailable in this libguestfs version"), mountable);
To be clear on why this is correct, it's because this code is only
used during option parsing in command line tools, where we want to
exit when an error happens.
That's not the case.
This is what I've been trying to express, but I've failed. :(
See:
$ virt-v2v -on converted --key /dev/sda2:clevis clevis
[ 0.0] Setting up the source: -i libvirt clevis
[ 1.2] Opening the source
virt-v2v: 'clevis_luks_unlock', needed for decrypting /dev/sda2, is unavailable in
this libguestfs version
This code runs (in virt-v2v, for example) on the following call path:
main [v2v/v2v.ml]
create_standard_options [common/mltools/tools_utils.ml]
Getopt.parse
parse_key_selector [common/mltools/tools_utils.ml]
List.push_back
(* at this point, cmdline_options.ks
* has been populated
*)
convert [convert/convert.ml]
(* skipped in virt-v2v, because it
* enables networking anyway:
*)
key_store_requires_network [common/mltools/tools_utils.ml]
g#set_network
g#launch
inspect_decrypt [common/mltools/tools_utils.ml]
c_inspect_decrypt [common/mltools/tools_utils.ml]
guestfs_int_mllib_inspect_decrypt [common/mltools/tools_utils-c.c]
loop over cmdline_options.ks:
key_store_import_key [common/options/keys.c]
inspect_do_decrypt [common/options/decrypt.c]
decrypt_mountables [common/options/decrypt.c]
MACRO CHECK HERE
guestfs_clevis_luks_unlock
free_key_store [common/options/keys.c]
This is why I said "it's too late to report this error in
decrypt_mountables()"!
In comparison, here's how the C tools work (such as virt-cat):
main [cat/cat.c]
OPTION_key [common/options/options.h]
key_store_add_from_selector [common/options/keys.c]
key_store_import_key [common/options/keys.c]
/* "ks" has been populated */
key_store_requires_network [common/options/keys.c]
guestfs_set_network
guestfs_launch
inspect_mount [common/options/options.h]
inspect_mount_handle [common/options/inspect.c]
inspect_do_decrypt [common/options/decrypt.c]
decrypt_mountables [common/options/decrypt.c]
MACRO CHECK HERE
guestfs_clevis_luks_unlock
free_key_store [common/options/keys.c]
Note the following important facts:
(1) The macro check is reached, in both kinds of tools, way after the
options have been parsed, and quite a bit after the appliance has been
launched.
(2) The OCaml and C *option parsers* share *nothing* related to keys.
Consider:
- OCaml does not call key_store_add_from_selector() at all,
- while OCaml does call key_store_import_key(), it does so *outside* of
option parsing. Namely, "parse_key_selector" records the command line
options in an OCaml-only data structure (cmdline_options.ks). That is
then translated to an *ephemeral* C-language keystore much later, with
key_store_import_key() -- only in guestfs_int_mllib_inspect_decrypt()!
That is, after the appliance is running.
- Correspondingly: in the C tools, the C-language keystore is freed
*after* inspection completes (that is, free_key_store() is called
*after* inspect_mount() returns), but in OCaml, the ephemeral keystore
is released (correctly) *inside* guestfs_int_mllib_inspect_decrypt() --
that is, internally to the outer function "inspect_decrypt".
If we want to catch the lack of GUESTFS_HAVE_CLEVIS_LUKS_UNLOCK during
option parsing, then I need to implement a brand new C function, and
wrap it for OCaml too (OCaml cannot check the C-language macro
GUESTFS_HAVE_CLEVIS_LUKS_UNLOCK).
For this: because we already have "key_store_requires_network" (same
name, but totally separate impl in C and OCaml), I'd have to rename that
function (both impls) to "key_store_contains_clevis", and use it for two
purposes:
- right after option parsing, if the key store contains clevis, but the
feature test macro is missing, bail.
- just before launching the appliance, if the key store contains clevis,
enable networking.
(My previous proposal went one step further: check the "clevisluks"
option group too, once the appliance is up and running.)
I'm sorry that I've failed to explain this before. I've tried, but it's
really complicated. I hope the call trees help!
Thanks,
Laszlo
> +#endif
> + else
> + r = guestfs_cryptsetup_open (g, mountable, key->passphrase, mapname,
> + -1);
> guestfs_pop_error_handler (g);
>
> if (r == 0)
> break;
> }
> diff --git a/options/keys.c b/options/keys.c
> index 56fca17a94b5..75c659561c52 100644
> --- a/options/keys.c
> +++ b/options/keys.c
> @@ -159,15 +159,17 @@ get_keys (struct key_store *ks, const char *device, const char
*uuid,
> switch (key->type) {
> case key_string:
> s = strdup (key->string.s);
> if (!s)
> error (EXIT_FAILURE, errno, "strdup");
> + match->clevis = false;
> match->passphrase = s;
> ++match;
> break;
> case key_file:
> s = read_first_line_from_file (key->file.name);
> + match->clevis = false;
> match->passphrase = s;
> ++match;
> break;
> }
> }
> @@ -176,10 +178,11 @@ get_keys (struct key_store *ks, const char *device, const char
*uuid,
> if (match == r) {
> /* Key not found in the key store, ask the user for it. */
> s = read_key (device);
> if (!s)
> error (EXIT_FAILURE, 0, _("could not read key from user"));
> + match->clevis = false;
> match->passphrase = s;
> ++match;
> }
>
> *nr_matches = (size_t)(match - r);
> @@ -192,11 +195,13 @@ free_keys (struct matching_key *keys, size_t nr_matches)
> size_t i;
>
> for (i = 0; i < nr_matches; ++i) {
> struct matching_key *key = keys + i;
>
> - free (key->passphrase);
> + assert (key->clevis == (key->passphrase == NULL));
> + if (!key->clevis)
> + free (key->passphrase);
> }
> free (keys);
> }
>
> struct key_store *
Reviewed-by: Richard W.M. Jones <rjones(a)redhat.com>