diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 0113fd59fa..e3daabe3bc 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 77acaab0f5..eff49af349 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 e24da3f893..67ed765233 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 e3eb62f198..2d17630d29 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 5a962699c7..d97de3f604 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"