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.
This commit is contained in:
Pino Toscano 2016-11-09 15:28:36 +01:00 committed by Peter Krempa
parent 6917467c2b
commit 22eaee8e01
6 changed files with 374 additions and 11 deletions

View File

@ -144,6 +144,13 @@ Remote libvirt supports a range of transports:
of the OpenSSH binary. This transport uses the libvirt authentication callback for of the OpenSSH binary. This transport uses the libvirt authentication callback for
all ssh authentication calls and therefore supports keyboard-interactive authentication all ssh authentication calls and therefore supports keyboard-interactive authentication
even with graphical management applications. As with the classic ssh transport even with graphical management applications. As with the classic ssh transport
netcat is required on the remote side.</dd>
<dt><code>libssh</code></dt>
<dd> Transport over the SSH protocol using
<a href="http://libssh.org/" title="libssh homepage">libssh</a> 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.</dd> netcat is required on the remote side.</dd>
</dl> </dl>
<p> <p>
@ -191,6 +198,9 @@ settings.
</li> </li>
<li><code>qemu+libssh2://user@host/system?known_hosts=/home/user/.ssh/known_hosts</code><br/> &#x2014; <li><code>qemu+libssh2://user@host/system?known_hosts=/home/user/.ssh/known_hosts</code><br/> &#x2014;
Connect to a remote host using a ssh connection with the libssh2 driver Connect to a remote host using a ssh connection with the libssh2 driver
and use a different known_hosts file.</li>
<li><code>qemu+libssh://user@host/system?known_hosts=/home/user/.ssh/known_hosts</code><br/> &#x2014;
Connect to a remote host using a ssh connection with the libssh driver
and use a different known_hosts file.</li> and use a different known_hosts file.</li>
</ul> </ul>
<h4> <h4>
@ -260,7 +270,7 @@ Note that parameter values must be
<td> <td>
<code>socket</code> <code>socket</code>
</td> </td>
<td> unix, ssh, libssh2 </td> <td> unix, ssh, libssh2, libssh </td>
<td> <td>
The path to the Unix domain socket, which overrides the The path to the Unix domain socket, which overrides the
compiled-in default. For ssh transport, this is passed to compiled-in default. For ssh transport, this is passed to
@ -275,7 +285,7 @@ Note that parameter values must be
<td> <td>
<code>netcat</code> <code>netcat</code>
</td> </td>
<td> ssh, libssh2 </td> <td> ssh, libssh2, libssh </td>
<td> <td>
The name of the netcat command on the remote machine. The name of the netcat command on the remote machine.
The default is <code>nc</code>. For ssh transport, libvirt The default is <code>nc</code>. For ssh transport, libvirt
@ -300,7 +310,7 @@ Note that parameter values must be
<td> <td>
<code>keyfile</code> <code>keyfile</code>
</td> </td>
<td> ssh, libssh2 </td> <td> ssh, libssh2, libssh </td>
<td> <td>
The name of the private key file to use to authentication to the remote 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. machine. If this option is not used the default keys are used.
@ -368,14 +378,15 @@ Note that parameter values must be
<td> <td>
<code>known_hosts</code> <code>known_hosts</code>
</td> </td>
<td> libssh2 </td> <td> libssh2, libssh </td>
<td> <td>
Path to the known_hosts file to verify the host key against. LibSSH2 Path to the known_hosts file to verify the host key against. LibSSH2 and
supports OpenSSH-style known_hosts files, although it does not support libssh support OpenSSH-style known_hosts files, although LibSSH2 does not
all key types, so using files created by the OpenSSH binary may result support all key types, so using files created by the OpenSSH binary may
into truncating the known_hosts file. It's recommended to use the default result into truncating the known_hosts file. Thus, with LibSSH2 it's
known_hosts file is located in libvirt's client local configuration recommended to use the default known_hosts file is located in libvirt's
directory e.g.: ~/.config/libvirt/known_hosts. Note: Use absolute paths. client local configuration directory e.g.: ~/.config/libvirt/known_hosts.
Note: Use absolute paths.
</td> </td>
</tr> </tr>
<tr> <tr>
@ -386,7 +397,7 @@ Note that parameter values must be
<td> <td>
<code>sshauth</code> <code>sshauth</code>
</td> </td>
<td> libssh2 </td> <td> libssh2, libssh </td>
<td> <td>
A comma separated list of authentication methods to use. Default (is A comma separated list of authentication methods to use. Default (is
"agent,privkey,keyboard-interactive". The order of the methods is preserved. "agent,privkey,keyboard-interactive". The order of the methods is preserved.

View File

@ -673,6 +673,7 @@ remoteConnectSupportsFeatureUnlocked(virConnectPtr conn,
* - xxx:/// -> UNIX domain socket * - xxx:/// -> UNIX domain socket
* - xxx+ssh:/// -> SSH connection (legacy) * - xxx+ssh:/// -> SSH connection (legacy)
* - xxx+libssh2:/// -> SSH connection (using libssh2) * - xxx+libssh2:/// -> SSH connection (using libssh2)
* - xxx+libssh:/// -> SSH connection (using libssh)
*/ */
static int static int
doRemoteOpen(virConnectPtr conn, doRemoteOpen(virConnectPtr conn,
@ -689,6 +690,7 @@ doRemoteOpen(virConnectPtr conn,
trans_libssh2, trans_libssh2,
trans_ext, trans_ext,
trans_tcp, trans_tcp,
trans_libssh,
} transport; } transport;
#ifndef WIN32 #ifndef WIN32
char *daemonPath = NULL; char *daemonPath = NULL;
@ -736,6 +738,8 @@ doRemoteOpen(virConnectPtr conn,
transport = trans_ext; transport = trans_ext;
} else if (STRCASEEQ(transport_str, "tcp")) { } else if (STRCASEEQ(transport_str, "tcp")) {
transport = trans_tcp; transport = trans_tcp;
} else if (STRCASEEQ(transport_str, "libssh")) {
transport = trans_libssh;
} else { } else {
virReportError(VIR_ERR_INVALID_ARG, "%s", virReportError(VIR_ERR_INVALID_ARG, "%s",
_("remote_open: transport in URL not recognised " _("remote_open: transport in URL not recognised "
@ -959,6 +963,43 @@ doRemoteOpen(virConnectPtr conn,
priv->is_secure = 1; priv->is_secure = 1;
break; 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 #ifndef WIN32
case trans_unix: case trans_unix:
if (!sockname) { if (!sockname) {

View File

@ -536,6 +536,112 @@ virNetClientPtr virNetClientNewLibSSH2(const char *host,
} }
#undef DEFAULT_VALUE #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) virNetClientPtr virNetClientNewExternal(const char **cmdargv)
{ {
virNetSocketPtr sock; virNetSocketPtr sock;

View File

@ -67,6 +67,19 @@ virNetClientPtr virNetClientNewLibSSH2(const char *host,
virConnectAuthPtr authPtr, virConnectAuthPtr authPtr,
virURIPtr uri); 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); virNetClientPtr virNetClientNewExternal(const char **cmdargv);
int virNetClientRegisterAsyncIO(virNetClientPtr client); int virNetClientRegisterAsyncIO(virNetClientPtr client);

View File

@ -65,6 +65,10 @@
# include "virnetsshsession.h" # include "virnetsshsession.h"
#endif #endif
#if WITH_LIBSSH
# include "virnetlibsshsession.h"
#endif
#define VIR_FROM_THIS VIR_FROM_RPC #define VIR_FROM_THIS VIR_FROM_RPC
VIR_LOG_INIT("rpc.netsocket"); VIR_LOG_INIT("rpc.netsocket");
@ -107,6 +111,9 @@ struct _virNetSocket {
#if WITH_SSH2 #if WITH_SSH2
virNetSSHSessionPtr sshSession; virNetSSHSessionPtr sshSession;
#endif #endif
#if WITH_LIBSSH
virNetLibsshSessionPtr libsshSession;
#endif
}; };
@ -1027,6 +1034,143 @@ virNetSocketNewConnectLibSSH2(const char *host ATTRIBUTE_UNUSED,
} }
#endif /* WITH_SSH2 */ #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, int virNetSocketNewConnectExternal(const char **cmdargv,
virNetSocketPtr *retsock) virNetSocketPtr *retsock)
{ {
@ -1204,6 +1348,10 @@ void virNetSocketDispose(void *obj)
virObjectUnref(sock->sshSession); virObjectUnref(sock->sshSession);
#endif #endif
#if WITH_LIBSSH
virObjectUnref(sock->libsshSession);
#endif
if (sock->ownsFd) if (sock->ownsFd)
VIR_FORCE_CLOSE(sock->fd); VIR_FORCE_CLOSE(sock->fd);
VIR_FORCE_CLOSE(sock->errfd); VIR_FORCE_CLOSE(sock->errfd);
@ -1534,6 +1682,11 @@ bool virNetSocketHasCachedData(virNetSocketPtr sock ATTRIBUTE_UNUSED)
hasCached = true; hasCached = true;
#endif #endif
#if WITH_LIBSSH
if (virNetLibsshSessionHasCachedData(sock->libsshSession))
hasCached = true;
#endif
#if WITH_SASL #if WITH_SASL
if (sock->saslDecoded) if (sock->saslDecoded)
hasCached = true; hasCached = true;
@ -1558,6 +1711,22 @@ static ssize_t virNetSocketLibSSH2Write(virNetSocketPtr sock,
} }
#endif #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 virNetSocketHasPendingData(virNetSocketPtr sock ATTRIBUTE_UNUSED)
{ {
bool hasPending = false; bool hasPending = false;
@ -1581,6 +1750,11 @@ static ssize_t virNetSocketReadWire(virNetSocketPtr sock, char *buf, size_t len)
return virNetSocketLibSSH2Read(sock, buf, len); return virNetSocketLibSSH2Read(sock, buf, len);
#endif #endif
#if WITH_LIBSSH
if (sock->libsshSession)
return virNetSocketLibsshRead(sock, buf, len);
#endif
reread: reread:
#if WITH_GNUTLS #if WITH_GNUTLS
if (sock->tlsSession && if (sock->tlsSession &&
@ -1640,6 +1814,11 @@ static ssize_t virNetSocketWriteWire(virNetSocketPtr sock, const char *buf, size
return virNetSocketLibSSH2Write(sock, buf, len); return virNetSocketLibSSH2Write(sock, buf, len);
#endif #endif
#if WITH_LIBSSH
if (sock->libsshSession)
return virNetSocketLibsshWrite(sock, buf, len);
#endif
rewrite: rewrite:
#if WITH_GNUTLS #if WITH_GNUTLS
if (sock->tlsSession && if (sock->tlsSession &&

View File

@ -100,6 +100,19 @@ int virNetSocketNewConnectLibSSH2(const char *host,
virURIPtr uri, virURIPtr uri,
virNetSocketPtr *retsock); 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, int virNetSocketNewConnectExternal(const char **cmdargv,
virNetSocketPtr *addr); virNetSocketPtr *addr);