fbe740
From 4abdfae3b67295a0143f650768630e009d1b2798 Mon Sep 17 00:00:00 2001
fbe740
Message-Id: <4abdfae3b67295a0143f650768630e009d1b2798@dist-git>
fbe740
From: Peter Krempa <pkrempa@redhat.com>
fbe740
Date: Mon, 16 Mar 2020 22:11:57 +0100
fbe740
Subject: [PATCH] conf: Add support for cookies for HTTP based disks
fbe740
MIME-Version: 1.0
fbe740
Content-Type: text/plain; charset=UTF-8
fbe740
Content-Transfer-Encoding: 8bit
fbe740
fbe740
Add possibility to specify one or more cookies for http based disks.
fbe740
This patch adds the config parser, storage and validation of the
fbe740
cookies.
fbe740
fbe740
Signed-off-by: Peter Krempa <pkrempa@redhat.com>
fbe740
Reviewed-by: Ján Tomko <jtomko@redhat.com>
fbe740
(cherry picked from commit 3b076391befc3fe72deb0c244ac6c2b4c100b410)
fbe740
fbe740
https://bugzilla.redhat.com/show_bug.cgi?id=1804750
fbe740
Message-Id: <3135a30f0d0a1a4bb8da02c49f10a1bcf3a394f4.1584391727.git.pkrempa@redhat.com>
fbe740
Reviewed-by: Ján Tomko <jtomko@redhat.com>
fbe740
---
fbe740
 docs/formatdomain.html.in                     |  10 ++
fbe740
 docs/schemas/domaincommon.rng                 |  24 ++++
fbe740
 src/conf/domain_conf.c                        |  82 +++++++++++++
fbe740
 src/libvirt_private.syms                      |   1 +
fbe740
 src/util/virstoragefile.c                     | 115 ++++++++++++++++++
fbe740
 src/util/virstoragefile.h                     |  15 +++
fbe740
 .../disk-network-http.xml                     |   8 ++
fbe740
 7 files changed, 255 insertions(+)
fbe740
fbe740
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
fbe740
index 2cce247958..5a10d64e83 100644
fbe740
--- a/docs/formatdomain.html.in
fbe740
+++ b/docs/formatdomain.html.in
fbe740
@@ -2839,6 +2839,9 @@
fbe740
     <driver name='qemu' type='raw'/>
fbe740
     <source protocol="http" name="url_path">
fbe740
       <host name="hostname" port="80"/>
fbe740
+      <cookies>
fbe740
+        <cookie name="test">somevalue</cookie>
fbe740
+      </cookies>
fbe740
     </source>
fbe740
     <target dev='hde' bus='ide' tray='open'/>
fbe740
     <readonly/>
fbe740
@@ -3382,6 +3385,13 @@
fbe740
             certificate validation. Supported values are yes and
fbe740
             no. Since 6.2.0
fbe740
           
fbe740
+          
cookies
fbe740
+          
fbe740
+            For http and https accessed storage it's
fbe740
+            possible to pass one or more cookies. The cookie name and value
fbe740
+            must conform to the HTTP specification.
fbe740
+            Since 6.2.0
fbe740
+          
fbe740
         
fbe740
 
fbe740
         

fbe740
diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng
fbe740
index 548601b61c..bdf35e64f6 100644
fbe740
--- a/docs/schemas/domaincommon.rng
fbe740
+++ b/docs/schemas/domaincommon.rng
fbe740
@@ -1817,6 +1817,24 @@
fbe740
     </element>
fbe740
   </define>
fbe740
 
fbe740
+  <define name="diskSourceNetworkProtocolHTTPCookies">
fbe740
+    <element name="cookies">
fbe740
+      <oneOrMore>
fbe740
+        <element name="cookie">
fbe740
+          <attribute name="name">
fbe740
+            <data type="string">
fbe740
+              <param name="pattern">[!#$%&'*+\-.0-9A-Z\^_`a-z|~]+</param>
fbe740
+            </data>
fbe740
+          </attribute>
fbe740
+          <data type="string">
fbe740
+            <param name="pattern">[!#$%&'()*+\-./0-9:>=<?@A-Z\^_`\[\]a-z|~]+</param>
fbe740
+          </data>
fbe740
+        </element>
fbe740
+      </oneOrMore>
fbe740
+      <empty/>
fbe740
+    </element>
fbe740
+  </define>
fbe740
+
fbe740
   <define name="diskSourceNetworkProtocolHTTPS">
fbe740
     <element name="source">
fbe740
       <attribute name="protocol">
fbe740
@@ -1833,6 +1851,9 @@
fbe740
       <optional>
fbe740
         <ref name="diskSourceNetworkProtocolSSLVerify"/>
fbe740
       </optional>
fbe740
+      <optional>
fbe740
+        <ref name="diskSourceNetworkProtocolHTTPCookies"/>
fbe740
+      </optional>
fbe740
     </element>
fbe740
   </define>
fbe740
 
fbe740
@@ -1849,6 +1870,9 @@
fbe740
       <optional>
fbe740
         <ref name="encryption"/>
fbe740
       </optional>
fbe740
+      <optional>
fbe740
+        <ref name="diskSourceNetworkProtocolHTTPCookies"/>
fbe740
+      </optional>
fbe740
     </element>
fbe740
   </define>
fbe740
 
fbe740
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
fbe740
index 70bbc35bb3..d066d3aac1 100644
fbe740
--- a/src/conf/domain_conf.c
fbe740
+++ b/src/conf/domain_conf.c
fbe740
@@ -9249,6 +9249,62 @@ virDomainDiskSourcePoolDefParse(xmlNodePtr node,
fbe740
 }
fbe740
 
fbe740
 
fbe740
+static virStorageNetCookieDefPtr
fbe740
+virDomainStorageNetCookieParse(xmlNodePtr node,
fbe740
+                               xmlXPathContextPtr ctxt)
fbe740
+{
fbe740
+    VIR_XPATH_NODE_AUTORESTORE(ctxt);
fbe740
+    g_autoptr(virStorageNetCookieDef) cookie = NULL;
fbe740
+
fbe740
+    ctxt->node = node;
fbe740
+
fbe740
+    cookie = g_new0(virStorageNetCookieDef, 1);
fbe740
+
fbe740
+    if (!(cookie->name = virXPathString("string(./@name)", ctxt))) {
fbe740
+        virReportError(VIR_ERR_XML_ERROR, "%s", _("missing cookie name"));
fbe740
+        return NULL;
fbe740
+    }
fbe740
+
fbe740
+    if (!(cookie->value = virXPathString("string(.)", ctxt))) {
fbe740
+        virReportError(VIR_ERR_XML_ERROR, _("missing value for cookie '%s'"),
fbe740
+                       cookie->name);
fbe740
+        return NULL;
fbe740
+    }
fbe740
+
fbe740
+    return g_steal_pointer(&cookie);
fbe740
+}
fbe740
+
fbe740
+
fbe740
+static int
fbe740
+virDomainStorageNetCookiesParse(xmlNodePtr node,
fbe740
+                                xmlXPathContextPtr ctxt,
fbe740
+                                virStorageSourcePtr src)
fbe740
+{
fbe740
+    VIR_XPATH_NODE_AUTORESTORE(ctxt);
fbe740
+    g_autofree xmlNodePtr *nodes = NULL;
fbe740
+    ssize_t nnodes;
fbe740
+    size_t i;
fbe740
+
fbe740
+    ctxt->node = node;
fbe740
+
fbe740
+    if ((nnodes = virXPathNodeSet("./cookie", ctxt, &nodes)) < 0)
fbe740
+        return -1;
fbe740
+
fbe740
+    src->cookies = g_new0(virStorageNetCookieDefPtr, nnodes);
fbe740
+    src->ncookies = nnodes;
fbe740
+
fbe740
+    for (i = 0; i < nnodes; i++) {
fbe740
+        if (!(src->cookies[i] = virDomainStorageNetCookieParse(nodes[i], ctxt)))
fbe740
+            return -1;
fbe740
+    }
fbe740
+
fbe740
+    if (virStorageSourceNetCookiesValidate(src) < 0)
fbe740
+        return -1;
fbe740
+
fbe740
+    return 0;
fbe740
+}
fbe740
+
fbe740
+
fbe740
 static int
fbe740
 virDomainDiskSourceNetworkParse(xmlNodePtr node,
fbe740
                                 xmlXPathContextPtr ctxt,
fbe740
@@ -9260,6 +9316,7 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node,
fbe740
     g_autofree char *haveTLS = NULL;
fbe740
     g_autofree char *tlsCfg = NULL;
fbe740
     g_autofree char *sslverifystr = NULL;
fbe740
+    xmlNodePtr tmpnode;
fbe740
 
fbe740
     if (!(protocol = virXMLPropString(node, "protocol"))) {
fbe740
         virReportError(VIR_ERR_XML_ERROR, "%s",
fbe740
@@ -9345,6 +9402,13 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node,
fbe740
         src->sslverify = verify;
fbe740
     }
fbe740
 
fbe740
+    if ((src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
fbe740
+         src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS) &&
fbe740
+        (tmpnode = virXPathNode("./cookies", ctxt))) {
fbe740
+        if (virDomainStorageNetCookiesParse(tmpnode, ctxt, src) < 0)
fbe740
+            return -1;
fbe740
+    }
fbe740
+
fbe740
     return 0;
fbe740
 }
fbe740
 
fbe740
@@ -24281,6 +24345,22 @@ virDomainSourceDefFormatSeclabel(virBufferPtr buf,
fbe740
 }
fbe740
 
fbe740
 
fbe740
+static void
fbe740
+virDomainDiskSourceFormatNetworkCookies(virBufferPtr buf,
fbe740
+                                        virStorageSourcePtr src)
fbe740
+{
fbe740
+    g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
fbe740
+    size_t i;
fbe740
+
fbe740
+    for (i = 0; i < src->ncookies; i++) {
fbe740
+        virBufferEscapeString(&childBuf, "<cookie name='%s'>", src->cookies[i]->name);
fbe740
+        virBufferEscapeString(&childBuf, "%s</cookie>\n", src->cookies[i]->value);
fbe740
+    }
fbe740
+
fbe740
+    virXMLFormatElement(buf, "cookies", NULL, &childBuf);
fbe740
+}
fbe740
+
fbe740
+
fbe740
 static int
fbe740
 virDomainDiskSourceFormatNetwork(virBufferPtr attrBuf,
fbe740
                                  virBufferPtr childBuf,
fbe740
@@ -24331,6 +24411,8 @@ virDomainDiskSourceFormatNetwork(virBufferPtr attrBuf,
fbe740
                           virTristateBoolTypeToString(src->sslverify));
fbe740
     }
fbe740
 
fbe740
+    virDomainDiskSourceFormatNetworkCookies(childBuf, src);
fbe740
+
fbe740
     return 0;
fbe740
 }
fbe740
 
fbe740
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
fbe740
index dbbec0d567..ac5527ef01 100644
fbe740
--- a/src/libvirt_private.syms
fbe740
+++ b/src/libvirt_private.syms
fbe740
@@ -3123,6 +3123,7 @@ virStorageSourceIsEmpty;
fbe740
 virStorageSourceIsLocalStorage;
fbe740
 virStorageSourceIsRelative;
fbe740
 virStorageSourceIsSameLocation;
fbe740
+virStorageSourceNetCookiesValidate;
fbe740
 virStorageSourceNetworkAssignDefaultPorts;
fbe740
 virStorageSourceNew;
fbe740
 virStorageSourceNewFromBacking;
fbe740
diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c
fbe740
index cfa77fccf8..6350168d73 100644
fbe740
--- a/src/util/virstoragefile.c
fbe740
+++ b/src/util/virstoragefile.c
fbe740
@@ -2157,6 +2157,118 @@ virStorageSourceSeclabelsCopy(virStorageSourcePtr to,
fbe740
 }
fbe740
 
fbe740
 
fbe740
+void
fbe740
+virStorageNetCookieDefFree(virStorageNetCookieDefPtr def)
fbe740
+{
fbe740
+    if (!def)
fbe740
+        return;
fbe740
+
fbe740
+    g_free(def->name);
fbe740
+    g_free(def->value);
fbe740
+
fbe740
+    g_free(def);
fbe740
+}
fbe740
+
fbe740
+
fbe740
+static void
fbe740
+virStorageSourceNetCookiesClear(virStorageSourcePtr src)
fbe740
+{
fbe740
+    size_t i;
fbe740
+
fbe740
+    if (!src || !src->cookies)
fbe740
+        return;
fbe740
+
fbe740
+    for (i = 0; i < src->ncookies; i++)
fbe740
+        virStorageNetCookieDefFree(src->cookies[i]);
fbe740
+
fbe740
+    g_clear_pointer(&src->cookies, g_free);
fbe740
+    src->ncookies = 0;
fbe740
+}
fbe740
+
fbe740
+
fbe740
+static void
fbe740
+virStorageSourceNetCookiesCopy(virStorageSourcePtr to,
fbe740
+                               const virStorageSource *from)
fbe740
+{
fbe740
+    size_t i;
fbe740
+
fbe740
+    if (from->ncookies == 0)
fbe740
+        return;
fbe740
+
fbe740
+    to->cookies = g_new0(virStorageNetCookieDefPtr, from->ncookies);
fbe740
+    to->ncookies = from->ncookies;
fbe740
+
fbe740
+    for (i = 0; i < from->ncookies; i++) {
fbe740
+        to->cookies[i]->name = g_strdup(from->cookies[i]->name);
fbe740
+        to->cookies[i]->value = g_strdup(from->cookies[i]->value);
fbe740
+    }
fbe740
+}
fbe740
+
fbe740
+
fbe740
+/* see https://tools.ietf.org/html/rfc6265#section-4.1.1 */
fbe740
+static const char virStorageSourceCookieValueInvalidChars[] =
fbe740
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
fbe740
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
fbe740
+ " \",;\\";
fbe740
+
fbe740
+/* in addition cookie name can't contain these */
fbe740
+static const char virStorageSourceCookieNameInvalidChars[] =
fbe740
+ "()<>@:/[]?={}";
fbe740
+
fbe740
+static int
fbe740
+virStorageSourceNetCookieValidate(virStorageNetCookieDefPtr def)
fbe740
+{
fbe740
+    /* name must have at least 1 character */
fbe740
+    if (*(def->name) == '\0') {
fbe740
+        virReportError(VIR_ERR_XML_ERROR, "%s",
fbe740
+                       _("cookie name must not be empty"));
fbe740
+        return -1;
fbe740
+    }
fbe740
+
fbe740
+    /* check invalid characters in name */
fbe740
+    if (virStringHasChars(def->name, virStorageSourceCookieValueInvalidChars) ||
fbe740
+        virStringHasChars(def->name, virStorageSourceCookieNameInvalidChars)) {
fbe740
+        virReportError(VIR_ERR_XML_ERROR,
fbe740
+                       _("cookie name '%s' contains invalid characters"),
fbe740
+                       def->name);
fbe740
+        return -1;
fbe740
+    }
fbe740
+
fbe740
+    /* check invalid characters in value */
fbe740
+    if (virStringHasChars(def->value, virStorageSourceCookieValueInvalidChars)) {
fbe740
+        virReportError(VIR_ERR_XML_ERROR,
fbe740
+                       _("value of cookie '%s' contains invalid characters"),
fbe740
+                       def->name);
fbe740
+        return -1;
fbe740
+    }
fbe740
+
fbe740
+    return 0;
fbe740
+}
fbe740
+
fbe740
+
fbe740
+int
fbe740
+virStorageSourceNetCookiesValidate(virStorageSourcePtr src)
fbe740
+{
fbe740
+    size_t i;
fbe740
+    size_t j;
fbe740
+
fbe740
+    for (i = 0; i < src->ncookies; i++) {
fbe740
+        if (virStorageSourceNetCookieValidate(src->cookies[i]) < 0)
fbe740
+            return -1;
fbe740
+
fbe740
+        for (j = i + 1; j < src->ncookies; j++) {
fbe740
+            if (STREQ(src->cookies[i]->name, src->cookies[j]->name)) {
fbe740
+                virReportError(VIR_ERR_XML_ERROR, _("duplicate cookie '%s'"),
fbe740
+                               src->cookies[i]->name);
fbe740
+                return -1;
fbe740
+            }
fbe740
+        }
fbe740
+    }
fbe740
+
fbe740
+    return 0;
fbe740
+}
fbe740
+
fbe740
+
fbe740
 static virStorageTimestampsPtr
fbe740
 virStorageTimestampsCopy(const virStorageTimestamps *src)
fbe740
 {
fbe740
@@ -2299,6 +2411,8 @@ virStorageSourceCopy(const virStorageSource *src,
fbe740
         def->nhosts = src->nhosts;
fbe740
     }
fbe740
 
fbe740
+    virStorageSourceNetCookiesCopy(def, src);
fbe740
+
fbe740
     if (src->srcpool &&
fbe740
         !(def->srcpool = virStorageSourcePoolDefCopy(src->srcpool)))
fbe740
         return NULL;
fbe740
@@ -2560,6 +2674,7 @@ virStorageSourceClear(virStorageSourcePtr def)
fbe740
     VIR_FREE(def->volume);
fbe740
     VIR_FREE(def->snapshot);
fbe740
     VIR_FREE(def->configFile);
fbe740
+    virStorageSourceNetCookiesClear(def);
fbe740
     virStorageSourcePoolDefFree(def->srcpool);
fbe740
     virBitmapFree(def->features);
fbe740
     VIR_FREE(def->compat);
fbe740
diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h
fbe740
index fab4248c3d..1c7c046ad6 100644
fbe740
--- a/src/util/virstoragefile.h
fbe740
+++ b/src/util/virstoragefile.h
fbe740
@@ -162,6 +162,17 @@ struct _virStorageNetHostDef {
fbe740
     char *socket;  /* path to unix socket */
fbe740
 };
fbe740
 
fbe740
+typedef struct _virStorageNetCookieDef virStorageNetCookieDef;
fbe740
+typedef virStorageNetCookieDef *virStorageNetCookieDefPtr;
fbe740
+struct _virStorageNetCookieDef {
fbe740
+    char *name;
fbe740
+    char *value;
fbe740
+};
fbe740
+
fbe740
+void virStorageNetCookieDefFree(virStorageNetCookieDefPtr def);
fbe740
+
fbe740
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virStorageNetCookieDef, virStorageNetCookieDefFree);
fbe740
+
fbe740
 /* Information for a storage volume from a virStoragePool */
fbe740
 
fbe740
 /*
fbe740
@@ -276,6 +287,8 @@ struct _virStorageSource {
fbe740
                          the source definition */
fbe740
     size_t nhosts;
fbe740
     virStorageNetHostDefPtr hosts;
fbe740
+    size_t ncookies;
fbe740
+    virStorageNetCookieDefPtr *cookies;
fbe740
     virStorageSourcePoolDefPtr srcpool;
fbe740
     virStorageAuthDefPtr auth;
fbe740
     bool authInherited;
fbe740
@@ -477,6 +490,8 @@ int virStorageSourceUpdateCapacity(virStorageSourcePtr src,
fbe740
 int virStorageSourceNewFromBacking(virStorageSourcePtr parent,
fbe740
                                    virStorageSourcePtr *backing);
fbe740
 
fbe740
+int virStorageSourceNetCookiesValidate(virStorageSourcePtr src);
fbe740
+
fbe740
 virStorageSourcePtr virStorageSourceCopy(const virStorageSource *src,
fbe740
                                          bool backingChain)
fbe740
     ATTRIBUTE_NONNULL(1);
fbe740
diff --git a/tests/genericxml2xmlindata/disk-network-http.xml b/tests/genericxml2xmlindata/disk-network-http.xml
fbe740
index bdcc1977f2..bafb77c8ec 100644
fbe740
--- a/tests/genericxml2xmlindata/disk-network-http.xml
fbe740
+++ b/tests/genericxml2xmlindata/disk-network-http.xml
fbe740
@@ -33,6 +33,10 @@
fbe740
       <driver name='qemu' type='raw'/>
fbe740
       <source protocol='http' name='test3.img'>
fbe740
         <host name='example.org' port='1234'/>
fbe740
+        <cookies>
fbe740
+          <cookie name='test'>testcookievalue</cookie>
fbe740
+          <cookie name='test2'>blurb</cookie>
fbe740
+        </cookies>
fbe740
       </source>
fbe740
       <target dev='vdc' bus='virtio'/>
fbe740
     </disk>
fbe740
@@ -41,6 +45,10 @@
fbe740
       <source protocol='https' name='test4.img'>
fbe740
         <host name='example.org' port='1234'/>
fbe740
         <ssl verify='yes'/>
fbe740
+        <cookies>
fbe740
+          <cookie name='test'>testcookievalue</cookie>
fbe740
+          <cookie name='test2'>blurb</cookie>
fbe740
+        </cookies>
fbe740
       </source>
fbe740
       <target dev='vdd' bus='virtio'/>
fbe740
     </disk>
fbe740
-- 
fbe740
2.25.1
fbe740