[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH FYI 45/46] migration: support TLS encryption with TC
From: |
Daniel P. Berrange |
Subject: |
[Qemu-devel] [PATCH FYI 45/46] migration: support TLS encryption with TCP migration backend |
Date: |
Thu, 3 Sep 2015 16:39:27 +0100 |
This extends the TCP migration backend so that it can make use
of QIOChannelTLS to provide transparent TLS encryption. To
trigger enablement the URI on the incoming and outgoing sides
should have 'tls-creds=ID' appended, eg
tcp:$HOST:$PORT,tls-creds=ID
where ID is the object identifier of a set of TLS credentials
previously created using object_add / -object. There is not
currently any support for checking the migration client
certificate names against ACLs. This is pending a conversion
of the ACL code to QOM.
There is no support for dynamically negotiating use of TLS
between the incoming/outgoing side. Both sides must agree
on use of TLS out of band and set the URI accordingly. In
practice it is expected that the administrator will just
turn on use of TLS on their hosts in the libvirt config
and then libvirt will instruct QEMU to use TLS for migration.
Signed-off-by: Daniel P. Berrange <address@hidden>
---
migration/tcp.c | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
qemu-options.hx | 7 +-
2 files changed, 264 insertions(+), 16 deletions(-)
diff --git a/migration/tcp.c b/migration/tcp.c
index 2347d9d..68ecaa4 100644
--- a/migration/tcp.c
+++ b/migration/tcp.c
@@ -22,6 +22,8 @@
#include "migration/migration.h"
#include "migration/qemu-file.h"
#include "io/channel-socket.h"
+#include "io/channel-tls.h"
+#include "crypto/tlscreds.h"
//#define DEBUG_MIGRATION_TCP
@@ -33,6 +35,22 @@
do { } while (0)
#endif
+typedef struct {
+ MigrationState *s;
+ QCryptoTLSCreds *tlscreds;
+ char *hostname;
+} TCPConnectData;
+
+typedef struct {
+ MigrationState *s;
+ QCryptoTLSCreds *tlscreds;
+} TCPListenData;
+
+typedef struct {
+ MigrationState *s;
+ QIOChannel *ioc;
+} TCPConnectTLSData;
+
static SocketAddress *tcp_build_address(const char *host_port, Error **errp)
{
@@ -51,21 +69,174 @@ static SocketAddress *tcp_build_address(const char
*host_port, Error **errp)
}
+static void tcp_connect_data_free(gpointer opaque)
+{
+ TCPConnectData *data = opaque;
+ if (!data) {
+ return;
+ }
+ g_free(data->hostname);
+ object_unref(OBJECT(data->tlscreds));
+ g_free(data);
+}
+
+
+static void tcp_listen_data_free(gpointer opaque)
+{
+ TCPListenData *data = opaque;
+ if (!data) {
+ return;
+ }
+ object_unref(OBJECT(data->tlscreds));
+ g_free(data);
+}
+
+
+static void tcp_connect_tls_data_free(gpointer opaque)
+{
+ TCPConnectTLSData *data = opaque;
+ if (!data) {
+ return;
+ }
+ object_unref(OBJECT(data->ioc));
+ g_free(data);
+}
+
+
+static char *tcp_get_opt_str(const char *host_port,
+ const char *key)
+{
+ const char *offset, *end;
+
+ offset = strstr(host_port, key);
+ if (!offset) {
+ return NULL;
+ }
+
+ offset += strlen(key);
+ if (offset[0] != '=') {
+ return NULL;
+ }
+ offset++;
+ end = strchr(offset, ',');
+ if (end) {
+ return g_strndup(offset, end - offset);
+ } else {
+ return g_strdup(offset);
+ }
+}
+
+
+static QCryptoTLSCreds *tcp_get_tls_creds(const char *host_port,
+ bool is_listen,
+ Error **errp)
+{
+ char *credname = NULL;
+ Object *creds;
+ QCryptoTLSCreds *ret;
+
+ credname = tcp_get_opt_str(host_port, "tls-creds");
+ if (!credname) {
+ return NULL;
+ }
+
+ creds = object_resolve_path_component(
+ object_get_objects_root(), credname);
+ if (!creds) {
+ error_setg(errp, "No TLS credentials with id '%s'",
+ credname);
+ goto error;
+ }
+ ret = (QCryptoTLSCreds *)object_dynamic_cast(
+ creds, TYPE_QCRYPTO_TLS_CREDS);
+ if (!ret) {
+ error_setg(errp, "Object with id '%s' is not TLS credentials",
+ credname);
+ goto error;
+ }
+ if (is_listen) {
+ if (ret->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+ error_setg(errp, "%s",
+ "Expected TLS credentials for server endpoint");
+ goto error;
+ }
+ } else {
+ if (ret->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
+ error_setg(errp, "%s",
+ "Expected TLS credentials for client endpoint");
+ goto error;
+ }
+ }
+
+ g_free(credname);
+ object_ref(OBJECT(ret));
+ return ret;
+
+ error:
+ g_free(credname);
+ return NULL;
+}
+
+
+static void tcp_outgoing_migration_tls(Object *src,
+ Error *err,
+ gpointer opaque)
+{
+ TCPConnectTLSData *data = opaque;
+ QIOChannel *ioc = QIO_CHANNEL(src);
+
+ if (err) {
+ DPRINTF("migrate connect TLS error: %s\n", error_get_pretty(err));
+ data->s->file = NULL;
+ migrate_fd_error(data->s);
+ } else {
+ DPRINTF("migrate connect success\n");
+ data->s->file = qemu_fopen_channel_output(ioc);
+ migrate_fd_connect(data->s);
+ }
+}
+
+
static void tcp_outgoing_migration(Object *src,
Error *err,
gpointer opaque)
{
- MigrationState *s = opaque;
+ TCPConnectData *data = opaque;
QIOChannel *sioc = QIO_CHANNEL(src);
if (err) {
DPRINTF("migrate connect error: %s\n", error_get_pretty(err));
- s->file = NULL;
- migrate_fd_error(s);
+ data->s->file = NULL;
+ migrate_fd_error(data->s);
} else {
- DPRINTF("migrate connect success\n");
- s->file = qemu_fopen_channel_output(sioc);
- migrate_fd_connect(s);
+ if (data->tlscreds) {
+ Error *local_err = NULL;
+ QIOChannelTLS *tioc = qio_channel_tls_new_client(
+ sioc, data->tlscreds, data->hostname,
+ &local_err);
+ if (!tioc) {
+ DPRINTF("migrate tls setup error: %s\n",
+ error_get_pretty(local_err));
+ error_free(local_err);
+ data->s->file = NULL;
+ migrate_fd_error(data->s);
+ } else {
+ TCPConnectTLSData *tdata =
+ g_new0(TCPConnectTLSData, 1);
+ DPRINTF("migrate connect tls handshake begin\n");
+ tdata->s = data->s;
+ tdata->ioc = sioc;
+ object_ref(OBJECT(sioc));
+ qio_channel_tls_handshake(tioc,
+ tcp_outgoing_migration_tls,
+ tdata,
+ tcp_connect_tls_data_free);
+ }
+ } else {
+ DPRINTF("migrate connect success\n");
+ data->s->file = qemu_fopen_channel_output(sioc);
+ migrate_fd_connect(data->s);
+ }
}
object_unref(src);
}
@@ -77,21 +248,56 @@ void tcp_start_outgoing_migration(MigrationState *s,
{
SocketAddress *saddr = tcp_build_address(host_port, errp);
QIOChannelSocket *sioc;
+ Error *local_err = NULL;
+ QCryptoTLSCreds *creds;
+ TCPConnectData *data;
if (!saddr) {
return;
}
+ creds = tcp_get_tls_creds(host_port, false, errp);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ qapi_free_SocketAddress(saddr);
+ return;
+ }
+
+ data = g_new0(TCPConnectData, 1);
+ data->s = s;
+ if (creds) {
+ data->hostname = g_strdup(saddr->inet->host);
+ data->tlscreds = creds;
+ }
+
sioc = qio_channel_socket_new();
qio_channel_socket_connect_async(sioc,
saddr,
tcp_outgoing_migration,
- s,
- NULL);
+ data,
+ tcp_connect_data_free);
qapi_free_SocketAddress(saddr);
}
+static void tcp_incoming_migration_tls(Object *src,
+ Error *err,
+ gpointer opaque)
+{
+ QIOChannel *ioc = QIO_CHANNEL(src);
+
+ if (err) {
+ DPRINTF("migrate listen TLS error: %s\n", error_get_pretty(err));
+ object_unref(OBJECT(ioc));
+ } else {
+ DPRINTF("migrate listen success\n");
+ QEMUFile *f = qemu_fopen_channel_input(ioc);
+
+ process_incoming_migration(f);
+ }
+}
+
+
static gboolean tcp_accept_incoming_migration(QIOChannel *ioc,
GIOCondition condition,
gpointer opaque)
@@ -99,6 +305,7 @@ static gboolean tcp_accept_incoming_migration(QIOChannel
*ioc,
QEMUFile *f;
QIOChannelSocket *cioc;
Error *err = NULL;
+ TCPListenData *data = opaque;
cioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc),
&err);
@@ -108,16 +315,38 @@ static gboolean tcp_accept_incoming_migration(QIOChannel
*ioc,
goto out;
}
- DPRINTF("accepted migration\n");
+ if (data->tlscreds) {
+ DPRINTF("Starting client TLS\n");
+ QIOChannelTLS *tioc = qio_channel_tls_new_server(
+ QIO_CHANNEL(cioc), data->tlscreds,
+ NULL, /* XXX pass ACL name */
+ &err);
+ object_unref(OBJECT(cioc));
+ if (!tioc) {
+ DPRINTF("migrate tls setup error: %s\n",
+ error_get_pretty(err));
+ error_free(err);
+ goto out;
+ } else {
+ DPRINTF("migrate connect tls handshake begin\n");
+ qio_channel_tls_handshake(tioc,
+ tcp_incoming_migration_tls,
+ NULL,
+ NULL);
+ }
+ } else {
+ DPRINTF("accepted migration\n");
- f = qemu_fopen_channel_input(QIO_CHANNEL(cioc));
- object_unref(OBJECT(cioc));
+ f = qemu_fopen_channel_input(QIO_CHANNEL(cioc));
+ object_unref(OBJECT(cioc));
- process_incoming_migration(f);
+ process_incoming_migration(f);
+ }
out:
/* Close listening socket as its no longer needed */
qio_channel_close(ioc, NULL);
+ object_unref(OBJECT(ioc));
return FALSE;
}
@@ -126,23 +355,39 @@ void tcp_start_incoming_migration(const char *host_port,
Error **errp)
{
SocketAddress *saddr = tcp_build_address(host_port, errp);
QIOChannelSocket *listen_ioc;
+ TCPListenData *data;
+ Error *local_err = NULL;
+ QCryptoTLSCreds *creds;
if (!saddr) {
return;
}
+ creds = tcp_get_tls_creds(host_port, true, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ qapi_free_SocketAddress(saddr);
+ return;
+ }
+
listen_ioc = qio_channel_socket_new();
if (qio_channel_socket_listen_sync(listen_ioc, saddr, errp) < 0) {
object_unref(OBJECT(listen_ioc));
qapi_free_SocketAddress(saddr);
+ object_unref(OBJECT(creds));
return;
}
+ data = g_new0(TCPListenData, 1);
+ if (creds) {
+ data->tlscreds = creds;
+ }
+
qio_channel_add_watch(QIO_CHANNEL(listen_ioc),
G_IO_IN,
tcp_accept_incoming_migration,
- listen_ioc,
- (GDestroyNotify)object_unref);
+ data,
+ tcp_listen_data_free);
qapi_free_SocketAddress(saddr);
}
diff --git a/qemu-options.hx b/qemu-options.hx
index 6acd400..c1bf4a7 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3299,7 +3299,7 @@ Set TB size.
ETEXI
DEF("incoming", HAS_ARG, QEMU_OPTION_incoming, \
- "-incoming tcp:[host]:port[,to=maxport][,ipv4][,ipv6]\n" \
+ "-incoming tcp:[host]:port[,to=maxport][,ipv4][,ipv6][,tls-creds=ID]\n" \
"-incoming rdma:host:port[,ipv4][,ipv6]\n" \
"-incoming unix:socketpath\n" \
" prepare for incoming migration, listen on\n" \
@@ -3312,11 +3312,14 @@ DEF("incoming", HAS_ARG, QEMU_OPTION_incoming, \
" wait for the URI to be specified via migrate_incoming\n",
QEMU_ARCH_ALL)
STEXI
address@hidden -incoming
tcp:address@hidden:@var{port}[,address@hidden,ipv4][,ipv6]
address@hidden -incoming
tcp:address@hidden:@var{port}[,address@hidden,ipv4][,ipv6][,tls-creds=ID]
@itemx -incoming rdma:@var{host}:@var{port}[,ipv4][,ipv6]
@findex -incoming
Prepare for incoming migration, listen on a given tcp port.
+If the @var{tls-creds} parameter is specified, it should refer to the ID
+of a TLS credentials object previously created with @var{-object}.
+
@item -incoming unix:@var{socketpath}
Prepare for incoming migration, listen on a given unix socket.
--
2.4.3
- [Qemu-devel] [PATCH FYI 36/46] migration: introduce a new QEMUFile impl based on QIOChannel, (continued)
- [Qemu-devel] [PATCH FYI 36/46] migration: introduce a new QEMUFile impl based on QIOChannel, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 35/46] migration: force QEMUFile to blocking mode for outgoing migration, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 37/46] migration: convert unix socket protocol to use QIOChannel, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 39/46] migration: convert fd socket protocol to use QIOChannel, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 38/46] migration: convert tcp socket protocol to use QIOChannel, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 42/46] migration: convert savevm to use QIOChannel for writing to files, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 40/46] migration: convert exec socket protocol to use QIOChannel, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 43/46] migration: delete QEMUFile sockets implementation, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 44/46] migration: delete QEMUFile stdio implementation, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 41/46] migration: convert RDMA to use QIOChannel interface, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 45/46] migration: support TLS encryption with TCP migration backend,
Daniel P. Berrange <=
- [Qemu-devel] [PATCH FYI 46/46] migration: remove support for non-iovec based write handlers, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 07/46] io: add helper module for creating watches on FDs, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 23/46] nbd: convert to use the QAPI SocketAddress object, Daniel P. Berrange, 2015/09/03
- [Qemu-devel] [PATCH FYI 21/46] char: don't assume telnet initialization will not block, Daniel P. Berrange, 2015/09/03