From cfd89202435751a0c46a46a81a6d83fb92f4b4db Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 12 Jun 2025 11:38:21 +0200 Subject: [PATCH] ssh-generator: generate /etc/issue.d/ with VSOCK ssh info data I find myself trying to log into a fresh ParticleOS VM started via systemd-vmspawn all the time, but I don't know its CID. Let's show it on the getty screen, to make it immediately visible. --- man/rules/meson.build | 1 + man/systemd-ssh-issue.xml | 99 ++++++++++++ src/ssh-generator/meson.build | 4 + src/ssh-generator/ssh-generator.c | 9 ++ src/ssh-generator/ssh-issue.c | 242 ++++++++++++++++++++++++++++++ 5 files changed, 355 insertions(+) create mode 100644 man/systemd-ssh-issue.xml create mode 100644 src/ssh-generator/ssh-issue.c diff --git a/man/rules/meson.build b/man/rules/meson.build index b5dfdb925c4..12e9fd97aaa 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1118,6 +1118,7 @@ manpages = [ ['systemd-socket-proxyd', '8', [], ''], ['systemd-soft-reboot.service', '8', [], ''], ['systemd-ssh-generator', '8', [], ''], + ['systemd-ssh-issue', '1', [], ''], ['systemd-ssh-proxy', '1', [], ''], ['systemd-stdio-bridge', '1', [], ''], ['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'], diff --git a/man/systemd-ssh-issue.xml b/man/systemd-ssh-issue.xml new file mode 100644 index 00000000000..4e887796764 --- /dev/null +++ b/man/systemd-ssh-issue.xml @@ -0,0 +1,99 @@ + + + + + + + + systemd-ssh-issue + systemd + + + + systemd-ssh-issue + 1 + + + + systemd-ssh-issue + Generator for SSH login prompt drop-ins + + + + + /usr/lib/systemd/systemd-ssh-issue + /usr/lib/systemd/systemd-ssh-issue + + + + + Description + + systemd-ssh-issue is a small tool that generates a + /run/issue.d/50-ssh-vsock.issue drop-in file in case AF_VSOCK + support is available in the kernel and the VM environment. The file contains brief information about how + to contact the local system via SSH-over-AF_VSOCK, in particular it reports the + system's AF_VSOCK CID. The file is typically read and displayed by agetty8 on + console or serial login prompts. + + This tool is automatically used by + systemd-ssh-generator8 + in the AF_VSOCK socket units it generates. + + + + Options + The following options are understood: + + + + + Generates the issue file. This command has no effect if called on systems lacking + AF_VSOCK support. + + + + + + + Removes the issue file if it exists. + + + + + + + Changes the path to the issue file to write to/remove. If not specified, defaults to + /run/issue.d/50-ssh-vsock.issue. If specified as empty string or + - writes the issue file contents to standard output. + + + + + + + + + + + Exit status + + On success, 0 is returned, a non-zero failure code + otherwise. + + + + See Also + + systemd1 + systemd-ssh-generator8 + vsock7 + ssh1 + sshd8 + agetty8 + + + diff --git a/src/ssh-generator/meson.build b/src/ssh-generator/meson.build index b14ef46ff5a..f281a251849 100644 --- a/src/ssh-generator/meson.build +++ b/src/ssh-generator/meson.build @@ -9,6 +9,10 @@ executables += [ 'name' : 'systemd-ssh-proxy', 'sources' : files('ssh-proxy.c'), }, + libexec_template + { + 'name' : 'systemd-ssh-issue', + 'sources' : files('ssh-issue.c'), + }, ] if conf.get('ENABLE_SSH_PROXY_CONFIG') == 1 diff --git a/src/ssh-generator/ssh-generator.c b/src/ssh-generator/ssh-generator.c index 3f1fd8cc30b..0c7eba366a0 100644 --- a/src/ssh-generator/ssh-generator.c +++ b/src/ssh-generator/ssh-generator.c @@ -134,6 +134,7 @@ static int write_socket_unit( const char *unit, const char *listen_stream, const char *comment, + const char *extra, bool with_ssh_access_target_dependency) { int r; @@ -172,6 +173,9 @@ static int write_socket_unit( "PollLimitBurst=50\n", listen_stream); + if (extra) + fputs(extra, f); + r = fflush_and_check(f); if (r < 0) return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment); @@ -245,6 +249,8 @@ static int add_vsock_socket( "sshd-vsock.socket", "vsock::22", "AF_VSOCK", + "ExecStartPost=-/usr/lib/systemd/systemd-ssh-issue --make-vsock\n" + "ExecStopPre=-/usr/lib/systemd/systemd-ssh-issue --rm-vsock\n", /* with_ssh_access_target_dependency= */ true); if (r < 0) return r; @@ -280,6 +286,7 @@ static int add_local_unix_socket( "sshd-unix-local.socket", "/run/ssh-unix-local/socket", "AF_UNIX Local", + /* extra= */ NULL, /* with_ssh_access_target_dependency= */ false); if (r < 0) return r; @@ -336,6 +343,7 @@ static int add_export_unix_socket( "sshd-unix-export.socket", "/run/host/unix-export/ssh", "AF_UNIX Export", + /* extra= */ NULL, /* with_ssh_access_target_dependency= */ true); if (r < 0) return r; @@ -387,6 +395,7 @@ static int add_extra_sockets( socket ?: "sshd-extra.socket", *i, *i, + /* extra= */ NULL, /* with_ssh_access_target_dependency= */ true); if (r < 0) return r; diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c new file mode 100644 index 00000000000..68ad66bfd09 --- /dev/null +++ b/src/ssh-generator/ssh-issue.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "alloc-util.h" +#include "ansi-color.h" +#include "build.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "log.h" +#include "main-func.h" +#include "mkdir.h" +#include "parse-argument.h" +#include "pretty-print.h" +#include "socket-util.h" +#include "string-util.h" +#include "tmpfile-util.h" +#include "virt.h" + +static enum { + ACTION_MAKE_VSOCK, + ACTION_RM_VSOCK, +} arg_action = ACTION_MAKE_VSOCK; + +static char* arg_issue_path = NULL; +static bool arg_issue_stdout = false; + +STATIC_DESTRUCTOR_REGISTER(arg_issue_path, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-ssh-issue", "1", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...] --make-vsock\n" + "%s [OPTIONS...] --rm-vsock\n" + "\n%sCreate ssh /run/issue.d/ file reporting VSOCK address.%s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --issue-path=PATH Change path to /run/issue.d/50-ssh-vsock.issue\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_MAKE_VSOCK = 0x100, + ARG_RM_VSOCK, + ARG_ISSUE_PATH, + ARG_VERSION, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "make-vsock", no_argument, NULL, ARG_MAKE_VSOCK }, + { "rm-vsock", no_argument, NULL, ARG_RM_VSOCK }, + { "issue-path", required_argument, NULL, ARG_ISSUE_PATH }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_MAKE_VSOCK: + arg_action = ACTION_MAKE_VSOCK; + break; + + case ARG_RM_VSOCK: + arg_action = ACTION_RM_VSOCK; + break; + + case ARG_ISSUE_PATH: + if (isempty(optarg) || streq(optarg, "-")) { + arg_issue_path = mfree(arg_issue_path); + arg_issue_stdout = true; + break; + } + + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_issue_path); + if (r < 0) + return r; + + arg_issue_stdout = false; + break; + } + } + + if (!arg_issue_path && !arg_issue_stdout) { + arg_issue_path = strdup("/run/issue.d/50-ssh-vsock.issue"); + if (!arg_issue_path) + return log_oom(); + } + + return 1; +} + +static int acquire_cid(unsigned *ret_cid) { + int r; + + assert(ret_cid); + + Virtualization v = detect_virtualization(); + if (v < 0) + return log_error_errno(v, "Failed to detect if we run in a VM: %m"); + if (!VIRTUALIZATION_IS_VM(v)) { + /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */ + log_debug("Not running in a VM, not creating issue file."); + *ret_cid = 0; + 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 issue file, since AF_VSOCK is not available."); + *ret_cid = 0; + return 0; + } + + return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m"); + } + + vsock_fd = safe_close(vsock_fd); + + unsigned local_cid; + r = vsock_get_local_cid(&local_cid); + if (r < 0) { + if (ERRNO_IS_DEVICE_ABSENT(r)) { + log_debug("Not creating issue file, since /dev/vsock is not available (even though AF_VSOCK is)."); + *ret_cid = 0; + return 0; + } + + return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m"); + } + + *ret_cid = local_cid; + return 1; +} + +static int run(int argc, char* argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + switch (arg_action) { + case ACTION_MAKE_VSOCK: { + unsigned cid; + + r = acquire_cid(&cid); + if (r < 0) + return r; + if (r == 0) { + log_debug("Not running in a VSOCK enabled VM, skipping."); + break; + } + + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_(fclosep) FILE *f = NULL; + FILE *out; + + if (arg_issue_path) { + r = mkdir_parents(arg_issue_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create parent directories of '%s': %m", arg_issue_path); + + r = fopen_tmpfile_linkable(arg_issue_path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to create '%s': %m", arg_issue_path); + + out = f; + } else + out = stdout; + + fprintf(out, + "Try contacting this VM's SSH server via 'ssh vsock%%%u' from host.\n" + "\n", cid); + + if (f) { + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to adjust access mode of '%s': %m", arg_issue_path); + + r = flink_tmpfile(f, t, arg_issue_path, LINK_TMPFILE_REPLACE); + if (r < 0) + return log_error_errno(r, "Failed to move '%s' into place: %m", arg_issue_path); + } + + break; + } + + case ACTION_RM_VSOCK: + if (arg_issue_path) { + if (unlink(arg_issue_path) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to remove '%s': %m", arg_issue_path); + + log_debug_errno(errno, "File '%s' does not exist, no operation executed.", arg_issue_path); + } else + log_debug("Successfully removed '%s'.", arg_issue_path); + } else + log_notice("STDOUT selected for issue file, not removing."); + + break; + + default: + assert_not_reached(); + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); -- 2.47.3