execute: Add new PassEnvironment= directive

This directive allows passing environment variables from the system
manager to spawned services. Variables in the system manager can be set
inside a container by passing `--set-env=...` options to systemd-spawn.

Tested with an on-disk test.service unit. Tested using multiple variable
names on a single line, with an empty setting to clear the current list
of variables, with non-existing variables.

Tested using `systemd-run -p PassEnvironment=VARNAME` to confirm it
works with transient units.

Confirmed that `systemctl show` will display the PassEnvironment
settings.

Checked that man pages are generated correctly.

No regressions in `make check`.
This commit is contained in:
Filipe Brandenburger 2015-09-06 23:06:53 -07:00
parent 3116c225d2
commit b4c14404b3
13 changed files with 197 additions and 10 deletions

2
TODO
View File

@ -79,8 +79,6 @@ Features:
prefixed with /sys generally special.
http://lists.freedesktop.org/archives/systemd-devel/2015-June/032962.html
* Add PassEnvironment= setting to service units, to import select env vars from PID 1 into the service env block
* nspawn: fix logic always print a final newline on output.
https://github.com/systemd/systemd/pull/272#issuecomment-113153176

View File

@ -302,6 +302,33 @@
earlier setting.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>PassEnvironment=</varname></term>
<listitem><para>Pass environment variables from the systemd system
manager to executed processes. Takes a space-separated list of variable
names. This option may be specified more than once, in which case all
listed variables will be set. If the empty string is assigned to this
option, the list of environment variables is reset, all prior
assignments have no effect. Variables that are not set in the system
manager will not be passed and will be silently ignored.</para>
<para>Variables passed from this setting are overridden by those passed
from <varname>Environment=</varname> or
<varname>EnvironmentFile=</varname>.</para>
<para>Example:
<programlisting>PassEnvironment=VAR1 VAR2 VAR3</programlisting>
passes three variables <literal>VAR1</literal>,
<literal>VAR2</literal>, <literal>VAR3</literal>
with the values set for those variables in PID1.</para>
<para>
See
<citerefentry project='man-pages'><refentrytitle>environ</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for details about environment variables.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>StandardInput=</varname></term>
<listitem><para>Controls where file descriptor 0 (STDIN) of

View File

@ -86,7 +86,7 @@ _systemd_run() {
TTYPath= SyslogIdentifier= SyslogLevelPrefix= SyslogLevel=
SyslogFacility= TimerSlackNSec= OOMScoreAdjust= ReadWriteDirectories=
ReadOnlyDirectories= InaccessibleDirectories= EnvironmentFile=
ProtectSystem= ProtectHome= RuntimeDirectory='
ProtectSystem= ProtectHome= RuntimeDirectory= PassEnvironment='
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0

View File

@ -39,7 +39,7 @@ _arguments \
TTYPath= SyslogIdentifier= SyslogLevelPrefix= SyslogLevel= \
SyslogFacility= TimerSlackNSec= OOMScoreAdjust= ReadWriteDirectories= \
ReadOnlyDirectories= InaccessibleDirectories= EnvironmentFile= \
ProtectSystem= ProtectHome= RuntimeDirectory= \
ProtectSystem= ProtectHome= RuntimeDirectory= PassEnvironment= \
))' \
'--description=[Description for unit]:description' \
'--slice=[Run in the specified slice]:slices:__slices' \

View File

@ -138,6 +138,21 @@ bool strv_env_is_valid(char **e) {
return true;
}
bool strv_env_name_is_valid(char **l) {
char **p, **q;
STRV_FOREACH(p, l) {
if (!env_name_is_valid(*p))
return false;
STRV_FOREACH(q, p + 1)
if (streq(*p, *q))
return false;
}
return true;
}
bool strv_env_name_or_assignment_is_valid(char **l) {
char **p, **q;

View File

@ -36,6 +36,7 @@ bool strv_env_is_valid(char **e);
#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL)
char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata);
bool strv_env_name_is_valid(char **l);
bool strv_env_name_or_assignment_is_valid(char **l);
char **strv_env_merge(unsigned n_lists, ...);

View File

@ -629,6 +629,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("EnvironmentFiles", "a(sb)", property_get_environment_files, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PassEnvironment", "as", NULL, offsetof(ExecContext, pass_environment), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("UMask", "u", bus_property_get_mode, offsetof(ExecContext, umask), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitCPU", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitFSIZE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST),
@ -1289,6 +1290,38 @@ int bus_exec_context_set_transient_property(
return 1;
} else if (streq(name, "PassEnvironment")) {
_cleanup_strv_free_ char **l = NULL;
r = sd_bus_message_read_strv(message, &l);
if (r < 0)
return r;
if (!strv_env_name_is_valid(l))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment block.");
if (mode != UNIT_CHECK) {
if (strv_isempty(l)) {
c->pass_environment = strv_free(c->pass_environment);
unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=\n");
} else {
_cleanup_free_ char *joined = NULL;
r = strv_extend_strv(&c->pass_environment, l, true);
if (r < 0)
return r;
joined = strv_join_quoted(c->pass_environment);
if (!joined)
return -ENOMEM;
unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=%s\n", joined);
}
}
return 1;
} else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) {
_cleanup_strv_free_ char **l = NULL;

View File

@ -1332,6 +1332,34 @@ static int build_environment(
return 0;
}
static int build_pass_environment(const ExecContext *c, char ***ret) {
_cleanup_strv_free_ char **pass_env = NULL;
size_t n_env = 0, n_bufsize = 0;
char **i;
STRV_FOREACH(i, c->pass_environment) {
_cleanup_free_ char *x = NULL;
char *v;
v = getenv(*i);
if (!v)
continue;
x = strjoin(*i, "=", v, NULL);
if (!x)
return -ENOMEM;
if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2))
return -ENOMEM;
pass_env[n_env++] = x;
pass_env[n_env] = NULL;
x = NULL;
}
*ret = pass_env;
pass_env = NULL;
return 0;
}
static bool exec_needs_mount_namespace(
const ExecContext *context,
const ExecParameters *params,
@ -1412,7 +1440,7 @@ static int exec_child(
char **files_env,
int *exit_status) {
_cleanup_strv_free_ char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL;
_cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL;
_cleanup_free_ char *mac_selinux_context_net = NULL;
const char *username = NULL, *home = NULL, *shell = NULL, *wd;
uid_t uid = UID_INVALID;
@ -1928,9 +1956,16 @@ static int exec_child(
return r;
}
final_env = strv_env_merge(5,
r = build_pass_environment(context, &pass_env);
if (r < 0) {
*exit_status = EXIT_MEMORY;
return r;
}
final_env = strv_env_merge(6,
params->environment,
our_env,
pass_env,
context->environment,
files_env,
pam_env,
@ -2088,6 +2123,7 @@ void exec_context_done(ExecContext *c) {
c->environment = strv_free(c->environment);
c->environment_files = strv_free(c->environment_files);
c->pass_environment = strv_free(c->pass_environment);
for (l = 0; l < ELEMENTSOF(c->rlimit); l++)
c->rlimit[l] = mfree(c->rlimit[l]);
@ -2358,6 +2394,9 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
STRV_FOREACH(e, c->environment_files)
fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e);
STRV_FOREACH(e, c->pass_environment)
fprintf(f, "%sPassEnvironment: %s\n", prefix, *e);
fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode);
STRV_FOREACH(d, c->runtime_directory)

View File

@ -99,6 +99,7 @@ struct ExecRuntime {
struct ExecContext {
char **environment;
char **environment_files;
char **pass_environment;
struct rlimit *rlimit[_RLIMIT_MAX];
char *working_directory, *root_directory;

View File

@ -33,6 +33,7 @@ $1.CPUAffinity, config_parse_exec_cpu_affinity, 0,
$1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask)
$1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment)
$1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files)
$1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment)
$1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input)
$1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output)
$1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error)

View File

@ -2196,6 +2196,70 @@ int config_parse_environ(const char *unit,
return 0;
}
int config_parse_pass_environ(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) {
const char *whole_rvalue = rvalue;
char*** passenv = data;
_cleanup_strv_free_ char **n = NULL;
size_t nlen = 0, nbufsize = 0;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue)) {
/* Empty assignment resets the list */
*passenv = strv_free(*passenv);
return 0;
}
for (;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES);
if (r == 0)
break;
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r,
"Trailing garbage in %s, ignoring: %s", lvalue, whole_rvalue);
break;
}
if (!env_name_is_valid(word)) {
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Invalid environment name for %s, ignoring: %s", lvalue, word);
continue;
}
if (!GREEDY_REALLOC(n, nbufsize, nlen + 2))
return log_oom();
n[nlen++] = word;
n[nlen] = NULL;
word = NULL;
}
if (n) {
r = strv_extend_strv(passenv, n, true);
if (r < 0)
return r;
}
return 0;
}
int config_parse_ip_tos(const char *unit,
const char *filename,
unsigned line,

View File

@ -84,6 +84,7 @@ int config_parse_syscall_filter(const char *unit, const char *filename, unsigned
int config_parse_syscall_archs(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);
int config_parse_syscall_errno(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);
int config_parse_environ(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);
int config_parse_pass_environ(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);
int config_parse_unit_slice(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);
int config_parse_cpu_shares(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);
int config_parse_memory_limit(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);

View File

@ -1654,7 +1654,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "v", "i", i);
} else if (streq(field, "Environment")) {
} else if (STR_IN_SET(field, "Environment", "PassEnvironment")) {
const char *p;
r = sd_bus_message_open_container(m, 'v', "as");
@ -1678,9 +1678,16 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
if (r == 0)
break;
if (!env_assignment_is_valid(word)) {
log_error("Invalid environment assignment: %s", eq);
return -EINVAL;
if (streq(field, "Environment")) {
if (!env_assignment_is_valid(word)) {
log_error("Invalid environment assignment: %s", word);
return -EINVAL;
}
} else { /* PassEnvironment */
if (!env_name_is_valid(word)) {
log_error("Invalid environment variable name: %s", word);
return -EINVAL;
}
}
r = sd_bus_message_append_basic(m, 's', word);