mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
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:
1 parent
277255af4c
commit
bbca861320
10 files changed
+474
-42
No files matched your search
+10
-4
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user