From 212708dee7c5c483dd0ce76889f7e20abba7f859 Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Thu, 30 Jun 2022 14:20:48 +0200 Subject: [PATCH] guestfish, guestmount: enable networking for "--key ID:clevis" Call the C-language helper key_store_requires_network() in guestfish and guestmount. (Short log for the "common" submodule, commit range 35467027f657..af6cb55bc58a: Laszlo Ersek (12): options: fix UUID comparison logic bug in get_keys() mltools/tools_utils: remove unused function "key_store_to_cli" mltools/tools_utils: allow multiple "--key" options for OCaml tools too options: replace NULL-termination with number-of-elements in get_keys() options: wrap each passphrase from get_keys() into a struct options: add back-end for LUKS decryption with Clevis+Tang options: introduce selector type "key_clevis" options: generalize "--key" selector parsing for C-language utilities mltools/tools_utils-c: handle internal type error with abort() mltools/tools_utils: generalize "--key" selector parsing for OCaml utils options, mltools/tools_utils: parse "--key ID:clevis" options options, mltools/tools_utils: add helper for network dependency ). Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1809453 Signed-off-by: Laszlo Ersek Reviewed-by: Richard W.M. Jones Message-Id: <20220630122048.19335-4-lersek@redhat.com> (cherry picked from commit 6a5b44f538065a9f661510234a4235bf38348213) --- common | 2 +- fish/fish.c | 3 +++ fuse/guestmount.c | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) Submodule common 35467027f..af6cb55bc: diff --git a/common/options/decrypt.c b/common/options/decrypt.c index 1cd7b627e..97c8b88d1 100644 --- a/common/options/decrypt.c +++ b/common/options/decrypt.c @@ -124,10 +124,10 @@ decrypt_mountables (guestfs_h *g, const char * const *mountables, while ((mountable = *mnt_scan++) != NULL) { CLEANUP_FREE char *type = NULL; CLEANUP_FREE char *uuid = NULL; - CLEANUP_FREE_STRING_LIST char **keys = NULL; + struct matching_key *keys; + size_t nr_matches; CLEANUP_FREE char *mapname = NULL; - const char * const *key_scan; - const char *key; + size_t scan; type = guestfs_vfs_type (g, mountable); if (type == NULL) @@ -144,33 +144,45 @@ decrypt_mountables (guestfs_h *g, const char * const *mountables, /* Grab the keys that we should try with this device, based on device name, * or UUID (if any). */ - keys = get_keys (ks, mountable, uuid); - assert (keys[0] != NULL); + keys = get_keys (ks, mountable, uuid, &nr_matches); + assert (nr_matches > 0); /* Generate a node name for the plaintext (decrypted) device node. */ if (uuid == NULL || asprintf (&mapname, "luks-%s", uuid) == -1) mapname = make_mapname (mountable); /* Try each key in turn. */ - key_scan = (const char * const *)keys; - while ((key = *key_scan++) != NULL) { + 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, 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); +#endif + else + r = guestfs_cryptsetup_open (g, mountable, key->passphrase, mapname, + -1); guestfs_pop_error_handler (g); if (r == 0) break; } - if (key == NULL) + if (scan == nr_matches) error (EXIT_FAILURE, 0, _("could not find key to open LUKS encrypted %s.\n\n" "Try using --key on the command line.\n\n" "Original error: %s (%d)"), mountable, guestfs_last_error (g), guestfs_last_errno (g)); + free_keys (keys, nr_matches); decrypted_some = true; } diff --git a/common/options/key-option.pod b/common/options/key-option.pod index 90a3b15c5..6bc04df17 100644 --- a/common/options/key-option.pod +++ b/common/options/key-option.pod @@ -14,4 +14,13 @@ Use the specified C as passphrase. Read the passphrase from F. +=item B<--key> C:clevis + +Attempt passphrase-less unlocking for C with Clevis, over the +network. Please refer to L for more +information on network-bound disk encryption (NBDE). + +Note that if any such option is present on the command line, QEMU user +networking will be automatically enabled for the libguestfs appliance. + =back diff --git a/common/options/keys.c b/common/options/keys.c index d27a7123e..d987ae561 100644 --- a/common/options/keys.c +++ b/common/options/keys.c @@ -125,11 +125,12 @@ read_first_line_from_file (const char *filename) * keystore. There may be multiple. If none are read from the * keystore, ask the user. */ -char ** -get_keys (struct key_store *ks, const char *device, const char *uuid) +struct matching_key * +get_keys (struct key_store *ks, const char *device, const char *uuid, + size_t *nr_matches) { - size_t i, j, nmemb; - char **r; + size_t i, nmemb; + struct matching_key *r, *match; char *s; /* We know the returned list must have at least one element and not @@ -139,22 +140,20 @@ get_keys (struct key_store *ks, const char *device, const char *uuid) if (ks && ks->nr_keys > nmemb) nmemb = ks->nr_keys; - /* make room for the terminating NULL */ - if (nmemb == (size_t)-1) + if (nmemb > (size_t)-1 / sizeof *r) error (EXIT_FAILURE, 0, _("size_t overflow")); - nmemb++; - r = calloc (nmemb, sizeof (char *)); + r = malloc (nmemb * sizeof *r); if (r == NULL) - error (EXIT_FAILURE, errno, "calloc"); + error (EXIT_FAILURE, errno, "malloc"); - j = 0; + match = r; if (ks) { for (i = 0; i < ks->nr_keys; ++i) { struct key_store_key *key = &ks->keys[i]; - if (STRNEQ (key->id, device) && (uuid && STRNEQ (key->id, uuid))) + if (STRNEQ (key->id, device) && (!uuid || STRNEQ (key->id, uuid))) continue; switch (key->type) { @@ -162,68 +161,101 @@ get_keys (struct key_store *ks, const char *device, const char *uuid) s = strdup (key->string.s); if (!s) error (EXIT_FAILURE, errno, "strdup"); - r[j++] = s; + match->clevis = false; + match->passphrase = s; + ++match; break; case key_file: s = read_first_line_from_file (key->file.name); - r[j++] = s; + match->clevis = false; + match->passphrase = s; + ++match; + break; + case key_clevis: + match->clevis = true; + match->passphrase = NULL; + ++match; break; } } } - if (j == 0) { + 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")); - r[0] = s; + match->clevis = false; + match->passphrase = s; + ++match; } + *nr_matches = (size_t)(match - r); return r; } +void +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; + + assert (key->clevis == (key->passphrase == NULL)); + if (!key->clevis) + free (key->passphrase); + } + free (keys); +} + struct key_store * key_store_add_from_selector (struct key_store *ks, const char *selector) { - CLEANUP_FREE_STRING_LIST char **fields = - guestfs_int_split_string (':', selector); + CLEANUP_FREE_STRING_LIST char **fields = NULL; + size_t field_count; struct key_store_key key; + fields = guestfs_int_split_string (':', selector); if (!fields) error (EXIT_FAILURE, errno, "guestfs_int_split_string"); + field_count = guestfs_int_count_strings (fields); - if (guestfs_int_count_strings (fields) != 3) { - invalid_selector: - error (EXIT_FAILURE, 0, "invalid selector for --key: %s", selector); - } - - /* 1: device */ + /* field#0: ID */ + if (field_count < 1) + error (EXIT_FAILURE, 0, _("selector '%s': missing ID"), selector); key.id = strdup (fields[0]); if (!key.id) error (EXIT_FAILURE, errno, "strdup"); - /* 2: key type */ - if (STREQ (fields[1], "key")) + /* field#1...: TYPE, and TYPE-specific properties */ + if (field_count < 2) + error (EXIT_FAILURE, 0, _("selector '%s': missing TYPE"), selector); + + if (STREQ (fields[1], "key")) { key.type = key_string; - else if (STREQ (fields[1], "file")) - key.type = key_file; - else - goto invalid_selector; - - /* 3: actual key */ - switch (key.type) { - case key_string: + if (field_count != 3) + error (EXIT_FAILURE, 0, + _("selector '%s': missing KEY_STRING, or too many fields"), + selector); key.string.s = strdup (fields[2]); if (!key.string.s) error (EXIT_FAILURE, errno, "strdup"); - break; - case key_file: + } else if (STREQ (fields[1], "file")) { + key.type = key_file; + if (field_count != 3) + error (EXIT_FAILURE, 0, + _("selector '%s': missing FILENAME, or too many fields"), + selector); key.file.name = strdup (fields[2]); if (!key.file.name) error (EXIT_FAILURE, errno, "strdup"); - break; - } + } else if (STREQ (fields[1], "clevis")) { + key.type = key_clevis; + if (field_count != 2) + error (EXIT_FAILURE, 0, _("selector '%s': too many fields"), selector); + } else + error (EXIT_FAILURE, 0, _("selector '%s': invalid TYPE"), selector); return key_store_import_key (ks, &key); } @@ -252,6 +284,21 @@ key_store_import_key (struct key_store *ks, const struct key_store_key *key) return ks; } +bool +key_store_requires_network (const struct key_store *ks) +{ + size_t i; + + if (ks == NULL) + return false; + + for (i = 0; i < ks->nr_keys; ++i) + if (ks->keys[i].type == key_clevis) + return true; + + return false; +} + void free_key_store (struct key_store *ks) { @@ -270,6 +317,9 @@ free_key_store (struct key_store *ks) case key_file: free (key->file.name); break; + case key_clevis: + /* nothing */ + break; } free (key->id); } diff --git a/common/options/options.h b/common/options/options.h index 80df91a85..60d5d8064 100644 --- a/common/options/options.h +++ b/common/options/options.h @@ -115,6 +115,7 @@ struct key_store_key { enum { key_string, /* key specified as string */ key_file, /* key stored in a file */ + key_clevis, /* key reconstructed with Clevis+Tang */ } type; union { struct { @@ -134,6 +135,19 @@ struct key_store { size_t nr_keys; }; +/* 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); @@ -151,9 +165,12 @@ extern void print_inspect_prompt (void); /* in key.c */ extern char *read_key (const char *param); -extern char **get_keys (struct key_store *ks, const char *device, const char *uuid); +extern struct matching_key *get_keys (struct key_store *ks, const char *device, + const char *uuid, size_t *nr_matches); +extern void free_keys (struct matching_key *keys, size_t nr_matches); extern struct key_store *key_store_add_from_selector (struct key_store *ks, const char *selector); extern struct key_store *key_store_import_key (struct key_store *ks, const struct key_store_key *key); +extern bool key_store_requires_network (const struct key_store *ks); extern void free_key_store (struct key_store *ks); /* in options.c */ diff --git a/fish/fish.c b/fish/fish.c index 23d9bb94f..19e3d2799 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -476,6 +476,9 @@ main (int argc, char *argv[]) /* If we've got drives to add, add them now. */ add_drives (drvs); + if (key_store_requires_network (ks) && guestfs_set_network (g, 1) == -1) + exit (EXIT_FAILURE); + /* If we've got mountpoints or prepared drives or -i option, we must * launch the guest and mount them. */ diff --git a/fuse/guestmount.c b/fuse/guestmount.c index 77c534828..3c6d57bde 100644 --- a/fuse/guestmount.c +++ b/fuse/guestmount.c @@ -348,6 +348,10 @@ main (int argc, char *argv[]) /* Do the guest drives and mountpoints. */ add_drives (drvs); + + if (key_store_requires_network (ks) && guestfs_set_network (g, 1) == -1) + exit (EXIT_FAILURE); + if (guestfs_launch (g) == -1) exit (EXIT_FAILURE); if (inspector) -- 2.31.1