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

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