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

Eric Blake eblake at redhat.com
Thu May 30 03:13:38 UTC 2019


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

I'd love to also add a uri=... parameter, but until I fix configure.ac
to permit a newer API than libnbd 0.1 that is not possible.

Signed-off-by: Eric Blake <eblake at redhat.com>

---

RFC: Compiled, but I have not heavily tested it yet.  Ideally, I
should enhance or copy test-tls{,-psk}.sh to show nbdkit as the
encryption client forwarding on to a plaintext qemu-io Unix socket.
---
 plugins/nbd/nbdkit-nbd-plugin.pod | 77 +++++++++++++++++++++++++------
 plugins/nbd/nbd.c                 | 72 +++++++++++++++++++++++++++--
 TODO                              | 13 +-----
 3 files changed, 132 insertions(+), 30 deletions(-)

diff --git a/plugins/nbd/nbdkit-nbd-plugin.pod b/plugins/nbd/nbdkit-nbd-plugin.pod
index 7baff98..77f34d3 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] } [export=NAME]
-    [retry=N] [shared=BOOL]
+    [retry=N] [shared=BOOL] [tls=MODE] [tls-certificates=DIR] [tls-verify=BOOL]
+    [tls-username=NAME] [tls-psk=FILE]

 =head1 DESCRIPTION

@@ -20,13 +21,11 @@ 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.
-
 =head1 PARAMETERS

+The following parameters are available whether or not the plugin was
+compiled against libnbd:
+
 =over 4

 =item B<socket=>SOCKNAME
@@ -69,6 +68,46 @@ nbdkit will share that single connection.

 =back

+The following parameters are only available if the plugin was compiled
+against libnbd:
+
+=over 4
+
+=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.
+
+=item B<tls-certificates=>DIR
+
+This specifies the directory containing X.509 client certificates to
+present to the server.
+
+=item B<tls-verify=>BOOL
+
+Setting this to true 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

 Expose the contents of an export served by an old style server over a
@@ -80,9 +119,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
@@ -97,16 +136,23 @@ 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 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    └────────────┘
+
+Look for the C<libnbd_version> line to learn if the nbd plugin was
+compiled against libnbd for TLS support (required for the previous
+example):
+
+ nbdkit --dump-plugin nbd

 =head1 SEE ALSO

@@ -115,6 +161,7 @@ L<nbdkit-captive(1)>,
 L<nbdkit-filter(3)>,
 L<nbdkit-tls(1)>,
 L<nbdkit-plugin(3)>,
+L<libnbd(3)>,
 L<qemu-nbd(1)>.

 =head1 AUTHORS
diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c
index b1e978a..dbf9096 100644
--- a/plugins/nbd/nbd.c
+++ b/plugins/nbd/nbd.c
@@ -87,6 +87,9 @@ static char *sockname;
 static const char *hostname;
 static const char *port;

+/* XXX Need libnbd 0.1.1 to connect via URI */
+/* static const char *uri; */
+
 /* Human-readable server description */
 static char *servname;

@@ -100,6 +103,13 @@ static unsigned long retry;
 static bool shared;
 static struct handle *shared_handle;

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

@@ -113,9 +123,10 @@ nbdplug_unload (void)
 }

 /* Called for each key=value passed on the command line.  This plugin
- * accepts socket=<sockname> or hostname=<hostname>/port=<port>
- * (exactly one connection required), and optional parameters
- * export=<name>, retry=<n>.
+ * accepts socket=<sockname>, hostname=<hostname>/port=<port>, or
+ * uri=<uri> (exactly one connection required), and optional
+ * parameters export=<name>, retry=<n>, shared=<bool>, and various tls
+ * settings.
  */
 static int
 nbdplug_config (const char *key, const char *value)
@@ -134,6 +145,11 @@ nbdplug_config (const char *key, const char *value)
     hostname = value;
   else if (strcmp (key, "port") == 0)
     port = value;
+  else if (strcmp (key, "uri") == 0) {
+    /* XXX Implement once we build against newer libnbd */
+    nbdkit_error ("libnbd too old for uri support");
+    return -1;
+  }
   else if (strcmp (key, "export") == 0)
     export = value;
   else if (strcmp (key, "retry") == 0) {
@@ -150,6 +166,29 @@ nbdplug_config (const char *key, const char *value)
       return -1;
     shared = r;
   }
+  else if (strcmp (key, "tls") == 0) {
+    if (strcasecmp (optarg, "require") == 0 ||
+        strcasecmp (optarg, "required") == 0 ||
+        strcasecmp (optarg, "force") == 0)
+      tls = 2;
+    else {
+      tls = nbdkit_parse_bool (optarg);
+      if (tls == -1)
+        exit (EXIT_FAILURE);
+    }
+  }
+  else if (strcmp (key, "tls-certificates") == 0)
+    tls_certificates = value;
+  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)
+    tls_psk = value;
   else {
     nbdkit_error ("unknown parameter '%s'", key);
     return -1;
@@ -197,6 +236,9 @@ nbdplug_config_complete (void)
   if (!export)
     export = "";

+  if (tls == -1)
+    tls = tls_certificates || tls_verify >= 0 || tls_username || tls_psk;
+
   if (shared && (shared_handle = nbdplug_open_handle (false)) == NULL)
     return -1;
   return 0;
@@ -207,6 +249,18 @@ nbdplug_config_complete (void)
   "hostname=<HOST>        The hostname for the TCP socket to connect to.\n" \
   "port=<PORT>            TCP port or service name to use (default 10809).\n" \
   "export=<NAME>          Export name to connect to (default \"\").\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)
+{
+  /* XXX libnbd 0.1 doesn't expose a version in libnbd.h */
+  printf ("libnbd_version=%s\n", "0.1");
+}

 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL

@@ -398,6 +452,17 @@ nbdplug_open_handle (int readonly)
     goto err;
   if (nbd_request_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 (sockname)
     r = nbd_connect_unix (h->nbd, sockname);
   else
@@ -749,6 +814,7 @@ static struct nbdkit_plugin plugin = {
   .config             = nbdplug_config,
   .config_complete    = nbdplug_config_complete,
   .config_help        = nbdplug_config_help,
+  .dump_plugin        = nbdplug_dump_plugin,
   .open               = nbdplug_open,
   .close              = nbdplug_close,
   .get_size           = nbdplug_get_size,
diff --git a/TODO b/TODO
index b9ddb1e..332400b 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
 -------------
-- 
2.20.1




More information about the Libguestfs mailing list