Merge pull request #32103 from yuwata/udev-load-credentials

udev: load udev rules from credentials
This commit is contained in:
Yu Watanabe
2024-04-16 11:45:22 +09:00
committed by GitHub
11 changed files with 311 additions and 108 deletions
+25
View File
@@ -377,6 +377,31 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>udev.conf.*</varname></term>
<term><varname>udev.rules.*</varname></term>
<listitem>
<para>Configures udev configuration file and udev rules. Read by
<filename>systemd-udev-load-credentials.service</filename>, which invokes
<command>udevadm control --load-credentials</command>. These credentials directly translate to a
matching
<citerefentry><refentrytitle>udev.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> or
<citerefentry><refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum></citerefentry> rules
file. Example: the contents of a credential
<filename>udev.conf.50-foobar</filename> will be copied into a file
<filename>/run/udev/udev.conf.d/50-foobar.conf</filename>, and
<filename>udev.rules.50-foobar</filename> will be copied into a file
<filename>/run/udev/rules.d/50-foobar.rules</filename>. See
<citerefentry><refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry><refentrytitle>udev.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, and
<citerefentry><refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for details.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
+41
View File
@@ -727,6 +727,47 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--load-credentials</option></term>
<listitem>
<para>When specified, the following credentials are used when passed in:</para>
<variablelist>
<varlistentry>
<term><varname>udev.conf.*</varname></term>
<listitem>
<para>These credentials should contain valid
<citerefentry><refentrytitle>udev.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
configuration data. From each matching credential a separate file is created. Example: a
passed credential <filename>udev.conf.50-foobar</filename> will be copied into a
configuration file <filename>/run/udev/udev.conf.d/50-foobar.conf</filename>.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>udev.rules.*</varname></term>
<listitem>
<para>These credentials should contain valid
<citerefentry><refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum></citerefentry>
rules. From each matching credential a separate file is created. Example: a passed credential
<filename>udev.rules.50-foobar</filename> will be copied into a configuration file
<filename>/run/udev/rules.d/50-foobar.rules</filename>.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
</variablelist>
<para>Note, this <emphasis>does not</emphasis> imply <option>--reload</option> option. So, if
<command>systemd-udevd</command> is already running, please consider to also specify
<option>-reload</option> to make the copied udev rules files used by
<command>systemd-udevd</command>.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
</variablelist>
</refsect2>
+10 -76
View File
@@ -3,7 +3,6 @@
#include <getopt.h>
#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;
}
+90 -1
View File
@@ -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;
}
+8
View File
@@ -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);
+71 -29
View File
@@ -8,6 +8,7 @@
#include <string.h>
#include <unistd.h>
#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;
}
+2 -2
View File
@@ -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
+33
View File
@@ -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 <<EOF
udev_log=debug
EOF
cat > /run/credstore/udev.rules.50-testme <<EOF
SUBSYSTEM=="net", OPTIONS="log_level=debug"
EOF
systemctl edit systemd-udev-load-credentials.service --stdin --drop-in=50-testme.conf <<EOF
[Service]
LoadCredential=udev.conf.50-testme
LoadCredential=udev.rules.50-testme
EOF
systemctl restart systemd-udev-load-credentials.service
diff /run/credstore/udev.conf.50-testme /run/udev/udev.conf.d/50-testme.conf
diff /run/credstore/udev.rules.50-testme /run/udev/rules.d/50-testme.rules
+1
View File
@@ -688,6 +688,7 @@ units = [
'conditions' : ['ENABLE_TMPFILES'],
'symlinks' : ['sysinit.target.wants/'],
},
{ 'file' : 'systemd-udev-load-credentials.service' },
{ 'file' : 'systemd-udev-settle.service' },
{
'file' : 'systemd-udev-trigger.service',
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Load udev Rules from Credentials
Documentation=man:udevadm(8)
Documentation=man:udev(7)
Documentation=man:systemd.system-credentials(7)
DefaultDependencies=no
Before=systemd-udevd.service
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=udevadm control --load-credentials
ImportCredential=udev.conf.*
ImportCredential=udev.rules.*
[Install]
WantedBy=sysinit.target
+1
View File
@@ -13,6 +13,7 @@ Documentation=man:systemd-udevd.service(8) man:udev(7)
DefaultDependencies=no
After=systemd-sysusers.service systemd-hwdb-update.service
Before=sysinit.target
Wants=systemd-udev-load-credentials.service
ConditionPathIsReadWrite=/sys
[Service]