]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network-generator: pick up .netdev/.link/.network configuration via credentials 30827/head
authorLennart Poettering <lennart@poettering.net>
Mon, 8 Jan 2024 10:25:56 +0000 (11:25 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 8 Jan 2024 11:59:08 +0000 (12:59 +0100)
To me this is the last major basic functionality that couldn't be
configured via credentials: the network.

We do not invent any new format for this, but simply copy relevant creds
1:1 into /run/systemd/network/ to open up the full functionality of
networkd to VM hosts.

man/systemd-network-generator.service.xml
man/systemd.system-credentials.xml
src/network/generator/main.c
test/units/testsuite-74.network-generator.sh [new file with mode: 0755]
units/systemd-network-generator.service.in

index 5682339560af6cd26ad48a14ae637334f5751f7b..1d498cbf1dd1e80013340d035ca6f9baea45466f 100644 (file)
     for option syntax and details.</para>
   </refsect1>
 
+  <refsect1>
+    <title>Credentials</title>
+
+    <para><command>systemd-network-generator</command> supports the service credentials logic as implemented
+    by
+    <varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
+    (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+    details). The following credentials are used when passed in:</para>
+
+    <variablelist class='system-credentials'>
+      <varlistentry>
+        <term><varname>network.netdev.*</varname></term>
+        <term><varname>network.link.*</varname></term>
+        <term><varname>network.network.*</varname></term>
+
+        <listitem><para>These credentials should contain valid
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+        <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+        <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        configuration data. From each matching credential a separate file is created. Example: a passed
+        credential <filename>network.link.50-foobar</filename> will be copied into a configuration file
+        <filename>50-foobar.link</filename>.</para>
+
+        <para>Note that the resulting files are created world-readable, it's hence recommended to not include
+        secrets in these credentials, but supply them via separate credentials directly to
+        <filename>systemd-networkd.service</filename>.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+    </variablelist>
+
+    <para>Note that by default the <filename>systemd-network-generator.service</filename> unit file is set up
+    to inherit the these credentials from the service manager.</para>
+  </refsect1>
+
   <refsect1>
     <title>See Also</title>
     <para><simplelist type="inline">
index ab6cab2e06a95c6e6253635b6e13eb3ff4c279fc..b2d491fe5813aa49a3a94536f50229ac0733078b 100644 (file)
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>network.netdev.*</varname></term>
+        <term><varname>network.link.*</varname></term>
+        <term><varname>network.network.*</varname></term>
+        <listitem>
+          <para>Configures network devices. Read by
+          <citerefentry><refentrytitle>systemd-network-generator.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. These
+          credentials directly translate to a matching <filename>*.netdev</filename>,
+          <filename>*.link</filename> or <filename>*.network</filename> file. Example: the contents of a
+          credential <filename>network.link.50-foobar</filename> will be copied into a file
+          <filename>50-foobar.link</filename>. See
+          <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+          <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+          <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+          for details.</para>
+
+          <para>Note that the resulting files are created world-readable, it's hence recommended to not include
+          secrets in these credentials, but supply them via separate credentials directly to
+          <filename>systemd-networkd.service</filename>.</para>
+
+          <xi:include href="version-info.xml" xpointer="v256"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>passwd.hashed-password.root</varname></term>
         <term><varname>passwd.plaintext-password.root</varname></term>
index 0439a9d7697ea7b35cb2858f89884d136263b355..540b6df4fc55b824411a45e8e44690c7726178af 100644 (file)
@@ -3,6 +3,8 @@
 #include <getopt.h>
 
 #include "build.h"
+#include "copy.h"
+#include "creds-util.h"
 #include "fd-util.h"
 #include "fs-util.h"
 #include "generator.h"
@@ -12,6 +14,7 @@
 #include "network-generator.h"
 #include "path-util.h"
 #include "proc-cmdline.h"
+#include "recurse-dir.h"
 
 #define NETWORKD_UNIT_DIRECTORY "/run/systemd/network"
 
@@ -122,6 +125,76 @@ static int context_save(Context *context) {
         return r;
 }
 
+static int pick_up_credentials(void) {
+        _cleanup_close_ int credential_dir_fd = -EBADF;
+        int r, ret = 0;
+
+        credential_dir_fd = open_credentials_dir();
+        if (IN_SET(credential_dir_fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */
+                return 0;
+        if (credential_dir_fd < 0)
+                return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m");
+
+        _cleanup_free_ DirectoryEntries *des = NULL;
+        r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enumerate credentials: %m");
+
+        FOREACH_ARRAY(i, des->entries, des->n_entries) {
+                static const struct {
+                        const char *credential_prefix;
+                        const char *filename_suffix;
+                } table[] = {
+                        { "network.link.",    ".link"    },
+                        { "network.netdev.",  ".netdev"  },
+                        { "network.network.", ".network" },
+                };
+
+                _cleanup_free_ char *fn = NULL;
+                struct dirent *de = *i;
+
+                if (de->d_type != DT_REG)
+                        continue;
+
+                FOREACH_ARRAY(t, table, ELEMENTSOF(table)) {
+                        const char *e = startswith(de->d_name, t->credential_prefix);
+
+                        if (e) {
+                                fn = strjoin(e, t->filename_suffix);
+                                if (!fn)
+                                        return log_oom();
+
+                                break;
+                        }
+                }
+
+                if (!fn)
+                        continue;
+
+                if (!filename_is_valid(fn)) {
+                        log_warning("Passed credential '%s' would result in invalid filename '%s', ignoring.", de->d_name, fn);
+                        continue;
+                }
+
+                _cleanup_free_ char *output = path_join(NETWORKD_UNIT_DIRECTORY, fn);
+                if (!output)
+                        return log_oom();
+
+                r = copy_file_at(
+                                credential_dir_fd, de->d_name,
+                                AT_FDCWD, output,
+                                /* open_flags= */ 0,
+                                0644,
+                                /* flags= */ 0);
+                if (r < 0)
+                        RET_GATHER(ret, log_warning_errno(r, "Failed to copy credential %s → file %s: %m", de->d_name, output));
+                else
+                        log_info("Installed %s from credential.", output);
+        }
+
+        return ret;
+}
+
 static int help(void) {
         printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n"
                "  -h --help                       Show this help\n"
@@ -174,7 +247,7 @@ static int parse_argv(int argc, char *argv[]) {
 
 static int run(int argc, char *argv[]) {
         _cleanup_(context_clear) Context context = {};
-        int r;
+        int r, ret = 0;
 
         log_setup();
 
@@ -212,7 +285,10 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return log_warning_errno(r, "Failed to merge multiple command line options: %m");
 
-        return context_save(&context);
+        RET_GATHER(ret, context_save(&context));
+        RET_GATHER(ret, pick_up_credentials());
+
+        return ret;
 }
 
 DEFINE_MAIN_FUNCTION(run);
diff --git a/test/units/testsuite-74.network-generator.sh b/test/units/testsuite-74.network-generator.sh
new file mode 100755 (executable)
index 0000000..e7ccea1
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+at_exit() {
+    rm -f /run/credstore/network.network.50-testme
+    rm -f /run/systemd/system/systemd-network-generator.service.d/50-testme.conf
+}
+
+trap at_exit EXIT
+
+mkdir -p /run/credstore
+cat > /run/credstore/network.network.50-testme <<EOF
+[Match]
+Property=IDONTEXIST
+EOF
+
+systemctl edit systemd-network-generator.service --stdin --drop-in=50-testme.conf <<EOF
+[Service]
+LoadCredential=network.network.50-testme
+EOF
+
+systemctl restart systemd-network-generator
+
+test -f /run/systemd/network/50-testme.network
index d87e1a4adcd9e9825cace02a2fbe62c6889ccd8a..f7d13d3084679924eeb32043deaf7e454ee23506 100644 (file)
@@ -21,6 +21,9 @@ Before=shutdown.target initrd-switch-root.target
 Type=oneshot
 RemainAfterExit=yes
 ExecStart={{LIBEXECDIR}}/systemd-network-generator
+ImportCredential=network.netdev.*
+ImportCredential=network.link.*
+ImportCredential=network.network.*
 
 [Install]
 WantedBy=sysinit.target