diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml
index b0fb70c6c5..271c495d2a 100644
--- a/man/systemd.system-credentials.xml
+++ b/man/systemd.system-credentials.xml
@@ -377,6 +377,31 @@
+
+
+ udev.conf.*
+ udev.rules.*
+
+
+ Configures udev configuration file and udev rules. Read by
+ systemd-udev-load-credentials.service, which invokes
+ udevadm control --load-credentials. These credentials directly translate to a
+ matching
+ udev.conf5 or
+ udev7 rules
+ file. Example: the contents of a credential
+ udev.conf.50-foobar will be copied into a file
+ /run/udev/udev.conf.d/50-foobar.conf, and
+ udev.rules.50-foobar will be copied into a file
+ /run/udev/rules.d/50-foobar.rules. See
+ udev7,
+ udev.conf5, and
+ udevadm8
+ for details.
+
+
+
+
diff --git a/man/udevadm.xml b/man/udevadm.xml
index 41c8ae7a12..b515f3c79c 100644
--- a/man/udevadm.xml
+++ b/man/udevadm.xml
@@ -727,6 +727,47 @@
+
+
+
+ When specified, the following credentials are used when passed in:
+
+
+
+ udev.conf.*
+
+ These credentials should contain valid
+ udev.conf5
+ configuration data. From each matching credential a separate file is created. Example: a
+ passed credential udev.conf.50-foobar will be copied into a
+ configuration file /run/udev/udev.conf.d/50-foobar.conf.
+
+
+
+
+
+ udev.rules.*
+
+ These credentials should contain valid
+ udev7
+ rules. From each matching credential a separate file is created. Example: a passed credential
+ udev.rules.50-foobar will be copied into a configuration file
+ /run/udev/rules.d/50-foobar.rules.
+
+
+
+
+
+
+ Note, this does not imply option. So, if
+ systemd-udevd is already running, please consider to also specify
+ to make the copied udev rules files used by
+ systemd-udevd.
+
+
+
+
+
diff --git a/src/network/generator/main.c b/src/network/generator/main.c
index 4c8e5dee37..5a26a54fc5 100644
--- a/src/network/generator/main.c
+++ b/src/network/generator/main.c
@@ -3,7 +3,6 @@
#include
#include "build.h"
-#include "copy.h"
#include "creds-util.h"
#include "fd-util.h"
#include "fs-util.h"
@@ -14,9 +13,8 @@
#include "network-generator.h"
#include "path-util.h"
#include "proc-cmdline.h"
-#include "recurse-dir.h"
-#define NETWORKD_UNIT_DIRECTORY "/run/systemd/network"
+#define NETWORK_UNIT_DIRECTORY "/run/systemd/network/"
static const char *arg_root = NULL;
@@ -125,11 +123,11 @@ static int context_save(Context *context) {
Link *link;
int r;
- const char *p = prefix_roota(arg_root, NETWORKD_UNIT_DIRECTORY);
+ const char *p = prefix_roota(arg_root, NETWORK_UNIT_DIRECTORY);
r = mkdir_p(p, 0755);
if (r < 0)
- return log_error_errno(r, "Failed to create directory " NETWORKD_UNIT_DIRECTORY ": %m");
+ return log_error_errno(r, "Failed to create directory " NETWORK_UNIT_DIRECTORY ": %m");
HASHMAP_FOREACH(network, context->networks_by_name)
RET_GATHER(r, network_save(network, p));
@@ -143,76 +141,6 @@ 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"
@@ -304,7 +232,13 @@ static int run(int argc, char *argv[]) {
return log_warning_errno(r, "Failed to merge multiple command line options: %m");
RET_GATHER(ret, context_save(&context));
- RET_GATHER(ret, pick_up_credentials());
+
+ static const PickUpCredential table[] = {
+ { "network.link.", NETWORK_UNIT_DIRECTORY, ".link" },
+ { "network.netdev.", NETWORK_UNIT_DIRECTORY, ".netdev" },
+ { "network.network.", NETWORK_UNIT_DIRECTORY, ".network" },
+ };
+ RET_GATHER(ret, pick_up_credentials(table, ELEMENTSOF(table)));
return ret;
}
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index ba8aee3355..bd2af6d172 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -12,6 +12,7 @@
#include "capability-util.h"
#include "chattr-util.h"
#include "constants.h"
+#include "copy.h"
#include "creds-util.h"
#include "efi-api.h"
#include "env-util.h"
@@ -21,11 +22,12 @@
#include "fs-util.h"
#include "io-util.h"
#include "memory-util.h"
-#include "mkdir.h"
+#include "mkdir-label.h"
#include "openssl-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "random-util.h"
+#include "recurse-dir.h"
#include "sparse-endian.h"
#include "stat-util.h"
#include "tmpfile-util.h"
@@ -1652,3 +1654,90 @@ int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp,
return 0;
}
+
+static int pick_up_credential_one(
+ int credential_dir_fd,
+ const char *credential_name,
+ const PickUpCredential *table_entry) {
+
+ _cleanup_free_ char *fn = NULL, *target_path = NULL;
+ const char *e;
+ int r;
+
+ assert(credential_dir_fd >= 0);
+ assert(credential_name);
+ assert(table_entry);
+
+ e = startswith(credential_name, table_entry->credential_prefix);
+ if (!e)
+ return 0; /* unmatched */
+
+ fn = strjoin(e, table_entry->filename_suffix);
+ if (!fn)
+ return log_oom();
+
+ if (!filename_is_valid(fn))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Passed credential '%s' would result in invalid filename '%s'.",
+ credential_name, fn);
+
+ r = mkdir_p_label(table_entry->target_dir, 0755);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to create '%s': %m", table_entry->target_dir);
+
+ target_path = path_join(table_entry->target_dir, fn);
+ if (!target_path)
+ return log_oom();
+
+ r = copy_file_at(
+ credential_dir_fd, credential_name,
+ AT_FDCWD, target_path,
+ /* open_flags= */ 0,
+ 0644,
+ /* flags= */ 0);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to copy credential %s → file %s: %m",
+ credential_name, target_path);
+
+ log_info("Installed %s from credential.", target_path);
+ return 1; /* done */
+}
+
+int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry) {
+ _cleanup_close_ int credential_dir_fd = -EBADF;
+ int r, ret = 0;
+
+ assert(table);
+ assert(n_table_entry > 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. */
+ log_debug("No credentials found.");
+ 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) {
+ struct dirent *de = *i;
+
+ if (de->d_type != DT_REG)
+ continue;
+
+ FOREACH_ARRAY(t, table, n_table_entry) {
+ r = pick_up_credential_one(credential_dir_fd, de->d_name, t);
+ if (r != 0) {
+ RET_GATHER(ret, r);
+ break; /* Done, or failed. Let's move to the next credential. */
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h
index e56292c795..b80755b7d7 100644
--- a/src/shared/creds-util.h
+++ b/src/shared/creds-util.h
@@ -93,3 +93,11 @@ int decrypt_credential_and_warn(const char *validate_name, usec_t validate_times
int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
+
+typedef struct PickUpCredential {
+ const char *credential_prefix;
+ const char *target_dir;
+ const char *filename_suffix;
+} PickUpCredential;
+
+int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry);
diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c
index b8215ddd1f..29dc88330c 100644
--- a/src/udev/udevadm-control.c
+++ b/src/udev/udevadm-control.c
@@ -8,6 +8,7 @@
#include
#include
+#include "creds-util.h"
#include "parse-util.h"
#include "process-util.h"
#include "static-destruct.h"
@@ -26,9 +27,21 @@ static bool arg_exit = false;
static int arg_max_children = -1;
static int arg_log_level = -1;
static int arg_start_exec_queue = -1;
+static bool arg_load_credentials = false;
STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
+static bool arg_has_control_commands(void) {
+ return
+ arg_exit ||
+ arg_log_level >= 0 ||
+ arg_start_exec_queue >= 0 ||
+ arg_reload ||
+ !strv_isempty(arg_env) ||
+ arg_max_children >= 0 ||
+ arg_ping;
+}
+
static int help(void) {
printf("%s control OPTION\n\n"
"Control the udev daemon.\n\n"
@@ -42,7 +55,8 @@ static int help(void) {
" -p --property=KEY=VALUE Set a global property for all events\n"
" -m --children-max=N Maximum number of children\n"
" --ping Wait for udev to respond to a ping message\n"
- " -t --timeout=SECONDS Maximum time to block for a reply\n",
+ " -t --timeout=SECONDS Maximum time to block for a reply\n"
+ " --load-credentials Load udev rules from credentials\n",
program_invocation_short_name);
return 0;
@@ -51,23 +65,25 @@ static int help(void) {
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_PING = 0x100,
+ ARG_LOAD_CREDENTIALS,
};
static const struct option options[] = {
- { "exit", no_argument, NULL, 'e' },
- { "log-level", required_argument, NULL, 'l' },
- { "log-priority", required_argument, NULL, 'l' }, /* for backward compatibility */
- { "stop-exec-queue", no_argument, NULL, 's' },
- { "start-exec-queue", no_argument, NULL, 'S' },
- { "reload", no_argument, NULL, 'R' },
- { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */
- { "property", required_argument, NULL, 'p' },
- { "env", required_argument, NULL, 'p' }, /* alias for -p */
- { "children-max", required_argument, NULL, 'm' },
- { "ping", no_argument, NULL, ARG_PING },
- { "timeout", required_argument, NULL, 't' },
- { "version", no_argument, NULL, 'V' },
- { "help", no_argument, NULL, 'h' },
+ { "exit", no_argument, NULL, 'e' },
+ { "log-level", required_argument, NULL, 'l' },
+ { "log-priority", required_argument, NULL, 'l' }, /* for backward compatibility */
+ { "stop-exec-queue", no_argument, NULL, 's' },
+ { "start-exec-queue", no_argument, NULL, 'S' },
+ { "reload", no_argument, NULL, 'R' },
+ { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */
+ { "property", required_argument, NULL, 'p' },
+ { "env", required_argument, NULL, 'p' }, /* alias for -p */
+ { "children-max", required_argument, NULL, 'm' },
+ { "ping", no_argument, NULL, ARG_PING },
+ { "timeout", required_argument, NULL, 't' },
+ { "load-credentials", no_argument, NULL, ARG_LOAD_CREDENTIALS },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
{}
};
@@ -76,10 +92,6 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- if (argc <= 1)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "This command expects one or more options.");
-
while ((c = getopt_long(argc, argv, "el:sSRp:m:t:Vh", options, NULL)) >= 0)
switch (c) {
@@ -134,6 +146,10 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg);
break;
+ case ARG_LOAD_CREDENTIALS:
+ arg_load_credentials = true;
+ break;
+
case 'V':
return print_version();
@@ -147,6 +163,10 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached();
}
+ if (!arg_has_control_commands() && !arg_load_credentials)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "No control command option is specified.");
+
if (optind < argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Extraneous argument: %s", argv[optind]);
@@ -154,19 +174,10 @@ static int parse_argv(int argc, char *argv[]) {
return 1;
}
-int control_main(int argc, char *argv[], void *userdata) {
+static int send_control_commands(void) {
_cleanup_(udev_ctrl_unrefp) UdevCtrl *uctrl = NULL;
int r;
- if (running_in_chroot() > 0) {
- log_info("Running in chroot, ignoring request.");
- return 0;
- }
-
- r = parse_argv(argc, argv);
- if (r <= 0)
- return r;
-
r = udev_ctrl_new(&uctrl);
if (r < 0)
return log_error_errno(r, "Failed to initialize udev control: %m");
@@ -226,3 +237,34 @@ int control_main(int argc, char *argv[], void *userdata) {
return 0;
}
+
+int control_main(int argc, char *argv[], void *userdata) {
+ int r;
+
+ if (running_in_chroot() > 0) {
+ log_info("Running in chroot, ignoring request.");
+ return 0;
+ }
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ if (arg_load_credentials) {
+ static const PickUpCredential table[] = {
+ { "udev.conf.", "/run/udev/udev.conf.d/", ".conf" },
+ { "udev.rules.", "/run/udev/rules.d/", ".rules" },
+ };
+ r = pick_up_credentials(table, ELEMENTSOF(table));
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_has_control_commands()) {
+ r = send_control_commands();
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
diff --git a/test/units/testsuite-17.10.sh b/test/units/testsuite-17.10.sh
index f229dcf25f..a2b8b14d0c 100755
--- a/test/units/testsuite-17.10.sh
+++ b/test/units/testsuite-17.10.sh
@@ -45,8 +45,8 @@ udevadm control -S
udevadm control -R
udevadm control -p HELLO=world
udevadm control -m 42
-udevadm control --ping
-udevadm control -t 5
+udevadm control --ping -t 5
+udevadm control --load-credentials
udevadm control -h
udevadm info /dev/null
diff --git a/test/units/testsuite-17.credentials.sh b/test/units/testsuite-17.credentials.sh
new file mode 100755
index 0000000000..cbdc57037e
--- /dev/null
+++ b/test/units/testsuite-17.credentials.sh
@@ -0,0 +1,33 @@
+#!/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/udev.*
+ rm -f /run/udev/udev.conf.d/*
+ rm -f /run/udev/rules.d/*
+ rm -rf /run/systemd/system/systemd-udev-load-credentials.service.d
+}
+
+trap at_exit EXIT
+
+mkdir -p /run/credstore
+cat > /run/credstore/udev.conf.50-testme < /run/credstore/udev.rules.50-testme <