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