From: Lennart Poettering Date: Mon, 8 Jan 2024 21:26:17 +0000 (+0100) Subject: varlink: add "ssh:" transport X-Git-Tag: v256-rc1~1232^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a1bb30de7f5d9c4f7f01061240d984a786d5d00c;p=thirdparty%2Fsystemd.git varlink: add "ssh:" transport This uses openssh 9.4's -W support for AF_UNIX. Unfortunately older versions don't work with this, and I couldn#t figure a way that would work for older versions too, would not be racy and where we'd still could keep track of the forked off ssh process. Unfortunately, on older versions -W will just hang (because it tries to resolve the AF_UNIX path as regular host name), which sucks, but hopefully this issue will go away sooner or later on its own, as distributions update. Fedora is still stuck at 9.3 at the time of posting this (even on Fedora), even though 9.4, 9.5, 9.6 have all already been released by now. Example: varlinkctl call -j ssh:root@somehost:/run/systemd/io.systemd.Credentials io.systemd.Credentials.Encrypt '{"text":"foobar"}' --- diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 0113fd59fa2..e3daabe3bce 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -610,3 +610,8 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \ latter two via the environment variable unless `systemd-storagetm` is invoked to expose a single device only, since those identifiers better should be kept unique. + +Tools using the Varlink protocol, such as `varlinkctl`: + +* `$SYSTEMD_SSH` – the ssh binary to invoke when the `ssh:` transport is + used. May be a filename (which is searched for in `$PATH`) or absolute path. diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index 77acaab0f5b..eff49af349b 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -71,11 +71,16 @@ A Varlink service reference starting with the unix: string, followed - by an absolute AF_UNIX path, or by @ and an arbitrary string + by an absolute AF_UNIX socket path, or by @ and an arbitrary string (the latter for referencing sockets in the abstract namespace). A Varlink service reference starting with the exec: string, followed by an absolute path of a binary to execute. + + A Varlink service reference starting with the ssh: string, followed + by an SSH host specification, followed by :, followed by an absolute + AF_UNIX socket path. (This requires OpenSSH 9.4 or newer on the server side, + abstract namespace sockets are not supported.) For convenience these two simpler (redundant) service address syntaxes are also supported: diff --git a/src/shared/varlink.c b/src/shared/varlink.c index e24da3f8934..67ed7652336 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -511,39 +511,120 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) { return 0; } +static int varlink_connect_ssh(Varlink **ret, const char *where) { + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; + _cleanup_(sigkill_waitp) pid_t pid = 0; + int r; + + assert_return(ret, -EINVAL); + assert_return(where, -EINVAL); + + /* Connects to an SSH server via OpenSSH 9.4's -W switch to connect to a remote AF_UNIX socket. For + * now we do not expose this function directly, but only via varlink_connect_url(). */ + + const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; + if (!path_is_valid(ssh)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH path is not valid, refusing: %s", ssh); + + const char *e = strchr(where, ':'); + if (!e) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH specification lacks a : separator between host and path, refusing: %s", where); + + _cleanup_free_ char *h = strndup(where, e - where); + if (!h) + return log_oom_debug(); + + _cleanup_free_ char *c = strdup(e + 1); + if (!c) + return log_oom_debug(); + + if (!path_is_absolute(c)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Remote AF_UNIX socket path is not absolute, refusing: %s", c); + + _cleanup_free_ char *p = NULL; + r = path_simplify_alloc(c, &p); + if (r < 0) + return r; + + if (!path_is_normalized(p)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing: %s", p); + + log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h); + + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) + return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + + r = safe_fork_full( + "(sd-vlssh)", + /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO }, + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO, + &pid); + if (r < 0) + return log_debug_errno(r, "Failed to spawn process: %m"); + if (r == 0) { + /* Child */ + + execlp(ssh, "ssh", "-W", p, h, NULL); + log_debug_errno(errno, "Failed to invoke %s: %m", ssh); + _exit(EXIT_FAILURE); + } + + pair[1] = safe_close(pair[1]); + + Varlink *v; + r = varlink_new(&v); + if (r < 0) + return log_debug_errno(r, "Failed to create varlink object: %m"); + + v->fd = TAKE_FD(pair[0]); + v->af = AF_UNIX; + v->exec_pid = TAKE_PID(pid); + varlink_set_state(v, VARLINK_IDLE_CLIENT); + + *ret = v; + return 0; +} + int varlink_connect_url(Varlink **ret, const char *url) { _cleanup_free_ char *c = NULL; const char *p; - bool exec; + enum { + SCHEME_UNIX, + SCHEME_EXEC, + SCHEME_SSH, + } scheme; int r; assert_return(ret, -EINVAL); assert_return(url, -EINVAL); - // FIXME: Add support for vsock:, ssh-exec:, ssh-unix: URL schemes here. (The latter with OpenSSH - // 9.4's -W switch for referencing remote AF_UNIX sockets.) + // FIXME: Maybe add support for vsock: and ssh-exec: URL schemes here. - /* The Varlink URL scheme is a bit underdefined. We support only the unix: transport for now, plus an - * exec: transport we made up ourselves. Strictly speaking this shouldn't even be called URL, since - * it has nothing to do with Internet URLs by RFC. */ + /* The Varlink URL scheme is a bit underdefined. We support only the spec-defined unix: transport for + * now, plus exec:, ssh: transports we made up ourselves. Strictly speaking this shouldn't even be + * called "URL", since it has nothing to do with Internet URLs by RFC. */ p = startswith(url, "unix:"); if (p) - exec = false; - else { - p = startswith(url, "exec:"); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported."); - - exec = true; - } + scheme = SCHEME_UNIX; + else if ((p = startswith(url, "exec:"))) + scheme = SCHEME_EXEC; + else if ((p = startswith(url, "ssh:"))) + scheme = SCHEME_SSH; + else + return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported."); /* The varlink.org reference C library supports more than just file system paths. We might want to * support that one day too. For now simply refuse that. */ if (p[strcspn(p, ";?#")] != '\0') return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported."); - if (exec || p[0] != '@') { /* no validity checks for abstract namespace */ + if (scheme == SCHEME_SSH) + return varlink_connect_ssh(ret, p); + + if (scheme == SCHEME_EXEC || p[0] != '@') { /* no path validity checks for abstract namespace sockets */ if (!path_is_absolute(p)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path not absolute, refusing."); @@ -556,7 +637,7 @@ int varlink_connect_url(Varlink **ret, const char *url) { return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing."); } - if (exec) + if (scheme == SCHEME_EXEC) return varlink_connect_exec(ret, c, NULL); return varlink_connect_address(ret, c ?: p); diff --git a/test/TEST-74-AUX-UTILS/test.sh b/test/TEST-74-AUX-UTILS/test.sh index e3eb62f198c..2d17630d29e 100755 --- a/test/TEST-74-AUX-UTILS/test.sh +++ b/test/TEST-74-AUX-UTILS/test.sh @@ -25,6 +25,8 @@ test_append_files() { install_mdadm generate_module_dependencies fi + + image_install socat } do_test "$@" diff --git a/test/units/testsuite-74.varlinkctl.sh b/test/units/testsuite-74.varlinkctl.sh index 5a962699c70..d97de3f6043 100755 --- a/test/units/testsuite-74.varlinkctl.sh +++ b/test/units/testsuite-74.varlinkctl.sh @@ -53,6 +53,32 @@ if [[ -x /usr/lib/systemd/systemd-pcrextend ]]; then varlinkctl introspect /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend fi +# SSH transport +SSHBINDIR="$(mktemp -d)" + +rm_rf_sshbindir() { + rm -rf "$SSHBINDIR" +} + +trap rm_rf_sshbindir EXIT + +# Create a fake "ssh" binary that validates everything works as expected +cat > "$SSHBINDIR"/ssh <<'EOF' +#!/bin/sh + +set -xe + +test "$1" = "-W" +test "$2" = "/run/systemd/journal/io.systemd.journal" +test "$3" = "foobar" + +exec socat - UNIX-CONNECT:/run/systemd/journal/io.systemd.journal +EOF + +chmod +x "$SSHBINDIR"/ssh + +SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl info ssh:foobar:/run/systemd/journal/io.systemd.journal + # Go through all varlink sockets we can find under /run/systemd/ for some extra coverage find /run/systemd/ -name "io.systemd*" -type s | while read -r socket; do varlinkctl info "$socket"