Richard W.M. Jones
2014-Oct-30 13:41 UTC
[Libguestfs] [PATCH 0/2] v2v: Add --password-file parameter (RHBZ#1158526).
These patches add the --password-file parameter, allowing you to pass a single password via a file. https://bugzilla.redhat.com/show_bug.cgi?id=1158526 Rich.
Richard W.M. Jones
2014-Oct-30 13:41 UTC
[Libguestfs] [PATCH 1/2] v2v: vmware: Use 'curl --config' to pass arguments securely to curl.
Instead of making up an ordinary curl command line, write a temporary config file and use 'curl --config tmpfile' to pass the arguments. The advantage is that it's more secure if we want to supply passwords to curl, since a --password parameter on the command line could be read (eg. by 'ps ax'), but the temporary file has mode 0600 and cannot be read by other users. This is mostly code motion, but it also passes the '-q' option to curl to stop it from reading default configuration files. --- v2v/input_libvirt_vcenter_https.ml | 75 +++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/v2v/input_libvirt_vcenter_https.ml b/v2v/input_libvirt_vcenter_https.ml index c7a6012..ac373d3 100644 --- a/v2v/input_libvirt_vcenter_https.ml +++ b/v2v/input_libvirt_vcenter_https.ml @@ -36,23 +36,45 @@ let readahead_for_copying = Some (64 * 1024 * 1024) (* Return the session cookie. It is memoized, so you can call this * as often as required. *) -let get_session_cookie +let rec get_session_cookie let session_cookie = ref "" in fun verbose scheme uri sslverify url -> if !session_cookie <> "" then Some !session_cookie else ( - let cmd - sprintf "curl -s%s%s%s -I %s ||:" - (if not sslverify then " --insecure" else "") - (match uri.uri_user with Some _ -> " -u" | None -> "") - (match uri.uri_user with Some user -> " " ^ quote user | None -> "") - (quote url) in - let lines = external_command ~prog cmd in + let curl_args = [ + "head", None; + "silent", None; + "url", Some url; + ] in + let curl_args + match uri.uri_user with + | Some user -> ("user", Some user) :: curl_args + | None -> curl_args in + let curl_args + if not sslverify then ("insecure", None) :: curl_args else curl_args in + + let lines = run_curl_get_lines curl_args in let dump_response chan - fprintf chan "%s\n" cmd; - List.iter (fun x -> fprintf chan "%s\n" x) lines + (* Don't print passwords in the debug output. *) + let curl_args + List.map ( + function + | ("user", Some _) -> ("user", Some "<hidden>") + | x -> x + ) curl_args in + (* Dump out the approximate curl command that was run. *) + fprintf chan "curl -q"; + List.iter ( + function + | name, None -> fprintf chan " --%s" name + | name, Some value -> fprintf chan " --%s %s" name (quote value) + ) curl_args; + fprintf chan "\n"; + (* Dump out the output of the command. *) + List.iter (fun x -> fprintf chan "%s\n" x) lines; + flush chan in if verbose then dump_response stdout; @@ -109,6 +131,39 @@ let get_session_cookie Some !session_cookie ) +(* Run 'curl' and pass the arguments securely through the --config + * option and an external file. + *) +and run_curl_get_lines curl_args + let config_file, chan = Filename.open_temp_file "v2vcurl" ".conf" in + List.iter ( + function + | name, None -> fprintf chan "%s\n" name + | name, Some value -> + fprintf chan "%s = \"" name; + (* Write the quoted value. See 'curl' man page for what is + * allowed here. + *) + let len = String.length value in + for i = 0 to len-1 do + match value.[i] with + | '\\' -> output_string chan "\\\\" + | '"' -> output_string chan "\\\"" + | '\t' -> output_string chan "\\t" + | '\n' -> output_string chan "\\n" + | '\r' -> output_string chan "\\r" + | '\x0b' -> output_string chan "\\v" + | c -> output_char chan c + done; + fprintf chan "\"\n" + ) curl_args; + close_out chan; + + let cmd = sprintf "curl -q --config %s" (quote config_file) in + let lines = external_command ~prog cmd in + Unix.unlink config_file; + lines + (* Helper function to extract the datacenter from a URI. *) let get_datacenter uri scheme let default_dc = "ha-datacenter" in -- 2.0.4
Richard W.M. Jones
2014-Oct-30 13:41 UTC
[Libguestfs] [PATCH 2/2] v2v: Add --password-file parameter (RHBZ#1158526).
This allows you to send passwords to virt-v2v input modes without being interactive. --- v2v/cmdline.ml | 13 +++++++++- v2v/domainxml-c.c | 52 ++++++++++++++++++++++++++++++++----- v2v/domainxml.ml | 2 +- v2v/domainxml.mli | 4 +-- v2v/input_libvirt.ml | 12 ++++----- v2v/input_libvirt.mli | 4 +-- v2v/input_libvirt_other.ml | 8 +++--- v2v/input_libvirt_other.mli | 4 +-- v2v/input_libvirt_vcenter_https.ml | 29 +++++++++++++-------- v2v/input_libvirt_vcenter_https.mli | 2 +- v2v/input_libvirt_xen_ssh.ml | 6 ++--- v2v/input_libvirt_xen_ssh.mli | 2 +- v2v/input_libvirtxml.ml | 2 +- v2v/virt-v2v.pod | 10 ++++++- 14 files changed, 107 insertions(+), 43 deletions(-) diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml index 6f8a964..9c3253e 100644 --- a/v2v/cmdline.ml +++ b/v2v/cmdline.ml @@ -42,6 +42,7 @@ let parse_cmdline () let output_format = ref "" in let output_name = ref "" in let output_storage = ref "" in + let password_file = ref "" in let print_source = ref false in let qemu_boot = ref false in let quiet = ref false in @@ -165,6 +166,7 @@ let parse_cmdline () "-of", Arg.Set_string output_format, "raw|qcow2 " ^ s_"Set output format"; "-on", Arg.Set_string output_name, "name " ^ s_"Rename guest when converting"; "-os", Arg.Set_string output_storage, "storage " ^ s_"Set output storage location"; + "--password-file", Arg.Set_string password_file, "file " ^ s_"Use password from file"; "--print-source", Arg.Set print_source, " " ^ s_"Print source and stop"; "--qemu-boot", Arg.Set qemu_boot, " " ^ s_"Boot in qemu (-o qemu only)"; "-q", Arg.Set quiet, " " ^ s_"Quiet output"; @@ -227,6 +229,7 @@ read the man page virt-v2v(1). let output_mode = !output_mode in let output_name = match !output_name with "" -> None | s -> Some s in let output_storage = !output_storage in + let password_file = match !password_file with "" -> None | s -> Some s in let print_source = !print_source in let qemu_boot = !qemu_boot in let quiet = !quiet in @@ -256,6 +259,14 @@ read the man page virt-v2v(1). exit 0 ); + (* Parse out the password from the password file. *) + let password + match password_file with + | None -> None + | Some filename -> + let password = read_whole_file filename in + Some password in + (* Parsing of the argument(s) depends on the input mode. *) let input match input_mode with @@ -278,7 +289,7 @@ read the man page virt-v2v(1). | [guest] -> guest | _ -> error (f_"expecting a libvirt guest name on the command line") in - Input_libvirt.input_libvirt verbose input_conn guest + Input_libvirt.input_libvirt verbose password input_conn guest | `LibvirtXML -> (* -i libvirtxml: Expecting a filename (XML file). *) diff --git a/v2v/domainxml-c.c b/v2v/domainxml-c.c index 8a55030..6fa8270 100644 --- a/v2v/domainxml-c.c +++ b/v2v/domainxml-c.c @@ -74,13 +74,47 @@ get_dom_state (virDomainPtr dom) return -1; } +/* See src/libvirt-auth.c for why we need this. */ +static int +libvirt_auth_default_wrapper (virConnectCredentialPtr cred, + unsigned int ncred, + void *passwordvp) +{ + const char *password = passwordvp; + unsigned int i; + + if (password) { + /* If --password-file was specified on the command line, and the + * libvirt handler is asking for a password, return that. + */ + for (i = 0; i < ncred; ++i) { + if (cred[i].type == VIR_CRED_PASSPHRASE) { + cred[i].result = strdup (password); + cred[i].resultlen = strlen (password); + } + else { + cred[i].result = NULL; + cred[i].resultlen = 0; + } + } + return 0; + } + else { + /* No --password-file so call the default handler. */ + return virConnectAuthPtrDefault->cb (cred, ncred, + virConnectAuthPtrDefault->cbdata); + } +} + value -v2v_dumpxml (value connv, value domnamev) +v2v_dumpxml (value passwordv, value connv, value domnamev) { - CAMLparam2 (connv, domnamev); + CAMLparam3 (passwordv, connv, domnamev); CAMLlocal1 (retv); + const char *password = NULL; const char *conn_uri = NULL; const char *domname; + virConnectAuth authdata; /* We have to assemble the error on the stack because a dynamic * string couldn't be freed. */ @@ -91,16 +125,20 @@ v2v_dumpxml (value connv, value domnamev) int is_test_uri = 0; char *xml; + if (passwordv != Val_int (0)) + password = String_val (Field (passwordv, 0)); /* Some password */ + if (connv != Val_int (0)) { conn_uri = String_val (Field (connv, 0)); /* Some conn */ is_test_uri = STRPREFIX (conn_uri, "test:"); } - /* We have to call the default authentication handler, not least - * since it handles all the PolicyKit crap. However it also makes - * coding this simpler. - */ - conn = virConnectOpenAuth (conn_uri, virConnectAuthPtrDefault, VIR_CONNECT_RO); + /* Set up authentication wrapper. */ + authdata = *virConnectAuthPtrDefault; + authdata.cb = libvirt_auth_default_wrapper; + authdata.cbdata = (void *) password; + + conn = virConnectOpenAuth (conn_uri, &authdata, VIR_CONNECT_RO); if (conn == NULL) { if (conn_uri) snprintf (errmsg, sizeof errmsg, diff --git a/v2v/domainxml.ml b/v2v/domainxml.ml index d240918..61ed5e0 100644 --- a/v2v/domainxml.ml +++ b/v2v/domainxml.ml @@ -18,5 +18,5 @@ (* [virsh dumpxml] but with non-broken authentication handling. *) -external dumpxml : ?conn:string -> string -> string = "v2v_dumpxml" +external dumpxml : ?password:string -> ?conn:string -> string -> string = "v2v_dumpxml" external pool_dumpxml : ?conn:string -> string -> string = "v2v_pool_dumpxml" diff --git a/v2v/domainxml.mli b/v2v/domainxml.mli index ced55ce..ffb1c46 100644 --- a/v2v/domainxml.mli +++ b/v2v/domainxml.mli @@ -23,8 +23,8 @@ password prompt to stdout, which is the same place we would be reading the XML from. This file works around this brokenness. *) -val dumpxml : ?conn:string -> string -> string -(** [dumpxml ?conn dom] returns the libvirt XML of domain [dom]. +val dumpxml : ?password:string -> ?conn:string -> string -> string +(** [dumpxml ?password ?conn dom] returns the libvirt XML of domain [dom]. The optional [?conn] parameter is the libvirt connection URI. [dom] may be a guest name or UUID. *) diff --git a/v2v/input_libvirt.ml b/v2v/input_libvirt.ml index 5d5c39f..6aa9bcd 100644 --- a/v2v/input_libvirt.ml +++ b/v2v/input_libvirt.ml @@ -27,10 +27,10 @@ open Types open Utils (* Choose the right subclass based on the URI. *) -let input_libvirt verbose libvirt_uri guest +let input_libvirt verbose password libvirt_uri guest match libvirt_uri with | None -> - Input_libvirt_other.input_libvirt_other verbose libvirt_uri guest + Input_libvirt_other.input_libvirt_other verbose password libvirt_uri guest | Some orig_uri -> let { Xml.uri_server = server; uri_scheme = scheme } as parsed_uri @@ -45,15 +45,15 @@ let input_libvirt verbose libvirt_uri guest | Some _, None (* No scheme? *) | Some _, Some "" -> - Input_libvirt_other.input_libvirt_other verbose libvirt_uri guest + Input_libvirt_other.input_libvirt_other verbose password libvirt_uri guest | Some server, Some ("esx"|"gsx"|"vpx" as scheme) -> (* vCenter over https *) Input_libvirt_vcenter_https.input_libvirt_vcenter_https - verbose libvirt_uri parsed_uri scheme server guest + verbose password libvirt_uri parsed_uri scheme server guest | Some server, Some ("xen+ssh" as scheme) -> (* Xen over SSH *) Input_libvirt_xen_ssh.input_libvirt_xen_ssh - verbose libvirt_uri parsed_uri scheme server guest + verbose password libvirt_uri parsed_uri scheme server guest (* Old virt-v2v also supported qemu+ssh://. However I am * deliberately not supporting this in new virt-v2v. Don't @@ -63,6 +63,6 @@ let input_libvirt verbose libvirt_uri guest | Some _, Some _ -> (* Unknown remote scheme. *) warning (f_"no support for remote libvirt connections to '-ic %s'. The conversion may fail when it tries to read the source disks.") orig_uri; - Input_libvirt_other.input_libvirt_other verbose libvirt_uri guest + Input_libvirt_other.input_libvirt_other verbose password libvirt_uri guest let () = Modules_list.register_input_module "libvirt" diff --git a/v2v/input_libvirt.mli b/v2v/input_libvirt.mli index 1ed704b..bdd40b6 100644 --- a/v2v/input_libvirt.mli +++ b/v2v/input_libvirt.mli @@ -18,7 +18,7 @@ (** [-i libvirt] source. *) -val input_libvirt : bool -> string option -> string -> Types.input -(** [input_libvirt verbose libvirt_uri guest] creates and returns a +val input_libvirt : bool -> string option -> string option -> string -> Types.input +(** [input_libvirt verbose password libvirt_uri guest] creates and returns a new {!Types.input} object specialized for reading input from libvirt sources. *) diff --git a/v2v/input_libvirt_other.ml b/v2v/input_libvirt_other.ml index 9f3eedb..c704af6 100644 --- a/v2v/input_libvirt_other.ml +++ b/v2v/input_libvirt_other.ml @@ -43,7 +43,7 @@ let error_if_no_ssh_agent () error (f_"ssh-agent authentication has not been set up ($SSH_AUTH_SOCK is not set). Please read \"INPUT FROM RHEL 5 XEN\" in the virt-v2v(1) man page.") (* Superclass. *) -class virtual input_libvirt verbose libvirt_uri guest +class virtual input_libvirt verbose password libvirt_uri guest object inherit input verbose @@ -58,9 +58,9 @@ end (* Subclass specialized for handling anything that's *not* VMware vCenter * or Xen. *) -class input_libvirt_other verbose libvirt_uri guest +class input_libvirt_other verbose password libvirt_uri guest object - inherit input_libvirt verbose libvirt_uri guest + inherit input_libvirt verbose password libvirt_uri guest method source () if verbose then printf "input_libvirt_other: source()\n%!"; @@ -68,7 +68,7 @@ object (* Get the libvirt XML. This also checks (as a side-effect) * that the domain is not running. (RHBZ#1138586) *) - let xml = Domainxml.dumpxml ?conn:libvirt_uri guest in + let xml = Domainxml.dumpxml ?password ?conn:libvirt_uri guest in let source, disks = Input_libvirtxml.parse_libvirt_xml ~verbose xml in let disks diff --git a/v2v/input_libvirt_other.mli b/v2v/input_libvirt_other.mli index 013d3bb..3eb82cb 100644 --- a/v2v/input_libvirt_other.mli +++ b/v2v/input_libvirt_other.mli @@ -21,10 +21,10 @@ val error_if_libvirt_backend : unit -> unit val error_if_no_ssh_agent : unit -> unit -class virtual input_libvirt : bool -> string option -> string -> object +class virtual input_libvirt : bool -> string option -> string option -> string -> object method as_options : string method virtual source : unit -> Types.source method adjust_overlay_parameters : Types.overlay -> unit end -val input_libvirt_other : bool -> string option -> string -> Types.input +val input_libvirt_other : bool -> string option -> string option -> string -> Types.input diff --git a/v2v/input_libvirt_vcenter_https.ml b/v2v/input_libvirt_vcenter_https.ml index ac373d3..7f93439 100644 --- a/v2v/input_libvirt_vcenter_https.ml +++ b/v2v/input_libvirt_vcenter_https.ml @@ -38,7 +38,7 @@ let readahead_for_copying = Some (64 * 1024 * 1024) *) let rec get_session_cookie let session_cookie = ref "" in - fun verbose scheme uri sslverify url -> + fun verbose password scheme uri sslverify url -> if !session_cookie <> "" then Some !session_cookie else ( @@ -48,9 +48,15 @@ let rec get_session_cookie "url", Some url; ] in let curl_args - match uri.uri_user with - | Some user -> ("user", Some user) :: curl_args - | None -> curl_args in + match uri.uri_user, password with + | None, None -> curl_args + | None, Some _ -> + warning (f_"--password-file parameter ignored because 'user@' was not given in the URL"); + curl_args + | Some user, None -> + ("user", Some user) :: curl_args + | Some user, Some password -> + ("user", Some (user ^ ":" ^ password)) :: curl_args in let curl_args if not sslverify then ("insecure", None) :: curl_args else curl_args in @@ -204,7 +210,7 @@ let get_datacenter uri scheme *) let source_re = Str.regexp "^\\[\\(.*\\)\\] \\(.*\\)\\.vmdk$" -let map_source_to_uri ?readahead verbose uri scheme server path +let map_source_to_uri ?readahead verbose password uri scheme server path if not (Str.string_match source_re path 0) then path else ( @@ -237,7 +243,8 @@ let map_source_to_uri ?readahead verbose uri scheme server path string_find query "no_verify=1" = -1 in (* Now we have to query the server to get the session cookie. *) - let session_cookie = get_session_cookie verbose scheme uri sslverify url in + let session_cookie + get_session_cookie verbose password scheme uri sslverify url in (* Construct the JSON parameters. *) let json_params = [ @@ -274,9 +281,9 @@ let map_source_to_uri ?readahead verbose uri scheme server path (* Subclass specialized for handling VMware vCenter over https. *) class input_libvirt_vcenter_https - verbose libvirt_uri parsed_uri scheme server guest + verbose password libvirt_uri parsed_uri scheme server guest object - inherit input_libvirt verbose libvirt_uri guest + inherit input_libvirt verbose password libvirt_uri guest val saved_source_paths = Hashtbl.create 13 @@ -290,7 +297,7 @@ object (* Get the libvirt XML. This also checks (as a side-effect) * that the domain is not running. (RHBZ#1138586) *) - let xml = Domainxml.dumpxml ?conn:libvirt_uri guest in + let xml = Domainxml.dumpxml ?password ?conn:libvirt_uri guest in let source, disks = parse_libvirt_xml ~verbose xml in (* Save the original source paths, so that we can remap them again @@ -314,7 +321,7 @@ object | { p_source_disk = disk; p_source = P_dont_rewrite } -> disk | { p_source_disk = disk; p_source = P_source_file path } -> let qemu_uri = map_source_to_uri ?readahead - verbose parsed_uri scheme server path in + verbose password parsed_uri scheme server path in (* The libvirt ESX driver doesn't normally specify a format, but * the format of the -flat file is *always* raw, so force it here. @@ -335,7 +342,7 @@ object let readahead = readahead_for_copying in let backing_qemu_uri map_source_to_uri ?readahead - verbose parsed_uri scheme server orig_path in + verbose password parsed_uri scheme server orig_path in (* Rebase the qcow2 overlay to adjust the readahead parameter. *) let cmd diff --git a/v2v/input_libvirt_vcenter_https.mli b/v2v/input_libvirt_vcenter_https.mli index 82dce53..800c6ab 100644 --- a/v2v/input_libvirt_vcenter_https.mli +++ b/v2v/input_libvirt_vcenter_https.mli @@ -18,4 +18,4 @@ (** [-i libvirt] when the source is VMware vCenter *) -val input_libvirt_vcenter_https : bool -> string option -> Xml.uri -> string -> string -> string -> Types.input +val input_libvirt_vcenter_https : bool -> string option -> string option -> Xml.uri -> string -> string -> string -> Types.input diff --git a/v2v/input_libvirt_xen_ssh.ml b/v2v/input_libvirt_xen_ssh.ml index e1600a0..cf5f1ae 100644 --- a/v2v/input_libvirt_xen_ssh.ml +++ b/v2v/input_libvirt_xen_ssh.ml @@ -30,9 +30,9 @@ open Input_libvirt_other open Printf (* Subclass specialized for handling Xen over SSH. *) -class input_libvirt_xen_ssh verbose libvirt_uri parsed_uri scheme server guest +class input_libvirt_xen_ssh verbose password libvirt_uri parsed_uri scheme server guest object - inherit input_libvirt verbose libvirt_uri guest + inherit input_libvirt verbose password libvirt_uri guest method source () if verbose then @@ -45,7 +45,7 @@ object (* Get the libvirt XML. This also checks (as a side-effect) * that the domain is not running. (RHBZ#1138586) *) - let xml = Domainxml.dumpxml ?conn:libvirt_uri guest in + let xml = Domainxml.dumpxml ?password ?conn:libvirt_uri guest in let source, disks = parse_libvirt_xml ~verbose xml in (* Map the <source/> filename (which is relative to the remote diff --git a/v2v/input_libvirt_xen_ssh.mli b/v2v/input_libvirt_xen_ssh.mli index 85473ed..47eb62c 100644 --- a/v2v/input_libvirt_xen_ssh.mli +++ b/v2v/input_libvirt_xen_ssh.mli @@ -18,4 +18,4 @@ (** [-i libvirt] when the source is Xen *) -val input_libvirt_xen_ssh : bool -> string option -> Xml.uri -> string -> string -> string -> Types.input +val input_libvirt_xen_ssh : bool -> string option -> string option -> Xml.uri -> string -> string -> string -> Types.input diff --git a/v2v/input_libvirtxml.ml b/v2v/input_libvirtxml.ml index 85582fa..f06a1dd 100644 --- a/v2v/input_libvirtxml.ml +++ b/v2v/input_libvirtxml.ml @@ -170,7 +170,7 @@ let parse_libvirt_xml ~verbose xml * XXX Quoting, although it's not needed for virt-p2v. *) let path = sprintf "nbd:%s:%d" host port in - add_disk path format target_dev (P_dont_rewrite) + add_disk path format target_dev P_dont_rewrite ) | "" -> () | protocol -> diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod index bd83ee7..a6d01db 100644 --- a/v2v/virt-v2v.pod +++ b/v2v/virt-v2v.pod @@ -509,6 +509,13 @@ C<root>. You will get an error if virt-v2v is unable to mount/write to the Export Storage Domain. +=item B<--password-file> file + +Instead of asking for password(s) interactively, pass the password +through a file. Note the file should contain the whole password, +B<without any trailing newline>, and for security the file should have +mode C<0600> so that others cannot read it. + =item B<--print-source> Print information about the source guest and stop. This option is @@ -875,7 +882,8 @@ down). Note that you may be asked for the vCenter password I<twice>. This happens once because libvirt needs it, and a second time because -virt-v2v itself connects directly to the server. +virt-v2v itself connects directly to the server. Use +I<--password-file> to supply a password via a file. In this case the output flags are set to write the converted guest to a temporary directory as this is just an example, but you can also -- 2.0.4
Possibly Parallel Threads
- [PATCH 0/3] v2v: Various refactorings.
- [PATCH 0/5] v2v: Handle disks with snapshots (RHBZ#1172425).
- [PATCH 0/4] v2v: Use libvirt-supplied <vmware:datacenterpath> if available.
- [v2v PATCH] -i libvirt: print URI without connecting
- v2v: vddk: Switch to using ‘-it vddk’ to specify VDDK as input transport.