mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-01-11 05:17:44 +03:00
pid1: import creds from SMBIOS too, not just qemu's fw_cfg
This imports credentials also via SMBIOS' "OEM vendor string" section, similar to the existing import logic from fw_cfg. Functionality-wise this is very similar to the existing fw_cfg logic, both of which are easily settable on the qemu command line. Pros and cons of each: SMBIOS OEM vendor strings: - pro: fast, because memory mapped - pro: somewhat VMM independent, at least in theory - pro: qemu upstream sees this as the future - pro: no additional kernel module needed - con: strings only, thus binary data is base64 encoded fw_cfg: - pro: has been supported for longer in qemu - pro: supports binary data - con: slow, because IO port based - con: only qemu - con: requires qemu_fw_cfg.ko kernel module - con: qemu upstream sees this as legacy
This commit is contained in:
parent
08894b568f
commit
8de7de462b
@ -50,10 +50,11 @@ purpose. Specifically, the following features are provided:
|
||||
6. Service credentials are placed in non-swappable memory. (If permissions
|
||||
allow it, via `ramfs`.)
|
||||
|
||||
7. Credentials may be acquired from a hosting VM hypervisor (qemu `fw_cfg`), a
|
||||
hosting container manager, the kernel command line, or from the UEFI
|
||||
environment and the EFI System Partition (via `systemd-stub`). Such system
|
||||
credentials may then be propagated into individual services as needed.
|
||||
7. Credentials may be acquired from a hosting VM hypervisor (SMBIOS OEM strings
|
||||
or qemu `fw_cfg`), a hosting container manager, the kernel command line, or
|
||||
from the UEFI environment and the EFI System Partition (via
|
||||
`systemd-stub`). Such system credentials may then be propagated into
|
||||
individual services as needed.
|
||||
|
||||
8. Credentials are an effective way to pass parameters into services that run
|
||||
with `RootImage=` or `RootDirectory=` and thus cannot read these resources
|
||||
@ -254,10 +255,18 @@ services where they are ultimately consumed.
|
||||
the [Container Interface](CONTAINER_INTERFACE.md)
|
||||
documentation.
|
||||
|
||||
2. Quite similar, qemu VMs can be invoked with `-fw_cfg
|
||||
2. Quite similar, VMs can be passed credentials via SMBIOS OEM strings (example
|
||||
qemu command line switch `-smbios
|
||||
type=11,value=io.systemd.credential:foo=bar` or `-smbios
|
||||
type=11,value=io.systemd.credential.binary:foo=YmFyCg==`, the latter taking
|
||||
a Base64 encoded argument to permit binary credentials being passed
|
||||
in). Alternatively, qemu VMs can be invoked with `-fw_cfg
|
||||
name=opt/io.systemd.credentials/foo,string=bar` to pass credentials from
|
||||
host through the hypervisor into the VM. (This specific switch would set
|
||||
credential `foo` to `bar`.)
|
||||
host through the hypervisor into the VM via qemu's `fw_cfg` mechanism. (All
|
||||
three of these specific switches would set credential `foo` to `bar`.)
|
||||
Passing credentials via the SMBIOS mechanism is typically preferable over
|
||||
`fw_cfg` since it is faster and less specific to the chosen VMM
|
||||
implementation.
|
||||
|
||||
3. Credentials can also be passed into a system via the kernel command line,
|
||||
via the `systemd.set-credential=` kernel command line option. Note though
|
||||
@ -297,7 +306,7 @@ qemu-system-x86_64 \
|
||||
-drive if=none,id=hd,file=test.raw,format=raw \
|
||||
-device virtio-scsi-pci,id=scsi \
|
||||
-device scsi-hd,drive=hd,bootindex=1 \
|
||||
-fw_cfg name=opt/io.systemd.credentials/mycred,string=supersecret
|
||||
-smbios type=11,value=io.systemd.credential:mycred=supersecret
|
||||
```
|
||||
|
||||
Either of these lines will boot a disk image `test.raw`, once as container via
|
||||
@ -363,8 +372,8 @@ qemu-system-x86_64 \
|
||||
-drive if=none,id=hd,file=test.raw,format=raw \
|
||||
-device virtio-scsi-pci,id=scsi \
|
||||
-device scsi-hd,drive=hd,bootindex=1 \
|
||||
-fw_cfg name=opt/io.systemd.credentials/passwd.hashed-password.root,string=$(mkpasswd mysecret) \
|
||||
-fw_cfg name=opt/io.systemd.credentials/firstboot.locale,string=C.UTF-8
|
||||
-smbios type=11,value=io.systemd.credential:passwd.hashed-password.root=$(mkpasswd mysecret) \
|
||||
-smbios type=11,value=io.systemd.credential:firstboot.locale=C.UTF-8
|
||||
```
|
||||
|
||||
## Relevant Paths
|
||||
|
@ -3125,12 +3125,20 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
|
||||
<para>The service manager itself may receive system credentials that can be propagated to services
|
||||
from a hosting container manager or VM hypervisor. See the <ulink
|
||||
url="https://systemd.io/CONTAINER_INTERFACE">Container Interface</ulink> documentation for details
|
||||
about the former. For the latter, use the <command>qemu</command> <literal>fw_cfg</literal> node
|
||||
about the former. For the latter, pass <ulink
|
||||
url="https://www.dmtf.org/standards/smbios">DMI/SMBIOS</ulink> OEM string table entries (field type
|
||||
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 qemu 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 qemu 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</refentrytitle><manvolnum>1</manvolnum></citerefentry>) and from
|
||||
the UEFI firmware environment via
|
||||
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>If referencing an <constant>AF_UNIX</constant> stream socket to connect to, the connection will
|
||||
|
@ -965,7 +965,8 @@
|
||||
<term><varname>systemd.import_credentials=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If false disables importing credentials from the kernel
|
||||
command line, qemu_fw_cfg subsystem or the kernel command line.</para></listitem>
|
||||
command line, the DMI/SMBIOS OEM string table, the qemu_fw_cfg subsystem or the EFI kernel
|
||||
stub.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
|
@ -4,9 +4,11 @@
|
||||
|
||||
#include "copy.h"
|
||||
#include "creds-util.h"
|
||||
#include "escape.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "import-creds.h"
|
||||
#include "io-util.h"
|
||||
#include "mkdir-label.h"
|
||||
@ -24,7 +26,7 @@
|
||||
* generators invoked by it) can acquire credentials from outside, to mimic how we support it for containers,
|
||||
* but on VM/physical environments.
|
||||
*
|
||||
* This does three things:
|
||||
* This does four things:
|
||||
*
|
||||
* 1. It imports credentials picked up by sd-boot (and placed in the /.extra/credentials/ dir in the initrd)
|
||||
* and puts them in /run/credentials/@encrypted/. Note that during the initrd→host transition the initrd root
|
||||
@ -41,6 +43,11 @@
|
||||
* /sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials/ is picked up and also placed in
|
||||
* /run/credentials/@system/.
|
||||
*
|
||||
* 4. It imports credentials passed in via the DMI/SMBIOS OEM string tables, quite similar to fw_cfg. It
|
||||
* looks for strings starting with "io.systemd.credential:" and "io.systemd.credential.binary:". Both
|
||||
* expect a key=value assignment, but in the latter case the value is Base64 decoded, allowing binary
|
||||
* credentials to be passed in.
|
||||
*
|
||||
* If it picked up any credentials it will set the $CREDENTIALS_DIRECTORY and
|
||||
* $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables to point to these directories, so that processes
|
||||
* can find them there later on. If "ramfs" is available $CREDENTIALS_DIRECTORY will be backed by it (but
|
||||
@ -446,26 +453,177 @@ static int import_credentials_qemu(ImportCredentialContext *c) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_smbios_strings(ImportCredentialContext *c, const char *data, size_t size) {
|
||||
size_t left, skip;
|
||||
const char *p;
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(data || size == 0);
|
||||
|
||||
/* Unpacks a packed series of SMBIOS OEM vendor strings. These are a series of NUL terminated
|
||||
* strings, one after the other. */
|
||||
|
||||
for (p = data, left = size; left > 0; p += skip, left -= skip) {
|
||||
_cleanup_free_ void *buf = NULL;
|
||||
_cleanup_free_ char *cn = NULL;
|
||||
_cleanup_close_ int nfd = -1;
|
||||
const char *nul, *n, *eq;
|
||||
const void *cdata;
|
||||
size_t buflen, cdata_len;
|
||||
bool unbase64;
|
||||
|
||||
nul = memchr(p, 0, left);
|
||||
if (nul)
|
||||
skip = (nul - p) + 1;
|
||||
else {
|
||||
nul = p + left;
|
||||
skip = left;
|
||||
}
|
||||
|
||||
if (nul - p == 0) /* Skip empty strings */
|
||||
continue;
|
||||
|
||||
/* Only care about strings starting with either of these two prefixes */
|
||||
if ((n = memory_startswith(p, nul - p, "io.systemd.credential:")))
|
||||
unbase64 = false;
|
||||
else if ((n = memory_startswith(p, nul - p, "io.systemd.credential.binary:")))
|
||||
unbase64 = true;
|
||||
else {
|
||||
_cleanup_free_ char *escaped = NULL;
|
||||
|
||||
escaped = cescape_length(p, nul - p);
|
||||
log_debug("Ignoring OEM string: %s", strnull(escaped));
|
||||
continue;
|
||||
}
|
||||
|
||||
eq = memchr(n, '=', nul - n);
|
||||
if (!eq) {
|
||||
log_warning("SMBIOS OEM string lacks '=' character, ignoring.");
|
||||
continue;
|
||||
}
|
||||
|
||||
cn = memdup_suffix0(n, eq - n);
|
||||
if (!cn)
|
||||
return log_oom();
|
||||
|
||||
if (!credential_name_valid(cn)) {
|
||||
log_warning("SMBIOS credential name '%s' is not valid, ignoring: %m", cn);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Optionally base64 decode the data, if requested, to allow binary credentials */
|
||||
if (unbase64) {
|
||||
r = unbase64mem(eq + 1, nul - (eq + 1), &buf, &buflen);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to base64 decode credential '%s', ignoring: %m", cn);
|
||||
continue;
|
||||
}
|
||||
|
||||
cdata = buf;
|
||||
cdata_len = buflen;
|
||||
} else {
|
||||
cdata = eq + 1;
|
||||
cdata_len = nul - (eq + 1);
|
||||
}
|
||||
|
||||
if (!credential_size_ok(c, cn, cdata_len))
|
||||
continue;
|
||||
|
||||
r = acquire_credential_directory(c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, cn);
|
||||
if (nfd == -EEXIST)
|
||||
continue;
|
||||
if (nfd < 0)
|
||||
return nfd;
|
||||
|
||||
r = loop_write(nfd, cdata, cdata_len, /* do_poll= */ false);
|
||||
if (r < 0) {
|
||||
(void) unlinkat(c->target_dir_fd, cn, 0);
|
||||
return log_error_errno(r, "Failed to write credential: %m");
|
||||
}
|
||||
|
||||
c->size_sum += cdata_len;
|
||||
c->n_credentials++;
|
||||
|
||||
log_debug("Successfully processed SMBIOS credential '%s'.", cn);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int import_credentials_smbios(ImportCredentialContext *c) {
|
||||
int r;
|
||||
|
||||
/* Parses DMI OEM strings fields (SMBIOS type 11), as settable with qemu's -smbios type=11,value=… switch. */
|
||||
|
||||
for (unsigned i = 0;; i++) {
|
||||
struct dmi_field_header {
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
uint16_t handle;
|
||||
uint8_t count;
|
||||
char contents[];
|
||||
} _packed_ *dmi_field_header;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
_cleanup_free_ void *data = NULL;
|
||||
size_t size;
|
||||
|
||||
assert_cc(offsetof(struct dmi_field_header, contents) == 5);
|
||||
|
||||
if (asprintf(&p, "/sys/firmware/dmi/entries/11-%u/raw", i) < 0)
|
||||
return log_oom();
|
||||
|
||||
r = read_virtual_file(p, sizeof(dmi_field_header) + CREDENTIALS_TOTAL_SIZE_MAX, (char**) &data, &size);
|
||||
if (r < 0) {
|
||||
/* Once we reach ENOENT there are no more DMI Type 11 fields around. */
|
||||
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, "Failed to open '%p', ignoring: %m", p);
|
||||
break;
|
||||
}
|
||||
|
||||
if (size < offsetof(struct dmi_field_header, contents))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "DMI field header of '%p' too short.", p);
|
||||
|
||||
dmi_field_header = data;
|
||||
if (dmi_field_header->type != 11 ||
|
||||
dmi_field_header->length != offsetof(struct dmi_field_header, contents))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid DMI field header.");
|
||||
|
||||
r = parse_smbios_strings(c, dmi_field_header->contents, size - offsetof(struct dmi_field_header, contents));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (i == UINT_MAX) /* Prevent overflow */
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int import_credentials_trusted(void) {
|
||||
_cleanup_(import_credentials_context_free) ImportCredentialContext c = {
|
||||
.target_dir_fd = -1,
|
||||
};
|
||||
int q, r;
|
||||
int q, w, r;
|
||||
|
||||
r = import_credentials_qemu(&c);
|
||||
w = import_credentials_smbios(&c);
|
||||
q = import_credentials_proc_cmdline(&c);
|
||||
|
||||
if (c.n_credentials > 0) {
|
||||
int z;
|
||||
|
||||
log_debug("Imported %u credentials from kernel command line/fw_cfg.", c.n_credentials);
|
||||
log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg.", c.n_credentials);
|
||||
|
||||
z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
|
||||
if (z < 0)
|
||||
return z;
|
||||
}
|
||||
|
||||
return r < 0 ? r : q;
|
||||
return r < 0 ? r : w < 0 ? w : q;
|
||||
}
|
||||
|
||||
static int symlink_credential_dir(const char *envvar, const char *path, const char *where) {
|
||||
|
@ -4,7 +4,7 @@ set -e
|
||||
|
||||
TEST_DESCRIPTION="test credentials"
|
||||
NSPAWN_ARGUMENTS="${NSPAWN_ARGUMENTS:-} --set-credential=mynspawncredential:strangevalue"
|
||||
QEMU_OPTIONS="${QEMU_OPTIONS:-} -fw_cfg name=opt/io.systemd.credentials/myqemucredential,string=othervalue"
|
||||
QEMU_OPTIONS="${QEMU_OPTIONS:-} -fw_cfg name=opt/io.systemd.credentials/myqemucredential,string=othervalue -smbios type=11,value=io.systemd.credential:smbioscredential=magicdata -smbios type=11,value=io.systemd.credential.binary:binarysmbioscredential=bWFnaWNiaW5hcnlkYXRh"
|
||||
KERNEL_APPEND="${KERNEL_APPEND:-} systemd.set_credential=kernelcmdlinecred:uff systemd.set_credential=sysctl.extra:kernel.domainname=sysctltest rd.systemd.import_credentials=no"
|
||||
|
||||
# shellcheck source=test/test-functions
|
||||
|
@ -29,6 +29,10 @@ elif [ -d /sys/firmware/qemu_fw_cfg/by_name ]; then
|
||||
# Verify that passing creds through kernel cmdline works
|
||||
[ "$(systemd-creds --system cat kernelcmdlinecred)" = "uff" ]
|
||||
|
||||
# And that it also works via SMBIOS
|
||||
[ "$(systemd-creds --system cat smbioscredential)" = "magicdata" ]
|
||||
[ "$(systemd-creds --system cat binarysmbioscredential)" = "magicbinarydata" ]
|
||||
|
||||
# If we aren't run in nspawn, we are run in qemu
|
||||
systemd-detect-virt -q -v
|
||||
expected_credential=myqemucredential
|
||||
|
Loading…
Reference in New Issue
Block a user