nspawn: add SetCredential= and LoadCredential= settings

Add SetCredential= and LoadCredential= to the [Exec] section of the
.nspawn settings file. They take the same arguments as the command line
switches. LoadCredential= is privileged, but SetCredential= is not.

The man page claimed that nspawn propagates the credentials of its
service to the container, which only happens when they are passed on
explicitly, so say so.

Fixes: #38966
This commit is contained in:
Skye Soss authored and Lennart Poettering committed 2026-10-29 17:22:24 +01:00
1 parent 277255af4c
commit bbca861320
10 files changed
+474 -42

No files matched your search

+10 -4
View File
@@ -1973,10 +1973,16 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
<para>Note: when <command>systemd-nspawn</command> runs as systemd system service it can propagate
the credentials it received via <varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
to the container payload. A systemd service manager running as PID 1 in the container can further
propagate them to the services it itself starts. It is thus possible to easily propagate credentials
from a parent service manager to a container manager service and from there into its payload. This
can even be done recursively.</para>
to the container payload, if they are passed on with
<option>--load-credential=<replaceable>ID</replaceable>:<replaceable>ID</replaceable></option>,
i.e. if <replaceable>PATH</replaceable> is the name of a credential rather than a path. A systemd
service manager running as PID 1 in the container can further propagate them to the services it
itself starts. It is thus possible to easily propagate credentials from a parent service manager to a
container manager service and from there into its payload. This can even be done recursively. For
<filename>systemd-nspawn@.service</filename>, credentials may also be passed with the
<varname>SetCredential=</varname> and <varname>LoadCredential=</varname> settings in
<citerefentry><refentrytitle>systemd.nspawn</refentrytitle><manvolnum>5</manvolnum></citerefentry>
files.</para>
<para>In order to embed binary data into the credential data for <option>--set-credential=</option>,
use C-style escaping (i.e. <literal>\n</literal> to embed a newline, or <literal>\x00</literal> to
+37
View File
@@ -352,6 +352,43 @@
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>SetCredential=</varname></term>
<term><varname>LoadCredential=</varname></term>
<listitem><para>Pass a credential to the container. These settings are equivalent to the
<option>--set-credential=</option> and <option>--load-credential=</option> command line switches,
and take <replaceable>ID</replaceable>:<replaceable>VALUE</replaceable> and
<replaceable>ID</replaceable>:<replaceable>PATH</replaceable> respectively, where
<replaceable>PATH</replaceable> is either an absolute path or the name of a credential passed to
<command>systemd-nspawn</command> itself. Each may be specified more than once to pass multiple
credentials. If the same credential ID is assigned more than once by the same setting, the last
assignment takes effect. If the empty string is assigned, the list of credentials set by the
respective setting so far is reset. Credentials from the settings file and from the command line are
not combined: if any credential is passed on the command line, these settings are ignored, unless
<option>--settings=override</option> is used, in which case the credentials from the settings file
replace those from the command line. See
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details.</para>
<para>As with the settings of the same names in unit files (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>),
if the same credential ID is specified with both settings, <varname>LoadCredential=</varname> takes
priority, and <varname>SetCredential=</varname> acts as a fallback if the file does not exist. A
credential that is to be inherited from <command>systemd-nspawn</command> itself, i.e. if
<replaceable>PATH</replaceable> is a credential name, is skipped if it was not passed. In both of
these cases, a missing credential is not considered fatal.</para>
<para><varname>LoadCredential=</varname> is privileged (see above), as it reads files on the host.
For example, the credentials of <filename>systemd-nspawn@.service</filename> may be propagated to
the container with <literal>LoadCredential=<replaceable>ID</replaceable>:<replaceable>ID</replaceable></literal>
in a settings file in <filename>/etc/systemd/nspawn/</filename>. Do not use
<varname>SetCredential=</varname> for secret data, as settings files are usually readable by
unprivileged users. Use <varname>LoadCredential=</varname> instead.</para>
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>LimitCPU=</varname></term>
<term><varname>LimitFSIZE=</varname></term>
+2
View File
@@ -62,6 +62,8 @@ Exec.LinkJournal, config_parse_link_journal, 0,
Exec.Timezone, config_parse_timezone_mode, 0, offsetof(Settings, timezone)
Exec.SuppressSync, config_parse_tristate, 0, offsetof(Settings, suppress_sync)
Exec.RestrictAddressFamilies, config_parse_restrict_address_families, 0, 0
Exec.SetCredential, config_parse_set_credential, 0, 0
Exec.LoadCredential, config_parse_load_credential, 0, 0
Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only)
Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode)
Files.Bind, config_parse_bind, 0, 0
+135
View File
@@ -6,7 +6,10 @@
#include "capability-list.h"
#include "conf-parser.h"
#include "cpu-set-util.h"
#include "creds-util.h"
#include "escape.h"
#include "extract-word.h"
#include "memory-util.h"
#include "namespace-util.h"
#include "nspawn-expose-ports.h"
#include "nspawn-mount.h"
@@ -14,6 +17,7 @@
#include "nspawn-settings.h"
#include "parse-helpers.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "rlimit-util.h"
#include "set.h"
@@ -142,6 +146,8 @@ Settings* settings_free(Settings *s) {
free(s->hostname);
cpu_set_done(&s->cpu_set);
set_free(s->restrict_address_families);
machine_credential_context_done(&s->credentials);
strv_free(s->load_credentials);
strv_free(s->bind_user);
free(s->bind_user_shell);
@@ -1093,3 +1099,132 @@ int config_parse_restrict_address_families(
return 0;
}
int config_parse_set_credential(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Settings *settings = ASSERT_PTR(data);
_cleanup_(erase_and_freep) char *value = NULL;
_cleanup_free_ char *id = NULL;
const char *p;
ssize_t l;
int r;
assert(rvalue);
/* Don't log the assignment or the data on failure, they might be secret. */
if (isempty(rvalue)) {
machine_credential_context_done(&settings->credentials);
settings->credentials = (MachineCredentialContext) {};
return 0;
}
r = machine_credential_split(rvalue, &id, &p);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid credential assignment, ignoring.");
return 0;
}
if (!credential_name_valid(id)) {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name is not valid, ignoring: %s", id);
return 0;
}
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &value);
if (l == -ENOMEM)
return log_oom();
if (l < 0) {
log_syntax(unit, LOG_WARNING, filename, line, l, "Failed to unescape data of credential '%s', ignoring: %m", id);
return 0;
}
/* As in unit files, a later assignment overrides an earlier one for the same credential. */
MachineCredential *cred = machine_credential_find(&settings->credentials, id);
if (cred) {
erase_and_free(cred->data);
cred->data = TAKE_PTR(value);
cred->size = l;
return 0;
}
r = machine_credential_add(&settings->credentials, id, value, l);
if (r < 0)
return log_oom();
return 0;
}
int config_parse_load_credential(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Settings *settings = ASSERT_PTR(data);
_cleanup_free_ char *id = NULL, *source = NULL;
const char *p;
int r;
assert(rvalue);
/* The credentials are only loaded once we know that the settings file is trusted, hence only
* validate the syntax for now. */
if (isempty(rvalue)) {
settings->load_credentials = strv_free(settings->load_credentials);
return 0;
}
r = machine_credential_split(rvalue, &id, &p);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid credential assignment, ignoring: %s", rvalue);
return 0;
}
if (!credential_name_valid(id)) {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name is not valid, ignoring: %s", id);
return 0;
}
if (path_is_absolute(p) ? !path_is_normalized(p) : !credential_name_valid(p)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Credential source is neither a normalized absolute path nor a credential name, ignoring: %s", p);
return 0;
}
source = strdup(p);
if (!source)
return log_oom();
/* As in unit files, a later assignment overrides an earlier one for the same credential. */
STRV_FOREACH_PAIR(i, s, settings->load_credentials)
if (streq(*i, id)) {
free_and_replace(*s, source);
return 0;
}
if (strv_consume_pair(&settings->load_credentials, TAKE_PTR(id), TAKE_PTR(source)) < 0)
return log_oom();
return 0;
}
+5
View File
@@ -7,6 +7,7 @@
#include "conf-parser-forward.h"
#include "cpu-set-util.h"
#include "forward.h"
#include "machine-credential.h"
#include "rlimit-util.h"
#include "seccomp-util.h"
#include "volatile-util.h"
@@ -195,6 +196,8 @@ typedef struct Settings {
int suppress_sync;
Set *restrict_address_families;
bool restrict_address_families_is_allowlist;
MachineCredentialContext credentials;
char **load_credentials; /* pairs of credential ID and source */
/* [Files] */
int read_only;
@@ -283,6 +286,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_userns_ownership);
CONFIG_PARSER_PROTOTYPE(config_parse_bind_user);
CONFIG_PARSER_PROTOTYPE(config_parse_bind_user_shell);
CONFIG_PARSER_PROTOTYPE(config_parse_restrict_address_families);
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
DECLARE_STRING_TABLE_LOOKUP(resolv_conf_mode, ResolvConfMode);
+77 -1
View File
@@ -65,6 +65,7 @@
#include "image-policy.h"
#include "in-addr-util.h"
#include "io-util.h"
#include "iovec-util.h"
#include "libmount-util.h"
#include "log.h"
#include "loop-util.h"
@@ -4754,6 +4755,73 @@ static int ptyfwd_hotkey(PTYForward *f, char c, void *userdata) {
return 0;
}
static int merge_settings_credentials(Settings *settings, const char *path) {
_cleanup_(machine_credential_context_done) MachineCredentialContext credentials = {};
int r;
assert(settings);
assert(path);
if (settings->credentials.n_credentials == 0 && strv_isempty(settings->load_credentials))
return 0;
/* Only allow LoadCredential= in trusted files */
char **load_credentials = settings->load_credentials;
if (!arg_settings_trusted && !strv_isempty(load_credentials)) {
log_warning("Ignoring LoadCredential= settings, file %s is not trusted.", path);
load_credentials = NULL;
}
STRV_FOREACH_PAIR(id, source, load_credentials) {
_cleanup_(iovec_done_erase) struct iovec iov = {};
r = machine_credential_read(*source, &iov);
if (r < 0) {
/* As in unit files, a missing credential is not fatal if it would have been
* inherited from our own credentials, or if SetCredential= provides a fallback for
* it. The source is either an absolute path or a credential name. */
bool inherited = !path_is_absolute(*source);
bool fallback = machine_credential_find(&settings->credentials, *id) != NULL;
bool missing = r == -ENOENT || (inherited && r == -ENXIO);
if (missing && inherited) {
log_full(fallback ? LOG_DEBUG : LOG_INFO,
"Credential '%s' was not passed to systemd-nspawn, %s credential '%s'.",
*source, fallback ? "using SetCredential= fallback for" : "skipping", *id);
continue;
}
if (missing && fallback) {
log_debug_errno(r, "Couldn't read credential '%s' from '%s', using SetCredential= fallback: %m",
*id, *source);
continue;
}
return log_error_errno(r, "Failed to read credential '%s' from '%s': %m", *id, *source);
}
r = machine_credential_add(&credentials, *id, iov.iov_base, iov.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to add credential '%s': %m", *id);
}
/* LoadCredential= takes precedence, SetCredential= only acts as fallback. */
FOREACH_ARRAY(cred, settings->credentials.credentials, settings->credentials.n_credentials) {
if (machine_credential_find(&credentials, cred->id))
continue;
r = machine_credential_add(&credentials, cred->id, cred->data, cred->size);
if (r < 0)
return log_error_errno(r, "Failed to add credential '%s': %m", cred->id);
}
if (credentials.n_credentials > 0 || !strv_isempty(load_credentials)) {
machine_credential_context_done(&arg_credentials);
arg_credentials = TAKE_STRUCT(credentials);
}
return 0;
}
static int merge_settings(Settings *settings, const char *path) {
int rl;
@@ -5099,6 +5167,14 @@ static int merge_settings(Settings *settings, const char *path) {
arg_restrict_address_families_is_allowlist = settings->restrict_address_families_is_allowlist;
}
if (!FLAGS_SET(arg_settings_mask, SETTING_CREDENTIALS)) {
int r;
r = merge_settings_credentials(settings, path);
if (r < 0)
return r;
}
/* The following properties can only be set through the OCI settings logic, not from the command line, hence we
* don't consult arg_settings_mask for them. */
@@ -6735,7 +6811,7 @@ static int run(int argc, char *argv[]) {
r = machine_credential_add(&arg_credentials, "journal.forward_to_socket", NSPAWN_JOURNAL_SOCKET_PATH, SIZE_MAX);
if (r == -EEXIST) {
log_error_errno(r, "Credential 'journal.forward_to_socket' already set via --set-credential=, refusing --forward-journal=.");
log_error_errno(r, "Credential 'journal.forward_to_socket' already set via --set-credential= or SetCredential=, refusing --forward-journal=.");
goto finish;
}
if (r < 0) {
+85 -37
View File
@@ -102,71 +102,119 @@ static int machine_credential_add_and_log(
return 0;
}
int machine_credential_split(const char *s, char **ret_id, const char **ret_value) {
_cleanup_free_ char *id = NULL;
const char *p = ASSERT_PTR(s);
int r;
assert(ret_id);
assert(ret_value);
/* Splits "ID:VALUE" at the first ':'. Doesn't validate the ID, so that callers can log it. Returns
* -EBADMSG if there's no separator. */
r = extract_first_word(&p, &id, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0 || !p)
return -EBADMSG;
*ret_id = TAKE_PTR(id);
*ret_value = p;
return 0;
}
static bool machine_credential_source_valid(const char *source) {
assert(source);
return (is_path(source) && path_is_valid(source)) || credential_name_valid(source);
}
int machine_credential_read(const char *source, struct iovec *ret) {
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
_cleanup_free_ char *path_alloc = NULL;
int r;
assert(source);
assert(ret);
/* Reads a credential from a path (which may refer to an AF_UNIX socket), or from a credential passed
* to us, if the source is a credential name. Returns -ENXIO in the latter case if we didn't receive
* any credentials. Doesn't log. */
if (is_path(source) && path_is_valid(source))
flags |= READ_FULL_FILE_CONNECT_SOCKET;
else if (credential_name_valid(source)) {
const char *e;
r = get_credentials_dir(&e);
if (r < 0)
return r;
path_alloc = path_join(e, source);
if (!path_alloc)
return -ENOMEM;
source = path_alloc;
} else
return -EINVAL;
return read_full_file_full(
AT_FDCWD, source,
/* offset= */ UINT64_MAX,
/* size= */ SIZE_MAX,
flags,
/* bind_name= */ NULL,
(char**) &ret->iov_base, &ret->iov_len);
}
int machine_credential_set(MachineCredentialContext *ctx, const char *cred_str) {
_cleanup_free_ char *id = NULL;
const char *p;
int r;
assert(ctx);
assert(cred_str);
const char *p = ASSERT_PTR(cred_str);
_cleanup_free_ char *id = NULL;
r = extract_first_word(&p, &id, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
/* Don't log the argument or the data on failure, they might be secret. */
r = machine_credential_split(cred_str, &id, &p);
if (r == -EBADMSG)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing ':' separator in --set-credential= argument.");
if (r < 0)
return log_error_errno(r, "Failed to parse --set-credential= parameter: %m");
if (r == 0 || !p)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Missing value for --set-credential=: %s", cred_str);
_cleanup_free_ char *data = NULL;
_cleanup_(erase_and_freep) char *data = NULL;
ssize_t l;
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &data);
if (l < 0)
return log_error_errno(l, "Failed to unescape credential data: %s", p);
return log_error_errno(l, "Failed to unescape data of credential '%s': %m", id);
return machine_credential_add_and_log(ctx, id, data, l);
}
int machine_credential_load(MachineCredentialContext *ctx, const char *cred_path) {
_cleanup_free_ char *id = NULL;
const char *p;
int r;
assert(ctx);
assert(cred_path);
const char *p = ASSERT_PTR(cred_path);
_cleanup_free_ char *id = NULL;
r = extract_first_word(&p, &id, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
r = machine_credential_split(cred_path, &id, &p);
if (r == -EBADMSG)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --load-credential=: %s", cred_path);
if (r < 0)
return log_error_errno(r, "Failed to parse --load-credential= parameter: %m");
if (r == 0 || !p)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --load-credential=: %s", cred_path);
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
_cleanup_free_ char *path_alloc = NULL;
if (is_path(p) && path_is_valid(p))
flags |= READ_FULL_FILE_CONNECT_SOCKET;
else if (credential_name_valid(p)) {
const char *e;
r = get_credentials_dir(&e);
if (r < 0)
return log_error_errno(r, "Credential not available (no credentials passed at all): %s", p);
path_alloc = path_join(e, p);
if (!path_alloc)
return log_oom();
p = path_alloc;
} else
if (!machine_credential_source_valid(p))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Credential source appears to be neither a valid path nor a credential name: %s", p);
_cleanup_(iovec_done_erase) struct iovec iov = {};
r = read_full_file_full(
AT_FDCWD, p,
/* offset= */ UINT64_MAX,
/* size= */ SIZE_MAX,
flags,
/* bind_name= */ NULL,
(char**) &iov.iov_base, &iov.iov_len);
r = machine_credential_read(p, &iov);
if (r == -ENXIO && !is_path(p))
return log_error_errno(r, "Credential not available (no credentials passed at all): %s", p);
if (r < 0)
return log_error_errno(r, "Failed to read credential '%s': %m", p);
+4
View File
@@ -19,5 +19,9 @@ void machine_credential_context_done(MachineCredentialContext *ctx);
MachineCredential* machine_credential_find(MachineCredentialContext *ctx, const char *id);
int machine_credential_add(MachineCredentialContext *ctx, const char *id, const char *value, size_t size);
int machine_credential_split(const char *s, char **ret_id, const char **ret_value);
int machine_credential_read(const char *source, struct iovec *ret);
int machine_credential_set(MachineCredentialContext *ctx, const char *cred_str);
int machine_credential_load(MachineCredentialContext *ctx, const char *cred_path);
@@ -4,6 +4,12 @@ ProcessTwo=off
Ephemeral=yes
Parameters=/sbin/init -x=1
Environment=THIS=that
SetCredential=firstboot.locale:C.UTF-8
SetCredential=binary:a\x00b
LoadCredential=passwd.hashed-password.root:/etc/credstore/root-password
LoadCredential=from-service:from-service
SetCredential=firstboot.locale:de_DE.UTF-8
LoadCredential=from-service:/etc/credstore/from-service
User=user
WorkingDirectory=/cwd
PivotRoot=/newroot
+113
View File
@@ -640,6 +640,119 @@ EOF
rm -fr "$root" "/run/systemd/nspawn/$container.nspawn"
}
testcase_nspawn_settings_credentials() {
local root container
mkdir -p /run/systemd/nspawn
root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.nspawn-settings-credentials.XXX)"
container="$(basename "$root")"
create_dummy_container "$root"
rm -f "/etc/systemd/nspawn/$container.nspawn"
echo "foo bar" >/tmp/cred.path
rm -f /tmp/cred.missing
# Settings files in /run/systemd/nspawn/ are trusted, hence may load credentials from the host
cat >"/run/systemd/nspawn/$container.nspawn" <<EOF
[Exec]
SetCredential=cred.reset:nope
SetCredential=
LoadCredential=cred.load-reset:/tmp/cred.path
LoadCredential=
SetCredential=cred.set:hello world
SetCredential=cred.escaped:a\\nb
SetCredential=cred.replaced:old
SetCredential=cred.replaced:new
LoadCredential=cred.path:/tmp/cred.missing
LoadCredential=cred.path:/tmp/cred.path
LoadCredential=cred.load-wins:/tmp/cred.path
SetCredential=cred.load-wins:fallback
SetCredential=cred.fallback:fallback
LoadCredential=cred.fallback:/tmp/cred.missing
LoadCredential=cred.inherited:svc.cred
LoadCredential=cred.inherited-missing:svc.missing
LoadCredential=cred.inherited-fallback:svc.missing
SetCredential=cred.inherited-fallback:fallback
SetCredential=cred.no-separator
LoadCredential=cred.no-separator
SetCredential=cred/invalid:value
LoadCredential=cred/invalid:/tmp/cred.path
LoadCredential=cred.relative:tmp/cred.path
LoadCredential=cred.unnormalized:/tmp/../tmp/cred.path
[Files]
${COVERAGE_BUILD_DIR:+"Bind=$COVERAGE_BUILD_DIR"}
EOF
cat >"$root/creds.sh" <<\EOF
#!/usr/bin/env bash
set -ex
[[ "$CREDENTIALS_DIRECTORY" == /run/host/credentials ]]
[[ "$(</run/host/credentials/cred.set)" == "hello world" ]]
[[ "$(</run/host/credentials/cred.escaped)" == $'a\nb' ]]
[[ "$(</run/host/credentials/cred.replaced)" == new ]]
[[ "$(</run/host/credentials/cred.path)" == "foo bar" ]]
[[ "$(</run/host/credentials/cred.load-wins)" == "foo bar" ]]
[[ "$(</run/host/credentials/cred.fallback)" == fallback ]]
[[ ! -e /run/host/credentials/cred.reset ]]
[[ ! -e /run/host/credentials/cred.load-reset ]]
# Invalid assignments are ignored
[[ ! -e /run/host/credentials/cred.no-separator ]]
[[ ! -e /run/host/credentials/cred.relative ]]
[[ ! -e /run/host/credentials/cred.unnormalized ]]
[[ ! -e /run/host/credentials/cred ]]
if [[ -n "${EXPECTED_INHERITED:-}" ]]; then
[[ "$(</run/host/credentials/cred.inherited)" == "$EXPECTED_INHERITED" ]]
else
[[ ! -e /run/host/credentials/cred.inherited ]]
fi
EOF
# A credential to be inherited from systemd-nspawn is skipped if it wasn't passed to it...
systemd-nspawn --register=no --directory="$root" bash /creds.sh
# ... and passed on if it was
systemd-run --wait \
-p SetCredential=svc.cred:from-service \
systemd-nspawn --register=no \
--directory="$root" \
--setenv=EXPECTED_INHERITED=from-service \
bash /creds.sh
# Credentials on the command line take precedence over all credential settings
systemd-nspawn --register=no \
--directory="$root" \
--set-credential=cred.cmdline:yes \
bash -xec 'test -e /run/host/credentials/cred.cmdline; test ! -e /run/host/credentials/cred.set'
# ... unless --settings=override is used, in which case the settings replace the command line ones
systemd-nspawn --register=no \
--directory="$root" \
--settings=override \
--set-credential=cred.set:cmdline \
--set-credential=cred.cmdline:yes \
bash -xec '[[ "$(</run/host/credentials/cred.set)" == "hello world" ]]; test ! -e /run/host/credentials/cred.cmdline'
# A settings file next to the image isn't trusted, so it may not load credentials from the host, and
# SetCredential= is used instead. Bind= is ignored in untrusted files too, hence pass the coverage
# directory on the command line.
mv "/run/systemd/nspawn/$container.nspawn" "/var/lib/machines/$container.nspawn"
systemd-nspawn --register=no \
--directory="$root" \
${COVERAGE_BUILD_DIR:+--bind="$COVERAGE_BUILD_DIR"} \
bash -xec '[[ "$(</run/host/credentials/cred.load-wins)" == fallback ]]; test ! -e /run/host/credentials/cred.path'
# A missing credential file is fatal if there is no fallback
cat >"/run/systemd/nspawn/$container.nspawn" <<EOF
[Exec]
LoadCredential=cred.missing:/tmp/cred.missing
[Files]
${COVERAGE_BUILD_DIR:+"Bind=$COVERAGE_BUILD_DIR"}
EOF
(! systemd-nspawn --register=no --directory="$root" true)
rm -fr "$root" "/run/systemd/nspawn/$container.nspawn" "/var/lib/machines/$container.nspawn" /tmp/cred.path
}
bind_user_cleanup() {
userdel --force --remove nspawn-bind-user-1
userdel --force --remove nspawn-bind-user-2