Blame SOURCES/0044-v2v-vcenter-Implement-cookie-scripts.patch

61e9b3
From cc9a507e2372b5b6408964f9c31a3bd526aabf7c Mon Sep 17 00:00:00 2001
61e9b3
From: "Richard W.M. Jones" <rjones@redhat.com>
61e9b3
Date: Wed, 23 Sep 2020 09:56:27 +0100
61e9b3
Subject: [PATCH] v2v: vcenter: Implement cookie scripts.
61e9b3
61e9b3
For conversions[*] which take longer than 30 minutes it can happen
61e9b3
that the HTTPS authorization cookie that we fetched from VMware when
61e9b3
we first connect expires.  This can especially happen when there are
61e9b3
multiple disks, because we may not "touch" (therefore autorenew) the
61e9b3
second disk while we are doing the long conversion.  This can lead to
61e9b3
failures, some of which are silent: again if there are multiple disks,
61e9b3
fstrim of the non-system disks can fail silently resulting in the copy
61e9b3
step taking a very long time.
61e9b3
61e9b3
The solution to this is to use the new nbdkit-curl-plugin
61e9b3
cookie-script feature which allows nbdkit to automatically renew the
61e9b3
cookie as required.
61e9b3
61e9b3
During the conversion or copying steps you may see the cookie being
61e9b3
autorenewed:
61e9b3
61e9b3
  nbdkit: curl[3]: debug: curl: running cookie-script
61e9b3
  nbdkit: curl[3]: debug: cookie-script returned cookies
61e9b3
61e9b3
This removes the ?user and ?password parameters from Nbdkit_sources.-
61e9b3
create_curl because they are no longer needed after this change.
61e9b3
Note for future: if we need to add them back, we must prevent both
61e9b3
user and cookie_script parameters from being used at the same time,
61e9b3
because simply having the user parameter will try basic
61e9b3
authentication, overriding the cookie, which will either fail (no
61e9b3
password) or run very slowly.
61e9b3
61e9b3
This change requires nbdkit >= 1.22 which is checked at runtime only
61e9b3
if this feature is used.
61e9b3
61e9b3
[*] Note here I mean conversions not the total runtime of virt-v2v.
61e9b3
When doing the copy the cookie does not expire because it is
61e9b3
continuously auto-renewed by VMware as we continuously access the disk
61e9b3
(this works differently from systems like Docker where the cookie is
61e9b3
only valid from the absolute time when it is first created).  This
61e9b3
change also implements the cookie-script logic for copying.
61e9b3
61e9b3
(cherry picked from commit 2b9a11743b74ef3716b66a7e395108a26382e331)
61e9b3
61e9b3
Notes for cherry pick to RHEL 8.6:
61e9b3
61e9b3
We no longer need the session_cookie field inside virt-v2v since it is
61e9b3
replaced by the cookie script.  However it is still needed by
61e9b3
virt-v2v-copy-to-local.  (This utility is removed upstream and in RHEL
61e9b3
9, but we need to keep it around at least for appearances in RHEL 8.)
61e9b3
61e9b3
So when cherry picking I had to retain the get_session_cookie function
61e9b3
which required also keeping fetch_headers_and_url as it was (not
61e9b3
dropping headers).
61e9b3
---
61e9b3
 v2v/nbdkit_sources.ml    | 34 ++++++++++++-----
61e9b3
 v2v/nbdkit_sources.mli   |  5 +--
61e9b3
 v2v/parse_libvirt_xml.ml |  3 +-
61e9b3
 v2v/vCenter.ml           | 80 +++++++++++++++++++++++++++++++---------
61e9b3
 4 files changed, 90 insertions(+), 32 deletions(-)
61e9b3
61e9b3
diff --git a/v2v/nbdkit_sources.ml b/v2v/nbdkit_sources.ml
61e9b3
index 7c177e35..16af5f5c 100644
61e9b3
--- a/v2v/nbdkit_sources.ml
61e9b3
+++ b/v2v/nbdkit_sources.ml
61e9b3
@@ -26,7 +26,6 @@ open Types
61e9b3
 open Utils
61e9b3
 
61e9b3
 let nbdkit_min_version = (1, 12, 0)
61e9b3
-let nbdkit_min_version_string = "1.12.0"
61e9b3
 
61e9b3
 type password =
61e9b3
 | NoPassword                    (* no password option at all *)
61e9b3
@@ -38,11 +37,16 @@ let error_unless_nbdkit_working () =
61e9b3
   if not (Nbdkit.is_installed ()) then
61e9b3
     error (f_"nbdkit is not installed or not working")
61e9b3
 
61e9b3
-let error_unless_nbdkit_min_version config =
61e9b3
+let error_unless_nbdkit_version_ge config min_version =
61e9b3
   let version = Nbdkit.version config in
61e9b3
-  if version < nbdkit_min_version then
61e9b3
-    error (f_"nbdkit is too old.  nbdkit >= %s is required.")
61e9b3
-          nbdkit_min_version_string
61e9b3
+  if version < min_version then (
61e9b3
+    let min_major, min_minor, min_release = min_version in
61e9b3
+    error (f_"nbdkit is too old.  nbdkit >= %d.%d.%d is required.")
61e9b3
+          min_major min_minor min_release
61e9b3
+  )
61e9b3
+
61e9b3
+let error_unless_nbdkit_min_version config =
61e9b3
+  error_unless_nbdkit_version_ge config nbdkit_min_version
61e9b3
 
61e9b3
 let error_unless_nbdkit_plugin_exists plugin =
61e9b3
   if not (Nbdkit.probe_plugin plugin) then
61e9b3
@@ -297,23 +301,35 @@ let create_ssh ?bandwidth ~password ?port ~server ?user path =
61e9b3
   common_create ?bandwidth password "ssh" (get_args ())
61e9b3
 
61e9b3
 (* Create an nbdkit module specialized for reading from Curl sources. *)
61e9b3
-let create_curl ?bandwidth ?cookie ~password ?(sslverify=true) ?user url =
61e9b3
+let create_curl ?bandwidth ?cookie_script ?cookie_script_renew
61e9b3
+                ?(sslverify=true) url =
61e9b3
   error_unless_nbdkit_plugin_exists "curl";
61e9b3
 
61e9b3
+  (* The cookie* parameters require nbdkit 1.22, so check that early. *)
61e9b3
+  if cookie_script <> None || cookie_script_renew <> None then (
61e9b3
+    let config = Nbdkit.config () in
61e9b3
+    error_unless_nbdkit_version_ge config (1, 22, 0)
61e9b3
+  );
61e9b3
+
61e9b3
   let add_arg, get_args =
61e9b3
     let args = ref [] in
61e9b3
     let add_arg (k, v) = List.push_front (k, v) args in
61e9b3
     let get_args () = List.rev !args in
61e9b3
     add_arg, get_args in
61e9b3
 
61e9b3
-  Option.may (fun s -> add_arg ("user", s)) user;
61e9b3
   (* https://bugzilla.redhat.com/show_bug.cgi?id=1146007#c10 *)
61e9b3
   add_arg ("timeout", "2000");
61e9b3
-  Option.may (fun s -> add_arg ("cookie", s)) cookie;
61e9b3
+  Option.may (fun s -> add_arg ("cookie-script", s)) cookie_script;
61e9b3
+  Option.may (fun i -> add_arg ("cookie-script-renew", string_of_int i))
61e9b3
+             cookie_script_renew;
61e9b3
   if not sslverify then add_arg ("sslverify", "false");
61e9b3
   add_arg ("url", url);
61e9b3
 
61e9b3
-  common_create ?bandwidth password "curl" (get_args ())
61e9b3
+  (* For lots of extra debugging, uncomment one or both lines. *)
61e9b3
+  (*add_arg ("--debug", "curl.verbose=1");*)
61e9b3
+  (*add_arg ("--debug", "curl.scripts=1");*)
61e9b3
+
61e9b3
+  common_create ?bandwidth NoPassword "curl" (get_args ())
61e9b3
 
61e9b3
 let run cmd =
61e9b3
   let sock, _ = Nbdkit.run_unix cmd in
61e9b3
diff --git a/v2v/nbdkit_sources.mli b/v2v/nbdkit_sources.mli
61e9b3
index 94810ea6..922642df 100644
61e9b3
--- a/v2v/nbdkit_sources.mli
61e9b3
+++ b/v2v/nbdkit_sources.mli
61e9b3
@@ -60,10 +60,9 @@ val create_ssh : ?bandwidth:Types.bandwidth ->
61e9b3
     Note this doesn't run nbdkit yet, it just creates the object. *)
61e9b3
 
61e9b3
 val create_curl : ?bandwidth:Types.bandwidth ->
61e9b3
-                  ?cookie:string ->
61e9b3
-                  password:password ->
61e9b3
+                  ?cookie_script:string ->
61e9b3
+                  ?cookie_script_renew:int ->
61e9b3
                   ?sslverify:bool ->
61e9b3
-                  ?user:string ->
61e9b3
                   string -> Nbdkit.cmd
61e9b3
 (** Create a nbdkit object using the Curl plugin.  The required
61e9b3
     string parameter is the URL.
61e9b3
diff --git a/v2v/parse_libvirt_xml.ml b/v2v/parse_libvirt_xml.ml
61e9b3
index 0b136839..fffc5a24 100644
61e9b3
--- a/v2v/parse_libvirt_xml.ml
61e9b3
+++ b/v2v/parse_libvirt_xml.ml
61e9b3
@@ -319,8 +319,7 @@ let parse_libvirt_xml ?bandwidth ?conn xml =
61e9b3
                | _, Some port ->
61e9b3
                   invalid_arg "invalid port number in libvirt XML" in
61e9b3
              sprintf "%s://%s%s%s" driver host port (uri_quote path) in
61e9b3
-           let nbdkit = Nbdkit_sources.create_curl ?bandwidth ~password:NoPassword
61e9b3
-                                           url in
61e9b3
+           let nbdkit = Nbdkit_sources.create_curl ?bandwidth url in
61e9b3
            let qemu_uri = Nbdkit_sources.run nbdkit in
61e9b3
            add_disk qemu_uri format controller P_dont_rewrite
61e9b3
         | Some protocol, _, _ ->
61e9b3
diff --git a/v2v/vCenter.ml b/v2v/vCenter.ml
61e9b3
index 4c128b0c..ead03364 100644
61e9b3
--- a/v2v/vCenter.ml
61e9b3
+++ b/v2v/vCenter.ml
61e9b3
@@ -46,11 +46,12 @@ let rec map_source ?bandwidth ?password_file dcPath uri server path =
61e9b3
        (* XXX only works if the query string is not URI-quoted *)
61e9b3
        String.find query "no_verify=1" = -1 in
61e9b3
 
61e9b3
+  (* Check the URL exists and authentication info is correct. *)
61e9b3
   let https_url =
61e9b3
     let https_url = get_https_url dcPath uri server path in
61e9b3
-    (* Check the URL exists. *)
61e9b3
-    let status, _, _ =
61e9b3
+    let status, _, dump_response =
61e9b3
       fetch_headers_from_url password_file uri sslverify https_url in
61e9b3
+
61e9b3
     (* If a disk is actually a snapshot image it will have '-00000n'
61e9b3
      * appended to its name, e.g.:
61e9b3
      *   [yellow:storage1] RHEL4-X/RHEL4-X-000003.vmdk
61e9b3
@@ -58,28 +59,71 @@ let rec map_source ?bandwidth ?password_file dcPath uri server path =
61e9b3
      * a 404 and the vmdk name looks like it might be a snapshot, try
61e9b3
      * again without the snapshot suffix.
61e9b3
      *)
61e9b3
-    if status = "404" && PCRE.matches snapshot_re path then (
61e9b3
-      let path = PCRE.sub 1 ^ PCRE.sub 2 in
61e9b3
-      get_https_url dcPath uri server path
61e9b3
-    )
61e9b3
-    else
61e9b3
-      (* Note that other non-200 status errors will be handled
61e9b3
-       * in get_session_cookie below, so we don't have to worry
61e9b3
-       * about them here.
61e9b3
-       *)
61e9b3
-      https_url in
61e9b3
+    let https_url, status, dump_response =
61e9b3
+      if status = "404" && PCRE.matches snapshot_re path then (
61e9b3
+        let path = PCRE.sub 1 ^ PCRE.sub 2 in
61e9b3
+        let https_url = get_https_url dcPath uri server path in
61e9b3
+        let status, _, dump_response =
61e9b3
+          fetch_headers_from_url password_file uri sslverify https_url in
61e9b3
+        https_url, status, dump_response
61e9b3
+      )
61e9b3
+      else (https_url, status, dump_response) in
61e9b3
+
61e9b3
+    if status = "401" then (
61e9b3
+      dump_response stderr;
61e9b3
+      if uri.uri_user <> None then
61e9b3
+        error (f_"vcenter: incorrect username or password")
61e9b3
+      else
61e9b3
+        error (f_"vcenter: incorrect username or password.  You might need to specify the username in the URI like this: [vpx|esx|..]://USERNAME@[etc]")
61e9b3
+    );
61e9b3
+
61e9b3
+    if status = "404" then (
61e9b3
+      dump_response stderr;
61e9b3
+      error (f_"vcenter: URL not found: %s") https_url
61e9b3
+    );
61e9b3
+
61e9b3
+    if status <> "200" then (
61e9b3
+      dump_response stderr;
61e9b3
+      error (f_"vcenter: invalid response from server: %s") status
61e9b3
+    );
61e9b3
+
61e9b3
+    https_url in
61e9b3
 
61e9b3
   let session_cookie =
61e9b3
     get_session_cookie password_file uri sslverify https_url in
61e9b3
 
61e9b3
-  let password =
61e9b3
-    match password_file with
61e9b3
-    | None -> Nbdkit_sources.NoPassword
61e9b3
-    | Some password_file -> Nbdkit_sources.PasswordFile password_file in
61e9b3
+  (* Write a cookie script to retrieve the session cookie.
61e9b3
+   * See nbdkit-curl-plugin(1) "Example: VMware ESXi cookies"
61e9b3
+   *)
61e9b3
+  let cookie_script, chan =
61e9b3
+    Filename.open_temp_file ~perms:0o700 "v2vcs" ".sh" in
61e9b3
+  unlink_on_exit cookie_script;
61e9b3
+  let fpf fs = fprintf chan fs in
61e9b3
+  fpf "#!/bin/sh -\n";
61e9b3
+  fpf "\n";
61e9b3
+  fpf "curl --head -s";
61e9b3
+  if not sslverify then fpf " --insecure";
61e9b3
+  (match uri.uri_user, password_file with
61e9b3
+   | None, None -> ()
61e9b3
+   | Some user, None -> fpf " -u %s" (quote user)
61e9b3
+   | None, Some password_file ->
61e9b3
+      fpf " -u \"$LOGNAME\":\"$(cat %s)\"" (quote password_file)
61e9b3
+   | Some user, Some password_file ->
61e9b3
+      fpf " -u %s:\"$(cat %s)\"" (quote user) (quote password_file)
61e9b3
+  );
61e9b3
+  fpf " %s" (quote https_url);
61e9b3
+  fpf " |\n";
61e9b3
+  fpf "\tsed -ne %s\n" (quote "{ s/^Set-Cookie: \\([^;]*\\);.*/\\1/ip }");
61e9b3
+  close_out chan;
61e9b3
+
61e9b3
+  (* VMware authentication expires after 30 minutes so we must renew
61e9b3
+   * after < 30 minutes.
61e9b3
+   *)
61e9b3
+  let cookie_script_renew = 25*60 in
61e9b3
 
61e9b3
   let nbdkit =
61e9b3
-    Nbdkit_sources.create_curl ?bandwidth ?cookie:session_cookie ~password ~sslverify
61e9b3
-                       ?user:uri.uri_user https_url in
61e9b3
+    Nbdkit_sources.create_curl ?bandwidth ~cookie_script ~cookie_script_renew
61e9b3
+                               ~sslverify https_url in
61e9b3
   let qemu_uri = Nbdkit_sources.run nbdkit in
61e9b3
 
61e9b3
   (* Return the struct. *)
61e9b3
-- 
8984ae
2.31.1
61e9b3