From def01c7efeb2d0af216b692f543dc1c64df26e2b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 30 Oct 2025 22:23:20 +0100 Subject: [PATCH] nspawn/vmspawn: Add --bind-user-group= option Useful to add the bound users to the wheel group. --- man/systemd-nspawn.xml | 13 +++++++++++++ man/systemd-vmspawn.xml | 14 ++++++++++++++ src/nspawn/nspawn.c | 22 +++++++++++++++++++++- src/shared/machine-bind-user.c | 4 ++++ src/shared/machine-bind-user.h | 1 + src/vmspawn/vmspawn.c | 22 +++++++++++++++++++++- 6 files changed, 74 insertions(+), 2 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 4e019ee732e..ed0acc69371 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -1667,6 +1667,19 @@ After=sys-subsystem-net-devices-ens1.device + + + + When used with , includes the specified group as an + auxiliary group in the user records of users bound into the container. Takes a group name. + + Note: This will not check whether the specified groups exist in the container. + + This operation is only supported in combination with . + + + + diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 6d677cf1069..7b26bec0676 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -526,6 +526,20 @@ + + + + + When used with , includes the specified group as an + auxiliary group in the user records of users bound into the virtual machine. Takes a group name. + + Note: This will not check whether the specified groups exist in the virtual machine. + + This operation is only supported in combination with . + + + + diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 814895b7818..4cd16d9bc07 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -240,6 +240,7 @@ 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 char **arg_bind_user_groups = NULL; static bool arg_suppress_sync = false; static char *arg_settings_filename = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; @@ -283,6 +284,7 @@ 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_bind_user_groups, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); @@ -429,6 +431,8 @@ static int help(void) { " --bind-user=NAME Bind user from host to container\n" " --bind-user-shell=BOOL|PATH\n" " Configure the shell to use for --bind-user= users\n" + " --bind-user-group=GROUP\n" + " Add an auxiliary group to --bind-user= users\n" "\n%3$sInput/Output:%4$s\n" " --console=MODE Select how stdin/stdout/stderr and /dev/console are\n" " set up for the container.\n" @@ -660,6 +664,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_LOAD_CREDENTIAL, ARG_BIND_USER, ARG_BIND_USER_SHELL, + ARG_BIND_USER_GROUP, ARG_SUPPRESS_SYNC, ARG_IMAGE_POLICY, ARG_BACKGROUND, @@ -738,6 +743,7 @@ static int parse_argv(int argc, char *argv[]) { { "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 }, + { "bind-user-group", required_argument, NULL, ARG_BIND_USER_GROUP }, { "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC }, { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, { "background", required_argument, NULL, ARG_BACKGROUND }, @@ -1514,6 +1520,15 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_BIND_USER_GROUP: + if (!valid_user_group_name(optarg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg); + + if (strv_extend(&arg_bind_user_groups, optarg) < 0) + return log_oom(); + + break; + case ARG_SUPPRESS_SYNC: r = parse_boolean_argument("--suppress-sync=", optarg, &arg_suppress_sync); if (r < 0) @@ -1689,12 +1704,16 @@ static int verify_arguments(void) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "AmbientCapability= setting is not useful for boot mode."); } - /* Drop duplicate --bind-user= entries */ + /* Drop duplicate --bind-user= and --bind-user-group= entries */ strv_uniq(arg_bind_user); + strv_uniq(arg_bind_user_groups); 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="); + if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user="); + r = custom_mount_check_all(); if (r < 0) return r; @@ -4023,6 +4042,7 @@ static int outer_child( arg_bind_user_shell, arg_bind_user_shell_copy, "/run/host/home", + arg_bind_user_groups, &bind_user_context); if (r < 0) return r; diff --git a/src/shared/machine-bind-user.c b/src/shared/machine-bind-user.c index 476536ea281..c0fd1a96d07 100644 --- a/src/shared/machine-bind-user.c +++ b/src/shared/machine-bind-user.c @@ -94,6 +94,7 @@ static int convert_user( const char *shell, bool shell_copy, const char *home_mount_directory, + char **groups, UserRecord **ret_converted_user, GroupRecord **ret_converted_group) { @@ -145,6 +146,7 @@ static int convert_user( 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_STRV("memberOf", groups), 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)))))); @@ -212,6 +214,7 @@ int machine_bind_user_prepare( const char *bind_user_shell, bool bind_user_shell_copy, const char *bind_user_home_mount_directory, + char **bind_user_groups, MachineBindUserContext **ret) { _cleanup_(machine_bind_user_context_freep) MachineBindUserContext *c = NULL; @@ -288,6 +291,7 @@ int machine_bind_user_prepare( bind_user_shell, bind_user_shell_copy, bind_user_home_mount_directory, + bind_user_groups, &cu, &cg); if (r < 0) return r; diff --git a/src/shared/machine-bind-user.h b/src/shared/machine-bind-user.h index c5537d34d2b..265fe137277 100644 --- a/src/shared/machine-bind-user.h +++ b/src/shared/machine-bind-user.h @@ -28,4 +28,5 @@ int machine_bind_user_prepare( const char *bind_user_shell, bool bind_user_shell_copy, const char *bind_user_home_mount_directory, + char **bind_user_groups, MachineBindUserContext **ret); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index f3ee2c28e70..14ae20fbe38 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -143,6 +143,7 @@ static bool arg_notify_ready = true; static char **arg_bind_user = NULL; static char *arg_bind_user_shell = NULL; static bool arg_bind_user_shell_copy = false; +static char **arg_bind_user_groups = NULL; STATIC_DESTRUCTOR_REGISTER(arg_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); @@ -164,6 +165,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm_state_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); +STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -227,6 +229,8 @@ static int help(void) { " --bind-user=NAME Bind user from host to virtual machine\n" " --bind-user-shell=BOOL|PATH\n" " Configure the shell to use for --bind-user= users\n" + " --bind-user-group=GROUP\n" + " Add an auxiliary group to --bind-user= users\n" "\n%3$sIntegration:%4$s\n" " --forward-journal=FILE|DIR\n" " Forward the VM's journal to the host\n" @@ -303,6 +307,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NOTIFY_READY, ARG_BIND_USER, ARG_BIND_USER_SHELL, + ARG_BIND_USER_GROUP, }; static const struct option options[] = { @@ -354,6 +359,7 @@ static int parse_argv(int argc, char *argv[]) { { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, { "bind-user", required_argument, NULL, ARG_BIND_USER }, { "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL }, + { "bind-user-group", required_argument, NULL, ARG_BIND_USER_GROUP }, {} }; @@ -715,6 +721,15 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_BIND_USER_GROUP: + if (!valid_user_group_name(optarg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg); + + if (strv_extend(&arg_bind_user_groups, optarg) < 0) + return log_oom(); + + break; + case '?': return -EINVAL; @@ -722,12 +737,16 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } - /* Drop duplicate --bind-user= entries */ + /* Drop duplicate --bind-user= and --bind-user-group= entries */ strv_uniq(arg_bind_user); + strv_uniq(arg_bind_user_groups); 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="); + if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user="); + if (argc > optind) { arg_kernel_cmdline_extra = strv_copy(argv + optind); if (!arg_kernel_cmdline_extra) @@ -1840,6 +1859,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { arg_bind_user_shell, arg_bind_user_shell_copy, "/run/vmhost/home", + arg_bind_user_groups, &bind_user_context); if (r < 0) return r; -- 2.47.3