<xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--bind-user-shell=</option></term>
+
+ <listitem><para>When used with <option>--bind-user=</option>, includes the specified shell in the
+ user records of users bound into the container. Takes either a boolean or an absolute path.</para>
+
+ <itemizedlist>
+ <listitem><para>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.</para></listitem>
+
+ <listitem><para>If true, the shells specified by the host user records are included in the user records of all users bound into the container.</para></listitem>
+
+ <listitem><para>If passed an absolute path, sets that path as the shell for user records of all users bound into the container.</para></listitem>
+ </itemizedlist>
+
+ <para>Note: This will not check whether the specified shells exist in the container.</para>
+
+ <para>This operation is only supported in combination with <option>--bind-user=</option>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--inaccessible=</option></term>
<xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>BindUserShell=</varname></term>
+
+ <listitem><para>When used with <varname>BindUser</varname>, 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 <option>--bind-user-shell=</option>, see
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ for details about the specific options supported. This setting is privileged (see above).</para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>TemporaryFileSystem=</varname></term>
UserRecord *u,
GroupRecord *g,
uid_t allocate_uid,
+ const char *shell,
+ bool shell_copy,
UserRecord **ret_converted_user,
GroupRecord **ret_converted_group) {
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;
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))))));
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,
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;
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);
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)
#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"
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);
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;
+}
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;
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;
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_;
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;
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);
ARG_SET_CREDENTIAL,
ARG_LOAD_CREDENTIAL,
ARG_BIND_USER,
+ ARG_BIND_USER_SHELL,
ARG_SUPPRESS_SYNC,
ARG_IMAGE_POLICY,
ARG_BACKGROUND,
{ "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 },
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)
/* 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;
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,
!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;
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