Blame SOURCES/0007-cat-log-ls-tail-diff-edit-insp.-set-networking-for-k.patch

7b180b
From d95394da96af41b03c9347721a177a4ad9b7f1b0 Mon Sep 17 00:00:00 2001
7b180b
From: Laszlo Ersek <lersek@redhat.com>
7b180b
Date: Fri, 1 Jul 2022 15:20:39 +0200
7b180b
Subject: [PATCH] cat, log, ls, tail, diff, edit, insp.: set networking for
7b180b
 "--key ID:clevis"
7b180b
7b180b
Call the C-language helper key_store_requires_network() in those C
7b180b
utilities that understand "OPTION_key".
7b180b
7b180b
(Short log for libguestfs-common commit range 35467027f657..af6cb55bc58a:
7b180b
7b180b
Laszlo Ersek (12):
7b180b
      options: fix UUID comparison logic bug in get_keys()
7b180b
      mltools/tools_utils: remove unused function "key_store_to_cli"
7b180b
      mltools/tools_utils: allow multiple "--key" options for OCaml tools too
7b180b
      options: replace NULL-termination with number-of-elements in get_keys()
7b180b
      options: wrap each passphrase from get_keys() into a struct
7b180b
      options: add back-end for LUKS decryption with Clevis+Tang
7b180b
      options: introduce selector type "key_clevis"
7b180b
      options: generalize "--key" selector parsing for C-language utilities
7b180b
      mltools/tools_utils-c: handle internal type error with abort()
7b180b
      mltools/tools_utils: generalize "--key" selector parsing for OCaml utils
7b180b
      options, mltools/tools_utils: parse "--key ID:clevis" options
7b180b
      options, mltools/tools_utils: add helper for network dependency
7b180b
).
7b180b
7b180b
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1809453
7b180b
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
7b180b
Message-Id: <20220628115702.5584-2-lersek@redhat.com>
7b180b
Reviewed-by: Richard W.M. Jones <rjones@redhat.com>
7b180b
(cherry picked from commit 14bf833e21cd89f1273e09f4952999b8da86b6ff)
7b180b
---
7b180b
 cat/cat.c             | 3 +++
7b180b
 cat/log.c             | 3 +++
7b180b
 cat/ls.c              | 3 +++
7b180b
 cat/tail.c            | 3 +++
7b180b
 common                | 2 +-
7b180b
 diff/diff.c           | 8 ++++++++
7b180b
 edit/edit.c           | 3 +++
7b180b
 inspector/inspector.c | 3 +++
7b180b
 8 files changed, 27 insertions(+), 1 deletion(-)
7b180b
7b180b
diff --git a/cat/cat.c b/cat/cat.c
7b180b
index 5b51b7df8..ea2021140 100644
7b180b
--- a/cat/cat.c
7b180b
+++ b/cat/cat.c
7b180b
@@ -250,6 +250,9 @@ main (int argc, char *argv[])
7b180b
   /* Add drives, inspect and mount. */
7b180b
   add_drives (drvs);
7b180b
 
7b180b
+  if (key_store_requires_network (ks) && guestfs_set_network (g, 1) == -1)
7b180b
+    exit (EXIT_FAILURE);
7b180b
+
7b180b
   if (guestfs_launch (g) == -1)
7b180b
     exit (EXIT_FAILURE);
7b180b
 
7b180b
diff --git a/cat/log.c b/cat/log.c
7b180b
index df7e2be92..0fe486c05 100644
7b180b
--- a/cat/log.c
7b180b
+++ b/cat/log.c
7b180b
@@ -224,6 +224,9 @@ main (int argc, char *argv[])
7b180b
    */
7b180b
   add_drives (drvs);
7b180b
 
7b180b
+  if (key_store_requires_network (ks) && guestfs_set_network (g, 1) == -1)
7b180b
+    exit (EXIT_FAILURE);
7b180b
+
7b180b
   if (guestfs_launch (g) == -1)
7b180b
     exit (EXIT_FAILURE);
7b180b
 
7b180b
diff --git a/cat/ls.c b/cat/ls.c
7b180b
index e062823b8..1b8e87225 100644
7b180b
--- a/cat/ls.c
7b180b
+++ b/cat/ls.c
7b180b
@@ -374,6 +374,9 @@ main (int argc, char *argv[])
7b180b
   /* Add drives, inspect and mount. */
7b180b
   add_drives (drvs);
7b180b
 
7b180b
+  if (key_store_requires_network (ks) && guestfs_set_network (g, 1) == -1)
7b180b
+    exit (EXIT_FAILURE);
7b180b
+
7b180b
   if (guestfs_launch (g) == -1)
7b180b
     exit (EXIT_FAILURE);
7b180b
 
7b180b
diff --git a/cat/tail.c b/cat/tail.c
7b180b
index 1cf1d6e0e..2a06e0ebd 100644
7b180b
--- a/cat/tail.c
7b180b
+++ b/cat/tail.c
7b180b
@@ -296,6 +296,9 @@ do_tail (int argc, char *argv[], /* list of files in the guest */
7b180b
     /* Add drives, inspect and mount. */
7b180b
     add_drives (drvs);
7b180b
 
7b180b
+    if (key_store_requires_network (ks) && guestfs_set_network (g, 1) == -1)
7b180b
+      exit (EXIT_FAILURE);
7b180b
+
7b180b
     if (guestfs_launch (g) == -1)
7b180b
       return -1;
7b180b
 
7b180b
Submodule common 35467027f..af6cb55bc:
7b180b
diff --git a/common/mltools/tools_utils-c.c b/common/mltools/tools_utils-c.c
7b180b
index 0814667..4ff42e5 100644
7b180b
--- a/common/mltools/tools_utils-c.c
7b180b
+++ b/common/mltools/tools_utils-c.c
7b180b
@@ -62,24 +62,31 @@ guestfs_int_mllib_inspect_decrypt (value gv, value gpv, value keysv)
7b180b
       caml_raise_out_of_memory ();
7b180b
 
7b180b
     v = Field (elemv, 1);
7b180b
-    switch (Tag_val (v)) {
7b180b
-    case 0:  /* KeyString of string */
7b180b
-      key.type = key_string;
7b180b
-      key.string.s = strdup (String_val (Field (v, 0)));
7b180b
-      if (!key.string.s)
7b180b
-        caml_raise_out_of_memory ();
7b180b
-      break;
7b180b
-    case 1:  /* KeyFileName of string */
7b180b
-      key.type = key_file;
7b180b
-      key.file.name = strdup (String_val (Field (v, 0)));
7b180b
-      if (!key.file.name)
7b180b
-        caml_raise_out_of_memory ();
7b180b
-      break;
7b180b
-    default:
7b180b
-      error (EXIT_FAILURE, 0,
7b180b
-             "internal error: unhandled Tag_val (v) = %d",
7b180b
-             Tag_val (v));
7b180b
-    }
7b180b
+    if (Is_block (v))
7b180b
+      switch (Tag_val (v)) {
7b180b
+      case 0:  /* KeyString of string */
7b180b
+        key.type = key_string;
7b180b
+        key.string.s = strdup (String_val (Field (v, 0)));
7b180b
+        if (!key.string.s)
7b180b
+          caml_raise_out_of_memory ();
7b180b
+        break;
7b180b
+      case 1:  /* KeyFileName of string */
7b180b
+        key.type = key_file;
7b180b
+        key.file.name = strdup (String_val (Field (v, 0)));
7b180b
+        if (!key.file.name)
7b180b
+          caml_raise_out_of_memory ();
7b180b
+        break;
7b180b
+      default:
7b180b
+        abort ();
7b180b
+      }
7b180b
+    else
7b180b
+      switch (Int_val (v)) {
7b180b
+      case 0:  /* KeyClevis */
7b180b
+        key.type = key_clevis;
7b180b
+        break;
7b180b
+      default:
7b180b
+        abort ();
7b180b
+      }
7b180b
 
7b180b
     ks = key_store_import_key (ks, &key);
7b180b
 
7b180b
diff --git a/common/mltools/tools_utils.ml b/common/mltools/tools_utils.ml
7b180b
index 695fda7..562bfad 100644
7b180b
--- a/common/mltools/tools_utils.ml
7b180b
+++ b/common/mltools/tools_utils.ml
7b180b
@@ -29,11 +29,12 @@ open Getopt.OptionName
7b180b
 let prog = ref prog
7b180b
 
7b180b
 type key_store = {
7b180b
-  keys : (string, key_store_key) Hashtbl.t;
7b180b
+  keys : (string * key_store_key) list ref;
7b180b
 }
7b180b
 and key_store_key =
7b180b
   | KeyString of string
7b180b
   | KeyFileName of string
7b180b
+  | KeyClevis
7b180b
 
7b180b
 external c_inspect_decrypt : Guestfs.t -> int64 -> (string * key_store_key) list -> unit = "guestfs_int_mllib_inspect_decrypt"
7b180b
 external c_set_echo_keys : unit -> unit = "guestfs_int_mllib_set_echo_keys" [@@noalloc]
7b180b
@@ -376,7 +377,7 @@ let create_standard_options argspec ?anon_fun ?(key_opts = false)
7b180b
       )
7b180b
   in
7b180b
   let ks = {
7b180b
-    keys = Hashtbl.create 13;
7b180b
+    keys = ref [];
7b180b
   } in
7b180b
   let argspec = ref argspec in
7b180b
   let add_argspec = List.push_back argspec in
7b180b
@@ -392,14 +393,28 @@ let create_standard_options argspec ?anon_fun ?(key_opts = false)
7b180b
 
7b180b
   if key_opts then (
7b180b
     let parse_key_selector arg =
7b180b
-      let parts = String.nsplit ~max:3 ":" arg in
7b180b
+      let parts = String.nsplit ":" arg in
7b180b
       match parts with
7b180b
+      | [] ->
7b180b
+        error (f_"selector '%s': missing ID") arg
7b180b
+      | [ _ ] ->
7b180b
+        error (f_"selector '%s': missing TYPE") arg
7b180b
+      | [ _; "key" ]
7b180b
+      |  _ :: "key" :: _ :: _ :: _ ->
7b180b
+        error (f_"selector '%s': missing KEY_STRING, or too many fields") arg
7b180b
       | [ device; "key"; key ] ->
7b180b
-         Hashtbl.replace ks.keys device (KeyString key)
7b180b
+         List.push_back ks.keys (device, KeyString key)
7b180b
+      | [ _; "file" ]
7b180b
+      |  _ :: "file" :: _ :: _ :: _ ->
7b180b
+        error (f_"selector '%s': missing FILENAME, or too many fields") arg
7b180b
       | [ device; "file"; file ] ->
7b180b
-         Hashtbl.replace ks.keys device (KeyFileName file)
7b180b
+         List.push_back ks.keys (device, KeyFileName file)
7b180b
+      |  _ :: "clevis" :: _ :: _ ->
7b180b
+        error (f_"selector '%s': too many fields") arg
7b180b
+      | [ device; "clevis" ] ->
7b180b
+         List.push_back ks.keys (device, KeyClevis)
7b180b
       | _ ->
7b180b
-         error (f_"invalid selector string for --key: %s") arg
7b180b
+         error (f_"selector '%s': invalid TYPE") arg
7b180b
     in
7b180b
 
7b180b
     add_argspec ([ L"echo-keys" ],       Getopt.Unit c_set_echo_keys,       s_"Don’t turn off echo for passphrases");
7b180b
@@ -420,16 +435,6 @@ let create_standard_options argspec ?anon_fun ?(key_opts = false)
7b180b
   let getopt = Getopt.create argspec ?anon_fun usage_msg in
7b180b
   { getopt; ks; debug_gc }
7b180b
 
7b180b
-let key_store_to_cli { keys } =
7b180b
-  Hashtbl.fold (
7b180b
-    fun k v acc ->
7b180b
-      let arg =
7b180b
-        match v with
7b180b
-        | KeyString s -> sprintf "%s:key:%s" k s
7b180b
-        | KeyFileName f -> sprintf "%s:file:%s" k f in
7b180b
-      "--key" :: arg :: acc
7b180b
-  ) keys []
7b180b
-
7b180b
 (* Run an external command, slurp up the output as a list of lines. *)
7b180b
 let external_command ?(echo_cmd = true) cmd =
7b180b
   if echo_cmd then
7b180b
@@ -691,21 +696,19 @@ let is_btrfs_subvolume g fs =
7b180b
     if g#last_errno () = Guestfs.Errno.errno_EINVAL then false
7b180b
     else raise exn
7b180b
 
7b180b
+let key_store_requires_network ks =
7b180b
+  List.exists (function
7b180b
+               | _, KeyClevis -> true
7b180b
+               | _ -> false) !(ks.keys)
7b180b
+
7b180b
 let inspect_decrypt g ks =
7b180b
-  (* Turn the keys in the key_store into a simpler struct, so it is possible
7b180b
-   * to read it using the C API.
7b180b
-   *)
7b180b
-  let keys_as_list = Hashtbl.fold (
7b180b
-    fun k v acc ->
7b180b
-      (k, v) :: acc
7b180b
-  ) ks.keys [] in
7b180b
   (* Note we pass original 'g' even though it is not used by the
7b180b
    * callee.  This is so that 'g' is kept as a root on the stack, and
7b180b
    * so cannot be garbage collected while we are in the c_inspect_decrypt
7b180b
    * function.
7b180b
    *)
7b180b
   c_inspect_decrypt g#ocaml_handle (Guestfs.c_pointer g#ocaml_handle)
7b180b
-    keys_as_list
7b180b
+    !(ks.keys)
7b180b
 
7b180b
 let with_timeout op timeout ?(sleep = 2) fn =
7b180b
   let start_t = Unix.gettimeofday () in
7b180b
diff --git a/common/mltools/tools_utils.mli b/common/mltools/tools_utils.mli
7b180b
index 5018300..ec900e6 100644
7b180b
--- a/common/mltools/tools_utils.mli
7b180b
+++ b/common/mltools/tools_utils.mli
7b180b
@@ -103,14 +103,6 @@ val create_standard_options : Getopt.speclist -> ?anon_fun:Getopt.anon_fun -> ?k
7b180b
 
7b180b
     Returns a new {!cmdline_options} structure. *)
7b180b
 
7b180b
-val key_store_to_cli : key_store -> string list
7b180b
-(** Convert a {!key_store} object back to a list of command line
7b180b
-    options, essentially undoing the effect of Getopt parsing.
7b180b
-    This is used in virt-v2v to pass the keystore to helpers.
7b180b
-    It is not particularly secure, especially if you use the
7b180b
-    [:key:] selector, although not any less secure than passing
7b180b
-    them via the command line in the first place. *)
7b180b
-
7b180b
 val external_command : ?echo_cmd:bool -> string -> string list
7b180b
 (** Run an external command, slurp up the output as a list of lines.
7b180b
 
7b180b
@@ -204,6 +196,10 @@ val inspect_mount_root_ro : Guestfs.guestfs -> string -> unit
7b180b
 val is_btrfs_subvolume : Guestfs.guestfs -> string -> bool
7b180b
 (** Checks if a filesystem is a btrfs subvolume. *)
7b180b
 
7b180b
+val key_store_requires_network : key_store -> bool
7b180b
+(** [key_store_requires_network ks] returns [true] iff [ks] contains at least
7b180b
+    one "ID:clevis" selector. *)
7b180b
+
7b180b
 val inspect_decrypt : Guestfs.guestfs -> key_store -> unit
7b180b
 (** Simple implementation of decryption: look for any encrypted
7b180b
     partitions and decrypt them, then rescan for VGs. *)
7b180b
diff --git a/common/options/decrypt.c b/common/options/decrypt.c
7b180b
index 1cd7b62..97c8b88 100644
7b180b
--- a/common/options/decrypt.c
7b180b
+++ b/common/options/decrypt.c
7b180b
@@ -124,10 +124,10 @@ decrypt_mountables (guestfs_h *g, const char * const *mountables,
7b180b
   while ((mountable = *mnt_scan++) != NULL) {
7b180b
     CLEANUP_FREE char *type = NULL;
7b180b
     CLEANUP_FREE char *uuid = NULL;
7b180b
-    CLEANUP_FREE_STRING_LIST char **keys = NULL;
7b180b
+    struct matching_key *keys;
7b180b
+    size_t nr_matches;
7b180b
     CLEANUP_FREE char *mapname = NULL;
7b180b
-    const char * const *key_scan;
7b180b
-    const char *key;
7b180b
+    size_t scan;
7b180b
 
7b180b
     type = guestfs_vfs_type (g, mountable);
7b180b
     if (type == NULL)
7b180b
@@ -144,33 +144,45 @@ decrypt_mountables (guestfs_h *g, const char * const *mountables,
7b180b
     /* Grab the keys that we should try with this device, based on device name,
7b180b
      * or UUID (if any).
7b180b
      */
7b180b
-    keys = get_keys (ks, mountable, uuid);
7b180b
-    assert (keys[0] != NULL);
7b180b
+    keys = get_keys (ks, mountable, uuid, &nr_matches);
7b180b
+    assert (nr_matches > 0);
7b180b
 
7b180b
     /* Generate a node name for the plaintext (decrypted) device node. */
7b180b
     if (uuid == NULL || asprintf (&mapname, "luks-%s", uuid) == -1)
7b180b
       mapname = make_mapname (mountable);
7b180b
 
7b180b
     /* Try each key in turn. */
7b180b
-    key_scan = (const char * const *)keys;
7b180b
-    while ((key = *key_scan++) != NULL) {
7b180b
+    for (scan = 0; scan < nr_matches; ++scan) {
7b180b
+      struct matching_key *key = keys + scan;
7b180b
       int r;
7b180b
 
7b180b
       guestfs_push_error_handler (g, NULL, NULL);
7b180b
-      r = guestfs_cryptsetup_open (g, mountable, key, mapname, -1);
7b180b
+      assert (key->clevis == (key->passphrase == NULL));
7b180b
+      if (key->clevis)
7b180b
+#ifdef GUESTFS_HAVE_CLEVIS_LUKS_UNLOCK
7b180b
+        r = guestfs_clevis_luks_unlock (g, mountable, mapname);
7b180b
+#else
7b180b
+        error (EXIT_FAILURE, 0,
7b180b
+               _("'clevis_luks_unlock', needed for decrypting %s, is "
7b180b
+                 "unavailable in this libguestfs version"), mountable);
7b180b
+#endif
7b180b
+      else
7b180b
+        r = guestfs_cryptsetup_open (g, mountable, key->passphrase, mapname,
7b180b
+                                     -1);
7b180b
       guestfs_pop_error_handler (g);
7b180b
 
7b180b
       if (r == 0)
7b180b
         break;
7b180b
     }
7b180b
 
7b180b
-    if (key == NULL)
7b180b
+    if (scan == nr_matches)
7b180b
       error (EXIT_FAILURE, 0,
7b180b
              _("could not find key to open LUKS encrypted %s.\n\n"
7b180b
                "Try using --key on the command line.\n\n"
7b180b
                "Original error: %s (%d)"),
7b180b
              mountable, guestfs_last_error (g), guestfs_last_errno (g));
7b180b
 
7b180b
+    free_keys (keys, nr_matches);
7b180b
     decrypted_some = true;
7b180b
   }
7b180b
 
7b180b
diff --git a/common/options/key-option.pod b/common/options/key-option.pod
7b180b
index 90a3b15..6bc04df 100644
7b180b
--- a/common/options/key-option.pod
7b180b
+++ b/common/options/key-option.pod
7b180b
@@ -14,4 +14,13 @@ Use the specified C<KEY_STRING> as passphrase.
7b180b
 
7b180b
 Read the passphrase from F<FILENAME>.
7b180b
 
7b180b
+=item B<--key> C<ID>:clevis
7b180b
+
7b180b
+Attempt passphrase-less unlocking for C<ID> with Clevis, over the
7b180b
+network.  Please refer to L<guestfs(3)/ENCRYPTED DISKS> for more
7b180b
+information on network-bound disk encryption (NBDE).
7b180b
+
7b180b
+Note that if any such option is present on the command line, QEMU user
7b180b
+networking will be automatically enabled for the libguestfs appliance.
7b180b
+
7b180b
 =back
7b180b
diff --git a/common/options/keys.c b/common/options/keys.c
7b180b
index d27a712..d987ae5 100644
7b180b
--- a/common/options/keys.c
7b180b
+++ b/common/options/keys.c
7b180b
@@ -125,11 +125,12 @@ read_first_line_from_file (const char *filename)
7b180b
  * keystore.  There may be multiple.  If none are read from the
7b180b
  * keystore, ask the user.
7b180b
  */
7b180b
-char **
7b180b
-get_keys (struct key_store *ks, const char *device, const char *uuid)
7b180b
+struct matching_key *
7b180b
+get_keys (struct key_store *ks, const char *device, const char *uuid,
7b180b
+          size_t *nr_matches)
7b180b
 {
7b180b
-  size_t i, j, nmemb;
7b180b
-  char **r;
7b180b
+  size_t i, nmemb;
7b180b
+  struct matching_key *r, *match;
7b180b
   char *s;
7b180b
 
7b180b
   /* We know the returned list must have at least one element and not
7b180b
@@ -139,22 +140,20 @@ get_keys (struct key_store *ks, const char *device, const char *uuid)
7b180b
   if (ks && ks->nr_keys > nmemb)
7b180b
     nmemb = ks->nr_keys;
7b180b
 
7b180b
-  /* make room for the terminating NULL */
7b180b
-  if (nmemb == (size_t)-1)
7b180b
+  if (nmemb > (size_t)-1 / sizeof *r)
7b180b
     error (EXIT_FAILURE, 0, _("size_t overflow"));
7b180b
-  nmemb++;
7b180b
 
7b180b
-  r = calloc (nmemb, sizeof (char *));
7b180b
+  r = malloc (nmemb * sizeof *r);
7b180b
   if (r == NULL)
7b180b
-    error (EXIT_FAILURE, errno, "calloc");
7b180b
+    error (EXIT_FAILURE, errno, "malloc");
7b180b
 
7b180b
-  j = 0;
7b180b
+  match = r;
7b180b
 
7b180b
   if (ks) {
7b180b
     for (i = 0; i < ks->nr_keys; ++i) {
7b180b
       struct key_store_key *key = &ks->keys[i];
7b180b
 
7b180b
-      if (STRNEQ (key->id, device) && (uuid && STRNEQ (key->id, uuid)))
7b180b
+      if (STRNEQ (key->id, device) && (!uuid || STRNEQ (key->id, uuid)))
7b180b
         continue;
7b180b
 
7b180b
       switch (key->type) {
7b180b
@@ -162,68 +161,101 @@ get_keys (struct key_store *ks, const char *device, const char *uuid)
7b180b
         s = strdup (key->string.s);
7b180b
         if (!s)
7b180b
           error (EXIT_FAILURE, errno, "strdup");
7b180b
-        r[j++] = s;
7b180b
+        match->clevis = false;
7b180b
+        match->passphrase = s;
7b180b
+        ++match;
7b180b
         break;
7b180b
       case key_file:
7b180b
         s = read_first_line_from_file (key->file.name);
7b180b
-        r[j++] = s;
7b180b
+        match->clevis = false;
7b180b
+        match->passphrase = s;
7b180b
+        ++match;
7b180b
+        break;
7b180b
+      case key_clevis:
7b180b
+        match->clevis = true;
7b180b
+        match->passphrase = NULL;
7b180b
+        ++match;
7b180b
         break;
7b180b
       }
7b180b
     }
7b180b
   }
7b180b
 
7b180b
-  if (j == 0) {
7b180b
+  if (match == r) {
7b180b
     /* Key not found in the key store, ask the user for it. */
7b180b
     s = read_key (device);
7b180b
     if (!s)
7b180b
       error (EXIT_FAILURE, 0, _("could not read key from user"));
7b180b
-    r[0] = s;
7b180b
+    match->clevis = false;
7b180b
+    match->passphrase = s;
7b180b
+    ++match;
7b180b
   }
7b180b
 
7b180b
+  *nr_matches = (size_t)(match - r);
7b180b
   return r;
7b180b
 }
7b180b
 
7b180b
+void
7b180b
+free_keys (struct matching_key *keys, size_t nr_matches)
7b180b
+{
7b180b
+  size_t i;
7b180b
+
7b180b
+  for (i = 0; i < nr_matches; ++i) {
7b180b
+    struct matching_key *key = keys + i;
7b180b
+
7b180b
+    assert (key->clevis == (key->passphrase == NULL));
7b180b
+    if (!key->clevis)
7b180b
+      free (key->passphrase);
7b180b
+  }
7b180b
+  free (keys);
7b180b
+}
7b180b
+
7b180b
 struct key_store *
7b180b
 key_store_add_from_selector (struct key_store *ks, const char *selector)
7b180b
 {
7b180b
-  CLEANUP_FREE_STRING_LIST char **fields =
7b180b
-    guestfs_int_split_string (':', selector);
7b180b
+  CLEANUP_FREE_STRING_LIST char **fields = NULL;
7b180b
+  size_t field_count;
7b180b
   struct key_store_key key;
7b180b
 
7b180b
+  fields = guestfs_int_split_string (':', selector);
7b180b
   if (!fields)
7b180b
     error (EXIT_FAILURE, errno, "guestfs_int_split_string");
7b180b
+  field_count = guestfs_int_count_strings (fields);
7b180b
 
7b180b
-  if (guestfs_int_count_strings (fields) != 3) {
7b180b
-   invalid_selector:
7b180b
-    error (EXIT_FAILURE, 0, "invalid selector for --key: %s", selector);
7b180b
-  }
7b180b
-
7b180b
-  /* 1: device */
7b180b
+  /* field#0: ID */
7b180b
+  if (field_count < 1)
7b180b
+    error (EXIT_FAILURE, 0, _("selector '%s': missing ID"), selector);
7b180b
   key.id = strdup (fields[0]);
7b180b
   if (!key.id)
7b180b
     error (EXIT_FAILURE, errno, "strdup");
7b180b
 
7b180b
-  /* 2: key type */
7b180b
-  if (STREQ (fields[1], "key"))
7b180b
+  /* field#1...: TYPE, and TYPE-specific properties */
7b180b
+  if (field_count < 2)
7b180b
+    error (EXIT_FAILURE, 0, _("selector '%s': missing TYPE"), selector);
7b180b
+
7b180b
+  if (STREQ (fields[1], "key")) {
7b180b
     key.type = key_string;
7b180b
-  else if (STREQ (fields[1], "file"))
7b180b
-    key.type = key_file;
7b180b
-  else
7b180b
-    goto invalid_selector;
7b180b
-
7b180b
-  /* 3: actual key */
7b180b
-  switch (key.type) {
7b180b
-  case key_string:
7b180b
+    if (field_count != 3)
7b180b
+      error (EXIT_FAILURE, 0,
7b180b
+             _("selector '%s': missing KEY_STRING, or too many fields"),
7b180b
+             selector);
7b180b
     key.string.s = strdup (fields[2]);
7b180b
     if (!key.string.s)
7b180b
       error (EXIT_FAILURE, errno, "strdup");
7b180b
-    break;
7b180b
-  case key_file:
7b180b
+  } else if (STREQ (fields[1], "file")) {
7b180b
+    key.type = key_file;
7b180b
+    if (field_count != 3)
7b180b
+      error (EXIT_FAILURE, 0,
7b180b
+             _("selector '%s': missing FILENAME, or too many fields"),
7b180b
+             selector);
7b180b
     key.file.name = strdup (fields[2]);
7b180b
     if (!key.file.name)
7b180b
       error (EXIT_FAILURE, errno, "strdup");
7b180b
-    break;
7b180b
-  }
7b180b
+  } else if (STREQ (fields[1], "clevis")) {
7b180b
+    key.type = key_clevis;
7b180b
+    if (field_count != 2)
7b180b
+      error (EXIT_FAILURE, 0, _("selector '%s': too many fields"), selector);
7b180b
+  } else
7b180b
+    error (EXIT_FAILURE, 0, _("selector '%s': invalid TYPE"), selector);
7b180b
 
7b180b
   return key_store_import_key (ks, &key);
7b180b
 }
7b180b
@@ -252,6 +284,21 @@ key_store_import_key (struct key_store *ks, const struct key_store_key *key)
7b180b
   return ks;
7b180b
 }
7b180b
 
7b180b
+bool
7b180b
+key_store_requires_network (const struct key_store *ks)
7b180b
+{
7b180b
+  size_t i;
7b180b
+
7b180b
+  if (ks == NULL)
7b180b
+    return false;
7b180b
+
7b180b
+  for (i = 0; i < ks->nr_keys; ++i)
7b180b
+    if (ks->keys[i].type == key_clevis)
7b180b
+      return true;
7b180b
+
7b180b
+  return false;
7b180b
+}
7b180b
+
7b180b
 void
7b180b
 free_key_store (struct key_store *ks)
7b180b
 {
7b180b
@@ -270,6 +317,9 @@ free_key_store (struct key_store *ks)
7b180b
     case key_file:
7b180b
       free (key->file.name);
7b180b
       break;
7b180b
+    case key_clevis:
7b180b
+      /* nothing */
7b180b
+      break;
7b180b
     }
7b180b
     free (key->id);
7b180b
   }
7b180b
diff --git a/common/options/options.h b/common/options/options.h
7b180b
index 80df91a..60d5d80 100644
7b180b
--- a/common/options/options.h
7b180b
+++ b/common/options/options.h
7b180b
@@ -115,6 +115,7 @@ struct key_store_key {
7b180b
   enum {
7b180b
     key_string,             /* key specified as string */
7b180b
     key_file,               /* key stored in a file */
7b180b
+    key_clevis,             /* key reconstructed with Clevis+Tang */
7b180b
   } type;
7b180b
   union {
7b180b
     struct {
7b180b
@@ -134,6 +135,19 @@ struct key_store {
7b180b
   size_t nr_keys;
7b180b
 };
7b180b
 
7b180b
+/* A key matching a particular ID (pathname of the libguestfs device node that
7b180b
+ * stands for the encrypted block device, or LUKS UUID).
7b180b
+ */
7b180b
+struct matching_key {
7b180b
+  /* True iff the passphrase should be reconstructed using Clevis, talking to
7b180b
+   * Tang servers over the network.
7b180b
+   */
7b180b
+  bool clevis;
7b180b
+
7b180b
+  /* Explicit passphrase, otherwise. */
7b180b
+  char *passphrase;
7b180b
+};
7b180b
+
7b180b
 /* in config.c */
7b180b
 extern void parse_config (void);
7b180b
 
7b180b
@@ -151,9 +165,12 @@ extern void print_inspect_prompt (void);
7b180b
 
7b180b
 /* in key.c */
7b180b
 extern char *read_key (const char *param);
7b180b
-extern char **get_keys (struct key_store *ks, const char *device, const char *uuid);
7b180b
+extern struct matching_key *get_keys (struct key_store *ks, const char *device,
7b180b
+                                      const char *uuid, size_t *nr_matches);
7b180b
+extern void free_keys (struct matching_key *keys, size_t nr_matches);
7b180b
 extern struct key_store *key_store_add_from_selector (struct key_store *ks, const char *selector);
7b180b
 extern struct key_store *key_store_import_key (struct key_store *ks, const struct key_store_key *key);
7b180b
+extern bool key_store_requires_network (const struct key_store *ks);
7b180b
 extern void free_key_store (struct key_store *ks);
7b180b
 
7b180b
 /* in options.c */
7b180b
diff --git a/diff/diff.c b/diff/diff.c
7b180b
index 6aae88e6a..c73129c82 100644
7b180b
--- a/diff/diff.c
7b180b
+++ b/diff/diff.c
7b180b
@@ -209,6 +209,7 @@ main (int argc, char *argv[])
7b180b
   int option_index;
7b180b
   struct tree *tree1, *tree2;
7b180b
   struct key_store *ks = NULL;
7b180b
+  bool network;
7b180b
 
7b180b
   g = guestfs_create ();
7b180b
   if (g == NULL)
7b180b
@@ -378,6 +379,10 @@ main (int argc, char *argv[])
7b180b
   /* Mount up first guest. */
7b180b
   add_drives (drvs);
7b180b
 
7b180b
+  network = key_store_requires_network (ks);
7b180b
+  if (guestfs_set_network (g, network) == -1)
7b180b
+    exit (EXIT_FAILURE);
7b180b
+
7b180b
   if (guestfs_launch (g) == -1)
7b180b
     exit (EXIT_FAILURE);
7b180b
 
7b180b
@@ -389,6 +394,9 @@ main (int argc, char *argv[])
7b180b
   /* Mount up second guest. */
7b180b
   add_drives_handle (g2, drvs2, 0);
7b180b
 
7b180b
+  if (guestfs_set_network (g2, network) == -1)
7b180b
+    exit (EXIT_FAILURE);
7b180b
+
7b180b
   if (guestfs_launch (g2) == -1)
7b180b
     exit (EXIT_FAILURE);
7b180b
 
7b180b
diff --git a/edit/edit.c b/edit/edit.c
7b180b
index 7f06bce7f..90c6b85d5 100644
7b180b
--- a/edit/edit.c
7b180b
+++ b/edit/edit.c
7b180b
@@ -274,6 +274,9 @@ main (int argc, char *argv[])
7b180b
   /* Add drives. */
7b180b
   add_drives (drvs);
7b180b
 
7b180b
+  if (key_store_requires_network (ks) && guestfs_set_network (g, 1) == -1)
7b180b
+    exit (EXIT_FAILURE);
7b180b
+
7b180b
   if (guestfs_launch (g) == -1)
7b180b
     exit (EXIT_FAILURE);
7b180b
 
7b180b
diff --git a/inspector/inspector.c b/inspector/inspector.c
7b180b
index 25ee40f3f..2702e3310 100644
7b180b
--- a/inspector/inspector.c
7b180b
+++ b/inspector/inspector.c
7b180b
@@ -294,6 +294,9 @@ main (int argc, char *argv[])
7b180b
    */
7b180b
   add_drives (drvs);
7b180b
 
7b180b
+  if (key_store_requires_network (ks) && guestfs_set_network (g, 1) == -1)
7b180b
+    exit (EXIT_FAILURE);
7b180b
+
7b180b
   if (guestfs_launch (g) == -1)
7b180b
     exit (EXIT_FAILURE);
7b180b
 
7b180b
-- 
7b180b
2.31.1
7b180b