qemu: implement ssh-agent auth for ssh disks with nbdkit

It's not possible to use password-protected ssh keys directly with
libvirt because libvirt doesn't have any way to prompt a user for the
password. To accomodate password-protected key files, an administrator
can add these keys to an ssh agent and then configure the domain with
the path to the ssh-agent socket.

Note that this requires an administrator or management app to
configure the ssh-agent with an appropriate socket path and add the
necessary keys to it. In addition, it does not currently work with
selinux enabled. The ssh-agent socket would need a label that libvirt
would be allowed to access rather than unconfined_t.

Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
This commit is contained in:
Jonathon Jongsma 2023-07-14 15:54:03 -05:00
parent 1195403c43
commit 3310b12d52
7 changed files with 51 additions and 8 deletions

View File

@ -7228,8 +7228,17 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node,
if (!(src->ssh_user = virXMLPropStringRequired(tmpnode, "username")))
return -1;
if (!(src->ssh_keyfile = virXMLPropStringRequired(tmpnode, "keyfile")))
/* optional path to an ssh key file */
src->ssh_keyfile = virXMLPropString(tmpnode, "keyfile");
/* optional ssh-agent socket location */
src->ssh_agent = virXMLPropString(tmpnode, "agentsock");
if (!src->ssh_keyfile && !src->ssh_agent) {
virReportError(VIR_ERR_XML_ERROR,
_("element '%1$s' requires either 'keyfile' or 'agentsock' attribute"),
tmpnode->name);
return -1;
}
}
}
@ -22222,11 +22231,12 @@ virDomainDiskSourceFormatNetwork(virBuffer *attrBuf,
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_SSH) {
if (src->ssh_known_hosts_file)
virBufferEscapeString(childBuf, "<knownHosts path='%s'/>\n", src->ssh_known_hosts_file);
if (src->ssh_keyfile) {
if (src->ssh_keyfile || src->ssh_agent) {
virBufferAddLit(childBuf, "<identity");
virBufferEscapeString(childBuf, " username='%s'", src->ssh_user);
virBufferEscapeString(childBuf, " keyfile='%s'", src->ssh_keyfile);
virBufferEscapeString(childBuf, " agentsock='%s'", src->ssh_agent);
virBufferAddLit(childBuf, "/>\n");
}

View File

@ -899,6 +899,7 @@ virStorageSourceCopy(const virStorageSource *src,
def->ssh_user = g_strdup(src->ssh_user);
def->ssh_known_hosts_file = g_strdup(src->ssh_known_hosts_file);
def->ssh_keyfile = g_strdup(src->ssh_keyfile);
def->ssh_agent = g_strdup(src->ssh_agent);
def->nfs_user = g_strdup(src->nfs_user);
def->nfs_group = g_strdup(src->nfs_group);
@ -1179,6 +1180,7 @@ virStorageSourceClear(virStorageSource *def)
VIR_FREE(def->ssh_user);
VIR_FREE(def->ssh_known_hosts_file);
VIR_FREE(def->ssh_keyfile);
VIR_FREE(def->ssh_agent);
VIR_FREE(def->nfs_user);
VIR_FREE(def->nfs_group);

View File

@ -413,6 +413,7 @@ struct _virStorageSource {
bool ssh_host_key_check_disabled;
char *ssh_known_hosts_file;
char *ssh_keyfile;
char *ssh_agent;
/* nfs_user and nfs_group store the strings passed in by the user for NFS params.
* nfs_uid and nfs_gid represent the converted/looked up ID numbers which are used

View File

@ -1057,6 +1057,9 @@ qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc,
virCommandAddArgPair(cmd, "user", proc->source->ssh_user);
}
if (proc->source->ssh_agent)
virCommandAddEnvPair(cmd, "SSH_AUTH_SOCK", proc->source->ssh_agent);
if (proc->source->ssh_host_key_check_disabled)
virCommandAddArgPair(cmd, "verify-remote-host", "false");
@ -1179,6 +1182,10 @@ qemuNbdkitProcessStart(qemuNbdkitProcess *proc,
qemuSecurityDomainSetPathLabel(driver, vm, proc->source->ssh_keyfile, false) < 0)
goto error;
if (proc->source->ssh_agent &&
qemuSecurityDomainSetPathLabel(driver, vm, proc->source->ssh_agent, false) < 0)
goto error;
if (proc->source->ssh_known_hosts_file &&
qemuSecurityDomainSetPathLabel(driver, vm, proc->source->ssh_known_hosts_file, false) < 0)
goto error;
@ -1267,6 +1274,9 @@ qemuNbdkitProcessStop(qemuNbdkitProcess *proc,
if (proc->source->ssh_keyfile)
qemuSecurityDomainRestorePathLabel(driver, vm, proc->source->ssh_keyfile);
if (proc->source->ssh_agent)
qemuSecurityDomainRestorePathLabel(driver, vm, proc->source->ssh_agent);
if (proc->pid < 0)
return 0;

View File

@ -1,9 +1,9 @@
SSH_AUTH_SOCK=/path/to/agent/socket \
nbdkit \
--unix /tmp/statedir-0/nbdkit-test-disk-0.socket \
--foreground ssh \
host=example.org \
port=2222 \
path=test.img \
identity=/path/to/id_rsa \
path=test1.img \
user=myuser \
known-hosts=/path/to/ssh_known_hosts
known-hosts=/path/to/ssh_known_hosts1

View File

@ -0,0 +1,9 @@
nbdkit \
--unix /tmp/statedir-1/nbdkit-test-disk-1.socket \
--foreground ssh \
host=example.org \
port=2222 \
path=test2.img \
identity=/path/to/id_rsa \
user=myuser2 \
known-hosts=/path/to/ssh_known_hosts2

View File

@ -15,12 +15,23 @@
<devices>
<disk type='network' device='disk'>
<driver name='qemu' type='raw'/>
<source protocol='ssh' name='test.img'>
<source protocol='ssh' name='test1.img'>
<host name='example.org' port='2222'/>
<timeout seconds='1234'/>
<readahead size='1024'/>
<identity username='myuser' keyfile='/path/to/id_rsa'/>
<knownHosts path="/path/to/ssh_known_hosts"/>
<identity username='myuser' agentsock='/path/to/agent/socket'/>
<knownHosts path="/path/to/ssh_known_hosts1"/>
</source>
<target dev='vda' bus='virtio'/>
</disk>
<disk type='network' device='disk'>
<driver name='qemu' type='raw'/>
<source protocol='ssh' name='test2.img'>
<host name='example.org' port='2222'/>
<timeout seconds='1234'/>
<readahead size='1024'/>
<identity username='myuser2' keyfile='/path/to/id_rsa'/>
<knownHosts path="/path/to/ssh_known_hosts2"/>
</source>
<target dev='vda' bus='virtio'/>
</disk>