Now that we can differentiate content based on export name, we also
need to be able to differentiate content for a --tls=on server, since
TLS is optional according to whether the client has authenticated.
For internal code and filters, this means adding a new parameter; the
sh plugin can do likewise. For plugins, we can't add a parameter
until the V3 protocol, so in the meantime, we add nbdkit_is_tls(),
even though it will be deprecated alongside nbdkit_export_name() when
we do get to V3.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
docs/nbdkit-filter.pod | 12 ++++++++----
docs/nbdkit-plugin.pod | 32 +++++++++++++++++++++++++++----
docs/nbdkit-tls.pod | 5 ++++-
filters/log/nbdkit-log-filter.pod | 2 +-
plugins/sh/nbdkit-sh-plugin.pod | 5 +++--
include/nbdkit-filter.h | 2 +-
include/nbdkit-plugin.h | 1 +
server/internal.h | 3 ++-
server/backend.c | 6 +++---
server/filters.c | 6 ++++--
server/nbdkit.syms | 1 +
server/plugins.c | 14 +++++++++-----
server/public.c | 16 ++++++++++++++++
plugins/sh/methods.c | 1 +
filters/cow/cow.c | 2 +-
filters/exitlast/exitlast.c | 2 +-
filters/ext2/ext2.c | 2 +-
filters/gzip/gzip.c | 2 +-
filters/limit/limit.c | 2 +-
filters/log/log.c | 16 +++++++++-------
filters/partition/partition.c | 2 +-
filters/rate/rate.c | 2 +-
filters/retry/retry.c | 2 +-
filters/tar/tar.c | 2 +-
filters/truncate/truncate.c | 2 +-
filters/xz/xz.c | 2 +-
tests/test-layers-filter.c | 2 +-
TODO | 13 ++++++-------
28 files changed, 109 insertions(+), 50 deletions(-)
diff --git a/docs/nbdkit-filter.pod b/docs/nbdkit-filter.pod
index 12343dbf..b6ed5504 100644
--- a/docs/nbdkit-filter.pod
+++ b/docs/nbdkit-filter.pod
@@ -403,7 +403,7 @@ Returns a copy of the C<i>'th export.
=head2 C<.open>
void * (*open) (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname);
+ int readonly, const char *exportname, int is_tls);
This is called when a new client connection is opened and can be used
to allocate any per-connection data structures needed by the filter.
@@ -420,8 +420,9 @@ error message and return C<NULL>.
This callback is optional, but if provided, it must call C<next>,
passing C<readonly> and C<exportname> possibly modified according to
-how the filter plans to use the plugin. Typically, the filter passes
-the same values as it received, or passes readonly=true to provide a
+how the filter plans to use the plugin (C<is_tls> is not passed,
+because a filter cannot modify it). Typically, the filter passes the
+same values as it received, or passes readonly=true to provide a
writable layer on top of a read-only backend. However, it is also
acceptable to attempt write access to the plugin even if this filter
is readonly, such as when a file system mounted read-only still
@@ -430,7 +431,10 @@ to be replayed for consistency as part of the mounting process.
The C<exportname> string is only guaranteed to be available during the
call. If the filter needs to use it (other than immediately passing
-it down to the next layer) it must take a copy.
+it down to the next layer) it must take a copy. The C<exportname> and
+C<is_tls> parameters are provided so that filters do not need to use
+the plugin-only interfaces of C<nbdkit_export_name> and
+C<nbdkit_is_tls>.
The filter should generally call C<next> as its first step, to
allocate from the plugin outwards, so that C<.close> running from the
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index 9341f282..6237b749 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -728,10 +728,18 @@ connection to be readonly (even if this flag is false) by returning
false from the C<.can_write> callback. So if your plugin can only
serve read-only, you can ignore this parameter.
-This callback is called after the NBD handshake has completed, which
-includes TLS authentication (if required). If the plugin defines a
-C<.preconnect> callback, then it must be called and return with
-success before C<.open> is called.
+If the plugin wants to differentiate the content it served based on
+client input, then this is the spot to use C<nbdkit_export_name()> to
+determine which export the client requested. See also L</EXPORT NAME>
+below.
+
+This callback is called after the NBD handshake has completed; if the
+server requires TLS authentication, then that has occurred as well.
+But if the server is set up to have optional TLS authentication, you
+may check C<nbdkit_is_tls> to learn whether the client has completed
+TLS authentication. When running the server in a mode that permits
+but not requires TLS, be careful that you do not allow unauthenticated
+clients to cause a denial of service against authentication.
If there is an error, C<.open> should call C<nbdkit_error> with an
error message and return C<NULL>.
@@ -1466,6 +1474,22 @@ client data, be cautious when parsing it.>
On error, C<nbdkit_error> is called and the call returns C<NULL>.
+=head1 AUTHENTICATION
+
+A server may use C<nbdkit_is_tls> to limit which export names work
+until after a client has completed TLS authentication. See
+L<nbdkit-tls(1)>.
+
+=head2 C<nbdkit_is_tls>
+
+ int nbdkit_is_tls (void);
+
+Return true if the client has completed TLS authentication, or false
+if the connection is still plaintext.
+
+On error (such as calling this function outside of the context of
+C<.open>), C<nbdkit_error> is called and the call returns C<-1>.
+
=head1 PEER NAME
It is possible to get the address of the client when you are running
diff --git a/docs/nbdkit-tls.pod b/docs/nbdkit-tls.pod
index 628512a9..450e0850 100644
--- a/docs/nbdkit-tls.pod
+++ b/docs/nbdkit-tls.pod
@@ -22,7 +22,10 @@ connect, it will be rejected by the server (in other words, as if the
server doesn't support TLS).
I<--tls=on> means that the client may choose to connect either with or
-without TLS.
+without TLS. In this mode, a plugin may wish to serve different
+content depending on whether the client has authenticated; the
+function C<nbdkit_is_tls()> can be used during the C<.open> callback
+for that purpose.
Because I<--tls=on> is subject to downgrade attacks where a malicious
proxy pretends not to support TLS in order to force either the client
diff --git a/filters/log/nbdkit-log-filter.pod b/filters/log/nbdkit-log-filter.pod
index 5f690457..721fc118 100644
--- a/filters/log/nbdkit-log-filter.pod
+++ b/filters/log/nbdkit-log-filter.pod
@@ -74,7 +74,7 @@ before performing a single successful read is:
2020-08-06 02:07:23.080415 ListExports id=1 readonly=0 default_only=0 ...
2020-08-06 02:07:23.080502 ...ListExports id=1 exports=[""] return=0
- 2020-08-06 02:07:23.080712 connection=1 Connect export='' size=0x400 write=1
flush=1 rotational=0 trim=1 zero=2 fua=2 extents=1 cache=2 fast_zero=1
+ 2020-08-06 02:07:23.080712 connection=1 Connect export='' tls=0 size=0x400
write=1 flush=1 rotational=0 trim=1 zero=2 fua=2 extents=1 cache=2 fast_zero=1
2020-08-06 02:07:23.080907 connection=1 Read id=1 offset=0x0 count=0x200 ...
2020-08-06 02:07:23.080927 connection=1 ...Read id=1 return=0 (Success)
2020-08-06 02:07:23.081255 connection=1 Disconnect transactions=1
diff --git a/plugins/sh/nbdkit-sh-plugin.pod b/plugins/sh/nbdkit-sh-plugin.pod
index 678116f2..07d90b57 100644
--- a/plugins/sh/nbdkit-sh-plugin.pod
+++ b/plugins/sh/nbdkit-sh-plugin.pod
@@ -320,11 +320,12 @@ as a name and no description.
=item C<open>
- /path/to/script open <readonly> <exportname>
+ /path/to/script open <readonly> <exportname> <tls>
The C<readonly> parameter will be C<true> or C<false>. The
C<exportname> parameter, if present, is the export name passed to the
-server from the client.
+server from the client. The C<tls> parameter, if present, will be
+C<true> or C<false> depending on whether the client is using TLS.
On success this should print the handle (any string) on stdout and
exit with code C<0>. If the handle ends with a newline character then
diff --git a/include/nbdkit-filter.h b/include/nbdkit-filter.h
index 08c6abc4..708a1b54 100644
--- a/include/nbdkit-filter.h
+++ b/include/nbdkit-filter.h
@@ -175,7 +175,7 @@ struct nbdkit_filter {
struct nbdkit_exports *exports);
void * (*open) (nbdkit_next_open *next, nbdkit_backend *nxdata,
- int readonly, const char *exportname);
+ int readonly, const char *exportname, int is_tls);
void (*close) (void *handle);
int (*prepare) (struct nbdkit_next_ops *next_ops, nbdkit_backend *nxdata,
diff --git a/include/nbdkit-plugin.h b/include/nbdkit-plugin.h
index 86b8565d..e20391b8 100644
--- a/include/nbdkit-plugin.h
+++ b/include/nbdkit-plugin.h
@@ -146,6 +146,7 @@ struct nbdkit_plugin {
extern void nbdkit_set_error (int err);
extern const char *nbdkit_export_name (void);
+extern int nbdkit_is_tls (void);
#define NBDKIT_REGISTER_PLUGIN(plugin) \
NBDKIT_CXX_LANG_C \
diff --git a/server/internal.h b/server/internal.h
index f84696ca..d043225a 100644
--- a/server/internal.h
+++ b/server/internal.h
@@ -367,7 +367,8 @@ struct backend {
int (*preconnect) (struct backend *, int readonly);
int (*list_exports) (struct backend *, int readonly, int default_only,
struct nbdkit_exports *exports);
- void *(*open) (struct backend *, int readonly, const char *exportname);
+ void *(*open) (struct backend *, int readonly, const char *exportname,
+ int is_tls);
int (*prepare) (struct backend *, void *handle, int readonly);
int (*finalize) (struct backend *, void *handle);
void (*close) (struct backend *, void *handle);
diff --git a/server/backend.c b/server/backend.c
index 75ca53be..8f4fed9d 100644
--- a/server/backend.c
+++ b/server/backend.c
@@ -186,8 +186,8 @@ backend_open (struct backend *b, int readonly, const char
*exportname)
GET_CONN;
struct handle *h = get_handle (conn, b->i);
- controlpath_debug ("%s: open readonly=%d exportname=\"%s\"",
- b->name, readonly, exportname);
+ controlpath_debug ("%s: open readonly=%d exportname=\"%s\"
tls=%d",
+ b->name, readonly, exportname, conn->using_tls);
assert (h->handle == NULL);
assert ((h->state & HANDLE_OPEN) == 0);
@@ -212,7 +212,7 @@ backend_open (struct backend *b, int readonly, const char
*exportname)
/* Most filters will call next_open first, resulting in
* inner-to-outer ordering.
*/
- h->handle = b->open (b, readonly, exportname);
+ h->handle = b->open (b, readonly, exportname, conn->using_tls);
controlpath_debug ("%s: open returned handle %p", b->name, h->handle);
if (h->handle == NULL) {
diff --git a/server/filters.c b/server/filters.c
index 20518354..90a9a948 100644
--- a/server/filters.c
+++ b/server/filters.c
@@ -250,7 +250,8 @@ filter_list_exports (struct backend *b, int readonly, int
default_only,
}
static void *
-filter_open (struct backend *b, int readonly, const char *exportname)
+filter_open (struct backend *b, int readonly, const char *exportname,
+ int is_tls)
{
struct backend_filter *f = container_of (b, struct backend_filter, backend);
void *handle;
@@ -259,7 +260,8 @@ filter_open (struct backend *b, int readonly, const char *exportname)
* inner-to-outer ordering.
*/
if (f->filter.open)
- handle = f->filter.open (backend_open, b->next, readonly, exportname);
+ handle = f->filter.open (backend_open, b->next, readonly, exportname,
+ is_tls);
else if (backend_open (b->next, readonly, exportname) == -1)
handle = NULL;
else
diff --git a/server/nbdkit.syms b/server/nbdkit.syms
index 6cc6ed32..a67669b7 100644
--- a/server/nbdkit.syms
+++ b/server/nbdkit.syms
@@ -53,6 +53,7 @@
nbdkit_extents_free;
nbdkit_extents_new;
nbdkit_get_extent;
+ nbdkit_is_tls;
nbdkit_nanosleep;
nbdkit_parse_bool;
nbdkit_parse_int8_t;
diff --git a/server/plugins.c b/server/plugins.c
index d4364cd2..bc57623a 100644
--- a/server/plugins.c
+++ b/server/plugins.c
@@ -292,7 +292,8 @@ plugin_list_exports (struct backend *b, int readonly, int
default_only,
}
static void *
-plugin_open (struct backend *b, int readonly, const char *exportname)
+plugin_open (struct backend *b, int readonly, const char *exportname,
+ int is_tls)
{
GET_CONN;
struct backend_plugin *p = container_of (b, struct backend_plugin, backend);
@@ -302,11 +303,14 @@ plugin_open (struct backend *b, int readonly, const char
*exportname)
/* Save the exportname since the lifetime of the string passed in
* here is likely to be brief. In addition this provides a place
* for nbdkit_export_name to retrieve it if called from the plugin.
+ * While readonly and the export name can be altered in plugins, the
+ * tls mode cannot.
*
- * In API V3 we propose to pass the exportname as an extra parameter
- * to the (new) plugin.open and deprecate nbdkit_export_name for V3
- * users. Even then we will still need to save it in the handle
- * because of the lifetime issue.
+ * In API V3 we propose to pass the exportname and tls mode as extra
+ * parameters to the (new) plugin.open and deprecate
+ * nbdkit_export_name and nbdkit_is_tls for V3 users. Even then we
+ * will still need to save the export name in the handle because of
+ * the lifetime issue.
*/
if (conn->exportname == NULL) {
conn->exportname = strdup (exportname);
diff --git a/server/public.c b/server/public.c
index c00a5cb6..f682d732 100644
--- a/server/public.c
+++ b/server/public.c
@@ -670,6 +670,22 @@ nbdkit_export_name (void)
return conn->exportname;
}
+/* This function will be deprecated for API V3 users. The preferred
+ * approach will be to get the tls mode from .open().
+ */
+int
+nbdkit_is_tls (void)
+{
+ struct connection *conn = threadlocal_get_conn ();
+
+ if (!conn) {
+ nbdkit_error ("no connection in this thread");
+ return -1;
+ }
+
+ return conn->using_tls;
+}
+
int
nbdkit_peer_name (struct sockaddr *addr, socklen_t *addrlen)
{
diff --git a/plugins/sh/methods.c b/plugins/sh/methods.c
index 9f247524..c59198ca 100644
--- a/plugins/sh/methods.c
+++ b/plugins/sh/methods.c
@@ -341,6 +341,7 @@ sh_open (int readonly)
{ script, method,
readonly ? "true" : "false",
nbdkit_export_name () ? : "",
+ nbdkit_is_tls () ? "true" : "false",
NULL };
struct sh_handle *h = malloc (sizeof *h);
diff --git a/filters/cow/cow.c b/filters/cow/cow.c
index 0faf2726..51ca64a4 100644
--- a/filters/cow/cow.c
+++ b/filters/cow/cow.c
@@ -94,7 +94,7 @@ cow_config (nbdkit_next_config *next, void *nxdata,
static void *
cow_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
/* Always pass readonly=1 to the underlying plugin. */
if (next (nxdata, 1, exportname) == -1)
diff --git a/filters/exitlast/exitlast.c b/filters/exitlast/exitlast.c
index 378cda75..4c879cc9 100644
--- a/filters/exitlast/exitlast.c
+++ b/filters/exitlast/exitlast.c
@@ -51,7 +51,7 @@ static _Atomic unsigned connections;
static void *
exitlast_open (nbdkit_next_open *next, nbdkit_backend *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
if (next (nxdata, readonly, exportname) == -1)
return NULL;
diff --git a/filters/ext2/ext2.c b/filters/ext2/ext2.c
index 72b7ac9f..75ac2c4c 100644
--- a/filters/ext2/ext2.c
+++ b/filters/ext2/ext2.c
@@ -122,7 +122,7 @@ struct handle {
/* Create the per-connection handle. */
static void *
ext2_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct handle *h;
diff --git a/filters/gzip/gzip.c b/filters/gzip/gzip.c
index 8323b882..d92e00d9 100644
--- a/filters/gzip/gzip.c
+++ b/filters/gzip/gzip.c
@@ -75,7 +75,7 @@ gzip_thread_model (void)
static void *
gzip_open (nbdkit_next_open *next, nbdkit_backend *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
/* Always pass readonly=1 to the underlying plugin. */
if (next (nxdata, 1, exportname) == -1)
diff --git a/filters/limit/limit.c b/filters/limit/limit.c
index 7c4477eb..fb862df7 100644
--- a/filters/limit/limit.c
+++ b/filters/limit/limit.c
@@ -91,7 +91,7 @@ limit_preconnect (nbdkit_next_preconnect *next, nbdkit_backend *nxdata,
static void *
limit_open (nbdkit_next_open *next, nbdkit_backend *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
if (next (nxdata, readonly, exportname) == -1)
return NULL;
diff --git a/filters/log/log.c b/filters/log/log.c
index c89f5001..f8da9ad8 100644
--- a/filters/log/log.c
+++ b/filters/log/log.c
@@ -135,6 +135,7 @@ struct handle {
uint64_t connection;
uint64_t id;
char *exportname;
+ int tls;
};
/* Compute the next id number on the current connection. */
@@ -283,7 +284,7 @@ log_list_exports (nbdkit_next_list_exports *next, void *nxdata,
/* Open a connection. */
static void *
log_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct handle *h;
@@ -296,9 +297,9 @@ log_open (nbdkit_next_open *next, void *nxdata,
return NULL;
}
- /* Save the exportname in the handle so we can display it in
- * log_prepare. We must take a copy because this string has a short
- * lifetime.
+ /* Save the exportname and tls state in the handle so we can display
+ * it in log_prepare. We must take a copy because this string has a
+ * short lifetime.
*/
h->exportname = strdup (exportname);
if (h->exportname == NULL) {
@@ -306,6 +307,7 @@ log_open (nbdkit_next_open *next, void *nxdata,
free (h);
return NULL;
}
+ h->tls = is_tls;
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
h->connection = ++connections;
@@ -343,9 +345,9 @@ log_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, void
*handle,
e < 0 || c < 0 || Z < 0)
return -1;
- output (h, "Connect", 0, "export='%s' size=0x%" PRIx64
" write=%d flush=%d "
- "rotational=%d trim=%d zero=%d fua=%d extents=%d cache=%d "
- "fast_zero=%d", exportname, size, w, f, r, t, z, F, e, c, Z);
+ output (h, "Connect", 0, "export='%s' tls=%d size=0x%"
PRIx64 " write=%d "
+ "flush=%d rotational=%d trim=%d zero=%d fua=%d extents=%d cache=%d "
+ "fast_zero=%d", exportname, h->tls, size, w, f, r, t, z, F, e, c,
Z);
return 0;
}
diff --git a/filters/partition/partition.c b/filters/partition/partition.c
index c348664b..849f0cc7 100644
--- a/filters/partition/partition.c
+++ b/filters/partition/partition.c
@@ -88,7 +88,7 @@ struct handle {
/* Open a connection. */
static void *
partition_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct handle *h;
diff --git a/filters/rate/rate.c b/filters/rate/rate.c
index 6e99fcc7..32c47fdf 100644
--- a/filters/rate/rate.c
+++ b/filters/rate/rate.c
@@ -163,7 +163,7 @@ rate_get_ready (nbdkit_next_get_ready *next, void *nxdata)
/* Create the per-connection handle. */
static void *
rate_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct rate_handle *h;
diff --git a/filters/retry/retry.c b/filters/retry/retry.c
index be334c39..a2e57d77 100644
--- a/filters/retry/retry.c
+++ b/filters/retry/retry.c
@@ -113,7 +113,7 @@ struct retry_handle {
static void *
retry_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct retry_handle *h;
diff --git a/filters/tar/tar.c b/filters/tar/tar.c
index c04f09dd..ab041153 100644
--- a/filters/tar/tar.c
+++ b/filters/tar/tar.c
@@ -113,7 +113,7 @@ struct handle {
static void *
tar_open (nbdkit_next_open *next, nbdkit_backend *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct handle *h;
diff --git a/filters/truncate/truncate.c b/filters/truncate/truncate.c
index ee384871..00b5d8ce 100644
--- a/filters/truncate/truncate.c
+++ b/filters/truncate/truncate.c
@@ -125,7 +125,7 @@ struct handle {
/* Open a connection. */
static void *
truncate_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct handle *h;
diff --git a/filters/xz/xz.c b/filters/xz/xz.c
index 9154dbeb..26cfa959 100644
--- a/filters/xz/xz.c
+++ b/filters/xz/xz.c
@@ -90,7 +90,7 @@ struct xz_handle {
/* Create the per-connection handle. */
static void *
xz_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct xz_handle *h;
diff --git a/tests/test-layers-filter.c b/tests/test-layers-filter.c
index 397af575..5c5b3f0f 100644
--- a/tests/test-layers-filter.c
+++ b/tests/test-layers-filter.c
@@ -117,7 +117,7 @@ test_layers_filter_list_exports (nbdkit_next_list_exports *next, void
*nxdata,
static void *
test_layers_filter_open (nbdkit_next_open *next, void *nxdata,
- int readonly, const char *exportname)
+ int readonly, const char *exportname, int is_tls)
{
struct handle *h = malloc (sizeof *h);
diff --git a/TODO b/TODO
index 46f92443..262327fb 100644
--- a/TODO
+++ b/TODO
@@ -62,11 +62,11 @@ General ideas for improvements
continue to keep their non-standard handshake while utilizing nbdkit
to prototype new behaviors in serving the kernel.
-* When using --tls=on (encryption is supported but not required),
- plugins need to be able to differentiate which content they serve to
- plaintext clients vs. secure clients. This may require an
- alteration of the signature for .list_exports and .open, or a new
- utility function nbdkit_tls_mode() similar to nbdkit_export_name().
+* The current signature of .list_exports is awkward; it overloads the
+ list response to NBD_OPT_LIST with the resolution of the default
+ export name in .open, and it is missing a tls parameter. It is
+ probably worth splitting out a new .default_export callback for the
+ latter purpose, as well as fixing the signature for the former.
* Background thread for filters. Some filters (readahead, cache and
proposed scan filter - see below) could be more effective if they
@@ -308,7 +308,6 @@ using ‘#define NBDKIT_API_VERSION <version>’.
and filters, and the type of the values, and both check and parse
them for the plugin.
-* Modify open() API so it takes an export name parameter, and maybe a
- tls mode.
+* Modify open() API so it takes an export name and tls parameter.
* Change config_help from a variable to a function.
--
2.28.0