diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
index da598d090c..99e6147b2b 100644
--- a/man/systemd-nspawn.xml
+++ b/man/systemd-nspawn.xml
@@ -938,6 +938,33 @@
+
+
+
+ Takes a non-negative integer. Requests that the specified number of additional 64K
+ UID/GID ranges are delegated into the container's user namespace. These delegated ranges are mapped
+ 1:1 (i.e. the same UID/GID values are used inside and outside the user namespace) and can be used by
+ nested containers to allocate their own transient UID/GID ranges via
+ systemd-nsresourced.service8.
+
+ This option requires , as the delegation is performed
+ by
+ systemd-nsresourced.service8
+ as part of the user namespace allocation. The maximum number of delegated ranges is 16. Defaults to
+ 0, i.e. no delegation.
+
+ When this option is used with a non-zero value, the
+ systemd-nsresourced.service8
+ Varlink socket (/run/systemd/io.systemd.NamespaceResource) is automatically
+ bind-mounted into the container along with the necessary discovery symlinks in
+ /run/systemd/userdb/ and /run/varlink/registry/. This
+ allows processes inside the container to contact
+ systemd-nsresourced on the host in order to allocate nested user namespaces from
+ the delegated ranges.
+
+
+
+
diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml
index 6492a8911a..bf9526df80 100644
--- a/man/systemd.nspawn.xml
+++ b/man/systemd.nspawn.xml
@@ -301,6 +301,23 @@
+
+ PrivateUsersDelegate=
+
+ Takes a non-negative integer. Configures delegation of additional 64K UID/GID ranges
+ into the container's user namespace for use by nested containers. When set to a value greater than
+ zero, the
+ systemd-nsresourced.service8
+ varlink socket will be bind-mounted into the container so that processes inside the container can
+ allocate further user namespaces from the delegated ranges. This is equivalent to the
+ command line switch. Requires
+ PrivateUsers=managed. Defaults to 0. See
+ systemd-nspawn1
+ for details.
+
+
+
+
NotifyReady=
diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf
index a44a13ef29..cdad70706e 100644
--- a/src/nspawn/nspawn-gperf.gperf
+++ b/src/nspawn/nspawn-gperf.gperf
@@ -34,6 +34,7 @@ Exec.MachineID, config_parse_id128, 0,
Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory)
Exec.PivotRoot, config_parse_pivot_root, 0, 0
Exec.PrivateUsers, config_parse_private_users, 0, 0
+Exec.PrivateUsersDelegate, config_parse_unsigned, 0, offsetof(Settings, delegate_container_ranges)
Exec.NotifyReady, config_parse_tristate, 0, offsetof(Settings, notify_ready)
Exec.SystemCallFilter, config_parse_syscall_filter, 0, 0
Exec.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof(Settings, rlimit)
diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h
index 197c6e2f79..84c342b83c 100644
--- a/src/nspawn/nspawn-settings.h
+++ b/src/nspawn/nspawn-settings.h
@@ -175,6 +175,7 @@ typedef struct Settings {
char *pivot_root_old;
UserNamespaceMode userns_mode;
uid_t uid_shift, uid_range;
+ unsigned delegate_container_ranges;
int notify_ready;
char **syscall_allow_list;
char **syscall_deny_list;
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 08afa171ae..722be8bbf7 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -214,6 +214,7 @@ static char **arg_property = NULL;
static sd_bus_message *arg_property_message = NULL;
static UserNamespaceMode arg_userns_mode; /* initialized depending on arg_privileged in run() */
static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U;
+static unsigned arg_delegate_container_ranges = 0;
static UserNamespaceOwnership arg_userns_ownership = _USER_NAMESPACE_OWNERSHIP_INVALID;
static int arg_kill_signal = 0;
static SettingsMask arg_settings_mask = 0;
@@ -430,6 +431,10 @@ static int help(void) {
" --private-users-ownership=MODE\n"
" Adjust ('chown') or map ('map') OS tree ownership\n"
" to private UID/GID range\n"
+ " --private-users-delegate=N\n"
+ " Delegate N additional 64K UID/GID ranges for use\n"
+ " by nested containers (requires managed user\n"
+ " namespaces)\n"
" -U Equivalent to --private-users=pick and\n"
" --private-users-ownership=auto\n"
"\n%3$sNetworking:%4$s\n"
@@ -710,6 +715,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_TEMPLATE,
ARG_PROPERTY,
ARG_PRIVATE_USERS,
+ ARG_PRIVATE_USERS_DELEGATE,
ARG_KILL_SIGNAL,
ARG_SETTINGS,
ARG_CHDIR,
@@ -794,6 +800,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "private-users", optional_argument, NULL, ARG_PRIVATE_USERS },
{ "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN }, /* obsolete */
{ "private-users-ownership",required_argument, NULL, ARG_PRIVATE_USERS_OWNERSHIP},
+ { "private-users-delegate", required_argument, NULL, ARG_PRIVATE_USERS_DELEGATE },
{ "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL },
{ "settings", required_argument, NULL, ARG_SETTINGS },
{ "chdir", required_argument, NULL, ARG_CHDIR },
@@ -1249,6 +1256,14 @@ static int parse_argv(int argc, char *argv[]) {
arg_settings_mask |= SETTING_USERNS;
break;
+ case ARG_PRIVATE_USERS_DELEGATE:
+ r = safe_atou(optarg, &arg_delegate_container_ranges);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --private-users-delegate= parameter: %s", optarg);
+
+ arg_settings_mask |= SETTING_USERNS;
+ break;
+
case ARG_KILL_SIGNAL:
if (streq(optarg, "help"))
return DUMP_STRING_TABLE(signal, int, _NSIG);
@@ -1648,6 +1663,9 @@ static int verify_arguments(void) {
if (arg_userns_mode == USER_NAMESPACE_MANAGED && !arg_private_network)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Managed user namespace operation requires private networking, as otherwise /sys/ may not be mounted.");
+ if (arg_delegate_container_ranges > 0 && arg_userns_mode != USER_NAMESPACE_MANAGED)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--private-users-delegate= requires --private-users=managed.");
+
if (!(arg_clone_ns_flags & CLONE_NEWPID) ||
!(arg_clone_ns_flags & CLONE_NEWUTS)) {
arg_register = false;
@@ -2881,6 +2899,43 @@ static int setup_machine_id(const char *directory) {
return 0;
}
+static int setup_varlink_socket(const char *directory, const char *name) {
+ int r;
+
+ assert(directory);
+
+ if (arg_delegate_container_ranges == 0)
+ return 0;
+
+ r = make_run_host(directory);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ char *src = path_join("/run/systemd", name);
+ if (!src)
+ return log_oom();
+
+ _cleanup_free_ char *dest = path_join(directory, "/run/host", name);
+ if (!dest)
+ return log_oom();
+
+ r = touch(dest);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create %s: %m", dest);
+
+ r = userns_lchown(dest, 0, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to chown %s: %m", dest);
+
+ return mount_nofollow_verbose(
+ LOG_ERR,
+ src,
+ dest,
+ /* fstype= */ NULL,
+ MS_BIND|MS_RDONLY,
+ /* options= */ NULL);
+}
+
static int recursive_chown(const char *directory, uid_t shift, uid_t range) {
int r;
@@ -4371,6 +4426,14 @@ static int outer_child(
if (r < 0)
return r;
+ r = setup_varlink_socket(directory, "io.systemd.NamespaceResource");
+ if (r < 0)
+ return r;
+
+ r = setup_varlink_socket(directory, "io.systemd.MountFileSystem");
+ if (r < 0)
+ return r;
+
/* The same stuff as the $container env var, but nicely readable for the entire payload */
free(p);
p = path_join(directory, "/run/host/container-manager");
@@ -4885,6 +4948,7 @@ static int merge_settings(Settings *settings, const char *path) {
arg_uid_shift = settings->uid_shift;
arg_uid_range = settings->uid_range;
arg_userns_ownership = settings->userns_ownership;
+ arg_delegate_container_ranges = settings->delegate_container_ranges;
}
}
@@ -6158,10 +6222,11 @@ static int run(int argc, char *argv[]) {
goto finish;
}
- userns_fd = nsresource_allocate_userns(
+ userns_fd = nsresource_allocate_userns_full(
nsresource_link,
userns_name,
- NSRESOURCE_UIDS_64K); /* allocate 64K UIDs */
+ NSRESOURCE_UIDS_64K,
+ arg_delegate_container_ranges);
if (userns_fd < 0) {
r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
goto finish;
diff --git a/src/shared/nsresource.c b/src/shared/nsresource.c
index 615f99eff1..6f70bcaf1f 100644
--- a/src/shared/nsresource.c
+++ b/src/shared/nsresource.c
@@ -75,7 +75,7 @@ int nsresource_connect(sd_varlink **ret) {
return 0;
}
-int nsresource_allocate_userns(sd_varlink *vl, const char *name, uint64_t size) {
+int nsresource_allocate_userns_full(sd_varlink *vl, const char *name, uint64_t size, uint64_t delegate_container_ranges) {
_cleanup_close_ int userns_fd = -EBADF;
_cleanup_free_ char *_name = NULL;
const char *error_id;
@@ -120,7 +120,8 @@ int nsresource_allocate_userns(sd_varlink *vl, const char *name, uint64_t size)
SD_JSON_BUILD_PAIR_STRING("name", name),
SD_JSON_BUILD_PAIR_BOOLEAN("mangleName", true),
SD_JSON_BUILD_PAIR_UNSIGNED("size", size),
- SD_JSON_BUILD_PAIR_UNSIGNED("userNamespaceFileDescriptor", userns_fd_idx));
+ SD_JSON_BUILD_PAIR_UNSIGNED("userNamespaceFileDescriptor", userns_fd_idx),
+ JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("delegateContainerRanges", delegate_container_ranges));
if (r < 0)
return log_debug_errno(r, "Failed to call AllocateUserRange() varlink call: %m");
if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceInterfaceNotSupported"))
diff --git a/src/shared/nsresource.h b/src/shared/nsresource.h
index 93957a10c8..5633fd9bf3 100644
--- a/src/shared/nsresource.h
+++ b/src/shared/nsresource.h
@@ -17,7 +17,10 @@ int nsresource_connect(sd_varlink **ret);
* operations under the original identity, until the connection is closed. The 'link' parameter may be passed
* as NULL in which case a short-lived connection is created, just to execute the requested operation. */
-int nsresource_allocate_userns(sd_varlink *vl, const char *name, uint64_t size);
+int nsresource_allocate_userns_full(sd_varlink *vl, const char *name, uint64_t size, uint64_t delegate_container_ranges);
+static inline int nsresource_allocate_userns(sd_varlink *vl, const char *name, uint64_t size) {
+ return nsresource_allocate_userns_full(vl, name, size, /* delegate_container_ranges= */ 0);
+}
int nsresource_register_userns(sd_varlink *vl, const char *name, int userns_fd);
int nsresource_add_mount(sd_varlink *vl, int userns_fd, int mount_fd);
int nsresource_add_cgroup(sd_varlink *vl, int userns_fd, int cgroup_fd);
diff --git a/tmpfiles.d/systemd-nspawn.conf b/tmpfiles.d/systemd-nspawn.conf
index 40e6787233..8254650e93 100644
--- a/tmpfiles.d/systemd-nspawn.conf
+++ b/tmpfiles.d/systemd-nspawn.conf
@@ -21,3 +21,8 @@ Q /var/lib/machines 0700 - - -
R! /var/lib/machines/.#*
R! /.#machine.*
+
+# If the nsresourced/mountfsd sockets are mounted into /run/host, symlink them to their canonical
+# location in /run/systemd.
+L? /run/systemd/io.systemd.NamespaceResource - - - - /run/host/io.systemd.NamespaceResource
+L? /run/systemd/io.systemd.MountFileSystem - - - - /run/host/io.systemd.MountFileSystem
diff --git a/tmpfiles.d/systemd.conf.in b/tmpfiles.d/systemd.conf.in
index 6436400cde..f601cb87f9 100644
--- a/tmpfiles.d/systemd.conf.in
+++ b/tmpfiles.d/systemd.conf.in
@@ -18,6 +18,7 @@ d$ /run/systemd/sessions 0755 root root -
d$ /run/systemd/users 0755 root root -
d /run/systemd/machines 0755 root root -
d$ /run/systemd/shutdown 0755 root root -
+d /run/systemd/dissect-root 0000 root root -
d /run/log 0755 root root -
diff --git a/units/systemd-mountfsd.socket b/units/systemd-mountfsd.socket
index 431369a1a1..a3e19cc418 100644
--- a/units/systemd-mountfsd.socket
+++ b/units/systemd-mountfsd.socket
@@ -13,6 +13,7 @@ Documentation=man:systemd-mountfsd.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
Before=sockets.target shutdown.target
+ConditionPathExists=!/run/host/io.systemd.MountFileSystem
[Socket]
ListenStream=/run/systemd/io.systemd.MountFileSystem
diff --git a/units/systemd-nsresourced.socket b/units/systemd-nsresourced.socket
index c159a5676a..6b4a883df3 100644
--- a/units/systemd-nsresourced.socket
+++ b/units/systemd-nsresourced.socket
@@ -13,6 +13,7 @@ Documentation=man:systemd-nsresourced.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
Before=sockets.target shutdown.target
+ConditionPathExists=!/run/host/io.systemd.NamespaceResource
[Socket]
ListenStream=/run/systemd/io.systemd.NamespaceResource