]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ssh-generator: generate /etc/issue.d/ with VSOCK ssh info data 37819/head
authorLennart Poettering <lennart@poettering.net>
Thu, 12 Jun 2025 09:38:21 +0000 (11:38 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 3 Jul 2025 15:36:09 +0000 (17:36 +0200)
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
man/systemd-ssh-issue.xml [new file with mode: 0644]
src/ssh-generator/meson.build
src/ssh-generator/ssh-generator.c
src/ssh-generator/ssh-issue.c [new file with mode: 0644]

index b5dfdb925c45c261fcf26eb5bcc6d75859a1dfa0..12e9fd97aaa232ebc897a9b4f18ca69493ee7bf0 100644 (file)
@@ -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 (file)
index 0000000..4e88779
--- /dev/null
@@ -0,0 +1,99 @@
+<?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-issue"
+    xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>systemd-ssh-issue</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-ssh-issue</refentrytitle>
+    <manvolnum>1</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-ssh-issue</refname>
+    <refpurpose>Generator for SSH login prompt drop-ins</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>/usr/lib/systemd/systemd-ssh-issue <option>--make-vsock</option></command>
+      <command>/usr/lib/systemd/systemd-ssh-issue <option>--rm-vsock</option></command>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>systemd-ssh-issue</command> is a small tool that generates a
+    <filename>/run/issue.d/50-ssh-vsock.issue</filename> drop-in file in case <constant>AF_VSOCK</constant>
+    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-<constant>AF_VSOCK</constant>, in particular it reports the
+    system's <constant>AF_VSOCK</constant> CID. The file is typically read and displayed by <citerefentry
+    project='man-pages'><refentrytitle>agetty</refentrytitle><manvolnum>8</manvolnum></citerefentry> on
+    console or serial login prompts.</para>
+
+    <para>This tool is automatically used by
+    <citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    in the <constant>AF_VSOCK</constant> socket units it generates.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+    <para>The following options are understood:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>--make-vsock</option></term>
+        <listitem><para>Generates the issue file. This command has no effect if called on systems lacking
+        <constant>AF_VSOCK</constant> support.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--rm-vsock</option></term>
+        <listitem><para>Removes the issue file if it exists.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--issue-path=</option></term>
+        <listitem><para>Changes the path to the issue file to write to/remove. If not specified, defaults to
+        <filename>/run/issue.d/50-ssh-vsock.issue</filename>. If specified as empty string or
+        <literal>-</literal> writes the issue file contents to standard output.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
+      <xi:include href="standard-options.xml" xpointer="help"/>
+      <xi:include href="standard-options.xml" xpointer="version"/>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Exit status</title>
+
+    <para>On success, 0 is returned, a non-zero failure code
+    otherwise.</para>
+  </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>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+      <member><citerefentry project="man-pages"><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+      <member><citerefentry project='man-pages'><refentrytitle>agetty</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+    </simplelist></para>
+  </refsect1>
+</refentry>
index b14ef46ff5aabbd992eb9d68f09b1387a11a0582..f281a251849198318fed43aa556c5e08fc06ffde 100644 (file)
@@ -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
index 3f1fd8cc30b7773635ff2d12bcf0d2fa2c5c6aa6..0c7eba366a08f0efc7397fc9bcfeb7ea46d3a839 100644 (file)
@@ -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 (file)
index 0000000..68ad66b
--- /dev/null
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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);