diff --git a/man/systemd-network-generator.service.xml b/man/systemd-network-generator.service.xml
index 5682339560..1d498cbf1d 100644
--- a/man/systemd-network-generator.service.xml
+++ b/man/systemd-network-generator.service.xml
@@ -117,6 +117,41 @@
for option syntax and details.
+
+ Credentials
+
+ systemd-network-generator supports the service credentials logic as implemented
+ by
+ ImportCredential=/LoadCredential=/SetCredential=
+ (see systemd.exec5 for
+ details). The following credentials are used when passed in:
+
+
+
+ network.netdev.*
+ network.link.*
+ network.network.*
+
+ These credentials should contain valid
+ systemd.netdev5,
+ systemd.link5,
+ systemd.network5
+ configuration data. From each matching credential a separate file is created. Example: a passed
+ credential network.link.50-foobar will be copied into a configuration file
+ 50-foobar.link.
+
+ 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
+ systemd-networkd.service.
+
+
+
+
+
+ Note that by default the systemd-network-generator.service unit file is set up
+ to inherit the these credentials from the service manager.
+
+
See Also
diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml
index ab6cab2e06..b2d491fe58 100644
--- a/man/systemd.system-credentials.xml
+++ b/man/systemd.system-credentials.xml
@@ -137,6 +137,30 @@
+
+ network.netdev.*
+ network.link.*
+ network.network.*
+
+ Configures network devices. Read by
+ systemd-network-generator.service8. These
+ credentials directly translate to a matching *.netdev,
+ *.link or *.network file. Example: the contents of a
+ credential network.link.50-foobar will be copied into a file
+ 50-foobar.link. See
+ systemd.netdev5,
+ systemd.link5,
+ systemd.network5
+ for details.
+
+ 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
+ systemd-networkd.service.
+
+
+
+
+
passwd.hashed-password.root
passwd.plaintext-password.root
diff --git a/src/network/generator/main.c b/src/network/generator/main.c
index 0439a9d769..540b6df4fc 100644
--- a/src/network/generator/main.c
+++ b/src/network/generator/main.c
@@ -3,6 +3,8 @@
#include
#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
index 0000000000..e7ccea1ede
--- /dev/null
+++ b/test/units/testsuite-74.network-generator.sh
@@ -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 <