Browse Source

qemu-nbd: add support for authorization of TLS clients

Currently any client which can complete the TLS handshake is able to use
the NBD server. The server admin can turn on the 'verify-peer' option
for the x509 creds to require the client to provide a x509 certificate.
This means the client will have to acquire a certificate from the CA
before they are permitted to use the NBD server. This is still a fairly
low bar to cross.

This adds a '--tls-authz OBJECT-ID' option to the qemu-nbd command which
takes the ID of a previously added 'QAuthZ' object instance. This will
be used to validate the client's x509 distinguished name. Clients
failing the authorization check will not be permitted to use the NBD
server.

For example to setup authorization that only allows connection from a client
whose x509 certificate distinguished name is

   CN=laptop.example.com,O=Example Org,L=London,ST=London,C=GB

escape the commas in the name and use:

  qemu-nbd --object tls-creds-x509,id=tls0,dir=/home/berrange/qemutls,\
                    endpoint=server,verify-peer=yes \
           --object 'authz-simple,id=auth0,identity=CN=laptop.example.com,,\
                     O=Example Org,,L=London,,ST=London,,C=GB' \
           --tls-creds tls0 \
           --tls-authz authz0 \
	   ....other qemu-nbd args...

NB: a real shell command line would not have leading whitespace after
the line continuation, it is just included here for clarity.

Reviewed-by: Juan Quintela <quintela@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
Message-Id: <2019022716.18543-2-berrange@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: split long line in --help text, tweak 233 to show that whitespace
after ,, in identity= portion is actually okay]
Signed-off-by: Eric Blake <eblake@redhat.com>
tags/v4.0.0-rc0
Daniel P. Berrange 3 months ago
parent
commit
b25e12daff
6 changed files with 73 additions and 12 deletions
  1. 1
    1
      include/block/nbd.h
  2. 5
    5
      nbd/server.c
  3. 18
    1
      qemu-nbd.c
  4. 9
    2
      qemu-nbd.texi
  5. 29
    3
      tests/qemu-iotests/233
  6. 11
    0
      tests/qemu-iotests/233.out

+ 1
- 1
include/block/nbd.h View File

@@ -326,7 +326,7 @@ void nbd_export_close_all(void);
326 326
 
327 327
 void nbd_client_new(QIOChannelSocket *sioc,
328 328
                     QCryptoTLSCreds *tlscreds,
329
-                    const char *tlsaclname,
329
+                    const char *tlsauthz,
330 330
                     void (*close_fn)(NBDClient *, bool));
331 331
 void nbd_client_get(NBDClient *client);
332 332
 void nbd_client_put(NBDClient *client);

+ 5
- 5
nbd/server.c View File

@@ -111,7 +111,7 @@ struct NBDClient {
111 111
 
112 112
     NBDExport *exp;
113 113
     QCryptoTLSCreds *tlscreds;
114
-    char *tlsaclname;
114
+    char *tlsauthz;
115 115
     QIOChannelSocket *sioc; /* The underlying data channel */
116 116
     QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */
117 117
 
@@ -686,7 +686,7 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
686 686
 
687 687
     tioc = qio_channel_tls_new_server(ioc,
688 688
                                       client->tlscreds,
689
-                                      client->tlsaclname,
689
+                                      client->tlsauthz,
690 690
                                       errp);
691 691
     if (!tioc) {
692 692
         return NULL;
@@ -1348,7 +1348,7 @@ void nbd_client_put(NBDClient *client)
1348 1348
         if (client->tlscreds) {
1349 1349
             object_unref(OBJECT(client->tlscreds));
1350 1350
         }
1351
-        g_free(client->tlsaclname);
1351
+        g_free(client->tlsauthz);
1352 1352
         if (client->exp) {
1353 1353
             QTAILQ_REMOVE(&client->exp->clients, client, next);
1354 1354
             nbd_export_put(client->exp);
@@ -2425,7 +2425,7 @@ static coroutine_fn void nbd_co_client_start(void *opaque)
2425 2425
  */
2426 2426
 void nbd_client_new(QIOChannelSocket *sioc,
2427 2427
                     QCryptoTLSCreds *tlscreds,
2428
-                    const char *tlsaclname,
2428
+                    const char *tlsauthz,
2429 2429
                     void (*close_fn)(NBDClient *, bool))
2430 2430
 {
2431 2431
     NBDClient *client;
@@ -2437,7 +2437,7 @@ void nbd_client_new(QIOChannelSocket *sioc,
2437 2437
     if (tlscreds) {
2438 2438
         object_ref(OBJECT(client->tlscreds));
2439 2439
     }
2440
-    client->tlsaclname = g_strdup(tlsaclname);
2440
+    client->tlsauthz = g_strdup(tlsauthz);
2441 2441
     client->sioc = sioc;
2442 2442
     object_ref(OBJECT(client->sioc));
2443 2443
     client->ioc = QIO_CHANNEL(sioc);

+ 18
- 1
qemu-nbd.c View File

@@ -58,6 +58,7 @@
58 58
 #define QEMU_NBD_OPT_TLSCREDS      261
59 59
 #define QEMU_NBD_OPT_IMAGE_OPTS    262
60 60
 #define QEMU_NBD_OPT_FORK          263
61
+#define QEMU_NBD_OPT_TLSAUTHZ      264
61 62
 
62 63
 #define MBR_SIZE 512
63 64
 
@@ -71,6 +72,7 @@ static int shared = 1;
71 72
 static int nb_fds;
72 73
 static QIONetListener *server;
73 74
 static QCryptoTLSCreds *tlscreds;
75
+static const char *tlsauthz;
74 76
 
75 77
 static void usage(const char *name)
76 78
 {
@@ -103,6 +105,8 @@ static void usage(const char *name)
103 105
 "  --object type,id=ID,...   define an object such as 'secret' for providing\n"
104 106
 "                            passwords and/or encryption keys\n"
105 107
 "  --tls-creds=ID            use id of an earlier --object to provide TLS\n"
108
+"  --tls-authz=ID            use id of an earlier --object to provide\n"
109
+"                            authorization\n"
106 110
 "  -T, --trace [[enable=]<pattern>][,events=<file>][,file=<file>]\n"
107 111
 "                            specify tracing options\n"
108 112
 "  --fork                    fork off the server process and exit the parent\n"
@@ -452,7 +456,7 @@ static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
452 456
 
453 457
     nb_fds++;
454 458
     nbd_update_server_watch();
455
-    nbd_client_new(cioc, tlscreds, NULL, nbd_client_closed);
459
+    nbd_client_new(cioc, tlscreds, tlsauthz, nbd_client_closed);
456 460
 }
457 461
 
458 462
 static void nbd_update_server_watch(void)
@@ -643,6 +647,7 @@ int main(int argc, char **argv)
643 647
         { "export-name", required_argument, NULL, 'x' },
644 648
         { "description", required_argument, NULL, 'D' },
645 649
         { "tls-creds", required_argument, NULL, QEMU_NBD_OPT_TLSCREDS },
650
+        { "tls-authz", required_argument, NULL, QEMU_NBD_OPT_TLSAUTHZ },
646 651
         { "image-opts", no_argument, NULL, QEMU_NBD_OPT_IMAGE_OPTS },
647 652
         { "trace", required_argument, NULL, 'T' },
648 653
         { "fork", no_argument, NULL, QEMU_NBD_OPT_FORK },
@@ -862,6 +867,9 @@ int main(int argc, char **argv)
862 867
             g_free(trace_file);
863 868
             trace_file = trace_opt_parse(optarg);
864 869
             break;
870
+        case QEMU_NBD_OPT_TLSAUTHZ:
871
+            tlsauthz = optarg;
872
+            break;
865 873
         case QEMU_NBD_OPT_FORK:
866 874
             fork_process = true;
867 875
             break;
@@ -934,12 +942,21 @@ int main(int argc, char **argv)
934 942
             error_report("TLS is not supported with a host device");
935 943
             exit(EXIT_FAILURE);
936 944
         }
945
+        if (tlsauthz && list) {
946
+            error_report("TLS authorization is incompatible with export list");
947
+            exit(EXIT_FAILURE);
948
+        }
937 949
         tlscreds = nbd_get_tls_creds(tlscredsid, list, &local_err);
938 950
         if (local_err) {
939 951
             error_report("Failed to get TLS creds %s",
940 952
                          error_get_pretty(local_err));
941 953
             exit(EXIT_FAILURE);
942 954
         }
955
+    } else {
956
+        if (tlsauthz) {
957
+            error_report("--tls-authz is not permitted without --tls-creds");
958
+            exit(EXIT_FAILURE);
959
+        }
943 960
     }
944 961
 
945 962
     if (list) {

+ 9
- 2
qemu-nbd.texi View File

@@ -117,6 +117,10 @@ option; or provide the credentials needed for connecting as a client
117 117
 in list mode.
118 118
 @item --fork
119 119
 Fork off the server process and exit the parent once the server is running.
120
+@item --tls-authz=ID
121
+Specify the ID of a qauthz object previously created with the
122
+--object option. This will be used to authorize connecting users
123
+against their x509 distinguished name.
120 124
 @item -v, --verbose
121 125
 Display extra debugging information.
122 126
 @item -h, --help
@@ -142,13 +146,16 @@ qemu-nbd -f qcow2 file.qcow2
142 146
 @end example
143 147
 
144 148
 Start a long-running server listening with encryption on port 10810,
145
-and require clients to have a correct X.509 certificate to connect to
149
+and whitelist clients with a specific X.509 certificate to connect to
146 150
 a 1 megabyte subset of a raw file, using the export name 'subset':
147 151
 
148 152
 @example
149 153
 qemu-nbd \
150 154
   --object tls-creds-x509,id=tls0,endpoint=server,dir=/path/to/qemutls \
151
-  --tls-creds tls0 -t -x subset -p 10810 \
155
+  --object 'authz-simple,id=auth0,identity=CN=laptop.example.com,,\
156
+            O=Example Org,,L=London,,ST=London,,C=GB' \
157
+  --tls-creds tls0 --tls-authz auth0 \
158
+  -t -x subset -p 10810 \
152 159
   --image-opts driver=raw,offset=1M,size=1M,file.driver=file,file.filename=file.raw
153 160
 @end example
154 161
 

+ 29
- 3
tests/qemu-iotests/233 View File

@@ -61,6 +61,7 @@ tls_x509_create_root_ca "ca2"
61 61
 tls_x509_create_server "ca1" "server1"
62 62
 tls_x509_create_client "ca1" "client1"
63 63
 tls_x509_create_client "ca2" "client2"
64
+tls_x509_create_client "ca1" "client3"
64 65
 
65 66
 echo
66 67
 echo "== preparing image =="
@@ -93,11 +94,15 @@ $QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port
93 94
 
94 95
 echo
95 96
 echo "== check TLS works =="
96
-obj=tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0
97
-$QEMU_IMG info --image-opts --object $obj \
97
+obj1=tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0
98
+obj2=tls-creds-x509,dir=${tls_dir}/client3,endpoint=client,id=tls0
99
+$QEMU_IMG info --image-opts --object $obj1 \
98 100
     driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \
99 101
     2>&1 | sed "s/$nbd_tcp_port/PORT/g"
100
-$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port --object $obj \
102
+$QEMU_IMG info --image-opts --object $obj2 \
103
+    driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \
104
+    2>&1 | sed "s/$nbd_tcp_port/PORT/g"
105
+$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port --object $obj1 \
101 106
     --tls-creds=tls0
102 107
 
103 108
 echo
@@ -119,6 +124,27 @@ $QEMU_IO -c 'r -P 0x11 1m 1m' -c 'w -P 0x22 1m 1m' --image-opts \
119 124
 
120 125
 $QEMU_IO -f $IMGFMT -r -U -c 'r -P 0x22 1m 1m' "$TEST_IMG" | _filter_qemu_io
121 126
 
127
+echo
128
+echo "== check TLS with authorization =="
129
+
130
+nbd_server_stop
131
+
132
+nbd_server_start_tcp_socket \
133
+    --object tls-creds-x509,dir=${tls_dir}/server1,endpoint=server,id=tls0,verify-peer=yes \
134
+    --object "authz-simple,id=authz0,identity=CN=localhost,, \
135
+      O=Cthulu Dark Lord Enterprises client1,,L=R'lyeh,,C=South Pacific" \
136
+    --tls-authz authz0 \
137
+    --tls-creds tls0 \
138
+    -f $IMGFMT "$TEST_IMG" 2>> "$TEST_DIR/server.log"
139
+
140
+$QEMU_IMG info --image-opts \
141
+    --object tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0 \
142
+    driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0
143
+
144
+$QEMU_IMG info --image-opts \
145
+    --object tls-creds-x509,dir=${tls_dir}/client3,endpoint=client,id=tls0 \
146
+    driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0
147
+
122 148
 echo
123 149
 echo "== final server log =="
124 150
 cat "$TEST_DIR/server.log"

+ 11
- 0
tests/qemu-iotests/233.out View File

@@ -6,6 +6,7 @@ Generating a self signed certificate...
6 6
 Generating a signed certificate...
7 7
 Generating a signed certificate...
8 8
 Generating a signed certificate...
9
+Generating a signed certificate...
9 10
 
10 11
 == preparing image ==
11 12
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
@@ -29,6 +30,10 @@ image: nbd://127.0.0.1:PORT
29 30
 file format: nbd
30 31
 virtual size: 64M (67108864 bytes)
31 32
 disk size: unavailable
33
+image: nbd://127.0.0.1:PORT
34
+file format: nbd
35
+virtual size: 64M (67108864 bytes)
36
+disk size: unavailable
32 37
 exports available: 1
33 38
  export: ''
34 39
   size:  67108864
@@ -51,7 +56,13 @@ wrote 1048576/1048576 bytes at offset 1048576
51 56
 read 1048576/1048576 bytes at offset 1048576
52 57
 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
53 58
 
59
+== check TLS with authorization ==
60
+qemu-img: Could not open 'driver=nbd,host=127.0.0.1,port=10809,tls-creds=tls0': Failed to read option reply: Cannot read from TLS channel: Software caused connection abort
61
+qemu-img: Could not open 'driver=nbd,host=127.0.0.1,port=10809,tls-creds=tls0': Failed to read option reply: Cannot read from TLS channel: Software caused connection abort
62
+
54 63
 == final server log ==
55 64
 qemu-nbd: option negotiation failed: Verify failed: No certificate was found.
56 65
 qemu-nbd: option negotiation failed: Verify failed: No certificate was found.
66
+qemu-nbd: option negotiation failed: TLS x509 authz check for CN=localhost,O=Cthulhu Dark Lord Enterprises client1,L=R'lyeh,C=South Pacific is denied
67
+qemu-nbd: option negotiation failed: TLS x509 authz check for CN=localhost,O=Cthulhu Dark Lord Enterprises client3,L=R'lyeh,C=South Pacific is denied
57 68
 *** done

Loading…
Cancel
Save