mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-22 21:55:25 +00:00
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:
parent
6917467c2b
commit
22eaee8e01
@ -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/> —
|
||||
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/> —
|
||||
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.
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 &&
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user