import-creds: define a new dir where initrd configurators can pass credentials to host

This commit is contained in:
Lennart Poettering
2023-07-04 22:59:07 +02:00
parent f76ce81b91
commit 0dea5b7719
2 changed files with 148 additions and 12 deletions
+11 -9
View File
@@ -3274,18 +3274,20 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
11) with a prefix of <literal>io.systemd.credential:</literal> or
<literal>io.systemd.credential.binary:</literal>. In both cases a key/value pair separated by
<literal>=</literal> is expected, in the latter case the right-hand side is Base64 decoded when
parsed (thus permitting binary data to be passed in). Example
<ulink url="https://www.qemu.org/docs/master/system/index.html">qemu</ulink>
switch: <literal>-smbios
parsed (thus permitting binary data to be passed in). Example <ulink
url="https://www.qemu.org/docs/master/system/index.html">qemu</ulink> switch: <literal>-smbios
type=11,value=io.systemd.credential:xx=yy</literal>, or <literal>-smbios
type=11,value=io.systemd.credential.binary:rick=TmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXA=</literal>. Alternatively,
use the <command>qemu</command> <literal>fw_cfg</literal> node
<literal>opt/io.systemd.credentials/</literal>. Example <command>qemu</command> switch: <literal>-fw_cfg
name=opt/io.systemd.credentials/mycred,string=supersecret</literal>. They may also be specified on
the kernel command line using the <literal>systemd.set_credential=</literal> switch (see
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>) and from
the UEFI firmware environment via
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
<literal>opt/io.systemd.credentials/</literal>. Example <command>qemu</command> switch:
<literal>-fw_cfg name=opt/io.systemd.credentials/mycred,string=supersecret</literal>. They may also
be passed from the UEFI firmware environment via
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
from the initrd (see
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>), or be
specified on the kernel command line using the <literal>systemd.set_credential=</literal> switch (see
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> – this is
not recommended since unprivileged userspace can read the kernel command line). </para>
<para>If referencing an <constant>AF_UNIX</constant> stream socket to connect to, the connection will
originate from an abstract namespace socket, that includes information about the unit and the
+137 -3
View File
@@ -611,27 +611,157 @@ static int import_credentials_smbios(ImportCredentialContext *c) {
return 0;
}
static int import_credentials_initrd(ImportCredentialContext *c) {
_cleanup_free_ DirectoryEntries *de = NULL;
_cleanup_close_ int source_dir_fd = -EBADF;
int r;
assert(c);
/* This imports credentials from /run/credentials/@initrd/ into our credentials directory and deletes
* the source directory afterwards. This is run once after the initrd → host transition. This is
* supposed to establish a well-defined avenue for initrd-based host configurators to pass
* credentials into the main system. */
if (in_initrd())
return 0;
source_dir_fd = open("/run/credentials/@initrd", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
if (source_dir_fd < 0) {
if (errno == ENOENT)
log_debug_errno(errno, "No credentials passed from initrd.");
else
log_warning_errno(errno, "Failed to open '/run/credentials/@initrd', ignoring: %m");
return 0;
}
r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
if (r < 0) {
log_warning_errno(r, "Failed to read '/run/credentials/@initrd' contents, ignoring: %m");
return 0;
}
FOREACH_ARRAY(entry, de->entries, de->n_entries) {
_cleanup_close_ int cfd = -EBADF, nfd = -EBADF;
const struct dirent *d = *entry;
struct stat st;
if (!credential_name_valid(d->d_name)) {
log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
continue;
}
cfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_CLOEXEC);
if (cfd < 0) {
log_warning_errno(errno, "Failed to open %s, ignoring: %m", d->d_name);
continue;
}
if (fstat(cfd, &st) < 0) {
log_warning_errno(errno, "Failed to stat %s, ignoring: %m", d->d_name);
continue;
}
r = stat_verify_regular(&st);
if (r < 0) {
log_warning_errno(r, "Credential file %s is not a regular file, ignoring: %m", d->d_name);
continue;
}
if (!credential_size_ok(c, d->d_name, st.st_size))
continue;
r = acquire_credential_directory(c);
if (r < 0)
return r;
nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
if (nfd == -EEXIST)
continue;
if (nfd < 0)
return nfd;
r = copy_bytes(cfd, nfd, st.st_size, 0);
if (r < 0) {
(void) unlinkat(c->target_dir_fd, d->d_name, 0);
return log_error_errno(r, "Failed to create credential '%s': %m", d->d_name);
}
c->size_sum += st.st_size;
c->n_credentials++;
log_debug("Successfully copied initrd credential '%s'.", d->d_name);
(void) unlinkat(source_dir_fd, d->d_name, 0);
}
source_dir_fd = safe_close(source_dir_fd);
if (rmdir("/run/credentials/@initrd") < 0)
log_warning_errno(errno, "Failed to remove /run/credentials/@initrd after import, ignoring: %m");
return 0;
}
static int import_credentials_trusted(void) {
_cleanup_(import_credentials_context_free) ImportCredentialContext c = {
.target_dir_fd = -EBADF,
};
int q, w, r;
int q, w, r, y;
/* This is invoked during early boot when no credentials have been imported so far. (Specifically, if
* the $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables are not set
* yet.) */
r = import_credentials_qemu(&c);
w = import_credentials_smbios(&c);
q = import_credentials_proc_cmdline(&c);
y = import_credentials_initrd(&c);
if (c.n_credentials > 0) {
int z;
log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg.", c.n_credentials);
log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg/initrd.", c.n_credentials);
z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
if (z < 0)
return z;
}
return r < 0 ? r : w < 0 ? w : q;
return r < 0 ? r : w < 0 ? w : q < 0 ? q : y;
}
static int merge_credentials_trusted(const char *creds_dir) {
_cleanup_(import_credentials_context_free) ImportCredentialContext c = {
.target_dir_fd = -EBADF,
};
int r;
/* This is invoked after the initrd → host transitions, when credentials already have been imported,
* but we might want to import some more from the initrd. */
if (in_initrd())
return 0;
/* Do not try to merge initrd credentials into foreign credentials directories */
if (!path_equal_ptr(creds_dir, SYSTEM_CREDENTIALS_DIRECTORY)) {
log_debug("Not importing initrd credentials, as foreign $CREDENTIALS_DIRECTORY has been set.");
return 0;
}
r = import_credentials_initrd(&c);
if (c.n_credentials > 0) {
int z;
log_debug("Merged %u credentials from initrd.", c.n_credentials);
z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
if (z < 0)
return z;
}
return r;
}
static int symlink_credential_dir(const char *envvar, const char *path, const char *where) {
@@ -690,6 +820,10 @@ int import_credentials(void) {
r = q;
}
q = merge_credentials_trusted(received_creds_dir);
if (r >= 0)
r = q;
} else {
_cleanup_free_ char *v = NULL;