]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ssh-proxy: add ssh ProxyCommand tool that can connect to AF_UNIX + AF_VSOCK sockets
authorLennart Poettering <lennart@poettering.net>
Thu, 4 Jan 2024 22:31:51 +0000 (23:31 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 11 Jan 2024 15:05:20 +0000 (16:05 +0100)
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.

man/rules/meson.build
man/systemd-ssh-proxy.xml [new file with mode: 0644]
meson.build
meson_options.txt
src/ssh-generator/20-systemd-ssh-proxy.conf.in [new file with mode: 0644]
src/ssh-generator/meson.build
src/ssh-generator/ssh-proxy.c [new file with mode: 0644]
tmpfiles.d/20-systemd-ssh-generator.conf.in [new file with mode: 0644]
tmpfiles.d/meson.build

index 84555df9057b376782754dde3a4a990f99493fb2..c43bffde693850ac56dfc6b9c49c7296ae9dd603 100644 (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',
diff --git a/man/systemd-ssh-proxy.xml b/man/systemd-ssh-proxy.xml
new file mode 100644 (file)
index 0000000..d9615ff
--- /dev/null
@@ -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>
index 9074f14069fdb95e013d342bb0f64db721033ce0..53a3d966ff4b30eb98fe53b25d6424a6a5aa786a 100644 (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,
index c677c7f4202f194e49a00d1cff39b206fdbdbfed..b74f949189635f0d802d892e56ffda79ca1ff1e5 100644 (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',
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 (file)
index 0000000..b97e0f5
--- /dev/null
@@ -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
index d21a7a36d9506173558e27ae4b1f3824d952dada..70a706f2aa8de647e1c08f9ade62897e17d4e512 100644 (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'))
diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c
new file mode 100644 (file)
index 0000000..4884c93
--- /dev/null
@@ -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);
diff --git a/tmpfiles.d/20-systemd-ssh-generator.conf.in b/tmpfiles.d/20-systemd-ssh-generator.conf.in
new file mode 100644 (file)
index 0000000..033379e
--- /dev/null
@@ -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
index 390076b6d5004712c2b307275ed65a1d79df0a2c..d05ea94c160583f0b7ed76b1a0302a375f6d1560 100644 (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