Blame SOURCES/0121-launch-libvirt-Implement-drive-secrets-RHBZ-1159016.patch

ffd6ed
From a72b602cfabd360335a63e70b6cb1abf7e82f69a Mon Sep 17 00:00:00 2001
ffd6ed
From: "Richard W.M. Jones" <rjones@redhat.com>
ffd6ed
Date: Fri, 31 Oct 2014 11:02:33 +0000
ffd6ed
Subject: [PATCH] launch: libvirt: Implement drive secrets (RHBZ#1159016).
ffd6ed
ffd6ed
Implement the GUESTFS_ADD_DRIVE_OPTS_SECRET argument of
ffd6ed
guestfs_add_drive_opts.  For libvirt we have to save the secret in
ffd6ed
libvirtd first, get a UUID, and then pass the UUID back through the
ffd6ed
domain XML.
ffd6ed
ffd6ed
(cherry picked from commit 6d6644d52d8092dd4f4add859ebda06bea4b5b56)
ffd6ed
---
ffd6ed
 bootstrap            |   1 +
ffd6ed
 generator/actions.ml |   2 +-
ffd6ed
 m4/.gitignore        |   4 +
ffd6ed
 src/launch-libvirt.c | 272 ++++++++++++++++++++++++++++++++++++++++++++++++++-
ffd6ed
 4 files changed, 275 insertions(+), 4 deletions(-)
ffd6ed
ffd6ed
diff --git a/bootstrap b/bootstrap
ffd6ed
index b4afcdb..70789e3 100755
ffd6ed
--- a/bootstrap
ffd6ed
+++ b/bootstrap
ffd6ed
@@ -41,6 +41,7 @@ accept4
ffd6ed
 areadlink
ffd6ed
 areadlinkat
ffd6ed
 arpa_inet
ffd6ed
+base64
ffd6ed
 byteswap
ffd6ed
 c-ctype
ffd6ed
 cloexec
ffd6ed
diff --git a/generator/actions.ml b/generator/actions.ml
ffd6ed
index ea715d0..4bc043b 100644
ffd6ed
--- a/generator/actions.ml
ffd6ed
+++ b/generator/actions.ml
ffd6ed
@@ -1452,7 +1452,7 @@ specify the remote username you want.
ffd6ed
 =item C<secret>
ffd6ed
 
ffd6ed
 For the C<rbd> protocol only, this specifies the 'secret' to use when
ffd6ed
-connecting to the remote device.
ffd6ed
+connecting to the remote device.  It must be base64 encoded.
ffd6ed
 
ffd6ed
 If not given, then a secret matching the given username will be looked up in the
ffd6ed
 default keychain locations, or if no username is given, then no authentication
ffd6ed
diff --git a/m4/.gitignore b/m4/.gitignore
ffd6ed
index ed73ab3..b0f8d4b 100644
ffd6ed
--- a/m4/.gitignore
ffd6ed
+++ b/m4/.gitignore
ffd6ed
@@ -5,6 +5,7 @@
ffd6ed
 /argmatch.m4
ffd6ed
 /arpa_inet_h.m4
ffd6ed
 /asm-underscore.m4
ffd6ed
+/base64.m4
ffd6ed
 /btowc.m4
ffd6ed
 /byteswap.m4
ffd6ed
 /canonicalize-lgpl.m4
ffd6ed
@@ -104,6 +105,7 @@
ffd6ed
 /inttypes-pri.m4
ffd6ed
 /ioctl.m4
ffd6ed
 /i-ring.m4
ffd6ed
+/isatty.m4
ffd6ed
 /isc-posix.m4
ffd6ed
 /largefile.m4
ffd6ed
 /lchown.m4
ffd6ed
@@ -166,6 +168,7 @@
ffd6ed
 /printf-posix.m4
ffd6ed
 /priv-set.m4
ffd6ed
 /progtest.m4
ffd6ed
+/ptsname_r.m4
ffd6ed
 /putenv.m4
ffd6ed
 /quotearg.m4
ffd6ed
 /quote.m4
ffd6ed
@@ -236,6 +239,7 @@
ffd6ed
 /thread.m4
ffd6ed
 /time_h.m4
ffd6ed
 /timespec.m4
ffd6ed
+/ttyname_r.m4
ffd6ed
 /uintmax_t.m4
ffd6ed
 /ulonglong.m4
ffd6ed
 /ungetc.m4
ffd6ed
diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c
ffd6ed
index 026fd5a..5daa396 100644
ffd6ed
--- a/src/launch-libvirt.c
ffd6ed
+++ b/src/launch-libvirt.c
ffd6ed
@@ -49,6 +49,7 @@
ffd6ed
 #endif
ffd6ed
 
ffd6ed
 #include "glthread/lock.h"
ffd6ed
+#include "base64.h"
ffd6ed
 
ffd6ed
 #include "guestfs.h"
ffd6ed
 #include "guestfs-internal.h"
ffd6ed
@@ -96,6 +97,27 @@ xmlBufferDetach (xmlBufferPtr buf)
ffd6ed
 }
ffd6ed
 #endif
ffd6ed
 
ffd6ed
+#ifdef HAVE_ATTRIBUTE_CLEANUP
ffd6ed
+#define CLEANUP_VIRSECRETFREE __attribute__((cleanup(cleanup_virSecretFree)))
ffd6ed
+
ffd6ed
+static void
ffd6ed
+cleanup_virSecretFree (void *ptr)
ffd6ed
+{
ffd6ed
+  virSecretPtr secret_obj = * (virSecretPtr *) ptr;
ffd6ed
+  if (secret_obj)
ffd6ed
+    virSecretFree (secret_obj);
ffd6ed
+}
ffd6ed
+
ffd6ed
+#else /* !HAVE_ATTRIBUTE_CLEANUP */
ffd6ed
+#define CLEANUP_VIRSECRETFREE
ffd6ed
+#endif
ffd6ed
+
ffd6ed
+/* List used to store a mapping of secret to libvirt secret UUID. */
ffd6ed
+struct secret {
ffd6ed
+  char *secret;
ffd6ed
+  char uuid[VIR_UUID_STRING_BUFLEN];
ffd6ed
+};
ffd6ed
+
ffd6ed
 #define DOMAIN_NAME_LEN (8+16+1) /* "guestfs-" + random + \0 */
ffd6ed
 
ffd6ed
 /* Per-handle data. */
ffd6ed
@@ -108,6 +130,8 @@ struct backend_libvirt_data {
ffd6ed
   char name[DOMAIN_NAME_LEN];   /* random name */
ffd6ed
   bool is_kvm;                  /* false = qemu, true = kvm (from capabilities)*/
ffd6ed
   unsigned long qemu_version;   /* qemu version (from libvirt) */
ffd6ed
+  struct secret *secrets;       /* list of secrets */
ffd6ed
+  size_t nr_secrets;
ffd6ed
   char *uefi_code;		/* UEFI (firmware) code and variables. */
ffd6ed
   char *uefi_vars;
ffd6ed
 };
ffd6ed
@@ -130,6 +154,9 @@ struct libvirt_xml_params {
ffd6ed
 };
ffd6ed
 
ffd6ed
 static int parse_capabilities (guestfs_h *g, const char *capabilities_xml, struct backend_libvirt_data *data);
ffd6ed
+static int add_secret (guestfs_h *g, virConnectPtr conn, struct backend_libvirt_data *data, const struct drive *drv);
ffd6ed
+static int find_secret (guestfs_h *g, const struct backend_libvirt_data *data, const struct drive *drv, const char **type, const char **uuid);
ffd6ed
+static int have_secret (guestfs_h *g, const struct backend_libvirt_data *data, const struct drive *drv);
ffd6ed
 static xmlChar *construct_libvirt_xml (guestfs_h *g, const struct libvirt_xml_params *params);
ffd6ed
 static void debug_appliance_permissions (guestfs_h *g);
ffd6ed
 static void debug_socket_permissions (guestfs_h *g);
ffd6ed
@@ -224,6 +251,8 @@ launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri)
ffd6ed
   CLEANUP_FREE xmlChar *xml = NULL;
ffd6ed
   CLEANUP_FREE char *appliance = NULL;
ffd6ed
   struct sockaddr_un addr;
ffd6ed
+  struct drive *drv;
ffd6ed
+  size_t i;
ffd6ed
   int r;
ffd6ed
   uint32_t size;
ffd6ed
   CLEANUP_FREE void *buf = NULL;
ffd6ed
@@ -456,6 +485,14 @@ launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri)
ffd6ed
       debug (g, "cannot find group 'qemu'");
ffd6ed
   }
ffd6ed
 
ffd6ed
+  /* Store any secrets in libvirtd, keeping a mapping from the secret
ffd6ed
+   * to its UUID.
ffd6ed
+   */
ffd6ed
+  ITER_DRIVES (g, i, drv) {
ffd6ed
+    if (add_secret (g, conn, data, drv) == -1)
ffd6ed
+      goto cleanup;
ffd6ed
+  }
ffd6ed
+
ffd6ed
   /* Construct the libvirt XML. */
ffd6ed
   if (g->verbose)
ffd6ed
     guestfs___print_timestamped_message (g, "create libvirt XML");
ffd6ed
@@ -1275,6 +1312,8 @@ construct_libvirt_xml_disk (guestfs_h *g,
ffd6ed
   CLEANUP_FREE char *path = NULL;
ffd6ed
   int is_host_device;
ffd6ed
   CLEANUP_FREE char *format = NULL;
ffd6ed
+  const char *type, *uuid;
ffd6ed
+  int r;
ffd6ed
 
ffd6ed
   /* XXX We probably could support this if we thought about it some more. */
ffd6ed
   if (drv->iface) {
ffd6ed
@@ -1386,9 +1425,15 @@ construct_libvirt_xml_disk (guestfs_h *g,
ffd6ed
         if (drv->src.username != NULL) {
ffd6ed
           start_element ("auth") {
ffd6ed
             attribute ("username", drv->src.username);
ffd6ed
-            /* TODO: write the drive secret, after first storing it separately
ffd6ed
-             * in libvirt
ffd6ed
-             */
ffd6ed
+            r = find_secret (g, data, drv, &type, &uuid);
ffd6ed
+            if (r == -1)
ffd6ed
+              return -1;
ffd6ed
+            if (r == 1) {
ffd6ed
+              start_element ("secret") {
ffd6ed
+                attribute ("type", type);
ffd6ed
+                attribute ("uuid", uuid);
ffd6ed
+              } end_element ();
ffd6ed
+            }
ffd6ed
           } end_element ();
ffd6ed
         }
ffd6ed
         break;
ffd6ed
@@ -1696,6 +1741,216 @@ construct_libvirt_xml_qemu_cmdline (guestfs_h *g,
ffd6ed
 }
ffd6ed
 
ffd6ed
 static int
ffd6ed
+construct_libvirt_xml_secret (guestfs_h *g,
ffd6ed
+                              const struct backend_libvirt_data *data,
ffd6ed
+                              const struct drive *drv,
ffd6ed
+                              xmlTextWriterPtr xo)
ffd6ed
+{
ffd6ed
+  start_element ("secret") {
ffd6ed
+    attribute ("ephemeral", "yes");
ffd6ed
+    attribute ("private", "yes");
ffd6ed
+    start_element ("description") {
ffd6ed
+      string_format ("guestfs secret associated with %s %s",
ffd6ed
+                     data->name, drv->src.u.path);
ffd6ed
+    } end_element ();
ffd6ed
+  } end_element ();
ffd6ed
+
ffd6ed
+  return 0;
ffd6ed
+}
ffd6ed
+
ffd6ed
+/* If drv->src.secret != NULL, store the secret in libvirt, and save
ffd6ed
+ * the UUID so we can retrieve it later.  Also there is some slight
ffd6ed
+ * variation depending on the protocol.  See
ffd6ed
+ * http://libvirt.org/formatsecret.html
ffd6ed
+ */
ffd6ed
+static int
ffd6ed
+add_secret (guestfs_h *g, virConnectPtr conn,
ffd6ed
+            struct backend_libvirt_data *data, const struct drive *drv)
ffd6ed
+{
ffd6ed
+  CLEANUP_XMLBUFFERFREE xmlBufferPtr xb = NULL;
ffd6ed
+  xmlOutputBufferPtr ob;
ffd6ed
+  CLEANUP_XMLFREETEXTWRITER xmlTextWriterPtr xo = NULL;
ffd6ed
+  CLEANUP_FREE xmlChar *xml = NULL;
ffd6ed
+  CLEANUP_VIRSECRETFREE virSecretPtr secret_obj = NULL;
ffd6ed
+  const char *secret = drv->src.secret;
ffd6ed
+  CLEANUP_FREE unsigned char *secret_raw = NULL;
ffd6ed
+  size_t secret_raw_len = 0;
ffd6ed
+  size_t i;
ffd6ed
+
ffd6ed
+  if (secret == NULL)
ffd6ed
+    return 0;
ffd6ed
+
ffd6ed
+  /* If it was already stored, don't create another secret. */
ffd6ed
+  if (have_secret (g, data, drv))
ffd6ed
+    return 0;
ffd6ed
+
ffd6ed
+  /* Create the XML for the secret. */
ffd6ed
+  xb = xmlBufferCreate ();
ffd6ed
+  if (xb == NULL) {
ffd6ed
+    perrorf (g, "xmlBufferCreate");
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+  ob = xmlOutputBufferCreateBuffer (xb, NULL);
ffd6ed
+  if (ob == NULL) {
ffd6ed
+    perrorf (g, "xmlOutputBufferCreateBuffer");
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+  xo = xmlNewTextWriter (ob);
ffd6ed
+  if (xo == NULL) {
ffd6ed
+    perrorf (g, "xmlNewTextWriter");
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  if (xmlTextWriterSetIndent (xo, 1) == -1 ||
ffd6ed
+      xmlTextWriterSetIndentString (xo, BAD_CAST "  ") == -1) {
ffd6ed
+    perrorf (g, "could not set XML indent");
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+  if (xmlTextWriterStartDocument (xo, NULL, NULL, NULL) == -1) {
ffd6ed
+    perrorf (g, "xmlTextWriterStartDocument");
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  if (construct_libvirt_xml_secret (g, data, drv, xo) == -1)
ffd6ed
+    return -1;
ffd6ed
+
ffd6ed
+  if (xmlTextWriterEndDocument (xo) == -1) {
ffd6ed
+    perrorf (g, "xmlTextWriterEndDocument");
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+  xml = xmlBufferDetach (xb);
ffd6ed
+  if (xml == NULL) {
ffd6ed
+    perrorf (g, "xmlBufferDetach");
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  debug (g, "libvirt secret XML:\n%s", xml);
ffd6ed
+
ffd6ed
+  /* Pass the XML to libvirt. */
ffd6ed
+  secret_obj = virSecretDefineXML (conn, (const char *) xml, 0);
ffd6ed
+  if (secret_obj == NULL) {
ffd6ed
+    libvirt_error (g, _("could not define libvirt secret"));
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  /* For Ceph, we have to base64 decode the secret.  For others, we
ffd6ed
+   * currently just pass the secret straight through.
ffd6ed
+   */
ffd6ed
+  switch (drv->src.protocol) {
ffd6ed
+  case drive_protocol_rbd:
ffd6ed
+    if (!base64_decode_alloc (secret, strlen (secret),
ffd6ed
+                              (char **) &secret_raw, &secret_raw_len)) {
ffd6ed
+      error (g, _("rbd protocol secret must be base64 encoded"));
ffd6ed
+      return -1;
ffd6ed
+    }
ffd6ed
+    if (secret_raw == NULL) {
ffd6ed
+      error (g, _("base64_decode_alloc: %m"));
ffd6ed
+      return -1;
ffd6ed
+    }
ffd6ed
+    break;
ffd6ed
+  case drive_protocol_file:
ffd6ed
+  case drive_protocol_ftp:
ffd6ed
+  case drive_protocol_ftps:
ffd6ed
+  case drive_protocol_gluster:
ffd6ed
+  case drive_protocol_http:
ffd6ed
+  case drive_protocol_https:
ffd6ed
+  case drive_protocol_iscsi:
ffd6ed
+  case drive_protocol_nbd:
ffd6ed
+  case drive_protocol_sheepdog:
ffd6ed
+  case drive_protocol_ssh:
ffd6ed
+  case drive_protocol_tftp:
ffd6ed
+    secret_raw = (unsigned char *) safe_strdup (g, secret);
ffd6ed
+    secret_raw_len = strlen (secret);
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  /* Set the secret. */
ffd6ed
+  if (virSecretSetValue (secret_obj, secret_raw, secret_raw_len, 0) == -1) {
ffd6ed
+    libvirt_error (g, _("could not set libvirt secret value"));
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  /* Get back the UUID and save it in the private data. */
ffd6ed
+  i = data->nr_secrets;
ffd6ed
+  data->nr_secrets++;
ffd6ed
+  data->secrets =
ffd6ed
+    safe_realloc (g, data->secrets, sizeof (struct secret) * data->nr_secrets);
ffd6ed
+
ffd6ed
+  data->secrets[i].secret = safe_strdup (g, secret);
ffd6ed
+
ffd6ed
+  if (virSecretGetUUIDString (secret_obj, data->secrets[i].uuid) == -1) {
ffd6ed
+    libvirt_error (g, _("could not get UUID from libvirt secret"));
ffd6ed
+    return -1;
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  return 0;
ffd6ed
+}
ffd6ed
+
ffd6ed
+static int
ffd6ed
+have_secret (guestfs_h *g,
ffd6ed
+             const struct backend_libvirt_data *data, const struct drive *drv)
ffd6ed
+{
ffd6ed
+  size_t i;
ffd6ed
+
ffd6ed
+  if (drv->src.secret == NULL)
ffd6ed
+    return 0;
ffd6ed
+
ffd6ed
+  for (i = 0; i < data->nr_secrets; ++i) {
ffd6ed
+    if (STREQ (data->secrets[i].secret, drv->src.secret))
ffd6ed
+      return 1;
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  return 0;
ffd6ed
+}
ffd6ed
+
ffd6ed
+/* Find a secret previously stored in libvirt.  Returns the
ffd6ed
+ * <secret type=... uuid=...> attributes.  This function returns -1
ffd6ed
+ * if there was an error, 0 if there is no secret, and 1 if the
ffd6ed
+ * secret was found and returned.
ffd6ed
+ */
ffd6ed
+static int
ffd6ed
+find_secret (guestfs_h *g,
ffd6ed
+             const struct backend_libvirt_data *data, const struct drive *drv,
ffd6ed
+             const char **type, const char **uuid)
ffd6ed
+{
ffd6ed
+  size_t i;
ffd6ed
+
ffd6ed
+  if (drv->src.secret == NULL)
ffd6ed
+    return 0;
ffd6ed
+
ffd6ed
+  for (i = 0; i < data->nr_secrets; ++i) {
ffd6ed
+    if (STREQ (data->secrets[i].secret, drv->src.secret)) {
ffd6ed
+      *uuid = data->secrets[i].uuid;
ffd6ed
+
ffd6ed
+      *type = "volume";
ffd6ed
+
ffd6ed
+      switch (drv->src.protocol) {
ffd6ed
+      case drive_protocol_rbd:
ffd6ed
+        *type = "ceph";
ffd6ed
+        break;
ffd6ed
+      case drive_protocol_iscsi:
ffd6ed
+        *type = "iscsi";
ffd6ed
+        break;
ffd6ed
+      case drive_protocol_file:
ffd6ed
+      case drive_protocol_ftp:
ffd6ed
+      case drive_protocol_ftps:
ffd6ed
+      case drive_protocol_gluster:
ffd6ed
+      case drive_protocol_http:
ffd6ed
+      case drive_protocol_https:
ffd6ed
+      case drive_protocol_nbd:
ffd6ed
+      case drive_protocol_sheepdog:
ffd6ed
+      case drive_protocol_ssh:
ffd6ed
+      case drive_protocol_tftp:
ffd6ed
+        /* set to a default value above */ ;
ffd6ed
+      }
ffd6ed
+
ffd6ed
+      return 1;
ffd6ed
+    }
ffd6ed
+  }
ffd6ed
+
ffd6ed
+  return 0;
ffd6ed
+}
ffd6ed
+
ffd6ed
+static int
ffd6ed
 is_blk (const char *path)
ffd6ed
 {
ffd6ed
   struct stat statbuf;
ffd6ed
@@ -1717,6 +1972,7 @@ shutdown_libvirt (guestfs_h *g, void *datav, int check_for_errors)
ffd6ed
   struct backend_libvirt_data *data = datav;
ffd6ed
   virConnectPtr conn = data->conn;
ffd6ed
   virDomainPtr dom = data->dom;
ffd6ed
+  size_t i;
ffd6ed
   int ret = 0;
ffd6ed
   int flags;
ffd6ed
 
ffd6ed
@@ -1751,6 +2007,12 @@ shutdown_libvirt (guestfs_h *g, void *datav, int check_for_errors)
ffd6ed
   free (data->uefi_vars);
ffd6ed
   data->uefi_vars = NULL;
ffd6ed
 
ffd6ed
+  for (i = 0; i < data->nr_secrets; ++i)
ffd6ed
+    free (data->secrets[i].secret);
ffd6ed
+  free (data->secrets);
ffd6ed
+  data->secrets = NULL;
ffd6ed
+  data->nr_secrets = 0;
ffd6ed
+
ffd6ed
   return ret;
ffd6ed
 }
ffd6ed
 
ffd6ed
@@ -1855,6 +2117,10 @@ hot_add_drive_libvirt (guestfs_h *g, void *datav,
ffd6ed
     return -1;
ffd6ed
   }
ffd6ed
 
ffd6ed
+  /* If the drive has an associated secret, store it in libvirt. */
ffd6ed
+  if (add_secret (g, conn, data, drv) == -1)
ffd6ed
+    return -1;
ffd6ed
+
ffd6ed
   /* Create the XML for the new disk. */
ffd6ed
   xml = construct_libvirt_xml_hot_add_disk (g, data, drv, drv_index);
ffd6ed
   if (xml == NULL)
ffd6ed
-- 
ffd6ed
1.8.3.1
ffd6ed