ssh-proxy: add ssh ProxyCommand tool that can connect to AF_UNIX + AF_VSOCK sockets

This adds a tiny binary that is hooked into SSH client config via
ProxyCommand and which simply connects to an AF_UNIX or AF_VSOCK socket
of choice.

The syntax is as simple as this:

     ssh unix/some/path     # (this connects to AF_UNIX socket /some/path)

or:

     ssh vsock/4711

I used "/" as separator of the protocol ID and the value since ":" is
already taken by SSH itself when doing sftp. And "@" is already taken
for separating the user name.
This commit is contained in:
Lennart Poettering 2024-01-04 23:31:51 +01:00
parent 0e3220684c
commit 0abd510f7f
9 changed files with 275 additions and 1 deletions

View file

@ -1055,6 +1055,7 @@ manpages = [
['systemd-socket-proxyd', '8', [], ''],
['systemd-soft-reboot.service', '8', [], ''],
['systemd-ssh-generator', '8', [], ''],
['systemd-ssh-proxy', '1', [], ''],
['systemd-stdio-bridge', '1', [], ''],
['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'],
['systemd-stub',

116
man/systemd-ssh-proxy.xml Normal file
View file

@ -0,0 +1,116 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-ssh-proxy"
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-ssh-proxy</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-ssh-proxy</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-ssh-proxy</refname>
<refpurpose>SSH client plugin for connecting to <constant>AF_VSOCK</constant> and
<constant>AF_UNIX</constant> sockets</refpurpose>
</refnamediv>
<refsynopsisdiv>
<programlisting>
Host unix/* vsock/*
ProxyCommand /usr/lib/systemd/systemd-ssh-proxy %h %p
ProxyUseFdpass yes
</programlisting>
<cmdsynopsis>
<command>/usr/lib/systemd/systemd-ssh-proxy</command> <arg>ADDRESS</arg> <arg>PORT</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-ssh-proxy</command> is a small "proxy" plugin for the <citerefentry
project="man-pages"><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool that allows connecting to <constant>AF_UNIX</constant> and <constant>AF_VSOCK</constant> sockets. It
implements the interface defined by <filename>ssh</filename>'s <varname>ProxyCommand</varname>
configuration option. It's supposed to be used with an <citerefentry
project="man-pages"><refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum></citerefentry>
configuration fragment like the following:</para>
<programlisting>
Host unix/* vsock/*
ProxyCommand /usr/lib/systemd/systemd-ssh-proxy %h %p
ProxyUseFdpass yes
CheckHostIP no
Host .host
ProxyCommand /usr/lib/systemd/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p
ProxyUseFdpass yes
CheckHostIP no
</programlisting>
<para>A configuration fragment along these lines is by default installed into
<filename>/etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf.in</filename>.</para>
<para>With this in place, SSH connections to host string <literal>unix/</literal> followed by an absolute
<constant>AF_UNIX</constant> file system path to a socket will be directed to the specified socket, which
must be of type <constant>SOCK_STREAM</constant>. Similar, SSH connections to <literal>vsock/</literal>
followed by an <constant>AF_VSOCK</constant> CID will result in an SSH connection made to that
CID. Moreover connecting to <literal>.host</literal> will connect to the local host via SSH, without
involving networking.</para>
<para>This tool is supposed to be used together with
<citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
which when run inside a VM or container will bind SSH to suitable
addresses. <command>systemd-ssh-generator</command> is supposed to run in the container of VM guest, and
<command>systemd-ssh-proxy</command> is run on the host, in order to connect to the container or VM
guest.</para>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code
otherwise.</para>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
<title>Talk to a local VM with CID 4711</title>
<programlisting>ssh vsock/4711</programlisting>
</example>
<example>
<title>Talk to the local host via ssh</title>
<programlisting>ssh .host</programlisting>
<para>or equivalent:</para>
<programlisting>ssh unix/run/ssh-unix-local/socket</programlisting>
</example>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry project="man-pages"><refentrytitle>vsock</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry project="man-pages"><refentrytitle>unix</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry project="man-pages"><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry project="man-pages"><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>

View file

@ -199,6 +199,11 @@ if pamconfdir == ''
pamconfdir = prefixdir / 'lib/pam.d'
endif
sshconfdir = get_option('sshconfdir')
if sshconfdir == ''
sshconfdir = sysconfdir / 'ssh/ssh_config.d'
endif
sshdconfdir = get_option('sshdconfdir')
if sshdconfdir == ''
sshdconfdir = sysconfdir / 'ssh/sshd_config.d'
@ -235,6 +240,7 @@ conf.set_quoted('PREFIX_NOSLASH', prefixdir_noslash)
conf.set_quoted('RANDOM_SEED', randomseeddir / 'random-seed')
conf.set_quoted('RANDOM_SEED_DIR', randomseeddir)
conf.set_quoted('RC_LOCAL_PATH', get_option('rc-local'))
conf.set_quoted('SSHCONFDIR', sshconfdir)
conf.set_quoted('SSHDCONFDIR', sshdconfdir)
conf.set_quoted('SYSCONF_DIR', sysconfdir)
conf.set_quoted('SYSCTL_DIR', sysctldir)
@ -2689,7 +2695,8 @@ summary({
'SysV rc?.d directories' : sysvrcnd_path,
'PAM modules directory' : pamlibdir,
'PAM configuration directory' : pamconfdir,
'ssh configuration directory' : sshdconfdir,
'ssh server configuration directory' : sshdconfdir,
'ssh client configuration directory' : sshconfdir,
'libcryptsetup plugins directory' : libcryptsetup_plugins_dir,
'RPM macros directory' : rpmmacrosdir,
'modprobe.d directory' : modprobedir,

View file

@ -211,6 +211,8 @@ option('pamlibdir', type : 'string',
description : 'directory for PAM modules')
option('pamconfdir', type : 'string',
description : 'directory for PAM configuration ["no" disables]')
option('sshconfdir', type : 'string',
description : 'directory for SSH client configuration ["no" disables]')
option('sshdconfdir', type : 'string',
description : 'directory for SSH server configuration ["no" disables]')
option('libcryptsetup-plugins-dir', type : 'string',

View file

@ -0,0 +1,18 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Make sure unix/* and vsock/* can be used to connect to AF_UNIX and AF_VSOCK paths
#
Host unix/* vsock/*
ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy %h %p
ProxyUseFdpass yes
CheckHostIP no
# Disable all kinds of host identity checks, since these addresses are generally ephemeral.
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
# Allow connecting to the local host directly via ".host"
Host .host
ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p
ProxyUseFdpass yes
CheckHostIP no

View file

@ -5,4 +5,21 @@ executables += [
'name' : 'systemd-ssh-generator',
'sources' : files('ssh-generator.c'),
},
libexec_template + {
'name' : 'systemd-ssh-proxy',
'sources' : files('ssh-proxy.c'),
},
]
custom_target(
'20-systemd-ssh-proxy.conf',
input : '20-systemd-ssh-proxy.conf.in',
output : '20-systemd-ssh-proxy.conf',
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
install : true,
install_dir : libexecdir / 'ssh_config.d')
install_emptydir(sshconfdir)
meson.add_install_script(sh, '-c',
ln_s.format(libexecdir / 'ssh_config.d' / '20-systemd-ssh-proxy.conf', sshconfdir / '20-systemd-ssh-proxy.conf'))

View file

@ -0,0 +1,102 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <net/if_arp.h>
#include <stdio.h>
#include <unistd.h>
#include "fd-util.h"
#include "iovec-util.h"
#include "log.h"
#include "main-func.h"
#include "missing_socket.h"
#include "parse-util.h"
#include "socket-util.h"
#include "string-util.h"
#include "strv.h"
static int process_vsock(const char *host, const char *port) {
int r;
assert(host);
assert(port);
union sockaddr_union sa = {
.vm.svm_family = AF_VSOCK,
};
r = vsock_parse_cid(host, &sa.vm.svm_cid);
if (r < 0)
return log_error_errno(r, "Failed to parse vsock cid: %s", host);
r = vsock_parse_port(port, &sa.vm.svm_port);
if (r < 0)
return log_error_errno(r, "Failed to parse vsock port: %s", port);
_cleanup_close_ int fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (fd < 0)
return log_error_errno(errno, "Failed to allocate AF_VSOCK socket: %m");
if (connect(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0)
return log_error_errno(errno, "Failed to connect to vsock:%u:%u: %m", sa.vm.svm_cid, sa.vm.svm_port);
/* OpenSSH wants us to send a single byte along with the file descriptor, hence do so */
r = send_one_fd_iov(STDOUT_FILENO, fd, &IOVEC_NUL_BYTE, /* n_iovec= */ 1, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to send socket via STDOUT: %m");
log_debug("Successfully sent AF_VSOCK socket via STDOUT.");
return 0;
}
static int process_unix(const char *path) {
int r;
assert(path);
/* We assume the path is absolute unless it starts with a dot (or is already explicitly absolute) */
_cleanup_free_ char *prefixed = NULL;
if (!STARTSWITH_SET(path, "/", "./")) {
prefixed = strjoin("/", path);
if (!prefixed)
return log_oom();
path = prefixed;
}
_cleanup_close_ int fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (fd < 0)
return log_error_errno(errno, "Failed to allocate AF_UNIX socket: %m");
r = connect_unix_path(fd, AT_FDCWD, path);
if (r < 0)
return log_error_errno(r, "Failed to connect to AF_UNIX socket %s: %m", path);
r = send_one_fd_iov(STDOUT_FILENO, fd, &IOVEC_NUL_BYTE, /* n_iovec= */ 1, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to send socket via STDOUT: %m");
log_debug("Successfully sent AF_UNIX socket via STDOUT.");
return 0;
}
static int run(int argc, char* argv[]) {
log_setup();
if (argc != 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments: host and port.");
const char *host = argv[1], *port = argv[2];
const char *p = startswith(host, "vsock/");
if (p)
return process_vsock(p, port);
p = startswith(host, "unix/");
if (p)
return process_unix(p);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Don't know how to parse host name specification: %s", host);
}
DEFINE_MAIN_FUNCTION(run);

View file

@ -0,0 +1,10 @@
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
# See tmpfiles.d(5) for details
L {{SSHCONFDIR}}/20-systemd-ssh-proxy.conf - - - - {{LIBEXECDIR}}/ssh_config.d/20-systemd-ssh-proxy.conf

View file

@ -35,6 +35,7 @@ in_files = [['etc.conf', ''],
['systemd.conf', ''],
['var.conf', ''],
['20-systemd-userdb.conf', 'ENABLE_USERDB'],
['20-systemd-ssh-generator.conf', ''],
]
foreach pair : in_files