network-generator: pick up .netdev/.link/.network configuration via credentials

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.
This commit is contained in:
Lennart Poettering 2024-01-08 11:25:56 +01:00
parent 96f1f03c03
commit 1a30285590
5 changed files with 167 additions and 2 deletions

View File

@ -117,6 +117,41 @@
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">

View File

@ -137,6 +137,30 @@
</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>

View 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);

View File

@ -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

View 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