Merge pull request #30777 from poettering/ssh-generator

ssh-generator which makes VMs and containers accessible to ssh via AF_UNIX and AF_VSOCK
This commit is contained in:
Lennart Poettering
2024-01-11 21:31:28 +01:00
committed by GitHub
27 changed files with 1230 additions and 32 deletions
+6
View File
@@ -8,6 +8,12 @@
#include "alloc-util.h"
#include "macro.h"
/* An iovec pointing to a single NUL byte */
#define IOVEC_NUL_BYTE (const struct iovec) { \
.iov_base = (void*) (const uint8_t[1]) { 0 }, \
.iov_len = 1, \
}
size_t iovec_total_size(const struct iovec *iovec, size_t n);
bool iovec_increment(struct iovec *iovec, size_t n, size_t k);
+21 -3
View File
@@ -28,7 +28,13 @@ static int network_save(Network *network, const char *dest_dir) {
assert(network);
r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
r = generator_open_unit_file_full(
dest_dir,
/* source= */ NULL,
/* name= */ NULL,
&f,
/* ret_final_path= */ NULL,
&temp_path);
if (r < 0)
return r;
@@ -56,7 +62,13 @@ static int netdev_save(NetDev *netdev, const char *dest_dir) {
assert(netdev);
r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
r = generator_open_unit_file_full(
dest_dir,
/* source= */ NULL,
/* name= */ NULL,
&f,
/* ret_final_path= */ NULL,
&temp_path);
if (r < 0)
return r;
@@ -81,7 +93,13 @@ static int link_save(Link *link, const char *dest_dir) {
assert(link);
r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
r = generator_open_unit_file_full(
dest_dir,
/* source= */ NULL,
/* name= */ NULL,
&f,
/* ret_final_path= */ NULL,
&temp_path);
if (r < 0)
return r;
+117 -3
View File
@@ -2,7 +2,6 @@
#include <errno.h>
#include <getopt.h>
#include <linux/fs.h>
#include <linux/loop.h>
#if HAVE_SELINUX
#include <selinux/selinux.h>
@@ -10,6 +9,7 @@
#include <stdlib.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/types.h>
@@ -17,6 +17,8 @@
#include <termios.h>
#include <unistd.h>
#include <linux/fs.h> /* Must be included after <sys/mount.h> */
#include "sd-bus.h"
#include "sd-daemon.h"
#include "sd-id128.h"
@@ -3608,6 +3610,102 @@ static int setup_notify_child(void) {
return TAKE_FD(fd);
}
static int setup_unix_export_dir_outside(char **ret) {
int r;
assert(ret);
_cleanup_free_ char *p = NULL;
p = path_join("/run/systemd/nspawn/unix-export", arg_machine);
if (!p)
return log_oom();
r = path_is_mount_point(p, /* root= */ NULL, 0);
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Mount point '%s' exists already, refusing.", p);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to detect if '%s' is a mount point: %m", p);
r = mkdir_p(p, 0755);
if (r < 0)
return log_error_errno(r, "Failed to create '%s': %m", p);
_cleanup_(rmdir_and_freep) char *q = TAKE_PTR(p);
/* Mount the "unix export" directory really tiny, just 64 inodes. We mark the superblock writable
* (since the container shall bind sockets into it). */
r = mount_nofollow_verbose(
LOG_ERR,
"tmpfs",
q,
"tmpfs",
MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
"size=4M,nr_inodes=64,mode=0755");
if (r < 0)
return r;
_cleanup_(umount_and_rmdir_and_freep) char *w = TAKE_PTR(q);
/* After creating the superblock we change the bind mount to be read-only. This means that the fs
* itself is writable, but not through the mount accessible from the host. */
r = mount_nofollow_verbose(
LOG_ERR,
/* source= */ NULL,
w,
/* fstype= */ NULL,
MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
/* options= */ NULL);
if (r < 0)
return r;
*ret = TAKE_PTR(w);
return 0;
}
static int setup_unix_export_host_inside(const char *directory, const char *unix_export_path) {
int r;
assert(directory);
assert(unix_export_path);
r = make_run_host(directory);
if (r < 0)
return r;
_cleanup_free_ char *p = path_join(directory, "run/host/unix-export");
if (!p)
return log_oom();
if (mkdir(p, 0755) < 0)
return log_error_errno(errno, "Failed to create '%s': %m", p);
r = mount_nofollow_verbose(
LOG_ERR,
unix_export_path,
p,
/* fstype= */ NULL,
MS_BIND,
/* options= */ NULL);
if (r < 0)
return r;
r = mount_nofollow_verbose(
LOG_ERR,
/* source= */ NULL,
p,
/* fstype= */ NULL,
MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
/* options= */ NULL);
if (r < 0)
return r;
r = userns_lchown(p, 0, 0);
if (r < 0)
return log_error_errno(r, "Failed to chown '%s': %m", p);
return 0;
}
static int outer_child(
Barrier *barrier,
const char *directory,
@@ -3615,7 +3713,8 @@ static int outer_child(
int fd_outer_socket,
int fd_inner_socket,
FDSet *fds,
int netns_fd) {
int netns_fd,
const char *unix_export_path) {
_cleanup_(bind_user_context_freep) BindUserContext *bind_user_context = NULL;
_cleanup_strv_free_ char **os_release_pairs = NULL;
@@ -3909,6 +4008,10 @@ static int outer_child(
p = prefix_roota(directory, "/run/host");
(void) make_inaccessible_nodes(p, arg_uid_shift, arg_uid_shift);
r = setup_unix_export_host_inside(directory, unix_export_path);
if (r < 0)
return r;
r = setup_pts(directory);
if (r < 0)
return r;
@@ -4760,6 +4863,7 @@ static int run_container(
_cleanup_close_ int notify_socket = -EBADF, mntns_fd = -EBADF, fd_kmsg_fifo = -EBADF;
_cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL;
_cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
_cleanup_(umount_and_rmdir_and_freep) char *unix_export_host_dir = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
@@ -4775,6 +4879,11 @@ static int run_container(
assert_se(sigemptyset(&mask_chld) == 0);
assert_se(sigaddset(&mask_chld, SIGCHLD) == 0);
/* Set up the unix export host directory on the host first */
r = setup_unix_export_dir_outside(&unix_export_host_dir);
if (r < 0)
return r;
if (arg_userns_mode == USER_NAMESPACE_PICK) {
/* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely
* check with getpwuid() if the specific user already exists. Note that /etc might be
@@ -4845,7 +4954,8 @@ static int run_container(
fd_outer_socket_pair[1],
fd_inner_socket_pair[1],
fds,
child_netns_fd);
child_netns_fd,
unix_export_host_dir);
if (r < 0)
_exit(EXIT_FAILURE);
@@ -5919,6 +6029,10 @@ finish:
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
(void) rm_rf(p, REMOVE_ROOT);
p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine);
(void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
(void) rmdir(p);
}
expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET, &expose_args.address4);
+22 -8
View File
@@ -29,6 +29,7 @@ int generator_open_unit_file_full(
const char *source,
const char *fn,
FILE **ret_file,
char **ret_final_path,
char **ret_temp_path) {
_cleanup_free_ char *p = NULL;
@@ -72,10 +73,13 @@ int generator_open_unit_file_full(
program_invocation_short_name);
*ret_file = f;
if (ret_final_path)
*ret_final_path = TAKE_PTR(p);
return 0;
}
int generator_add_symlink_full(
const char *dir,
const char *dst,
@@ -88,11 +92,13 @@ int generator_add_symlink_full(
assert(dir);
assert(dst);
assert(dep_type);
assert(src);
/* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). If
* <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
/* If 'dep_type' is specified adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise).
*
* If 'dep_type' is NULL, it will create a symlink to <src> (i.e. create an alias.
*
* If <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
r = path_extract_directory(src, &dn);
if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */
@@ -110,11 +116,19 @@ int generator_add_symlink_full(
return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance);
}
from = path_join(dn ?: "..", fn);
if (!from)
return log_oom();
if (dep_type) { /* Create a .wants/ style dep */
from = path_join(dn ?: "..", fn);
if (!from)
return log_oom();
to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
} else { /* or create an alias */
from = dn ? path_join(dn, fn) : strdup(fn);
if (!from)
return log_oom();
to = strjoin(dir, "/", dst);
}
if (!to)
return log_oom();
+2 -2
View File
@@ -6,10 +6,10 @@
#include "macro.h"
#include "main-func.h"
int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_temp_path);
int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_final_path, char **ret_temp_path);
static inline int generator_open_unit_file(const char *dest, const char *source, const char *name, FILE **ret_file) {
return generator_open_unit_file_full(dest, source, name, ret_file, NULL);
return generator_open_unit_file_full(dest, source, name, ret_file, NULL, NULL);
}
int generator_add_symlink_full(const char *dir, const char *dst, const char *dep_type, const char *src, const char *instance);
+27 -4
View File
@@ -3142,8 +3142,10 @@ int unit_file_get_state(
return unit_file_lookup_state(scope, &lp, name, ret);
}
int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name) {
_cleanup_(install_context_done) InstallContext c = { .scope = scope };
int unit_file_exists_full(RuntimeScope scope, const LookupPaths *lp, const char *name, char **ret_path) {
_cleanup_(install_context_done) InstallContext c = {
.scope = scope,
};
int r;
assert(lp);
@@ -3152,12 +3154,33 @@ int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name
if (!unit_name_is_valid(name, UNIT_NAME_ANY))
return -EINVAL;
r = install_info_discover(&c, lp, name, 0, NULL, NULL, NULL);
if (r == -ENOENT)
InstallInfo *info = NULL;
r = install_info_discover(
&c,
lp,
name,
/* flags= */ 0,
ret_path ? &info : NULL,
/* changes= */ NULL,
/* n_changes= */ NULL);
if (r == -ENOENT) {
if (ret_path)
*ret_path = NULL;
return 0;
}
if (r < 0)
return r;
if (ret_path) {
assert(info);
_cleanup_free_ char *p = strdup(info->path);
if (!p)
return -ENOMEM;
*ret_path = TAKE_PTR(p);
}
return 1;
}
+5 -1
View File
@@ -193,7 +193,11 @@ int unit_file_lookup_state(
UnitFileState *ret);
int unit_file_get_state(RuntimeScope scope, const char *root_dir, const char *filename, UnitFileState *ret);
int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name);
int unit_file_exists_full(RuntimeScope scope, const LookupPaths *paths, const char *name, char **ret_path);
static inline int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name) {
return unit_file_exists_full(scope, paths, name, NULL);
}
int unit_file_get_list(RuntimeScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns);
@@ -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
+25
View File
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
executables += [
generator_template + {
'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'))
+476
View File
@@ -0,0 +1,476 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "creds-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "generator.h"
#include "install.h"
#include "missing_socket.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "socket-netlink.h"
#include "socket-util.h"
#include "special.h"
#include "virt.h"
/* A small generator binding potentially five or more SSH sockets:
*
* 1. Listen on AF_VSOCK port 22 if we run in a VM with AF_VSOCK enabled
* 2. Listen on AF_UNIX socket /run/host/unix-export/ssh if we run in a container with /run/host/ support
* 3. Listen on AF_UNIX socket /run/ssh-unix-local/socket (always)
* 4. Listen on any socket specified via kernel command line option systemd.ssh_listen=
* 5. Similar, but from system credential ssh.listen
*
* The first two provide a nice way for hosts to connect to containers and VMs they invoke via the usual SSH
* logic, but without waiting for networking or suchlike. The third allows the same for local clients. */
static const char *arg_dest = NULL;
static bool arg_auto = true;
static char **arg_listen_extra = NULL;
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
int r;
assert(key);
if (proc_cmdline_key_streq(key, "systemd.ssh_auto")) {
r = value ? parse_boolean(value) : 1;
if (r < 0)
log_warning_errno(r, "Failed to parse systemd.ssh_auto switch \"%s\", ignoring: %m", value);
else
arg_auto = r;
} else if (proc_cmdline_key_streq(key, "systemd.ssh_listen")) {
if (proc_cmdline_value_missing(key, value))
return 0;
SocketAddress sa;
r = socket_address_parse(&sa, value);
if (r < 0)
log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", value);
else {
_cleanup_free_ char *s = NULL;
r = socket_address_print(&sa, &s);
if (r < 0)
return log_error_errno(r, "Failed to format socket address: %m");
if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
return log_oom();
}
}
return 0;
}
static int make_sshd_template_unit(
const char *dest,
const char *template,
const char *sshd_binary,
const char *found_sshd_template_service,
char **generated_sshd_template_unit) {
int r;
assert(dest);
assert(template);
assert(sshd_binary);
assert(generated_sshd_template_unit);
/* If the system has a suitable template already, symlink it to the name we want to reuse it */
if (found_sshd_template_service)
return generator_add_symlink(
dest,
template,
/* dep_type= */ NULL,
found_sshd_template_service);
if (!*generated_sshd_template_unit) {
_cleanup_fclose_ FILE *f = NULL;
r = generator_open_unit_file_full(
dest,
/* source= */ NULL,
"sshd-generated@.service", /* Give this generated unit a generic name, since we want to use it for both AF_UNIX and AF_VSOCK */
&f,
generated_sshd_template_unit,
/* ret_temp_path= */ NULL);
if (r < 0)
return r;
fprintf(f,
"[Unit]\n"
"Description=OpenSSH Per-Connection Server Daemon\n"
"Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n"
"[Service]\n"
"ExecStart=-%s -i\n"
"StandardInput=socket",
sshd_binary);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write sshd template: %m");
}
return generator_add_symlink(
dest,
template,
/* dep_type= */ NULL,
*generated_sshd_template_unit);
}
static int write_socket_unit(
const char *dest,
const char *unit,
const char *listen_stream,
const char *comment) {
int r;
assert(dest);
assert(unit);
assert(listen_stream);
assert(comment);
_cleanup_fclose_ FILE *f = NULL;
r = generator_open_unit_file(
dest,
/* source= */ NULL,
unit,
&f);
if (r < 0)
return r;
fprintf(f,
"[Unit]\n"
"Description=OpenSSH Server Socket (systemd-ssh-generator, %s)\n"
"Documentation=man:systemd-ssh-generator(8)\n"
"\n[Socket]\n"
"ListenStream=%s\n"
"Accept=yes\n"
"PollLimitIntervalSec=30s\n"
"PollLimitBurst=50\n",
comment,
listen_stream);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment);
r = generator_add_symlink(
dest,
SPECIAL_SOCKETS_TARGET,
"wants",
unit);
if (r < 0)
return r;
return 0;
}
static int add_vsock_socket(
const char *dest,
const char *sshd_binary,
const char *found_sshd_template_unit,
char **generated_sshd_template_unit) {
int r;
assert(dest);
assert(generated_sshd_template_unit);
Virtualization v = detect_vm();
if (v < 0)
return log_error_errno(v, "Failed to detect if we run in a VM: %m");
if (v == VIRTUALIZATION_NONE) {
log_debug("Not running in a VM, not listening on AF_VSOCK.");
return 0;
}
_cleanup_close_ int vsock_fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (vsock_fd < 0) {
if (ERRNO_IS_NOT_SUPPORTED(errno)) {
log_debug("Not creating AF_VSOCK ssh listener, since AF_VSOCK is not available.");
return 0;
}
return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m");
}
vsock_fd = safe_close(vsock_fd);
/* Determine the local CID so that we can log it to help users to connect to this VM */
unsigned local_cid;
r = vsock_get_local_cid(&local_cid);
if (r < 0)
return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m");
r = make_sshd_template_unit(
dest,
"sshd-vsock@.service",
sshd_binary,
found_sshd_template_unit,
generated_sshd_template_unit);
if (r < 0)
return r;
r = write_socket_unit(
dest,
"sshd-vsock.socket",
"vsock::22",
"AF_VSOCK");
if (r < 0)
return r;
log_info("Binding SSH to AF_VSOCK vsock::22.\n"
"→ connect via 'ssh vsock/%u' from host", local_cid);
return 0;
}
static int add_local_unix_socket(
const char *dest,
const char *sshd_binary,
const char *found_sshd_template_unit,
char **generated_sshd_template_unit) {
int r;
assert(dest);
assert(sshd_binary);
assert(generated_sshd_template_unit);
r = make_sshd_template_unit(
dest,
"sshd-unix-local@.service",
sshd_binary,
found_sshd_template_unit,
generated_sshd_template_unit);
if (r < 0)
return r;
r = write_socket_unit(
dest,
"sshd-unix-local.socket",
"/run/ssh-unix-local/socket",
"AF_UNIX Local");
if (r < 0)
return r;
log_info("Binding SSH to AF_UNIX socket /run/ssh-unix-local/socket.\n"
"→ connect via 'ssh .host' locally");
return 0;
}
static int add_export_unix_socket(
const char *dest,
const char *sshd_binary,
const char *found_sshd_template_unit,
char **generated_sshd_template_unit) {
int r;
assert(dest);
assert(sshd_binary);
assert(generated_sshd_template_unit);
Virtualization v = detect_container();
if (v < 0)
return log_error_errno(v, "Failed to detect if we run in a container: %m");
if (v == VIRTUALIZATION_NONE) {
log_debug("Not running in container, not listening on /run/host/unix-export/ssh");
return 0;
}
if (access("/run/host/unix-export/", W_OK) < 0) {
if (errno == ENOENT) {
log_debug("Container manager does not provide /run/host/unix-export/ mount, not binding AF_UNIX socket there.");
return 0;
}
if (errno == EROFS || ERRNO_IS_PRIVILEGE(errno)) {
log_debug("Container manager does not provide write access to /run/host/unix-export/, not binding AF_UNIX socket there.");
return 0;
}
return log_debug_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
}
r = make_sshd_template_unit(
dest,
"sshd-unix-export@.service",
sshd_binary,
found_sshd_template_unit,
generated_sshd_template_unit);
if (r < 0)
return r;
r = write_socket_unit(
dest,
"sshd-unix-export.socket",
"/run/host/unix-export/ssh",
"AF_UNIX Export");
if (r < 0)
return r;
log_info("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n"
"→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host");
return 0;
}
static int add_extra_sockets(
const char *dest,
const char *sshd_binary,
const char *found_sshd_template_unit,
char **generated_sshd_template_unit) {
unsigned n = 1;
int r;
assert(dest);
assert(sshd_binary);
assert(generated_sshd_template_unit);
if (strv_isempty(arg_listen_extra))
return 0;
STRV_FOREACH(i, arg_listen_extra) {
_cleanup_free_ char *service = NULL, *socket = NULL;
if (n > 1) {
if (asprintf(&service, "sshd-extra-%u@.service", n) < 0)
return log_oom();
if (asprintf(&socket, "sshd-extra-%u.socket", n) < 0)
return log_oom();
}
r = make_sshd_template_unit(
dest,
service ?: "sshd-extra@.service",
sshd_binary,
found_sshd_template_unit,
generated_sshd_template_unit);
if (r < 0)
return r;
r = write_socket_unit(
dest,
socket ?: "sshd-extra.socket",
*i,
*i);
if (r < 0)
return r;
log_info("Binding SSH to socket %s.", *i);
n++;
}
return 0;
}
static int parse_credentials(void) {
_cleanup_free_ char *b = NULL;
size_t sz = 0;
int r;
r = read_credential_with_decryption("ssh.listen", (void*) &b, &sz);
if (r < 0)
return r;
if (r == 0)
return 0;
_cleanup_fclose_ FILE *f = NULL;
f = fmemopen_unlocked(b, sz, "r");
if (!f)
return log_oom();
for (;;) {
_cleanup_free_ char *item = NULL;
r = read_stripped_line(f, LINE_MAX, &item);
if (r == 0)
break;
if (r < 0) {
log_error_errno(r, "Failed to parse credential 'ssh.listen': %m");
break;
}
if (startswith(item, "#"))
continue;
SocketAddress sa;
r = socket_address_parse(&sa, item);
if (r < 0) {
log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", item);
continue;
}
_cleanup_free_ char *s = NULL;
r = socket_address_print(&sa, &s);
if (r < 0)
return log_error_errno(r, "Failed to format socket address: %m");
if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
return log_oom();
}
return 0;
}
static int run(const char *dest, const char *dest_early, const char *dest_late) {
int r;
assert_se(arg_dest = dest);
r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, /* flags= */ 0);
if (r < 0)
log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
(void) parse_credentials();
strv_sort(arg_listen_extra);
strv_uniq(arg_listen_extra);
if (!arg_auto && strv_isempty(arg_listen_extra)) {
log_debug("Disabling SSH generator logic, because as it has been turned off explicitly.");
return 0;
}
_cleanup_free_ char *sshd_binary = NULL;
r = find_executable("sshd", &sshd_binary);
if (r == -ENOENT) {
log_info("Disabling SSH generator logic, since sshd is not installed.");
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to determine if sshd is installed: %m");
_cleanup_(lookup_paths_free) LookupPaths lp = {};
r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, /* root_dir= */ NULL);
if (r < 0)
return r;
_cleanup_free_ char *found_sshd_template_unit = NULL;
r = unit_file_exists_full(RUNTIME_SCOPE_SYSTEM, &lp, "sshd@.service", &found_sshd_template_unit);
if (r < 0)
return log_error_errno(r, "Unable to detect if sshd@.service exists: %m");
_cleanup_free_ char *generated_sshd_template_unit = NULL;
RET_GATHER(r, add_extra_sockets(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
if (arg_auto) {
RET_GATHER(r, add_vsock_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
RET_GATHER(r, add_local_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
RET_GATHER(r, add_export_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
}
return r;
}
DEFINE_MAIN_GENERATOR_FUNCTION(run);
+102
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);