Merge pull request #3728 from poettering/dynamic-users

This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2016-07-25 16:40:26 -04:00
42 changed files with 2022 additions and 132 deletions

View File

@@ -23,6 +23,7 @@ MANPAGES += \
man/localtime.5 \
man/machine-id.5 \
man/machine-info.5 \
man/nss-systemd.8 \
man/os-release.5 \
man/sd-bus-errors.3 \
man/sd-bus.3 \
@@ -255,6 +256,7 @@ MANPAGES_ALIAS += \
man/SD_WARNING.3 \
man/init.1 \
man/journald.conf.d.5 \
man/libnss_systemd.so.2.8 \
man/poweroff.8 \
man/reboot.8 \
man/sd_bus_creds_get_audit_login_uid.3 \
@@ -587,6 +589,7 @@ man/SD_NOTICE.3: man/sd-daemon.3
man/SD_WARNING.3: man/sd-daemon.3
man/init.1: man/systemd.1
man/journald.conf.d.5: man/journald.conf.5
man/libnss_systemd.so.2.8: man/nss-systemd.8
man/poweroff.8: man/halt.8
man/reboot.8: man/halt.8
man/sd_bus_creds_get_audit_login_uid.3: man/sd_bus_creds_get_pid.3
@@ -1071,6 +1074,9 @@ man/init.html: man/systemd.html
man/journald.conf.d.html: man/journald.conf.html
$(html-alias)
man/libnss_systemd.so.2.html: man/nss-systemd.html
$(html-alias)
man/poweroff.html: man/halt.html
$(html-alias)
@@ -2519,6 +2525,7 @@ EXTRA_DIST += \
man/nss-myhostname.xml \
man/nss-mymachines.xml \
man/nss-resolve.xml \
man/nss-systemd.xml \
man/os-release.xml \
man/pam_systemd.xml \
man/resolved.conf.xml \

View File

@@ -1199,6 +1199,8 @@ libcore_la_SOURCES = \
src/core/load-dropin.h \
src/core/execute.c \
src/core/execute.h \
src/core/dynamic-user.c \
src/core/dynamic-user.h \
src/core/kill.c \
src/core/kill.h \
src/core/dbus.c \
@@ -5001,6 +5003,27 @@ test_nss_LDADD = \
manual_tests += \
test-nss
# ------------------------------------------------------------------------------
libnss_systemd_la_SOURCES = \
src/nss-systemd/nss-systemd.sym \
src/nss-systemd/nss-systemd.c
libnss_systemd_la_LDFLAGS = \
$(AM_LDFLAGS) \
-module \
-export-dynamic \
-avoid-version \
-shared \
-shrext .so.2 \
-Wl,--version-script=$(top_srcdir)/src/nss-systemd/nss-systemd.sym
libnss_systemd_la_LIBADD = \
libsystemd-internal.la \
libbasic.la
lib_LTLIBRARIES += \
libnss_systemd.la
# ------------------------------------------------------------------------------
if HAVE_MYHOSTNAME
libnss_myhostname_la_SOURCES = \

21
README
View File

@@ -201,7 +201,7 @@ USERS AND GROUPS:
"systemd-coredump" system user and group to exist.
NSS:
systemd ships with three NSS modules:
systemd ships with four glibc NSS modules:
nss-myhostname resolves the local hostname to locally
configured IP addresses, as well as "localhost" to
@@ -210,15 +210,22 @@ NSS:
nss-resolve enables DNS resolution via the systemd-resolved
DNS/LLMNR caching stub resolver "systemd-resolved".
nss-mymachines enables resolution of all local containers
registered with machined to their respective IP addresses.
nss-mymachines enables resolution of all local containers registered
with machined to their respective IP addresses. It also maps UID/GIDs
ranges used by containers to useful names.
To make use of these NSS modules, please add them to the
"hosts: " line in /etc/nsswitch.conf. The "resolve" module
should replace the glibc "dns" module in this file.
nss-systemd enables resolution of all dynamically allocated service
users. (See the DynamicUser= setting in unit files.)
The three modules should be used in the following order:
To make use of these NSS modules, please add them to the "hosts:",
"passwd:" and "group:" lines in /etc/nsswitch.conf. The "resolve"
module should replace the glibc "dns" module in this file (and don't
worry, it chain-loads the "dns" module if it can't talk to resolved).
The four modules should be used in the following order:
passwd: compat mymachines systemd
group: compat mymachines systemd
hosts: files mymachines resolve myhostname
SYSV INIT.D SCRIPTS:

27
TODO
View File

@@ -33,6 +33,29 @@ Janitorial Clean-ups:
Features:
* RemoveIPC= in unit files for removing POSIX/SysV IPC objects
* Set SERVICE_RESULT= as env var while running ExecStop=
* Introduce ProtectSystem=strict for making the entire OS hierarchy read-only
except for a select few
* nspawn: start UID allocation loop from hash of container name
* in the DynamicUser=1 nss module, also map "nobody" and "root" statically
* pid1: log about all processes we kill with with SIGKILL or in abandoned scopes, as this should normally not happen
* nspawn: support that /proc, /sys/, /dev are pre-mounted
* nspawn: mount esp, so that bootctl can work
* define gpt header bits to select volatility mode
* nspawn: mount loopback filesystems with "discard"
* Make TasksMax= take percentages, taken relative to the pids_max sysctl and pids.max cgroup limit
* ProtectKernelLogs= (drops CAP_SYSLOG, add seccomp for syslog() syscall, and DeviceAllow to /dev/kmsg) in service files
* ProtectClock= (drops CAP_SYS_TIMES, adds seecomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc
@@ -46,7 +69,7 @@ Features:
* PrivateUsers= which maps the all user ids except root and the one specified
in User= to nobody
* Add AllocateUser= for allowing dynamic user ids per-service
* ProtectControlGroups= which mounts all of /sys/fs/cgroup read-only
* Add DataDirectory=, CacheDirectory= and LogDirectory= to match
RuntimeDirectory=, and create it as necessary when starting a service, owned by the right user.
@@ -60,8 +83,6 @@ Features:
* RestrictNamespaces= or so in services (taking away the ability to create namespaces, with setns, unshare, clone)
* nspawn: make /proc/sys/net writable?
* make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things
* journalctl: make sure -f ends when the container indicated by -M terminates

View File

@@ -106,8 +106,8 @@
<para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables
<command>nss-myhostname</command> correctly:</para>
<programlisting>passwd: compat mymachines
group: compat mymachines
<programlisting>passwd: compat mymachines systemd
group: compat mymachines systemd
shadow: compat
hosts: files mymachines resolve <command>myhostname</command>
@@ -138,6 +138,7 @@ netgroup: nis</programlisting>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,

View File

@@ -82,8 +82,8 @@
<para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables
<command>nss-mymachines</command> correctly:</para>
<programlisting>passwd: compat <command>mymachines</command>
group: compat <command>mymachines</command>
<programlisting>passwd: compat <command>mymachines</command> systemd
group: compat <command>mymachines</command> systemd
shadow: compat
hosts: files <command>mymachines</command> resolve myhostname
@@ -103,6 +103,7 @@ netgroup: nis</programlisting>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-machined.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,

View File

@@ -81,8 +81,8 @@
<para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables <command>nss-resolve</command>
correctly:</para>
<programlisting>passwd: compat mymachines
group: compat mymachines
<programlisting>passwd: compat mymachines systemd
group: compat mymachines systemd
shadow: compat
hosts: files mymachines <command>resolve</command> myhostname
@@ -102,8 +102,9 @@ netgroup: nis</programlisting>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-resolved</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
</para>
</refsect1>

107
man/nss-systemd.xml Normal file
View File

@@ -0,0 +1,107 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
-->
<refentry id="nss-systemd">
<refentryinfo>
<title>nss-systemd</title>
<productname>systemd</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Lennart</firstname>
<surname>Poettering</surname>
<email>lennart@poettering.net</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>nss-systemd</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>nss-systemd</refname>
<refname>libnss_systemd.so.2</refname>
<refpurpose>Provide UNIX user and group name resolution for dynamic users and groups.</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>libnss_systemd.so.2</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>nss-systemd</command> is a plug-in module for the GNU Name Service Switch (NSS) functionality of the
GNU C Library (<command>glibc</command>), providing UNIX user and group name resolution for dynamic users and
groups allocated through the <varname>DynamicUser=</varname> option in systemd unit files. See
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details on
this option.</para>
<para>To activate the NSS module, add <literal>systemd</literal> to the lines starting with
<literal>passwd:</literal> and <literal>group:</literal> in <filename>/etc/nsswitch.conf</filename>.</para>
<para>It is recommended to place <literal>systemd</literal> after the <literal>files</literal> or
<literal>compat</literal> entry of the <filename>/etc/nsswitch.conf</filename> lines so that
<filename>/etc/passwd</filename> and <filename>/etc/group</filename> based mappings take precedence.</para>
</refsect1>
<refsect1>
<title>Example</title>
<para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables
<command>nss-systemd</command> correctly:</para>
<programlisting>passwd: compat mymachines <command>systemd</command>
group: compat mymachines <command>systemd</command>
shadow: compat
hosts: files mymachines resolve myhostname
networks: files
protocols: db files
services: db files
ethers: db files
rpc: db files
netgroup: nis</programlisting>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>getent</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@@ -143,10 +143,38 @@
<term><varname>User=</varname></term>
<term><varname>Group=</varname></term>
<listitem><para>Sets the Unix user or group that the processes
are executed as, respectively. Takes a single user or group
name or ID as argument. If no group is set, the default group
of the user is chosen. These do not affect commands prefixed with <literal>+</literal>.</para></listitem>
<listitem><para>Set the UNIX user or group that the processes are executed as, respectively. Takes a single
user or group name, or numeric ID as argument. If no group is set, the default group of the user is used. This
setting does not affect commands whose command line is prefixed with <literal>+</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>DynamicUser=</varname></term>
<listitem><para>Takes a boolean parameter. If set, a UNIX user and group pair is allocated dynamically when the
unit is started, and released as soon as it is stopped. The user and group will not be added to
<filename>/etc/passwd</filename> or <filename>/etc/group</filename>, but are managed transiently during
runtime. The <citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
glibc NSS module provides integration of these dynamic users/groups into the system's user and group
databases. The user and group name to use may be configured via <varname>User=</varname> and
<varname>Group=</varname> (see above). If these options are not used and dynamic user/group allocation is
enabled for a unit, the name of the dynamic user/group is implicitly derived from the unit name. If the unit
name without the type suffix qualifies as valid user name it is used directly, otherwise a name incorporating a
hash of it is used. If a statically allocated user or group of the configured name already exists, it is used
and no dynamic user/group is allocated. Dynamic users/groups are allocated from the UID/GID range
61184…65519. It is recommended to avoid this range for regular system or login users. At any point in time
each UID/GID from this range is only assigned to zero or one dynamically allocated users/groups in
use. However, UID/GIDs are recycled after a unit is terminated. Care should be taken that any processes running
as part of a unit for which dynamic users/groups are enabled do not leave files or directories owned by these
users/groups around, as a different unit might get the same UID/GID assigned later on, and thus gain access to
these files or directories. If <varname>DynamicUser=</varname> is enabled, <varname>PrivateTmp=</varname> is
implied. This ensures that the lifetime of temporary files created by the executed processes is bound to the
runtime of the service, and hence the lifetime of the dynamic user/group. Since <filename>/tmp</filename> and
<filename>/var/tmp</filename> are usually the only world-writable directories on a system this ensures that a
unit making use of dynamic user/group allocation cannot leave files around after unit termination. Use
<varname>RuntimeDirectory=</varname> (see below) in order to assign a writable runtime directory to a service,
owned by the dynamic user/group and removed automatically when the unit is terminated. Defaults to
off.</para></listitem>
</varlistentry>
<varlistentry>

View File

@@ -1046,3 +1046,17 @@ int flush_accept(int fd) {
close(cfd);
}
}
struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length) {
struct cmsghdr *cmsg;
assert(mh);
CMSG_FOREACH(cmsg, mh)
if (cmsg->cmsg_level == level &&
cmsg->cmsg_type == type &&
(length == (socklen_t) -1 || length == cmsg->cmsg_len))
return cmsg;
return NULL;
}

View File

@@ -142,6 +142,8 @@ int flush_accept(int fd);
#define CMSG_FOREACH(cmsg, mh) \
for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
/* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */
#define SOCKADDR_UN_LEN(sa) \
({ \

View File

@@ -29,6 +29,7 @@
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utmp.h>
#include "missing.h"
#include "alloc-util.h"
@@ -39,6 +40,7 @@
#include "path-util.h"
#include "string-util.h"
#include "user-util.h"
#include "utf8.h"
bool uid_is_valid(uid_t uid) {
@@ -479,3 +481,94 @@ int take_etc_passwd_lock(const char *root) {
return fd;
}
bool valid_user_group_name(const char *u) {
const char *i;
long sz;
/* Checks if the specified name is a valid user/group name. */
if (isempty(u))
return false;
if (!(u[0] >= 'a' && u[0] <= 'z') &&
!(u[0] >= 'A' && u[0] <= 'Z') &&
u[0] != '_')
return false;
for (i = u+1; *i; i++) {
if (!(*i >= 'a' && *i <= 'z') &&
!(*i >= 'A' && *i <= 'Z') &&
!(*i >= '0' && *i <= '9') &&
*i != '_' &&
*i != '-')
return false;
}
sz = sysconf(_SC_LOGIN_NAME_MAX);
assert_se(sz > 0);
if ((size_t) (i-u) > (size_t) sz)
return false;
if ((size_t) (i-u) > UT_NAMESIZE - 1)
return false;
return true;
}
bool valid_user_group_name_or_id(const char *u) {
/* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right
* range, and not the invalid user ids. */
if (isempty(u))
return false;
if (valid_user_group_name(u))
return true;
return parse_uid(u, NULL) >= 0;
}
bool valid_gecos(const char *d) {
if (!d)
return false;
if (!utf8_is_valid(d))
return false;
if (string_has_cc(d, NULL))
return false;
/* Colons are used as field separators, and hence not OK */
if (strchr(d, ':'))
return false;
return true;
}
bool valid_home(const char *p) {
if (isempty(p))
return false;
if (!utf8_is_valid(p))
return false;
if (string_has_cc(p, NULL))
return false;
if (!path_is_absolute(p))
return false;
if (!path_is_safe(p))
return false;
/* Colons are used as field separators, and hence not OK */
if (strchr(p, ':'))
return false;
return true;
}

View File

@@ -68,3 +68,8 @@ int take_etc_passwd_lock(const char *root);
static inline bool userns_supported(void) {
return access("/proc/self/uid_map", F_OK) >= 0;
}
bool valid_user_group_name(const char *u);
bool valid_user_group_name_or_id(const char *u);
bool valid_gecos(const char *d);
bool valid_home(const char *p);

View File

@@ -44,6 +44,7 @@
#endif
#include "strv.h"
#include "syslog-util.h"
#include "user-util.h"
#include "utf8.h"
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput);
@@ -693,6 +694,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
@@ -840,6 +842,9 @@ int bus_exec_context_set_transient_property(
if (r < 0)
return r;
if (!isempty(uu) && !valid_user_group_name_or_id(uu))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user name: %s", uu);
if (mode != UNIT_CHECK) {
if (isempty(uu))
@@ -859,6 +864,9 @@ int bus_exec_context_set_transient_property(
if (r < 0)
return r;
if (!isempty(gg) && !valid_user_group_name_or_id(gg))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group name: %s", gg);
if (mode != UNIT_CHECK) {
if (isempty(gg))
@@ -1061,7 +1069,8 @@ int bus_exec_context_set_transient_property(
} else if (STR_IN_SET(name,
"IgnoreSIGPIPE", "TTYVHangup", "TTYReset",
"PrivateTmp", "PrivateDevices", "PrivateNetwork",
"NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute", "RestrictRealtime")) {
"NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute",
"RestrictRealtime", "DynamicUser")) {
int b;
r = sd_bus_message_read(message, "b", &b);
@@ -1089,6 +1098,8 @@ int bus_exec_context_set_transient_property(
c->memory_deny_write_execute = b;
else if (streq(name, "RestrictRealtime"))
c->restrict_realtime = b;
else if (streq(name, "DynamicUser"))
c->dynamic_user = b;
unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, yes_no(b));
}

View File

@@ -43,6 +43,7 @@
#include "string-util.h"
#include "strv.h"
#include "syslog-util.h"
#include "user-util.h"
#include "virt.h"
#include "watchdog.h"
@@ -1511,8 +1512,8 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd
}
static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) {
uint8_t code;
Manager *m = userdata;
uint8_t code;
int r;
assert(message);
@@ -1534,6 +1535,61 @@ static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_
return sd_bus_reply_method_return(message, NULL);
}
static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
const char *name;
uid_t uid;
int r;
assert(message);
assert(m);
r = sd_bus_message_read_basic(message, 's', &name);
if (r < 0)
return r;
if (!MANAGER_IS_SYSTEM(m))
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
if (!valid_user_group_name(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name);
r = dynamic_user_lookup_name(m, name, &uid);
if (r == -ESRCH)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user %s does not exist.", name);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, "u", (uint32_t) uid);
}
static int method_lookup_dynamic_user_by_uid(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ char *name = NULL;
Manager *m = userdata;
uid_t uid;
int r;
assert(message);
assert(m);
assert_cc(sizeof(uid) == sizeof(uint32_t));
r = sd_bus_message_read_basic(message, 'u', &uid);
if (r < 0)
return r;
if (!MANAGER_IS_SYSTEM(m))
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
if (!uid_is_valid(uid))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User ID invalid: " UID_FMT, uid);
r = dynamic_user_lookup_uid(m, uid, &name);
if (r == -ESRCH)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user ID " UID_FMT " does not exist.", uid);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, "s", name);
}
static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Manager *m = userdata;
@@ -2199,6 +2255,8 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("LookupDynamicUserByName", "s", "u", method_lookup_dynamic_user_by_name, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("LookupDynamicUserByUID", "u", "s", method_lookup_dynamic_user_by_uid, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("UnitNew", "so", 0),
SD_BUS_SIGNAL("UnitRemoved", "so", 0),

763
src/core/dynamic-user.c Normal file

File diff suppressed because it is too large Load Diff

66
src/core/dynamic-user.h Normal file
View File

@@ -0,0 +1,66 @@
#pragma once
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
typedef struct DynamicUser DynamicUser;
typedef struct DynamicCreds {
/* A combination of a dynamic user and group */
DynamicUser *user;
DynamicUser *group;
} DynamicCreds;
#include "manager.h"
/* Note that this object always allocates a pair of user and group under the same name, even if one of them isn't
* used. This means, if you want to allocate a group and user pair, and they might have two different names, then you
* need to allocated two of these objects. DynamicCreds below makes that easy. */
struct DynamicUser {
int n_ref;
Manager *manager;
/* An AF_UNIX socket pair that contains a datagram containing both the numeric ID assigned, as well as a lock
* file fd locking the user ID we picked. */
int storage_socket[2];
char name[];
};
int dynamic_user_acquire(Manager *m, const char *name, DynamicUser **ret);
int dynamic_user_realize(DynamicUser *d, uid_t *ret);
int dynamic_user_current(DynamicUser *d, uid_t *ret);
DynamicUser* dynamic_user_ref(DynamicUser *d);
DynamicUser* dynamic_user_unref(DynamicUser *d);
DynamicUser* dynamic_user_destroy(DynamicUser *d);
int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds);
void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds);
void dynamic_user_vacuum(Manager *m, bool close_user);
int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret);
int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret);
int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group);
int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid);
void dynamic_creds_unref(DynamicCreds *creds);
void dynamic_creds_destroy(DynamicCreds *creds);

View File

@@ -1526,14 +1526,28 @@ static bool exec_needs_mount_namespace(
return false;
}
static void append_socket_pair(int *array, unsigned *n, int pair[2]) {
assert(array);
assert(n);
if (!pair)
return;
if (pair[0] >= 0)
array[(*n)++] = pair[0];
if (pair[1] >= 0)
array[(*n)++] = pair[1];
}
static int close_remaining_fds(
const ExecParameters *params,
ExecRuntime *runtime,
DynamicCreds *dcreds,
int socket_fd,
int *fds, unsigned n_fds) {
unsigned n_dont_close = 0;
int dont_close[n_fds + 7];
int dont_close[n_fds + 11];
assert(params);
@@ -1551,11 +1565,14 @@ static int close_remaining_fds(
n_dont_close += n_fds;
}
if (runtime) {
if (runtime->netns_storage_socket[0] >= 0)
dont_close[n_dont_close++] = runtime->netns_storage_socket[0];
if (runtime->netns_storage_socket[1] >= 0)
dont_close[n_dont_close++] = runtime->netns_storage_socket[1];
if (runtime)
append_socket_pair(dont_close, &n_dont_close, runtime->netns_storage_socket);
if (dcreds) {
if (dcreds->user)
append_socket_pair(dont_close, &n_dont_close, dcreds->user->storage_socket);
if (dcreds->group)
append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket);
}
return close_all_fds(dont_close, n_dont_close);
@@ -1567,6 +1584,7 @@ static int exec_child(
const ExecContext *context,
const ExecParameters *params,
ExecRuntime *runtime,
DynamicCreds *dcreds,
char **argv,
int socket_fd,
int *fds, unsigned n_fds,
@@ -1617,7 +1635,7 @@ static int exec_child(
log_forget_fds();
r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds);
r = close_remaining_fds(params, runtime, dcreds, socket_fd, fds, n_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
return r;
@@ -1650,26 +1668,49 @@ static int exec_child(
}
}
if (context->user) {
username = context->user;
r = get_user_creds(&username, &uid, &gid, &home, &shell);
if (context->dynamic_user && dcreds) {
/* Make sure we bypass our own NSS module for any NSS checks */
if (putenv((char*) "SYSTEMD_NSS_DYNAMIC_BYPASS=1") != 0) {
*exit_status = EXIT_USER;
return -errno;
}
r = dynamic_creds_realize(dcreds, &uid, &gid);
if (r < 0) {
*exit_status = EXIT_USER;
return r;
}
}
if (context->group) {
const char *g = context->group;
if (uid == UID_INVALID || gid == GID_INVALID) {
*exit_status = EXIT_USER;
return -ESRCH;
}
r = get_group_creds(&g, &gid);
if (r < 0) {
*exit_status = EXIT_GROUP;
return r;
if (dcreds->user)
username = dcreds->user->name;
} else {
if (context->user) {
username = context->user;
r = get_user_creds(&username, &uid, &gid, &home, &shell);
if (r < 0) {
*exit_status = EXIT_USER;
return r;
}
}
if (context->group) {
const char *g = context->group;
r = get_group_creds(&g, &gid);
if (r < 0) {
*exit_status = EXIT_GROUP;
return r;
}
}
}
/* If a socket is connected to STDIN/STDOUT/STDERR, we
* must sure to drop O_NONBLOCK */
if (socket_fd >= 0)
@@ -2192,6 +2233,7 @@ int exec_spawn(Unit *unit,
const ExecContext *context,
const ExecParameters *params,
ExecRuntime *runtime,
DynamicCreds *dcreds,
pid_t *ret) {
_cleanup_strv_free_ char **files_env = NULL;
@@ -2250,6 +2292,7 @@ int exec_spawn(Unit *unit,
context,
params,
runtime,
dcreds,
argv,
socket_fd,
fds, n_fds,
@@ -2723,6 +2766,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
if (c->group)
fprintf(f, "%sGroup: %s\n", prefix, c->group);
fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user));
if (strv_length(c->supplementary_groups) > 0) {
fprintf(f, "%sSupplementaryGroups:", prefix);
strv_fprintf(f, c->supplementary_groups);

View File

@@ -92,6 +92,8 @@ struct ExecRuntime {
char *tmp_dir;
char *var_tmp_dir;
/* An AF_UNIX socket pair, that contains a datagram containing a file descriptor referring to the network
* namespace. */
int netns_storage_socket[2];
};
@@ -174,6 +176,8 @@ struct ExecContext {
bool no_new_privileges;
bool dynamic_user;
/* This is not exposed to the user but available
* internally. We need it to make sure that whenever we spawn
* /usr/bin/mount it is run in the same process group as us so
@@ -235,12 +239,14 @@ struct ExecParameters {
};
#include "unit.h"
#include "dynamic-user.h"
int exec_spawn(Unit *unit,
ExecCommand *command,
const ExecContext *context,
const ExecParameters *exec_params,
ExecRuntime *runtime,
DynamicCreds *dynamic_creds,
pid_t *ret);
void exec_command_done(ExecCommand *c);

View File

@@ -19,9 +19,9 @@ m4_dnl Define the context options only once
m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
`$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context)
$1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory)
$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user)
$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group)
$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups)
$1.User, config_parse_user_group, 0, offsetof($1, exec_context.user)
$1.Group, config_parse_user_group, 0, offsetof($1, exec_context.group)
$1.SupplementaryGroups, config_parse_user_group_strv, 0, offsetof($1, exec_context.supplementary_groups)
$1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context)
$1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context)
$1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context)
@@ -34,6 +34,7 @@ $1.UMask, config_parse_mode, 0,
$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.DynamicUser, config_parse_bool, 0, offsetof($1, exec_context.dynamic_user)
$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)
@@ -285,8 +286,8 @@ Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC
Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command)
Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command)
Socket.TimeoutSec, config_parse_sec, 0, offsetof(Socket, timeout_usec)
Socket.SocketUser, config_parse_unit_string_printf, 0, offsetof(Socket, user)
Socket.SocketGroup, config_parse_unit_string_printf, 0, offsetof(Socket, group)
Socket.SocketUser, config_parse_user_group, 0, offsetof(Socket, user)
Socket.SocketGroup, config_parse_user_group, 0, offsetof(Socket, group)
Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode)
Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode)
Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept)

Some files were not shown because too many files have changed in this diff Show More