Richard W.M. Jones
2010-Jul-21 19:57 UTC
[Libguestfs] [PATCH 0/2] Support for LUKS-encrypted Linux guests
These two patches add minimal support for LUKS-encrypted Linux guests (ie. most common whole-disk encryption schemes). Only opening LVM-on-LUKS is supported by this, not, for example, changing keys or creating new encrypted devices. It's also a little clunky to use from guestfish, but we can add more commands to fix that once we have a better handle on the problem. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v
Richard W.M. Jones
2010-Jul-21 19:57 UTC
[Libguestfs] [PATCH 1/2] generator: Add 'Key' parameter type.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From 581a7965faa5bf242ab3f8b7c259ab17c2e967f4 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 21 Jul 2010 12:52:51 +0100 Subject: [PATCH 1/2] generator: Add 'Key' parameter type. Add a 'Key' parameter type, used for passing sensitive key material into libguestfs. Eventually the plan is to mlock() key material into memory. However this is very difficult to achieve because the encoded XDR strings end up in many places. Therefore users should note that key material passed to libguestfs might end up in swap. The only difference between 'Key' and 'String' currently is that guestfish requests the key from /dev/tty with echoing turned off. --- fish/fish.c | 67 +++++++++++++++++ fish/fish.h | 1 + fish/guestfish.pod | 5 ++ src/generator.ml | 202 ++++++++++++++++++++++++++++++++------------------- src/guestfs.pod | 15 ++++ 5 files changed, 215 insertions(+), 75 deletions(-) diff --git a/fish/fish.c b/fish/fish.c index 4276ae1..68f26ed 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -29,6 +29,7 @@ #include <sys/types.h> #include <sys/wait.h> #include <locale.h> +#include <termios.h> #ifdef HAVE_LIBREADLINE #include <readline/readline.h> @@ -80,6 +81,7 @@ int remote_control_listen = 0; int remote_control = 0; int exit_on_error = 1; int command_num = 0; +int keys_from_stdin = 0; static void __attribute__((noreturn)) usage (int status) @@ -110,6 +112,7 @@ usage (int status) " -D|--no-dest-paths Don't tab-complete paths from guest fs\n" " -f|--file file Read commands from file\n" " -i|--inspector Run virt-inspector to get disk mountpoints\n" + " --keys-from-stdin Read passphrases from stdin\n" " --listen Listen for remote commands\n" " -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n" " -n|--no-sync Don't autosync\n" @@ -149,6 +152,7 @@ main (int argc, char *argv[]) { "file", 1, 0, 'f' }, { "help", 0, 0, HELP_OPTION }, { "inspector", 0, 0, 'i' }, + { "keys-from-stdin", 0, 0, 0 }, { "listen", 0, 0, 0 }, { "mount", 1, 0, 'm' }, { "new", 1, 0, 'N' }, @@ -239,6 +243,8 @@ main (int argc, char *argv[]) } } else if (STREQ (long_options[option_index].name, "selinux")) { guestfs_set_selinux (g, 1); + } else if (STREQ (long_options[option_index].name, "keys-from-stdin")) { + keys_from_stdin = 1; } else { fprintf (stderr, _("%s: unknown long option: %s (%d)\n"), program_name, long_options[option_index].name, option_index); @@ -1710,6 +1716,67 @@ file_out (const char *arg) return ret; } +/* Read a passphrase ('Key') from /dev/tty with echo off. + * The caller (cmds.c) will call free on the string afterwards. + * Based on the code in cryptsetup file lib/utils.c. + */ +char * +read_key (const char *param) +{ + FILE *infp, *outfp; + struct termios orig, temp; + char *ret = NULL; + + /* Read and write to /dev/tty if available. */ + if (keys_from_stdin || + (infp = outfp = fopen ("/dev/tty", "w+")) == NULL) { + infp = stdin; + outfp = stdout; + } + + /* Print the prompt and set no echo. */ + int tty = isatty (fileno (infp)); + int tcset = 0; + if (tty) { + fprintf (outfp, _("Enter key or passphrase (\"%s\"): "), param); + + if (tcgetattr (fileno (infp), &orig) == -1) { + perror ("tcgetattr"); + goto error; + } + memcpy (&temp, &orig, sizeof temp); + temp.c_lflag &= ~ECHO; + + tcsetattr (fileno (infp), TCSAFLUSH, &temp); + tcset = 1; + } + + size_t n = 0; + ssize_t len; + len = getline (&ret, &n, infp); + if (len == -1) { + perror ("getline"); + ret = NULL; + goto error; + } + + /* Remove the terminating \n if there is one. */ + if (len > 0 && ret[len-1] == '\n') + ret[len-1] = '\0'; + + error: + /* Restore echo, close file descriptor. */ + if (tty && tcset) { + printf ("\n"); + tcsetattr (fileno (infp), TCSAFLUSH, &orig); + } + + if (infp != stdin) + fclose (infp); /* outfp == infp, so this is closed also */ + + return ret; +} + static void print_shell_quote (FILE *stream, const char *str) { diff --git a/fish/fish.h b/fish/fish.h index 9f64979..da1b087 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -68,6 +68,7 @@ extern char *file_in (const char *arg); extern void free_file_in (char *s); extern char *file_out (const char *arg); extern void extended_help_message (void); +extern char *read_key (const char *param); /* in cmds.c (auto-generated) */ extern void list_commands (void); diff --git a/fish/guestfish.pod b/fish/guestfish.pod index 5737c46..86dcf58 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -179,6 +179,11 @@ I<--ro> might not behave correctly. See also: L<virt-inspector(1)>. +=item B<--keys-from-stdin> + +Read key or passphrase parameters from stdin. The default is +to try to read passphrases from the user by opening C</dev/tty>. + =item B<--listen> Fork into the background and listen for remote commands. See section diff --git a/src/generator.ml b/src/generator.ml index 2fb3f48..0df77a3 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -174,6 +174,14 @@ and argt * To return an arbitrary buffer, use RBufferOut. *) | BufferIn of string + (* Key material / passphrase. Eventually we should treat this + * as sensitive and mlock it into physical RAM. However this + * is highly complex because of all the places that XDR-encoded + * strings can end up. So currently the only difference from + * 'String' is the way that guestfish requests these parameters + * from the user. + *) + | Key of string type flags | ProtocolLimitWarning (* display warning about protocol size limits *) @@ -5294,7 +5302,7 @@ let map_chars f str let name_of_argt = function | Pathname n | Device n | Dev_or_Path n | String n | OptString n | StringList n | DeviceList n | Bool n | Int n | Int64 n - | FileIn n | FileOut n | BufferIn n -> n + | FileIn n | FileOut n | BufferIn n | Key n -> n let java_name_of_struct typ try List.assoc typ java_structs @@ -5655,6 +5663,10 @@ I<The caller must free the returned buffer after use>.\n\n" pr "%s\n\n" protocol_limit_warning; if List.mem DangerWillRobinson flags then pr "%s\n\n" danger_will_robinson; + if List.exists (function Key _ -> true | _ -> false) (snd style) then + pr "This function takes a key or passphrase parameter which +could contain sensitive material. Read the section +L</KEYS AND PASSPHRASES> for more information.\n\n"; match deprecation_notice flags with | None -> () | Some txt -> pr "%s\n\n" txt @@ -5760,7 +5772,7 @@ and generate_xdr () pr "struct %s_args {\n" name; List.iter ( function - | Pathname n | Device n | Dev_or_Path n | String n -> + | Pathname n | Device n | Dev_or_Path n | String n | Key n -> pr " string %s<>;\n" n | OptString n -> pr " str *%s;\n" n | StringList n | DeviceList n -> pr " str %s<>;\n" n @@ -6046,7 +6058,8 @@ check_state (guestfs_h *g, const char *caller) | FileOut n | BufferIn n | StringList n - | DeviceList n -> + | DeviceList n + | Key n -> pr " if (%s == NULL) {\n" n; pr " error (g, \"%%s: %%s: parameter cannot be NULL\",\n"; pr " \"%s\", \"%s\");\n" shortname n; @@ -6088,7 +6101,8 @@ check_state (guestfs_h *g, const char *caller) | Dev_or_Path n | FileIn n | FileOut n - | BufferIn n -> + | BufferIn n + | Key n -> (* guestfish doesn't support string escaping, so neither do we *) pr " printf (\" \\\"%%s\\\"\", %s);\n" n | OptString n -> (* string option *) @@ -6181,7 +6195,7 @@ check_state (guestfs_h *g, const char *caller) | args -> List.iter ( function - | Pathname n | Device n | Dev_or_Path n | String n -> + | Pathname n | Device n | Dev_or_Path n | String n | Key n -> pr " args.%s = (char *) %s;\n" n n | OptString n -> pr " args.%s = %s ? (char **) &%s : NULL;\n" n n n @@ -6461,7 +6475,8 @@ and generate_daemon_actions () function | Device n | Dev_or_Path n | Pathname n - | String n -> () + | String n + | Key n -> () | OptString n -> pr " char *%s;\n" n | StringList n | DeviceList n -> pr " char **%s;\n" n | Bool n -> pr " int %s;\n" n @@ -6518,7 +6533,7 @@ and generate_daemon_actions () pr_args n; pr " REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, %s, goto done);\n" n (if is_filein then "cancel_receive ()" else "0"); - | String n -> pr_args n + | String n | Key n -> pr_args n | OptString n -> pr " %s = args.%s ? *args.%s : NULL;\n" n n n | StringList n -> pr_list_handling_code n; @@ -7533,7 +7548,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd | Device n, arg | Dev_or_Path n, arg | String n, arg - | OptString n, arg -> + | OptString n, arg + | Key n, arg -> pr " const char *%s = \"%s\";\n" n (c_quote arg); | BufferIn n, arg -> pr " const char *%s = \"%s\";\n" n (c_quote arg); @@ -7588,7 +7604,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd | Pathname n, _ | Device n, _ | Dev_or_Path n, _ | String n, _ - | OptString n, _ -> + | OptString n, _ + | Key n, _ -> pr ", %s" n | BufferIn n, _ -> pr ", %s, %s_size" n n @@ -7714,6 +7731,7 @@ and generate_fish_cmds () match snd style with | [] -> name2 | args -> + let args = List.filter (function Key _ -> false | _ -> true) args in sprintf "%s %s" name2 (String.concat " " (List.map name_of_argt args)) in @@ -7879,7 +7897,8 @@ and generate_fish_cmds () | Pathname n | Dev_or_Path n | FileIn n - | FileOut n -> pr " char *%s;\n" n + | FileOut n + | Key n -> pr " char *%s;\n" n | BufferIn n -> pr " const char *%s;\n" n; pr " size_t %s_size;\n" n @@ -7890,7 +7909,10 @@ and generate_fish_cmds () ) (snd style); (* Check and convert parameters. *) - let argc_expected = List.length (snd style) in + let argc_expected + let args_no_keys + List.filter (function Key _ -> false | _ -> true) (snd style) in + List.length args_no_keys in pr " if (argc != %d) {\n" argc_expected; pr " fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n" argc_expected; @@ -7898,12 +7920,12 @@ and generate_fish_cmds () pr " return -1;\n"; pr " }\n"; - let parse_integer fn fntyp rtyp range name i + let parse_integer fn fntyp rtyp range name pr " {\n"; pr " strtol_error xerr;\n"; pr " %s r;\n" fntyp; pr "\n"; - pr " xerr = %s (argv[%d], NULL, 0, &r, xstrtol_suffixes);\n" fn i; + pr " xerr = %s (argv[i++], NULL, 0, &r, xstrtol_suffixes);\n" fn; pr " if (xerr != LONGINT_OK) {\n"; pr " fprintf (stderr,\n"; pr " _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n"; @@ -7925,43 +7947,49 @@ and generate_fish_cmds () pr " }\n"; in - iteri ( - fun i -> - function - | Device name - | String name -> - pr " %s = argv[%d];\n" name i - | Pathname name - | Dev_or_Path name -> - pr " %s = resolve_win_path (argv[%d]);\n" name i; - pr " if (%s == NULL) return -1;\n" name - | OptString name -> - pr " %s = STRNEQ (argv[%d], \"\") ? argv[%d] : NULL;\n" - name i i - | BufferIn name -> - pr " %s = argv[%d];\n" name i; - pr " %s_size = strlen (argv[%d]);\n" name i - | FileIn name -> - pr " %s = file_in (argv[%d]);\n" name i; - pr " if (%s == NULL) return -1;\n" name - | FileOut name -> - pr " %s = file_out (argv[%d]);\n" name i; - pr " if (%s == NULL) return -1;\n" name - | StringList name | DeviceList name -> - pr " %s = parse_string_list (argv[%d]);\n" name i; - pr " if (%s == NULL) return -1;\n" name; - | Bool name -> - pr " %s = is_true (argv[%d]) ? 1 : 0;\n" name i - | Int name -> - let range - let min = "(-(2LL<<30))" - and max = "((2LL<<30)-1)" - and comment - "The Int type in the generator is a signed 31 bit int." in - Some (min, max, comment) in - parse_integer "xstrtoll" "long long" "int" range name i - | Int64 name -> - parse_integer "xstrtoll" "long long" "int64_t" None name i + if snd style <> [] then + pr " size_t i = 0;\n"; + + List.iter ( + function + | Device name + | String name -> + pr " %s = argv[i++];\n" name + | Pathname name + | Dev_or_Path name -> + pr " %s = resolve_win_path (argv[i++]);\n" name; + pr " if (%s == NULL) return -1;\n" name + | OptString name -> + pr " %s = STRNEQ (argv[i], \"\") ? argv[i] : NULL;\n" name; + pr " i++;\n" + | BufferIn name -> + pr " %s = argv[i];\n" name; + pr " %s_size = strlen (argv[i]);\n" name; + pr " i++;\n" + | FileIn name -> + pr " %s = file_in (argv[i++]);\n" name; + pr " if (%s == NULL) return -1;\n" name + | FileOut name -> + pr " %s = file_out (argv[i++]);\n" name; + pr " if (%s == NULL) return -1;\n" name + | StringList name | DeviceList name -> + pr " %s = parse_string_list (argv[i++]);\n" name; + pr " if (%s == NULL) return -1;\n" name + | Key name -> + pr " %s = read_key (\"%s\");\n" name name; + pr " if (%s == NULL) return -1;\n" name + | Bool name -> + pr " %s = is_true (argv[i++]) ? 1 : 0;\n" name + | Int name -> + let range + let min = "(-(2LL<<30))" + and max = "((2LL<<30)-1)" + and comment + "The Int type in the generator is a signed 31 bit int." in + Some (min, max, comment) in + parse_integer "xstrtoll" "long long" "int" range name + | Int64 name -> + parse_integer "xstrtoll" "long long" "int64_t" None name ) (snd style); (* Call C API function. *) @@ -7975,7 +8003,8 @@ and generate_fish_cmds () | OptString _ | Bool _ | Int _ | Int64 _ | BufferIn _ -> () - | Pathname name | Dev_or_Path name | FileOut name -> + | Pathname name | Dev_or_Path name | FileOut name + | Key name -> pr " free (%s);\n" name | FileIn name -> pr " free_file_in (%s);\n" name @@ -8228,7 +8257,8 @@ and generate_fish_actions_pod () pr " %s" name; List.iter ( function - | Pathname n | Device n | Dev_or_Path n | String n -> pr " %s" n + | Pathname n | Device n | Dev_or_Path n | String n -> + pr " %s" n | OptString n -> pr " %s" n | StringList n | DeviceList n -> pr " '%s ...'" n | Bool _ -> pr " true|false" @@ -8236,6 +8266,7 @@ and generate_fish_actions_pod () | Int64 n -> pr " %s" n | FileIn n | FileOut n -> pr " (%s|-)" n | BufferIn n -> pr " %s" n + | Key _ -> () (* keys are entered at a prompt *) ) (snd style); pr "\n"; pr "\n"; @@ -8245,6 +8276,10 @@ and generate_fish_actions_pod () | _ -> false) (snd style) then pr "Use C<-> instead of a filename to read/write from stdin/stdout.\n\n"; + if List.exists (function Key _ -> true | _ -> false) (snd style) then + pr "This command has one or more key or passphrase parameters. +Guestfish will prompt for these separately.\n\n"; + if List.mem ProtocolLimitWarning flags then pr "%s\n\n" protocol_limit_warning; @@ -8299,7 +8334,8 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true) | Pathname n | Device n | Dev_or_Path n | String n - | OptString n -> + | OptString n + | Key n -> next (); pr "const char *%s" n | StringList n | DeviceList n -> @@ -8608,7 +8644,8 @@ copy_table (char * const * argv) | Device n | Dev_or_Path n | String n | FileIn n - | FileOut n -> + | FileOut n + | Key n -> (* Copy strings in case the GC moves them: RHBZ#604691 *) pr " char *%s = guestfs_safe_strdup (g, String_val (%sv));\n" n n | OptString n -> @@ -8664,7 +8701,7 @@ copy_table (char * const * argv) List.iter ( function | Pathname n | Device n | Dev_or_Path n | String n | OptString n - | FileIn n | FileOut n | BufferIn n -> + | FileIn n | FileOut n | BufferIn n | Key n -> pr " free (%s);\n" n | StringList n | DeviceList n -> pr " ocaml_guestfs_free_strings (%s);\n" n; @@ -8755,7 +8792,7 @@ and generate_ocaml_prototype ?(is_external = false) name style List.iter ( function | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ - | BufferIn _ -> pr "string -> " + | BufferIn _ | Key _ -> pr "string -> " | OptString _ -> pr "string option -> " | StringList _ | DeviceList _ -> pr "string array -> " | Bool _ -> pr "bool -> " @@ -8924,7 +8961,7 @@ close (g) fun i -> function | Pathname n | Device n | Dev_or_Path n | String n - | FileIn n | FileOut n -> + | FileIn n | FileOut n | Key n -> pr " char *%s;\n" n | BufferIn n -> pr " char *%s;\n" n; @@ -8947,7 +8984,7 @@ close (g) | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _ | Bool _ | Int _ | Int64 _ | FileIn _ | FileOut _ - | BufferIn _ -> () + | BufferIn _ | Key _ -> () | StringList n | DeviceList n -> pr " free (%s);\n" n ) (snd style) in @@ -9343,7 +9380,7 @@ and generate_perl_prototype name style match arg with | Pathname n | Device n | Dev_or_Path n | String n | OptString n | Bool n | Int n | Int64 n | FileIn n | FileOut n - | BufferIn n -> + | BufferIn n | Key n -> pr "$%s" n | StringList n | DeviceList n -> pr "\\@%s" n @@ -9614,7 +9651,7 @@ py_guestfs_close (PyObject *self, PyObject *args) List.iter ( function - | Pathname n | Device n | Dev_or_Path n | String n + | Pathname n | Device n | Dev_or_Path n | String n | Key n | FileIn n | FileOut n -> pr " const char *%s;\n" n | OptString n -> pr " const char *%s;\n" n @@ -9635,7 +9672,8 @@ py_guestfs_close (PyObject *self, PyObject *args) pr " if (!PyArg_ParseTuple (args, (char *) \"O"; List.iter ( function - | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ -> pr "s" + | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ + | FileIn _ | FileOut _ -> pr "s" | OptString _ -> pr "z" | StringList _ | DeviceList _ -> pr "O" | Bool _ -> pr "i" (* XXX Python has booleans? *) @@ -9649,7 +9687,8 @@ py_guestfs_close (PyObject *self, PyObject *args) pr " &py_g"; List.iter ( function - | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n -> pr ", &%s" n + | Pathname n | Device n | Dev_or_Path n | String n | Key n + | FileIn n | FileOut n -> pr ", &%s" n | OptString n -> pr ", &%s" n | StringList n | DeviceList n -> pr ", &py_%s" n | Bool n -> pr ", &%s" n @@ -9664,7 +9703,7 @@ py_guestfs_close (PyObject *self, PyObject *args) pr " g = get_handle (py_g);\n"; List.iter ( function - | Pathname _ | Device _ | Dev_or_Path _ | String _ + | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ | BufferIn _ -> () | StringList n | DeviceList n -> @@ -9680,7 +9719,7 @@ py_guestfs_close (PyObject *self, PyObject *args) List.iter ( function - | Pathname _ | Device _ | Dev_or_Path _ | String _ + | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ | BufferIn _ -> () | StringList n | DeviceList n -> @@ -9987,7 +10026,8 @@ static VALUE ruby_guestfs_close (VALUE gv) List.iter ( function - | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n -> + | Pathname n | Device n | Dev_or_Path n | String n | Key n + | FileIn n | FileOut n -> pr " Check_Type (%sv, T_STRING);\n" n; pr " const char *%s = StringValueCStr (%sv);\n" n n; pr " if (!%s)\n" n; @@ -10048,7 +10088,7 @@ static VALUE ruby_guestfs_close (VALUE gv) List.iter ( function - | Pathname _ | Device _ | Dev_or_Path _ | String _ + | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ | BufferIn _ -> () | StringList n | DeviceList n -> @@ -10365,7 +10405,8 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false) | String n | OptString n | FileIn n - | FileOut n -> + | FileOut n + | Key n -> pr "String %s" n | BufferIn n -> pr "byte[] %s" n @@ -10488,7 +10529,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close | String n | OptString n | FileIn n - | FileOut n -> + | FileOut n + | Key n -> pr ", jstring j%s" n | BufferIn n -> pr ", jbyteArray j%s" n @@ -10545,7 +10587,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close | String n | OptString n | FileIn n - | FileOut n -> + | FileOut n + | Key n -> pr " const char *%s;\n" n | BufferIn n -> pr " jbyte *%s;\n" n; @@ -10582,7 +10625,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close | Device n | Dev_or_Path n | String n | FileIn n - | FileOut n -> + | FileOut n + | Key n -> pr " %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n | OptString n -> (* This is completely undocumented, but Java null becomes @@ -10619,7 +10663,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close | Device n | Dev_or_Path n | String n | FileIn n - | FileOut n -> + | FileOut n + | Key n -> pr " (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n | OptString n -> pr " if (j%s)\n" n; @@ -10900,7 +10945,7 @@ last_error h = do function | FileIn n | FileOut n - | Pathname n | Device n | Dev_or_Path n | String n -> + | Pathname n | Device n | Dev_or_Path n | String n | Key n -> pr "withCString %s $ \\%s -> " n n | BufferIn n -> pr "withCStringLen %s $ \\(%s, %s_size) -> " n n n @@ -10916,7 +10961,10 @@ last_error h = do | Int n -> sprintf "(fromIntegral %s)" n | Int64 n -> sprintf "(fromIntegral %s)" n | FileIn n | FileOut n - | Pathname n | Device n | Dev_or_Path n | String n | OptString n | StringList n | DeviceList n -> n + | Pathname n | Device n | Dev_or_Path n + | String n | OptString n + | StringList n | DeviceList n + | Key n -> n | BufferIn n -> sprintf "%s (fromIntegral %s_size)" n n ) (snd style) in pr "withForeignPtr h (\\p -> c_%s %s)\n" name @@ -10967,7 +11015,8 @@ and generate_haskell_prototype ~handle ?(hs = false) style List.iter ( fun arg -> (match arg with - | Pathname _ | Device _ | Dev_or_Path _ | String _ -> pr "%s" string + | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ -> + pr "%s" string | BufferIn _ -> if hs then pr "String" else pr "CString -> CInt" @@ -11161,6 +11210,7 @@ namespace Guestfs function | Pathname n | Device n | Dev_or_Path n | String n | OptString n | FileIn n | FileOut n + | Key n | BufferIn n -> pr ", [In] string %s" n | StringList n | DeviceList n -> @@ -11185,6 +11235,7 @@ namespace Guestfs function | Pathname n | Device n | Dev_or_Path n | String n | OptString n | FileIn n | FileOut n + | Key n | BufferIn n -> next (); pr "string %s" n | StringList n | DeviceList n -> @@ -11289,7 +11340,8 @@ print_strings (char *const *argv) | Device n | Dev_or_Path n | String n | FileIn n - | FileOut n -> pr " printf (\"%%s\\n\", %s);\n" n + | FileOut n + | Key n -> pr " printf (\"%%s\\n\", %s);\n" n | BufferIn n -> pr " {\n"; pr " size_t i;\n"; diff --git a/src/guestfs.pod b/src/guestfs.pod index e876016..8e3d07c 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -675,6 +675,21 @@ L</UPLOADING> and L</DOWNLOADING> document how to do this. You might also consider mounting the disk image using our FUSE filesystem support (L<guestmount(1)>). +=head2 KEYS AND PASSPHRASES + +Certain libguestfs calls take a parameter that contains sensitive key +material, passed in as a C string. + +In the future we would hope to change the libguestfs implementation so +that keys are L<mlock(2)>-ed into physical RAM, and thus can never end +up in swap. However this is I<not> done at the moment, because of the +complexity of such an implementation. + +Therefore you should be aware that any key parameter you pass to +libguestfs might end up being written out to the swap partition. If +this is a concern, scrub the swap partition or don't use libguestfs on +encrypted devices. + =head1 CONNECTION MANAGEMENT =head2 guestfs_h * -- 1.7.1
Richard W.M. Jones
2010-Jul-21 19:58 UTC
[Libguestfs] [PATCH 2/2] New APIs: Support for opening LUKS-encrypted disks.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw -------------- next part -------------->From 637f8df83726ab9b50e8a6d2181bd1e0e93ec13e Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 21 Jul 2010 19:50:06 +0100 Subject: [PATCH 2/2] New APIs: Support for opening LUKS-encrypted disks. This adds support for opening LUKS-encrypted disks, via three new APIs: luks_open: Create a mapping for an encrypted disk. luks_open_ro: Same, but read-only mapping. luks_close: Close a mapping. A typical guestfish session using this functionality looks like this: $ guestfish --ro -a encrypted.img ><fs> run ><fs> list-devices /dev/vda ><fs> list-partitions /dev/vda1 /dev/vda2 ><fs> vfs-type /dev/vda2 crypto_LUKS ><fs> luks-open /dev/vda2 luksdev Enter key or passphrase ("key"): ><fs> vgscan ><fs> vg-activate-all true ><fs> pvs /dev/dm-0 ><fs> vgs vg_f13x64encrypted ><fs> lvs /dev/vg_f13x64encrypted/lv_root /dev/vg_f13x64encrypted/lv_swap ><fs> mount /dev/vg_f13x64encrypted/lv_root / ><fs> ll / total 132 dr-xr-xr-x. 24 root root 4096 Jul 21 12:01 . dr-xr-xr-x 20 root root 0 Jul 21 20:06 .. drwx------. 3 root root 4096 Jul 21 11:59 .dbus drwx------. 2 root root 4096 Jul 21 12:00 .pulse -rw-------. 1 root root 256 Jul 21 12:00 .pulse-cookie dr-xr-xr-x. 2 root root 4096 May 13 03:03 bin NOT included in this patch: - An easier way to use this from guestfish. - Ability to create LUKS devices. - Ability to change LUKS keys on existing devices. - Direct access to the /dev/mapper device (eg. if it contains anything apart from VGs). --- TODO | 13 ++++ appliance/kmod.whitelist.in | 15 +++++ appliance/packagelist.in | 2 + daemon/Makefile.am | 1 + daemon/luks.c | 138 +++++++++++++++++++++++++++++++++++++++++++ fish/guestfish.pod | 33 ++++++++++ po/POTFILES.in | 1 + src/MAX_PROC_NR | 2 +- src/generator.ml | 37 ++++++++++++ src/guestfs.pod | 31 ++++++++++ 10 files changed, 272 insertions(+), 1 deletions(-) create mode 100644 daemon/luks.c diff --git a/TODO b/TODO index fc6b3fd..d0196c8 100644 --- a/TODO +++ b/TODO @@ -356,3 +356,16 @@ Progress of long-running operations For example, copying in virt-resize. How can we display the progress of these operations? This is a basic usability requirement, and frequently requested. + +Better support for encrypted devices +------------------------------------ + +Currently LUKS support only works if the device contains volume +groups. If it contains, eg., partitions, you cannot access them. +We would like to add: + + - An easier way to use this from guestfish. + - Ability to create LUKS devices. + - Ability to change LUKS keys on existing devices. + - Direct access to the /dev/mapper device (eg. if it contains + anything apart from VGs). diff --git a/appliance/kmod.whitelist.in b/appliance/kmod.whitelist.in index 0a92122..850b7b8 100644 --- a/appliance/kmod.whitelist.in +++ b/appliance/kmod.whitelist.in @@ -69,3 +69,18 @@ loop.ko gfs2.ko dlm.ko configfs.ko + +# Used by dm-crypt. Probably many more crypto modules +# should be added here. +aes*.ko +blkcipher.ko +cbc.ko +cryptd.ko +crypto_blkcipher.ko +gf128mul.ko +padlock-aes.ko +sha256*.ko +sha512*.ko +xor.ko +xts.ko +zlib.ko diff --git a/appliance/packagelist.in b/appliance/packagelist.in index 16dc88d..4d45963 100644 --- a/appliance/packagelist.in +++ b/appliance/packagelist.in @@ -11,6 +11,7 @@ #if REDHAT == 1 augeas-libs btrfs-progs + cryptsetup-luks diffutils e2fsprogs /* e4fsprogs only exists on RHEL 5, will be ignored everywhere else. */ @@ -34,6 +35,7 @@ #elif DEBIAN == 1 bsdmainutils btrfs-tools + cryptsetup /* Dependency problem prevents installation of these two: gfs-tools gfs2-tools diff --git a/daemon/Makefile.am b/daemon/Makefile.am index cf9f7ca..27fca2a 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -98,6 +98,7 @@ guestfsd_SOURCES = \ inotify.c \ link.c \ ls.c \ + luks.c \ lvm.c \ lvm-filter.c \ mkfs.c \ diff --git a/daemon/luks.c b/daemon/luks.c new file mode 100644 index 0000000..f5a0b9d --- /dev/null +++ b/daemon/luks.c @@ -0,0 +1,138 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2010 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "daemon.h" +#include "c-ctype.h" +#include "actions.h" +#include "optgroups.h" + +int +optgroup_luks_available (void) +{ + return prog_exists ("cryptsetup"); +} + +static int +luks_open (const char *device, const char *key, const char *mapname, + int readonly) +{ + /* Sanity check: /dev/mapper/mapname must not exist already. Note + * that the device-mapper control device (/dev/mapper/control) is + * always there, so you can't ever have mapname == "control". + */ + size_t len = strlen (mapname); + char devmapper[len+32]; + snprintf (devmapper, len+32, "/dev/mapper/%s", mapname); + if (access (devmapper, F_OK) == 0) { + reply_with_error ("%s: device already exists", devmapper); + return -1; + } + + char tempfile[] = "/tmp/luksXXXXXX"; + int fd = mkstemp (tempfile); + if (fd == -1) { + reply_with_perror ("mkstemp"); + return -1; + } + + len = strlen (key); + if (xwrite (fd, key, len) == -1) { + reply_with_perror ("write"); + close (fd); + unlink (tempfile); + return -1; + } + + if (close (fd) == -1) { + reply_with_perror ("close"); + unlink (tempfile); + return -1; + } + + const char *argv[16]; + size_t i = 0; + + argv[i++] = "cryptsetup"; + argv[i++] = "-d"; + argv[i++] = tempfile; + if (readonly) argv[i++] = "--readonly"; + argv[i++] = "luksOpen"; + argv[i++] = device; + argv[i++] = mapname; + argv[i++] = NULL; + + char *err; + int r = commandv (NULL, &err, (const char * const *) argv); + unlink (tempfile); + + if (r == -1) { + reply_with_error ("%s", err); + free (err); + return -1; + } + + free (err); + + udev_settle (); + + return 0; +} + +int +do_luks_open (const char *device, const char *key, const char *mapname) +{ + return luks_open (device, key, mapname, 0); +} + +int +do_luks_open_ro (const char *device, const char *key, const char *mapname) +{ + return luks_open (device, key, mapname, 1); +} + +int +do_luks_close (const char *device) +{ + /* Must be /dev/mapper/... */ + if (! STRPREFIX (device, "/dev/mapper/")) { + reply_with_error ("luks_close: you must call this on the /dev/mapper device created by luks_open"); + return -1; + } + + const char *mapname = &device[12]; + + char *err; + int r = command (NULL, &err, "cryptsetup", "luksClose", mapname, NULL); + if (r == -1) { + reply_with_error ("%s", err); + free (err); + return -1; + } + + free (err); + + udev_settle (); + + return 0; +} diff --git a/fish/guestfish.pod b/fish/guestfish.pod index 86dcf58..bfcec5c 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -530,6 +530,39 @@ it, eg: echo "~" +=head1 ENCRYPTED DISKS + +Libguestfs has some support for Linux guests encrypted according to +the Linux Unified Key Setup (LUKS) standard, which includes nearly all +whole disk encryption systems used by modern Linux guests. Currently +only LVM-on-LUKS is supported. + +Identify encrypted block devices and partitions using L</vfs-type>: + + ><fs> vfs-type /dev/sda2 + crypto_LUKS + +Then open those devices using L</luks-open>. This creates a +device-mapper device called C</dev/mapper/luksdev>. + + ><fs> luks-open /dev/sda2 luksdev + Enter key or passphrase ("key"): <enter the passphrase> + +Finally you have to tell LVM to scan for volume groups on +the newly created mapper device: + + ><fs> vgscan + ><fs> vg-activate-all true + +The logical volume(s) can now be mounted in the usual way. + +Before closing a LUKS device you must unmount any logical volumes on +it and deactivate the volume groups by calling C<vg-activate false VG> +on each one. Then you can close the mapper device: + + ><fs> vg-activate false /dev/VG + ><fs> luks-close /dev/mapper/luksdev + =head1 WINDOWS PATHS If a path is prefixed with C<win:> then you can use Windows-style diff --git a/po/POTFILES.in b/po/POTFILES.in index 62df1fd..4a27d87 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -33,6 +33,7 @@ daemon/initrd.c daemon/inotify.c daemon/link.c daemon/ls.c +daemon/luks.c daemon/lvm-filter.c daemon/lvm.c daemon/mkfs.c diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR index 9183bf0..98ecf58 100644 --- a/src/MAX_PROC_NR +++ b/src/MAX_PROC_NR @@ -1 +1 @@ -256 +259 diff --git a/src/generator.ml b/src/generator.ml index 0df77a3..71421bc 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -4881,6 +4881,43 @@ will be able to see every block device. This command also clears the LVM cache and performs a volume group scan."); + ("luks_open", (RErr, [Device "device"; Key "key"; String "mapname"]), 257, [Optional "luks"], + [], + "open a LUKS-encrypted block device", + "\ +This command opens a block device which has been encrypted +according to the Linux Unified Key Setup (LUKS) standard. + +C<device> is the encrypted block device or partition. + +The caller must supply one of the keys associated with the +LUKS block device, in the C<key> parameter. + +This creates a new block device called C</dev/mapper/mapname>. +Reads and writes to this block device are decrypted from and +encrypted to the underlying C<device> respectively. + +If this block device contains LVM volume groups, then +calling C<guestfs_vgscan> followed by C<guestfs_vg_activate_all> +will make them visible."); + + ("luks_open_ro", (RErr, [Device "device"; Key "key"; String "mapname"]), 258, [Optional "luks"], + [], + "open a LUKS-encrypted block device read-only", + "\ +This is the same as C<guestfs_luks_open> except that a read-only +mapping is created."); + + ("luks_close", (RErr, [Device "device"]), 259, [Optional "luks"], + [], + "close a LUKS device", + "\ +This closes a LUKS device that was created earlier by +C<guestfs_luks_open> or C<guestfs_luks_open_ro>. The +C<device> parameter must be the name of the LUKS mapping +device (ie. C</dev/mapper/mapname>) and I<not> the name +of the underlying block device."); + ] let all_functions = non_daemon_functions @ daemon_functions diff --git a/src/guestfs.pod b/src/guestfs.pod index 8e3d07c..5a2e7a5 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -450,6 +450,37 @@ L</guestfs_chmod> after creating each file or directory. For more information about umask, see L<umask(2)>. +=head2 ENCRYPTED DISKS + +Libguestfs allows you to access Linux guests which have been +encrypted using whole disk encryption that conforms to the +Linux Unified Key Setup (LUKS) standard. This includes +nearly all whole disk encryption systems used by modern +Linux guests. + +Use L</guestfs_vfs_type> to identify LUKS-encrypted block +devices (it returns the string C<crypto_LUKS>). + +Then open these devices by calling L</guestfs_luks_open>. +Obviously you will require the passphrase! + +Opening a LUKS device creates a new device mapper device +called C</dev/mapper/mapname> (where C<mapname> is the +string you supply to L</guestfs_luks_open>). +Reads and writes to this mapper device are decrypted from and +encrypted to the underlying block device respectively. + +LVM volume groups on the device can be made visible by calling +L</guestfs_vgscan> followed by L</guestfs_vg_activate_all>. +The logical volume(s) can now be mounted in the usual way. + +Use the reverse process to close a LUKS device. Unmount +any logical volumes on it, deactivate the volume groups +by caling C<guestfs_vg_activate (g, 0, ["/dev/VG"])>. +Then close the mapper device by calling +L</guestfs_luks_close> on the C</dev/mapper/mapname> +device (I<not> the underlying encrypted block device). + =head2 SPECIAL CONSIDERATIONS FOR WINDOWS GUESTS Libguestfs can mount NTFS partitions. It does this using the -- 1.7.1
Reasonably Related Threads
- [PATCH 0/7] Add libvirt domain to core API
- [PATCH] Enable coredumps to be captured from the appliance (RHBZ#619334).
- [PATCH 0/8 v2 DISCUSSION ONLY] Connecting to live virtual machines
- Some more Virt-P2V CD results - screenshot attached as promised
- [PATCH 0/2] Deprecate zfile