From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:45635) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aIxNj-0001xX-6p for qemu-devel@nongnu.org; Tue, 12 Jan 2016 06:45:00 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aIxNh-0004LE-Jf for qemu-devel@nongnu.org; Tue, 12 Jan 2016 06:44:59 -0500 Received: from mx1.redhat.com ([209.132.183.28]:42174) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aIxNh-0004L7-AO for qemu-devel@nongnu.org; Tue, 12 Jan 2016 06:44:57 -0500 Received: from int-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) by mx1.redhat.com (Postfix) with ESMTPS id E97F0A37FA for ; Tue, 12 Jan 2016 11:44:56 +0000 (UTC) From: "Daniel P. Berrange" Date: Tue, 12 Jan 2016 11:44:14 +0000 Message-Id: <1452599056-27357-21-git-send-email-berrange@redhat.com> In-Reply-To: <1452599056-27357-1-git-send-email-berrange@redhat.com> References: <1452599056-27357-1-git-send-email-berrange@redhat.com> Subject: [Qemu-devel] [PATCH v1 20/22] migration: support TLS encryption with TCP migration backend List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: Amit Shah , "Dr. David Alan Gilbert" , Juan Quintela 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 --- 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 ac73977..bef861c 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->u.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 215d00d..3f16cfc 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -3364,7 +3364,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" \ @@ -3377,11 +3377,14 @@ DEF("incoming", HAS_ARG, QEMU_OPTION_incoming, \ " wait for the URI to be specified via migrate_incoming\n", QEMU_ARCH_ALL) STEXI -@item -incoming tcp:[@var{host}]:@var{port}[,to=@var{maxport}][,ipv4][,ipv6] +@item -incoming tcp:[@var{host}]:@var{port}[,to=@var{maxport}][,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.5.0