[Libguestfs] [nbdkit PATCH v3 5/5] nbd: Add TLS client support

Eric Blake eblake at redhat.com
Wed Jun 12 21:00:13 UTC 2019


Well, really add parameters to pass on to libnbd which does all the
heavy lifting :)

This also adds testsuite coverage that we support TLS over Unix sockets.

Signed-off-by: Eric Blake <eblake at redhat.com>
---
 plugins/nbd/nbdkit-nbd-plugin.pod | 68 +++++++++++++++++++------
 plugins/nbd/nbd.c                 | 77 ++++++++++++++++++++++++++++-
 TODO                              | 13 +----
 tests/Makefile.am                 |  6 ++-
 tests/test-nbd-tls-psk.sh         | 81 ++++++++++++++++++++++++++++++
 tests/test-nbd-tls.sh             | 82 +++++++++++++++++++++++++++++++
 tests/test-tls-psk.sh             |  2 +-
 tests/test-tls.sh                 |  4 +-
 8 files changed, 302 insertions(+), 31 deletions(-)
 create mode 100755 tests/test-nbd-tls-psk.sh
 create mode 100755 tests/test-nbd-tls.sh

diff --git a/plugins/nbd/nbdkit-nbd-plugin.pod b/plugins/nbd/nbdkit-nbd-plugin.pod
index 98f45a35..f4b8093e 100644
--- a/plugins/nbd/nbdkit-nbd-plugin.pod
+++ b/plugins/nbd/nbdkit-nbd-plugin.pod
@@ -5,7 +5,8 @@ nbdkit-nbd-plugin - nbdkit nbd plugin
 =head1 SYNOPSIS

  nbdkit nbd { socket=SOCKNAME | hostname=HOST [port=PORT] | [uri=]URI }
-    [export=NAME] [retry=N] [shared=BOOL]
+    [export=NAME] [retry=N] [shared=BOOL] [tls=MODE] [tls-certificates=DIR]
+    [tls-verify=BOOL] [tls-username=NAME] [tls-psk=FILE]

 =head1 DESCRIPTION

@@ -20,10 +21,9 @@ original server lacks it).  Use of this plugin along with nbdkit
 filters (adding I<--filter> to the nbdkit command line) makes it
 possible to apply any nbdkit filter to any other NBD server.

-For now, this is limited to connecting to another NBD server over an
-unencrypted connection; if the data is sensitive, it is better to
-stick to a Unix socket rather than transmitting plaintext over TCP. It
-is feasible that future additions will support encryption.
+Remember that when using this plugin as a bridge between an encrypted
+and a non-encrypted endpoint, it is best to preserve encryption over
+TCP and use plaintext only on a Unix socket.

 =head1 PARAMETERS

@@ -91,6 +91,45 @@ shell glob. The B<uri> parameter is only available when the plugin was
 compiled against libnbd with URI support; C<nbdkit --dump-plugin nbd>
 will contain C<libnbd_uri=1> if this is the case.

+=item B<tls=>MODE
+
+Selects which TLS mode to use with the server. If no other tls option
+is present, this defaults to C<off>, where the client does not attempt
+encryption (and may be rejected by a server that requires it). If
+omitted but another tls option is present, this defaults to C<on>,
+where the client opportunistically attempts a TLS handshake, but will
+continue running unencrypted if the server does not support
+encryption. If set to C<require>, this requires an encrypted
+connection to the server.
+
+The B<tls> parameter is only available when the plugin was compiled
+against libnbd with TLS support; C<nbdkit --dump-plugin nbd> will
+contain C<libnbd_tls=1> if this is the case.  Note the difference
+between C<--tls=...> controlling what nbdkit serves, and C<tls=...>
+controlling what the nbd plugin connects to as a client.
+
+=item B<tls-certificates=>DIR
+
+This specifies the directory containing X.509 client certificates to
+present to the server.
+
+=item B<tls-verify=>BOOL
+
+This defaults to true; setting it to false disables server name
+verification, which opens you to potential Man-in-the-Middle (MITM)
+attacks, but allows for a simpler setup for distributing certificates.
+
+=item B<tls-username=>NAME
+
+If provided, this overrides the user name to present to the server
+alongside the certificate.
+
+=item B<tls-psk=>FILE
+
+If provided, this is the filename containing the Pre-Shared Keys (PSK)
+to present to the server. While this is easier to set up than X.509,
+it requires that the PSK file be transmitted over a secure channel.
+
 =back

 =head1 EXAMPLES
@@ -104,9 +143,9 @@ that the old server exits.
    nbdkit --exit-with-parent --tls=require nbd socket=$sock &
    exec /path/to/oldserver --socket=$sock )

- ┌────────────┐          ┌────────┐          ┌────────────┐
- │ new client │ ────────▶│ nbdkit │ ────────▶│ old server │
- └────────────┘   TCP    └────────┘   Unix   └────────────┘
+ ┌────────────┐   TLS    ┌────────┐  plaintext  ┌────────────┐
+ │ new client │ ────────▶│ nbdkit │ ───────────▶│ old server │
+ └────────────┘   TCP    └────────┘    Unix     └────────────┘

 Combine nbdkit's partition filter with qemu-nbd's ability to visit
 qcow2 files (nbdkit does not have a native qcow2 plugin), performing
@@ -121,16 +160,17 @@ utilize a 5-second retry to give qemu-nbd time to create the socket:
    exec qemu-nbd -k $sock -f qcow2 /path/to/image.qcow2 )

 Conversely, expose the contents of export I<foo> from a new style
-server with unencrypted data to a client that can only consume
+server with encrypted data to a client that can only consume
 unencrypted old style. Use I<--run> to clean up nbdkit at the time the
-client exits.
+client exits.  In general, note that it is best to keep the plaintext
+connection limited to a Unix socket on the local machine.

- nbdkit -U - -o nbd hostname=example.com export=foo \
+ nbdkit -U - -o --tls=off nbd hostname=example.com export=foo tls=require \
   --run '/path/to/oldclient --socket=$unixsocket'

- ┌────────────┐          ┌────────┐          ┌────────────┐
- │ old client │ ────────▶│ nbdkit │ ────────▶│ new server │
- └────────────┘   Unix   └────────┘   TCP    └────────────┘
+ ┌────────────┐  plaintext  ┌────────┐   TLS    ┌────────────┐
+ │ old client │ ───────────▶│ nbdkit │ ────────▶│ new server │
+ └────────────┘    Unix     └────────┘   TCP    └────────────┘

 Learn which features are provided by libnbd by inspecting the
 C<libnbd_*> lines:
diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c
index 0f54805c..fa14ea1b 100644
--- a/plugins/nbd/nbd.c
+++ b/plugins/nbd/nbd.c
@@ -100,6 +100,13 @@ static unsigned long retry;
 static bool shared;
 static struct handle *shared_handle;

+/* Control TLS settings */
+static int tls = -1;
+static char *tls_certificates;
+static int tls_verify = -1;
+static const char *tls_username;
+static char *tls_psk;
+
 static struct handle *nbdplug_open_handle (int readonly);
 static void nbdplug_close_handle (struct handle *h);

@@ -109,12 +116,15 @@ nbdplug_unload (void)
   if (shared)
     nbdplug_close_handle (shared_handle);
   free (sockname);
+  free (tls_certificates);
+  free (tls_psk);
 }

 /* Called for each key=value passed on the command line.  This plugin
  * accepts socket=<sockname>, hostname=<hostname>/port=<port>, or
  * [uri=]<uri> (exactly one connection required), and optional
- * parameters export=<name>, retry=<n> and shared=<bool>.
+ * parameters export=<name>, retry=<n>, shared=<bool> and various
+ * tls settings.
  */
 static int
 nbdplug_config (const char *key, const char *value)
@@ -151,6 +161,37 @@ nbdplug_config (const char *key, const char *value)
       return -1;
     shared = r;
   }
+  else if (strcmp (key, "tls") == 0) {
+    if (strcasecmp (value, "require") == 0 ||
+        strcasecmp (value, "required") == 0 ||
+        strcasecmp (value, "force") == 0)
+      tls = 2;
+    else {
+      tls = nbdkit_parse_bool (value);
+      if (tls == -1)
+        exit (EXIT_FAILURE);
+    }
+  }
+  else if (strcmp (key, "tls-certificates") == 0) {
+    free (tls_certificates);
+    tls_certificates = nbdkit_absolute_path (value);
+    if (!tls_certificates)
+      return -1;
+  }
+  else if (strcmp (key, "tls-verify") == 0) {
+    r = nbdkit_parse_bool (value);
+    if (r == -1)
+      return -1;
+    tls_verify = r;
+  }
+  else if (strcmp (key, "tls-username") == 0)
+    tls_username = value;
+  else if (strcmp (key, "tls-psk") == 0) {
+    free (tls_psk);
+    tls_psk = nbdkit_absolute_path (value);
+    if (!tls_psk)
+      return -1;
+  }
   else {
     nbdkit_error ("unknown parameter '%s'", key);
     return -1;
@@ -208,6 +249,23 @@ nbdplug_config_complete (void)
   if (!export)
     export = "";

+  if (tls == -1)
+    tls = tls_certificates || tls_verify >= 0 || tls_username || tls_psk;
+  if (tls > 0) {
+    struct nbd_handle *nbd = nbd_create ();
+
+    if (!nbd) {
+      nbdkit_error ("unable to query libnbd details: %s", nbd_get_error ());
+      return -1;
+    }
+    if (!nbd_supports_tls (nbd)) {
+      nbdkit_error ("libnbd was compiled without tls support");
+      nbd_close (nbd);
+      return -1;
+    }
+    nbd_close (nbd);
+  }
+
   if (shared && (shared_handle = nbdplug_open_handle (false)) == NULL)
     return -1;
   return 0;
@@ -222,6 +280,11 @@ nbdplug_config_complete (void)
   "retry=<N>              Retry connection up to N seconds (default 0).\n" \
   "shared=<BOOL>          True to share one server connection among all clients,\n" \
   "                       rather than a connection per client (default false).\n" \
+  "tls=<MODE>             How to use TLS; one of 'off', 'on', or 'require'.\n" \
+  "tls-certificates=<DIR> Directory containing files for X.509 certificates.\n" \
+  "tls-verify=<BOOL>      True (default for X.509) to validate server.\n" \
+  "tls-username=<NAME>    Override username presented in X.509 TLS.\n" \
+  "tls-psk=<FILE>         File containing Pre-Shared Key for TLS.\n" \

 static void
 nbdplug_dump_plugin (void)
@@ -233,6 +296,7 @@ nbdplug_dump_plugin (void)
     exit (EXIT_FAILURE);
   }
   printf ("libnbd_version=%s\n", nbd_get_version (nbd));
+  printf ("libnbd_tls=%d\n", nbd_supports_tls (nbd));
   printf ("libnbd_uri=%d\n", nbd_supports_uri (nbd));
   nbd_close (nbd);
 }
@@ -427,6 +491,17 @@ nbdplug_open_handle (int readonly)
     goto err;
   if (nbd_add_meta_context (h->nbd, "base:allocation") == -1)
     goto err;
+  if (nbd_set_tls (h->nbd, tls) == -1)
+    goto err;
+  if (tls_certificates &&
+      nbd_set_tls_certificates (h->nbd, tls_certificates) == -1)
+    goto err;
+  if (tls_verify >= 0 && nbd_set_tls_verify_peer (h->nbd, tls_verify) == -1)
+    goto err;
+  if (tls_username && nbd_set_tls_username (h->nbd, tls_username) == -1)
+    goto err;
+  if (tls_psk && nbd_set_tls_psk_file (h->nbd, tls_psk) == -1)
+    goto err;
   if (uri)
     r = nbd_connect_uri (h->nbd, uri);
   else if (sockname)
diff --git a/TODO b/TODO
index b9ddb1ee..332400b3 100644
--- a/TODO
+++ b/TODO
@@ -90,13 +90,6 @@ qemu-nbd for these use cases.
   https://lists.gnu.org/archive/html/qemu-devel/2017-11/msg02971.html
   is a partial solution but it needs cleaning up.

-nbdkit-nbd-plugin could use enhancements:
-
-* Enable client-side TLS (right now, the nbd plugin allows us to
-  support an encrypted client connecting to a plain server; but we
-  would need TLS to support a plain client connecting to an encrypted
-  server).
-
 nbdkit-floppy-plugin:

 * Add boot sector support.  In theory this is easy (eg. using
@@ -159,11 +152,7 @@ Filters allow certain types of composition, but others would not be
 possible, for example RAIDing over multiple nbd sources.  Because the
 plugin API limits us to loading a single plugin to the server, the
 best way to do this (and the most robust) is to compose multiple
-nbdkit processes.
-
-The nbd plugin (plugins/nbd) already contains an NBD client, so we
-could factor this client out and make it available to other plugins to
-use.
+nbdkit processes.  Perhaps libnbd will prove useful for this purpose.

 Build-related
 -------------
diff --git a/tests/Makefile.am b/tests/Makefile.am
index aaf74502..26ca2a12 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -81,6 +81,8 @@ EXTRA_DIST = \
 	test-memory-largest.sh \
 	test-memory-largest-for-qemu.sh \
 	test-nbd-extents.sh \
+	test-nbd-tls.sh \
+	test-nbd-tls-psk.sh \
 	test-nozero.sh \
 	test-null-extents.sh \
 	test_ocaml_plugin.ml \
@@ -534,7 +536,9 @@ TESTS += \
 # nbd plugin test.
 LIBGUESTFS_TESTS += test-nbd
 TESTS += \
-	test-nbd-extents.sh
+	test-nbd-extents.sh \
+	test-nbd-tls.sh \
+	test-nbd-tls-psk.sh

 test_nbd_SOURCES = test-nbd.c test.h
 test_nbd_CFLAGS = $(WARNINGS_CFLAGS) $(LIBGUESTFS_CFLAGS)
diff --git a/tests/test-nbd-tls-psk.sh b/tests/test-nbd-tls-psk.sh
new file mode 100755
index 00000000..d0bbc468
--- /dev/null
+++ b/tests/test-nbd-tls-psk.sh
@@ -0,0 +1,81 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2019 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+source ./functions.sh
+set -e
+set -x
+
+requires qemu-img --version
+
+# Does the nbdkit binary support TLS?
+if ! nbdkit --dump-config | grep -sq tls=yes; then
+    echo "$0: nbdkit built without TLS support"
+    exit 77
+fi
+
+# Does the nbd plugin support TLS?
+if ! nbdkit --dump-plugin nbd | grep -sq libnbd_tls=1; then
+    echo "$0: nbd plugin built without TLS support"
+    exit 77
+fi
+
+# Did we create the PSK keys file?
+# Probably 'certtool' is missing.
+if [ ! -s keys.psk ]; then
+    echo "$0: PSK keys file was not created by the test harness"
+    exit 77
+fi
+
+sock1=`mktemp -u`
+sock2=`mktemp -u`
+pid1="test-nbd-tls-psk.pid1"
+pid2="test-nbd-tls-psk.pid2"
+
+files="$sock1 $sock2 $pid1 $pid2 nbd-tls-psk.out"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Run encrypted server
+start_nbdkit -P "$pid1" -U "$sock1" \
+    --tls=require --tls-psk=keys.psk example1
+
+# Run nbd plugin as intermediary
+LIBNBD_DEBUG=1 start_nbdkit -P "$pid2" -U "$sock2" --tls=off \
+    nbd tls=require tls-psk=keys.psk tls-username=qemu socket="$sock1"
+
+# Run unencrypted client
+LANG=C qemu-img info -f raw "nbd+unix:///?socket=$sock2" > nbd-tls-psk.out
+
+cat nbd-tls-psk.out
+
+grep -sq "^file format: raw" nbd-tls-psk.out
+grep -sq "^virtual size: 100M" nbd-tls-psk.out
diff --git a/tests/test-nbd-tls.sh b/tests/test-nbd-tls.sh
new file mode 100755
index 00000000..af824d23
--- /dev/null
+++ b/tests/test-nbd-tls.sh
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2019 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+source ./functions.sh
+set -e
+set -x
+
+requires qemu-img --version
+
+# Does the nbdkit binary support TLS?
+if ! nbdkit --dump-config | grep -sq tls=yes; then
+    echo "$0: nbdkit built without TLS support"
+    exit 77
+fi
+
+# Does the nbd plugin support TLS?
+if ! nbdkit --dump-plugin nbd | grep -sq libnbd_tls=1; then
+    echo "$0: nbd plugin built without TLS support"
+    exit 77
+fi
+
+# Did we create the PKI files?
+# Probably 'certtool' is missing.
+pkidir="$PWD/pki"
+if [ ! -f "$pkidir/ca-cert.pem" ]; then
+    echo "$0: PKI files were not created by the test harness"
+    exit 77
+fi
+
+sock1=`mktemp -u`
+sock2=`mktemp -u`
+pid1="test-nbd-tls.pid1"
+pid2="test-nbd-tls.pid2"
+
+files="$sock1 $sock2 $pid1 $pid2 nbd-tls.out"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Run encrypted server
+start_nbdkit -P "$pid1" -U "$sock1" \
+    --tls=require --tls-certificates="$pkidir" example1
+
+# Run nbd plugin as intermediary
+LIBNBD_DEBUG=1 start_nbdkit -P "$pid2" -U "$sock2" --tls=off \
+    nbd tls=require tls-certificates="$pkidir" socket="$sock1"
+
+# Run unencrypted client
+LANG=C qemu-img info -f raw "nbd+unix:///?socket=$sock2" > nbd-tls.out
+
+cat nbd-tls.out
+
+grep -sq "^file format: raw" nbd-tls.out
+grep -sq "^virtual size: 100M" nbd-tls.out
diff --git a/tests/test-tls-psk.sh b/tests/test-tls-psk.sh
index c5f4f974..393f5893 100755
--- a/tests/test-tls-psk.sh
+++ b/tests/test-tls-psk.sh
@@ -63,7 +63,7 @@ if [ ! -s keys.psk ]; then
 fi

 # Unfortunately qemu cannot do TLS over a Unix domain socket (nbdkit
-# probably can, although it is not tested).  Find an unused port to
+# can, but that is tested in tests-nbd-tls-psk.sh).  Find an unused port to
 # listen on.
 pick_unused_port

diff --git a/tests/test-tls.sh b/tests/test-tls.sh
index 4c3d010e..70d40aea 100755
--- a/tests/test-tls.sh
+++ b/tests/test-tls.sh
@@ -55,8 +55,8 @@ if [ ! -f "$pkidir/ca-cert.pem" ]; then
     exit 77
 fi

-# Unfortunately qemu cannot do TLS over a Unix domain socket (nbdkit
-# probably can, although it is not tested).  Find an unused port to
+# Unfortunately qemu 4.0 cannot do TLS over a Unix domain socket (nbdkit
+# can, but that is tested in tests-nbd-tls.sh).  Find an unused port to
 # listen on.
 pick_unused_port

-- 
2.20.1




More information about the Libguestfs mailing list