diff --git a/man/rules/meson.build b/man/rules/meson.build
index 84555df905..c43bffde69 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -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',
diff --git a/man/systemd-ssh-proxy.xml b/man/systemd-ssh-proxy.xml
new file mode 100644
index 0000000000..d9615ff62c
--- /dev/null
+++ b/man/systemd-ssh-proxy.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+ systemd-ssh-proxy
+ systemd
+
+
+
+ systemd-ssh-proxy
+ 1
+
+
+
+ systemd-ssh-proxy
+ SSH client plugin for connecting to AF_VSOCK and
+ AF_UNIX sockets
+
+
+
+
+Host unix/* vsock/*
+ ProxyCommand /usr/lib/systemd/systemd-ssh-proxy %h %p
+ ProxyUseFdpass yes
+
+
+ /usr/lib/systemd/systemd-ssh-proxy ADDRESS PORT
+
+
+
+
+
+ Description
+
+ systemd-ssh-proxy is a small "proxy" plugin for the ssh1
+ tool that allows connecting to AF_UNIX and AF_VSOCK sockets. It
+ implements the interface defined by ssh's ProxyCommand
+ configuration option. It's supposed to be used with an ssh_config5
+ configuration fragment like the following:
+
+
+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
+
+
+ A configuration fragment along these lines is by default installed into
+ /etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf.in.
+
+ With this in place, SSH connections to host string unix/ followed by an absolute
+ AF_UNIX file system path to a socket will be directed to the specified socket, which
+ must be of type SOCK_STREAM. Similar, SSH connections to vsock/
+ followed by an AF_VSOCK CID will result in an SSH connection made to that
+ CID. Moreover connecting to .host will connect to the local host via SSH, without
+ involving networking.
+
+ This tool is supposed to be used together with
+ systemd-ssh-generator8
+ which when run inside a VM or container will bind SSH to suitable
+ addresses. systemd-ssh-generator is supposed to run in the container of VM guest, and
+ systemd-ssh-proxy is run on the host, in order to connect to the container or VM
+ guest.
+
+
+
+ Exit status
+
+ On success, 0 is returned, a non-zero failure code
+ otherwise.
+
+
+
+ Examples
+
+
+ Talk to a local VM with CID 4711
+
+ ssh vsock/4711
+
+
+
+ Talk to the local host via ssh
+
+ ssh .host
+
+ or equivalent:
+
+ ssh unix/run/ssh-unix-local/socket
+
+
+
+
+ See Also
+
+ systemd1
+ systemd-ssh-generator8
+ vsock7
+ unix7
+ ssh1
+ sshd8
+
+
+
diff --git a/meson.build b/meson.build
index 9074f14069..53a3d966ff 100644
--- a/meson.build
+++ b/meson.build
@@ -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,
diff --git a/meson_options.txt b/meson_options.txt
index c677c7f420..b74f949189 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -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',
diff --git a/src/ssh-generator/20-systemd-ssh-proxy.conf.in b/src/ssh-generator/20-systemd-ssh-proxy.conf.in
new file mode 100644
index 0000000000..b97e0f5340
--- /dev/null
+++ b/src/ssh-generator/20-systemd-ssh-proxy.conf.in
@@ -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
diff --git a/src/ssh-generator/meson.build b/src/ssh-generator/meson.build
index d21a7a36d9..70a706f2aa 100644
--- a/src/ssh-generator/meson.build
+++ b/src/ssh-generator/meson.build
@@ -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'))
diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c
new file mode 100644
index 0000000000..4884c934d7
--- /dev/null
+++ b/src/ssh-generator/ssh-proxy.c
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+#include
+#include
+
+#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);
diff --git a/tmpfiles.d/20-systemd-ssh-generator.conf.in b/tmpfiles.d/20-systemd-ssh-generator.conf.in
new file mode 100644
index 0000000000..033379ec7a
--- /dev/null
+++ b/tmpfiles.d/20-systemd-ssh-generator.conf.in
@@ -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
diff --git a/tmpfiles.d/meson.build b/tmpfiles.d/meson.build
index 390076b6d5..d05ea94c16 100644
--- a/tmpfiles.d/meson.build
+++ b/tmpfiles.d/meson.build
@@ -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