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@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