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
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>
<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>
</dl>
<p>
@ -191,6 +198,9 @@ settings.
</li>
<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
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>
</ul>
<h4>
@ -260,7 +270,7 @@ Note that parameter values must be
<td>
<code>socket</code>
</td>
<td> unix, ssh, libssh2 </td>
<td> unix, ssh, libssh2, libssh </td>
<td>
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
<td>
<code>netcat</code>
</td>
<td> ssh, libssh2 </td>
<td> ssh, libssh2, libssh </td>
<td>
The name of the netcat command on the remote machine.
The default is <code>nc</code>. For ssh transport, libvirt
@ -300,7 +310,7 @@ Note that parameter values must be
<td>
<code>keyfile</code>
</td>
<td> ssh, libssh2 </td>
<td> ssh, libssh2, libssh </td>
<td>
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
<td>
<code>known_hosts</code>
</td>
<td> libssh2 </td>
<td> libssh2, libssh </td>
<td>
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.
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.
</td>
</tr>
<tr>
@ -386,7 +397,7 @@ Note that parameter values must be
<td>
<code>sshauth</code>
</td>
<td> libssh2 </td>
<td> libssh2, libssh </td>
<td>
A comma separated list of authentication methods to use. Default (is
"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+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) {

View File

@ -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;

View File

@ -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);

View File

@ -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 &&

View File

@ -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);