From 22eaee8e011645162e4b00d313bf2abdd4eb54e5 Mon Sep 17 00:00:00 2001 From: Pino Toscano Date: Wed, 9 Nov 2016 15:28:36 +0100 Subject: [PATCH] remote: expose a new libssh transport Implement in virtNetClient and VirNetSocket the needed functions to expose a new libssh transport, providing all the options that the libssh2 transport supports. --- docs/remote.html.in | 35 +++++--- src/remote/remote_driver.c | 41 +++++++++ src/rpc/virnetclient.c | 106 ++++++++++++++++++++++ src/rpc/virnetclient.h | 13 +++ src/rpc/virnetsocket.c | 179 +++++++++++++++++++++++++++++++++++++ src/rpc/virnetsocket.h | 13 +++ 6 files changed, 375 insertions(+), 12 deletions(-) diff --git a/docs/remote.html.in b/docs/remote.html.in index f65454ea5e..594e94e50e 100644 --- a/docs/remote.html.in +++ b/docs/remote.html.in @@ -144,6 +144,13 @@ Remote libvirt supports a range of transports: of the OpenSSH binary. This transport uses the libvirt authentication callback for all ssh authentication calls and therefore supports keyboard-interactive authentication even with graphical management applications. As with the classic ssh transport +netcat is required on the remote side. +
libssh
+
Transport over the SSH protocol using + libssh instead +of the OpenSSH binary. This transport uses the libvirt authentication callback for +all ssh authentication calls and therefore supports keyboard-interactive authentication +even with graphical management applications. As with the classic ssh transport netcat is required on the remote side.

@@ -191,6 +198,9 @@ settings.

  • qemu+libssh2://user@host/system?known_hosts=/home/user/.ssh/known_hosts
    — Connect to a remote host using a ssh connection with the libssh2 driver +and use a different known_hosts file.
  • +
  • qemu+libssh://user@host/system?known_hosts=/home/user/.ssh/known_hosts
    — +Connect to a remote host using a ssh connection with the libssh driver and use a different known_hosts file.
  • @@ -260,7 +270,7 @@ Note that parameter values must be socket - unix, ssh, libssh2 + unix, ssh, libssh2, libssh The path to the Unix domain socket, which overrides the compiled-in default. For ssh transport, this is passed to @@ -275,7 +285,7 @@ Note that parameter values must be netcat - ssh, libssh2 + ssh, libssh2, libssh The name of the netcat command on the remote machine. The default is nc. For ssh transport, libvirt @@ -300,7 +310,7 @@ Note that parameter values must be keyfile - ssh, libssh2 + ssh, libssh2, libssh The name of the private key file to use to authentication to the remote machine. If this option is not used the default keys are used. @@ -368,14 +378,15 @@ Note that parameter values must be known_hosts - libssh2 - - Path to the known_hosts file to verify the host key against. LibSSH2 - supports OpenSSH-style known_hosts files, although it does not support - all key types, so using files created by the OpenSSH binary may result - into truncating the known_hosts file. It's recommended to use the default - known_hosts file is located in libvirt's client local configuration - directory e.g.: ~/.config/libvirt/known_hosts. Note: Use absolute paths. + libssh2, libssh + + Path to the known_hosts file to verify the host key against. LibSSH2 and + libssh support OpenSSH-style known_hosts files, although LibSSH2 does not + support all key types, so using files created by the OpenSSH binary may + result into truncating the known_hosts file. Thus, with LibSSH2 it's + recommended to use the default known_hosts file is located in libvirt's + client local configuration directory e.g.: ~/.config/libvirt/known_hosts. + Note: Use absolute paths. @@ -386,7 +397,7 @@ Note that parameter values must be sshauth - libssh2 + libssh2, libssh A comma separated list of authentication methods to use. Default (is "agent,privkey,keyboard-interactive". The order of the methods is preserved. diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index a3cd7cd632..db2bdd4e55 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -673,6 +673,7 @@ remoteConnectSupportsFeatureUnlocked(virConnectPtr conn, * - xxx:/// -> UNIX domain socket * - xxx+ssh:/// -> SSH connection (legacy) * - xxx+libssh2:/// -> SSH connection (using libssh2) + * - xxx+libssh:/// -> SSH connection (using libssh) */ static int doRemoteOpen(virConnectPtr conn, @@ -689,6 +690,7 @@ doRemoteOpen(virConnectPtr conn, trans_libssh2, trans_ext, trans_tcp, + trans_libssh, } transport; #ifndef WIN32 char *daemonPath = NULL; @@ -736,6 +738,8 @@ doRemoteOpen(virConnectPtr conn, transport = trans_ext; } else if (STRCASEEQ(transport_str, "tcp")) { transport = trans_tcp; + } else if (STRCASEEQ(transport_str, "libssh")) { + transport = trans_libssh; } else { virReportError(VIR_ERR_INVALID_ARG, "%s", _("remote_open: transport in URL not recognised " @@ -959,6 +963,43 @@ doRemoteOpen(virConnectPtr conn, priv->is_secure = 1; break; + case trans_libssh: + if (!sockname) { + /* Right now we don't support default session connections */ + if (STREQ_NULLABLE(conn->uri->path, "/session")) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("Connecting to session instance without " + "socket path is not supported by the libssh " + "connection driver")); + goto failed; + } + + if (VIR_STRDUP(sockname, + flags & VIR_DRV_OPEN_REMOTE_RO ? + LIBVIRTD_PRIV_UNIX_SOCKET_RO : LIBVIRTD_PRIV_UNIX_SOCKET) < 0) + goto failed; + } + + VIR_DEBUG("Starting libssh session"); + + priv->client = virNetClientNewLibssh(priv->hostname, + port, + AF_UNSPEC, + username, + keyfile, + knownHosts, + knownHostsVerify, + sshauth, + netcat, + sockname, + auth, + conn->uri); + if (!priv->client) + goto failed; + + priv->is_secure = 1; + break; + #ifndef WIN32 case trans_unix: if (!sockname) { diff --git a/src/rpc/virnetclient.c b/src/rpc/virnetclient.c index 713b8d53bd..34475d9277 100644 --- a/src/rpc/virnetclient.c +++ b/src/rpc/virnetclient.c @@ -536,6 +536,112 @@ virNetClientPtr virNetClientNewLibSSH2(const char *host, } #undef DEFAULT_VALUE +#define DEFAULT_VALUE(VAR, VAL) \ + if (!VAR) \ + VAR = VAL; +virNetClientPtr virNetClientNewLibssh(const char *host, + const char *port, + int family, + const char *username, + const char *privkeyPath, + const char *knownHostsPath, + const char *knownHostsVerify, + const char *authMethods, + const char *netcatPath, + const char *socketPath, + virConnectAuthPtr authPtr, + virURIPtr uri) +{ + virNetSocketPtr sock = NULL; + virNetClientPtr ret = NULL; + + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *nc = NULL; + char *command = NULL; + + char *homedir = virGetUserDirectory(); + char *confdir = virGetUserConfigDirectory(); + char *knownhosts = NULL; + char *privkey = NULL; + + /* Use default paths for known hosts an public keys if not provided */ + if (confdir) { + if (!knownHostsPath) { + if (virFileExists(confdir)) { + if (virAsprintf(&knownhosts, "%s/known_hosts", confdir) < 0) + goto cleanup; + } + } else { + if (VIR_STRDUP(knownhosts, knownHostsPath) < 0) + goto cleanup; + } + } + + if (homedir) { + if (!privkeyPath) { + if (virNetClientFindDefaultSshKey(homedir, &privkey) < 0) + goto no_memory; + } else { + if (VIR_STRDUP(privkey, privkeyPath) < 0) + goto cleanup; + } + } + + if (!authMethods) { + if (privkey) + authMethods = "agent,privkey,password,keyboard-interactive"; + else + authMethods = "agent,password,keyboard-interactive"; + } + + DEFAULT_VALUE(host, "localhost"); + DEFAULT_VALUE(port, "22"); + DEFAULT_VALUE(username, "root"); + DEFAULT_VALUE(netcatPath, "nc"); + DEFAULT_VALUE(knownHostsVerify, "normal"); + + virBufferEscapeShell(&buf, netcatPath); + if (!(nc = virBufferContentAndReset(&buf))) + goto no_memory; + + if (virAsprintf(&command, + "sh -c " + "'if '%s' -q 2>&1 | grep \"requires an argument\" >/dev/null 2>&1; then " + "ARG=-q0;" + "else " + "ARG=;" + "fi;" + "'%s' $ARG -U %s'", + nc, nc, socketPath) < 0) + goto cleanup; + + if (virNetSocketNewConnectLibssh(host, port, + family, + username, privkey, + knownhosts, knownHostsVerify, authMethods, + command, authPtr, uri, &sock) != 0) + goto cleanup; + + if (!(ret = virNetClientNew(sock, NULL))) + goto cleanup; + sock = NULL; + + cleanup: + VIR_FREE(command); + VIR_FREE(privkey); + VIR_FREE(knownhosts); + VIR_FREE(homedir); + VIR_FREE(confdir); + VIR_FREE(nc); + virObjectUnref(sock); + return ret; + + no_memory: + virReportOOMError(); + goto cleanup; +} +#undef DEFAULT_VALUE + virNetClientPtr virNetClientNewExternal(const char **cmdargv) { virNetSocketPtr sock; diff --git a/src/rpc/virnetclient.h b/src/rpc/virnetclient.h index c772d0bca6..9cf32091f5 100644 --- a/src/rpc/virnetclient.h +++ b/src/rpc/virnetclient.h @@ -67,6 +67,19 @@ virNetClientPtr virNetClientNewLibSSH2(const char *host, virConnectAuthPtr authPtr, virURIPtr uri); +virNetClientPtr virNetClientNewLibssh(const char *host, + const char *port, + int family, + const char *username, + const char *privkeyPath, + const char *knownHostsPath, + const char *knownHostsVerify, + const char *authMethods, + const char *netcatPath, + const char *socketPath, + virConnectAuthPtr authPtr, + virURIPtr uri); + virNetClientPtr virNetClientNewExternal(const char **cmdargv); int virNetClientRegisterAsyncIO(virNetClientPtr client); diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c index 05f20a5953..325a7c7cf6 100644 --- a/src/rpc/virnetsocket.c +++ b/src/rpc/virnetsocket.c @@ -65,6 +65,10 @@ # include "virnetsshsession.h" #endif +#if WITH_LIBSSH +# include "virnetlibsshsession.h" +#endif + #define VIR_FROM_THIS VIR_FROM_RPC VIR_LOG_INIT("rpc.netsocket"); @@ -107,6 +111,9 @@ struct _virNetSocket { #if WITH_SSH2 virNetSSHSessionPtr sshSession; #endif +#if WITH_LIBSSH + virNetLibsshSessionPtr libsshSession; +#endif }; @@ -1027,6 +1034,143 @@ virNetSocketNewConnectLibSSH2(const char *host ATTRIBUTE_UNUSED, } #endif /* WITH_SSH2 */ +#if WITH_LIBSSH +int +virNetSocketNewConnectLibssh(const char *host, + const char *port, + int family, + const char *username, + const char *privkey, + const char *knownHosts, + const char *knownHostsVerify, + const char *authMethods, + const char *command, + virConnectAuthPtr auth, + virURIPtr uri, + virNetSocketPtr *retsock) +{ + virNetSocketPtr sock = NULL; + virNetLibsshSessionPtr sess = NULL; + unsigned int verify; + int ret = -1; + int portN; + + char *authMethodNext = NULL; + char *authMethodsCopy = NULL; + char *authMethod; + + /* port number will be verified while opening the socket */ + if (virStrToLong_i(port, NULL, 10, &portN) < 0) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("Failed to parse port number")); + goto error; + } + + /* create ssh session context */ + if (!(sess = virNetLibsshSessionNew(username))) + goto error; + + /* set ssh session parameters */ + if (virNetLibsshSessionAuthSetCallback(sess, auth) != 0) + goto error; + + if (STRCASEEQ("auto", knownHostsVerify)) { + verify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_AUTO_ADD; + } else if (STRCASEEQ("ignore", knownHostsVerify)) { + verify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE; + } else if (STRCASEEQ("normal", knownHostsVerify)) { + verify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_NORMAL; + } else { + virReportError(VIR_ERR_INVALID_ARG, + _("Invalid host key verification method: '%s'"), + knownHostsVerify); + goto error; + } + + if (virNetLibsshSessionSetHostKeyVerification(sess, + host, + portN, + knownHosts, + verify) != 0) + goto error; + + if (virNetLibsshSessionSetChannelCommand(sess, command) != 0) + goto error; + + if (VIR_STRDUP(authMethodsCopy, authMethods) < 0) + goto error; + + authMethodNext = authMethodsCopy; + + while ((authMethod = strsep(&authMethodNext, ","))) { + if (STRCASEEQ(authMethod, "keyboard-interactive")) { + ret = virNetLibsshSessionAuthAddKeyboardAuth(sess, -1); + } else if (STRCASEEQ(authMethod, "password")) { + ret = virNetLibsshSessionAuthAddPasswordAuth(sess, uri); + } else if (STRCASEEQ(authMethod, "privkey")) { + ret = virNetLibsshSessionAuthAddPrivKeyAuth(sess, + privkey, + NULL); + } else if (STRCASEEQ(authMethod, "agent")) { + ret = virNetLibsshSessionAuthAddAgentAuth(sess); + } else { + virReportError(VIR_ERR_INVALID_ARG, + _("Invalid authentication method: '%s'"), + authMethod); + ret = -1; + goto error; + } + + if (ret != 0) + goto error; + } + + /* connect to remote server */ + if ((ret = virNetSocketNewConnectTCP(host, port, family, &sock)) < 0) + goto error; + + /* connect to the host using ssh */ + if ((ret = virNetLibsshSessionConnect(sess, virNetSocketGetFD(sock))) != 0) + goto error; + + sock->libsshSession = sess; + /* libssh owns the FD and closes it on its own, and thus + * we must not close it (otherwise there are warnings about + * trying to close an invalid FD). + */ + sock->ownsFd = false; + *retsock = sock; + + VIR_FREE(authMethodsCopy); + return 0; + + error: + virObjectUnref(sock); + virObjectUnref(sess); + VIR_FREE(authMethodsCopy); + return ret; +} +#else +int +virNetSocketNewConnectLibssh(const char *host ATTRIBUTE_UNUSED, + const char *port ATTRIBUTE_UNUSED, + int family ATTRIBUTE_UNUSED, + const char *username ATTRIBUTE_UNUSED, + const char *privkey ATTRIBUTE_UNUSED, + const char *knownHosts ATTRIBUTE_UNUSED, + const char *knownHostsVerify ATTRIBUTE_UNUSED, + const char *authMethods ATTRIBUTE_UNUSED, + const char *command ATTRIBUTE_UNUSED, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + virURIPtr uri ATTRIBUTE_UNUSED, + virNetSocketPtr *retsock ATTRIBUTE_UNUSED) +{ + virReportSystemError(ENOSYS, "%s", + _("libssh transport support was not enabled")); + return -1; +} +#endif /* WITH_LIBSSH */ + int virNetSocketNewConnectExternal(const char **cmdargv, virNetSocketPtr *retsock) { @@ -1204,6 +1348,10 @@ void virNetSocketDispose(void *obj) virObjectUnref(sock->sshSession); #endif +#if WITH_LIBSSH + virObjectUnref(sock->libsshSession); +#endif + if (sock->ownsFd) VIR_FORCE_CLOSE(sock->fd); VIR_FORCE_CLOSE(sock->errfd); @@ -1534,6 +1682,11 @@ bool virNetSocketHasCachedData(virNetSocketPtr sock ATTRIBUTE_UNUSED) hasCached = true; #endif +#if WITH_LIBSSH + if (virNetLibsshSessionHasCachedData(sock->libsshSession)) + hasCached = true; +#endif + #if WITH_SASL if (sock->saslDecoded) hasCached = true; @@ -1558,6 +1711,22 @@ static ssize_t virNetSocketLibSSH2Write(virNetSocketPtr sock, } #endif +#if WITH_LIBSSH +static ssize_t virNetSocketLibsshRead(virNetSocketPtr sock, + char *buf, + size_t len) +{ + return virNetLibsshChannelRead(sock->libsshSession, buf, len); +} + +static ssize_t virNetSocketLibsshWrite(virNetSocketPtr sock, + const char *buf, + size_t len) +{ + return virNetLibsshChannelWrite(sock->libsshSession, buf, len); +} +#endif + bool virNetSocketHasPendingData(virNetSocketPtr sock ATTRIBUTE_UNUSED) { bool hasPending = false; @@ -1581,6 +1750,11 @@ static ssize_t virNetSocketReadWire(virNetSocketPtr sock, char *buf, size_t len) return virNetSocketLibSSH2Read(sock, buf, len); #endif +#if WITH_LIBSSH + if (sock->libsshSession) + return virNetSocketLibsshRead(sock, buf, len); +#endif + reread: #if WITH_GNUTLS if (sock->tlsSession && @@ -1640,6 +1814,11 @@ static ssize_t virNetSocketWriteWire(virNetSocketPtr sock, const char *buf, size return virNetSocketLibSSH2Write(sock, buf, len); #endif +#if WITH_LIBSSH + if (sock->libsshSession) + return virNetSocketLibsshWrite(sock, buf, len); +#endif + rewrite: #if WITH_GNUTLS if (sock->tlsSession && diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h index ec064bbb2e..56c75c0303 100644 --- a/src/rpc/virnetsocket.h +++ b/src/rpc/virnetsocket.h @@ -100,6 +100,19 @@ int virNetSocketNewConnectLibSSH2(const char *host, virURIPtr uri, virNetSocketPtr *retsock); +int virNetSocketNewConnectLibssh(const char *host, + const char *port, + int family, + const char *username, + const char *privkey, + const char *knownHosts, + const char *knownHostsVerify, + const char *authMethods, + const char *command, + virConnectAuthPtr auth, + virURIPtr uri, + virNetSocketPtr *retsock); + int virNetSocketNewConnectExternal(const char **cmdargv, virNetSocketPtr *addr); -- GitLab