diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 794d6e8f29..b05744dc08 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -1648,6 +1648,28 @@ After=sys-subsystem-net-devices-ens1.device + + + + When used with , includes the specified shell in the + user records of users bound into the container. Takes either a boolean or an absolute path. + + + If false (the default), no shell is passed in the user records for users bound into + the container. This causes bound users to the use the container's default shell. + + If true, the shells specified by the host user records are included in the user records of all users bound into the container. + + If passed an absolute path, sets that path as the shell for user records of all users bound into the container. + + + Note: This will not check whether the specified shells exist in the container. + + This operation is only supported in combination with . + + + + diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index 4870d6b7ab..1db41a1b72 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -495,6 +495,18 @@ + + BindUserShell= + + When used with BindUser, specifies the shell that is included in + the user record of users bound from the host into the container. This option is equivalent to the + command line switch , see + systemd-nspawn1 + for details about the specific options supported. This setting is privileged (see above). + + + + TemporaryFileSystem= diff --git a/src/nspawn/nspawn-bind-user.c b/src/nspawn/nspawn-bind-user.c index da927347c6..e7d976d199 100644 --- a/src/nspawn/nspawn-bind-user.c +++ b/src/nspawn/nspawn-bind-user.c @@ -91,6 +91,8 @@ static int convert_user( UserRecord *u, GroupRecord *g, uid_t allocate_uid, + const char *shell, + bool shell_copy, UserRecord **ret_converted_user, GroupRecord **ret_converted_group) { @@ -104,6 +106,9 @@ static int convert_user( assert(g); assert(user_record_gid(u) == g->gid); + if (shell_copy) + shell = u->shell; + r = check_etc_passwd_collisions(directory, u->user_name, UID_INVALID); if (r < 0) return r; @@ -138,6 +143,7 @@ static int convert_user( SD_JSON_BUILD_PAIR_CONDITION(u->disposition >= 0, "disposition", SD_JSON_BUILD_STRING(user_disposition_to_string(u->disposition))), SD_JSON_BUILD_PAIR("homeDirectory", SD_JSON_BUILD_STRING(h)), SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NSpawn")), + JSON_BUILD_PAIR_STRING_NON_EMPTY("shell", shell), SD_JSON_BUILD_PAIR("privileged", SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(u->hashed_password), "hashedPassword", SD_JSON_BUILD_VARIANT(hp)), SD_JSON_BUILD_PAIR_CONDITION(!!ssh, "sshAuthorizedKeys", SD_JSON_BUILD_VARIANT(ssh)))))); @@ -203,6 +209,8 @@ BindUserContext* bind_user_context_free(BindUserContext *c) { int bind_user_prepare( const char *directory, char **bind_user, + const char *bind_user_shell, + bool bind_user_shell_copy, uid_t uid_shift, uid_t uid_range, CustomMount **custom_mounts, @@ -285,7 +293,7 @@ int bind_user_prepare( if (r < 0) return r; - r = convert_user(directory, u, g, current_uid, &cu, &cg); + r = convert_user(directory, u, g, current_uid, bind_user_shell, bind_user_shell_copy, &cu, &cg); if (r < 0) return r; diff --git a/src/nspawn/nspawn-bind-user.h b/src/nspawn/nspawn-bind-user.h index b6d4e1e4ad..cb4d246bec 100644 --- a/src/nspawn/nspawn-bind-user.h +++ b/src/nspawn/nspawn-bind-user.h @@ -24,6 +24,6 @@ BindUserContext* bind_user_context_free(BindUserContext *c); DEFINE_TRIVIAL_CLEANUP_FUNC(BindUserContext*, bind_user_context_free); -int bind_user_prepare(const char *directory, char **bind_user, uid_t uid_shift, uid_t uid_range, CustomMount **custom_mounts, size_t *n_custom_mounts, BindUserContext **ret); +int bind_user_prepare(const char *directory, char **bind_user, const char *bind_user_shell, bool bind_user_shell_copy, uid_t uid_shift, uid_t uid_range, CustomMount **custom_mounts, size_t *n_custom_mounts, BindUserContext **ret); int bind_user_setup(const BindUserContext *c, const char *root); diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index b735093691..7a8a8d8b3d 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -71,6 +71,7 @@ Files.OverlayReadOnly, config_parse_overlay, 1, Files.PrivateUsersChown, config_parse_userns_chown, 0, offsetof(Settings, userns_ownership) Files.PrivateUsersOwnership, config_parse_userns_ownership, 0, offsetof(Settings, userns_ownership) Files.BindUser, config_parse_bind_user, 0, offsetof(Settings, bind_user) +Files.BindUserShell, config_parse_bind_user_shell, 0, 0 Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) Network.Interface, config_parse_network_iface_pair, 0, offsetof(Settings, network_interfaces) Network.MACVLAN, config_parse_macvlan_iface_pair, 0, offsetof(Settings, network_macvlan) diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 035796994c..f16a3b61b3 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -13,6 +13,7 @@ #include "nspawn-network.h" #include "nspawn-settings.h" #include "parse-util.h" +#include "path-util.h" #include "process-util.h" #include "rlimit-util.h" #include "socket-util.h" @@ -138,6 +139,7 @@ Settings* settings_free(Settings *s) { free(s->hostname); cpu_set_done(&s->cpu_set); strv_free(s->bind_user); + free(s->bind_user_shell); strv_free(s->network_interfaces); strv_free(s->network_macvlan); @@ -1000,3 +1002,68 @@ int config_parse_bind_user( return 0; } + +int parse_bind_user_shell(const char *s, char **ret_sh, bool *ret_copy) { + char *sh; + int r; + + if (path_is_absolute(s) && path_is_normalized(s)) { + sh = strdup(s); + if (!sh) + return -ENOMEM; + + *ret_sh = sh; + *ret_copy = false; + } else { + r = parse_boolean(s); + if (r < 0) + return r; + + *ret_sh = NULL; + *ret_copy = r; + } + + return 0; +} + +int config_parse_bind_user_shell( + 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); + char *sh = NULL; + bool copy = false; + int r; + + assert(rvalue); + + if (isempty(rvalue)) { + settings->bind_user_shell = mfree(settings->bind_user_shell); + settings->bind_user_shell_copy = false; + settings->bind_user_shell_set = false; + + return 0; + } + + r = parse_bind_user_shell(rvalue, &sh, ©); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse BindUserShell= value, ignoring: %s", rvalue); + return 0; + } + + free_and_replace(settings->bind_user_shell, sh); + settings->bind_user_shell_copy = copy; + settings->bind_user_shell_set = true; + + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 607db87feb..56a65d5a21 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -123,10 +123,11 @@ typedef enum SettingsMask { SETTING_CONSOLE_MODE = UINT64_C(1) << 29, SETTING_CREDENTIALS = UINT64_C(1) << 30, SETTING_BIND_USER = UINT64_C(1) << 31, - SETTING_SUPPRESS_SYNC = UINT64_C(1) << 32, - SETTING_RLIMIT_FIRST = UINT64_C(1) << 33, /* we define one bit per resource limit here */ - SETTING_RLIMIT_LAST = UINT64_C(1) << (33 + _RLIMIT_MAX - 1), - _SETTINGS_MASK_ALL = (UINT64_C(1) << (33 + _RLIMIT_MAX)) -1, + SETTING_BIND_USER_SHELL = UINT64_C(1) << 32, + SETTING_SUPPRESS_SYNC = UINT64_C(1) << 33, + SETTING_RLIMIT_FIRST = UINT64_C(1) << 34, /* we define one bit per resource limit here */ + SETTING_RLIMIT_LAST = UINT64_C(1) << (34 + _RLIMIT_MAX - 1), + _SETTINGS_MASK_ALL = (UINT64_C(1) << (34 + _RLIMIT_MAX)) -1, _SETTING_FORCE_ENUM_WIDTH = UINT64_MAX } SettingsMask; @@ -195,6 +196,9 @@ typedef struct Settings { size_t n_custom_mounts; UserNamespaceOwnership userns_ownership; char **bind_user; + char *bind_user_shell; + bool bind_user_shell_copy; + bool bind_user_shell_set; /* [Network] */ int private_network; @@ -270,6 +274,9 @@ CONFIG_PARSER_PROTOTYPE(config_parse_timezone_mode); CONFIG_PARSER_PROTOTYPE(config_parse_userns_chown); CONFIG_PARSER_PROTOTYPE(config_parse_userns_ownership); CONFIG_PARSER_PROTOTYPE(config_parse_bind_user); +CONFIG_PARSER_PROTOTYPE(config_parse_bind_user_shell); + +int parse_bind_user_shell(const char *s, char **ret_sh, bool *ret_copy); const char* resolv_conf_mode_to_string(ResolvConfMode a) _const_; ResolvConfMode resolv_conf_mode_from_string(const char *s) _pure_; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 92e2c5ec20..27d26f9809 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -240,6 +240,8 @@ static char **arg_sysctl = NULL; static ConsoleMode arg_console_mode = _CONSOLE_MODE_INVALID; static MachineCredentialContext arg_credentials = {}; static char **arg_bind_user = NULL; +static char *arg_bind_user_shell = NULL; +static bool arg_bind_user_shell_copy = false; static bool arg_suppress_sync = false; static char *arg_settings_filename = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; @@ -282,6 +284,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); STATIC_DESTRUCTOR_REGISTER(arg_cpu_set, cpu_set_done); STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); @@ -692,6 +695,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_SET_CREDENTIAL, ARG_LOAD_CREDENTIAL, ARG_BIND_USER, + ARG_BIND_USER_SHELL, ARG_SUPPRESS_SYNC, ARG_IMAGE_POLICY, ARG_BACKGROUND, @@ -769,6 +773,7 @@ static int parse_argv(int argc, char *argv[]) { { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, { "bind-user", required_argument, NULL, ARG_BIND_USER }, + { "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL }, { "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC }, { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, { "background", required_argument, NULL, ARG_BACKGROUND }, @@ -1536,6 +1541,22 @@ static int parse_argv(int argc, char *argv[]) { arg_settings_mask |= SETTING_BIND_USER; break; + case ARG_BIND_USER_SHELL: { + bool copy = false; + char *sh = NULL; + r = parse_bind_user_shell(optarg, &sh, ©); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(r, "Invalid user shell to bind: %s", optarg); + + free_and_replace(arg_bind_user_shell, sh); + arg_bind_user_shell_copy = copy; + + arg_settings_mask |= SETTING_BIND_USER_SHELL; + break; + } + case ARG_SUPPRESS_SYNC: r = parse_boolean_argument("--suppress-sync=", optarg, &arg_suppress_sync); if (r < 0) @@ -1722,6 +1743,9 @@ static int verify_arguments(void) { /* Drop duplicate --bind-user= entries */ strv_uniq(arg_bind_user); + if (arg_bind_user_shell && strv_isempty(arg_bind_user)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-shell= without --bind-user="); + r = custom_mount_check_all(); if (r < 0) return r; @@ -4022,6 +4046,8 @@ static int outer_child( r = bind_user_prepare( directory, arg_bind_user, + arg_bind_user_shell, + arg_bind_user_shell_copy, chown_uid, chown_range, &arg_custom_mounts, &arg_n_custom_mounts, @@ -4841,6 +4867,12 @@ static int merge_settings(Settings *settings, const char *path) { !strv_isempty(settings->bind_user)) strv_free_and_replace(arg_bind_user, settings->bind_user); + if (!FLAGS_SET(arg_settings_mask, SETTING_BIND_USER_SHELL) && + settings->bind_user_shell_set) { + free_and_replace(arg_bind_user_shell, settings->bind_user_shell); + arg_bind_user_shell_copy = settings->bind_user_shell_copy; + } + if ((arg_settings_mask & SETTING_NOTIFY_READY) == 0 && settings->notify_ready >= 0) arg_notify_ready = settings->notify_ready; diff --git a/test/units/TEST-13-NSPAWN.nspawn.sh b/test/units/TEST-13-NSPAWN.nspawn.sh index ffe5e8507d..a0fbb3637d 100755 --- a/test/units/TEST-13-NSPAWN.nspawn.sh +++ b/test/units/TEST-13-NSPAWN.nspawn.sh @@ -580,6 +580,151 @@ testcase_bind_user() { rm -fr "$root" } +testcase_bind_user_shell() { + local root + + root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.bind-user.XXX)" + create_dummy_container "$root" + useradd --create-home --user-group --shell=/usr/bin/bash nspawn-bind-user-1 + useradd --create-home --user-group --shell=/usr/bin/sh nspawn-bind-user-2 + trap bind_user_cleanup RETURN + + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + bash -xec 'grep -qv "\"shell\"" /run/host/userdb/nspawn-bind-user-1.user' + + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user-shell=no \ + bash -xec 'grep -qv "\"shell\"" /run/host/userdb/nspawn-bind-user-1.user' + + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user-shell=yes \ + bash -xec 'grep -q "\"shell\":\"/usr/bin/bash\"" /run/host/userdb/nspawn-bind-user-1.user' + + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user-shell=/bin/bash \ + bash -xec 'grep -q "\"shell\":\"/bin/bash\"" /run/host/userdb/nspawn-bind-user-1.user' + + (! systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user-shell=bad-argument \ + bash -xec 'true') + + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user=nspawn-bind-user-2 \ + bash -xec 'grep -qv "\"shell\"" /run/host/userdb/nspawn-bind-user-1.user && grep -qv "\"shell\"" /run/host/userdb/nspawn-bind-user-2.user' + + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user=nspawn-bind-user-2 \ + --bind-user-shell=yes \ + bash -xec 'grep -q "\"shell\":\"/usr/bin/bash\"" /run/host/userdb/nspawn-bind-user-1.user && grep -q "\"shell\":\"/usr/bin/sh\"" /run/host/userdb/nspawn-bind-user-2.user' + + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user=nspawn-bind-user-2 \ + --bind-user-shell=/bin/sh \ + bash -xec 'grep -q "\"shell\":\"/bin/sh\"" /run/host/userdb/nspawn-bind-user-1.user && grep -q "\"shell\":\"/bin/sh\"" /run/host/userdb/nspawn-bind-user-2.user' + + mach=$(basename "$root") + mkdir -p /run/systemd/nspawn + conf=/run/systemd/nspawn/"$mach".nspawn + + cat <<'EOF' >"$conf" +# [Files] +# BindUserShell=no by default +EOF + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-2 \ + bash -xec 'grep -qv "\"shell\"" /run/host/userdb/nspawn-bind-user-2.user' + + cat <<'EOF' >"$conf" +[Files] +BindUserShell=no +EOF + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-2 \ + bash -xec 'grep -qv "\"shell\"" /run/host/userdb/nspawn-bind-user-2.user' + + cat <<'EOF' >"$conf" +[Files] +BindUserShell=yes +EOF + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-2 \ + bash -xec 'grep -q "\"shell\":\"/usr/bin/sh\"" /run/host/userdb/nspawn-bind-user-2.user' + + cat <<'EOF' >"$conf" +[Files] +BindUserShell=/bin/sh +EOF + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-2 \ + bash -xec 'grep -q "\"shell\":\"/bin/sh\"" /run/host/userdb/nspawn-bind-user-2.user' + + cat <<'EOF' >"$conf" +# [Files] +# BindUserShell=no default doesn't override +EOF + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --settings=override \ + --bind-user=nspawn-bind-user-2 \ + --bind-user-shell=yes \ + bash -xec 'grep -q "\"shell\":\"/usr/bin/sh\"" /run/host/userdb/nspawn-bind-user-2.user' + + cat <<'EOF' >"$conf" +[Files] +BindUserShell=no +EOF + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --settings=override \ + --bind-user=nspawn-bind-user-2 \ + --bind-user-shell=yes \ + bash -xec 'grep -qv "\"shell\"" /run/host/userdb/nspawn-bind-user-2.user' + + cat <<'EOF' >"$conf" +[Files] +BindUserShell=no +EOF + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --settings=override \ + --bind-user=nspawn-bind-user-2 \ + --bind-user-shell=/foo \ + bash -xec 'grep -qv "\"shell\"" /run/host/userdb/nspawn-bind-user-2.user' + + cat <<'EOF' >"$conf" +[Files] +BindUserShell=/bin/sh +EOF + systemd-nspawn --directory="$root" \ + --private-users=pick \ + --settings=override \ + --bind-user=nspawn-bind-user-2 \ + --bind-user-shell=yes \ + bash -xec 'grep -q "\"shell\":\"/bin/sh\"" /run/host/userdb/nspawn-bind-user-2.user' + + rm -fr "$root" +} + testcase_bind_tmp_path() { # https://github.com/systemd/systemd/issues/4789 local root