]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn/vmspawn: Add --bind-user-group= option 39498/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 30 Oct 2025 21:23:20 +0000 (22:23 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 31 Oct 2025 07:57:38 +0000 (08:57 +0100)
Useful to add the bound users to the wheel group.

man/systemd-nspawn.xml
man/systemd-vmspawn.xml
src/nspawn/nspawn.c
src/shared/machine-bind-user.c
src/shared/machine-bind-user.h
src/vmspawn/vmspawn.c

index 4e019ee732e984113fde0155d24af4ad1ee82fb1..ed0acc693717805bce79be6733ee10685b1b252f 100644 (file)
@@ -1667,6 +1667,19 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
         <xi:include href="version-info.xml" xpointer="v258"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--bind-user-group=<replaceable>NAME</replaceable></option></term>
+
+        <listitem><para>When used with <option>--bind-user=</option>, includes the specified group as an
+        auxiliary group in the user records of users bound into the container. Takes a group name.</para>
+
+        <para>Note: This will not check whether the specified groups 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="v259"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--inaccessible=</option></term>
 
index 6d677cf10695f60046bbe1f588642e09ba1baa9e..7b26bec0676d6e2d51b8205ba023ac42165a0214 100644 (file)
 
           <xi:include href="version-info.xml" xpointer="v259"/></listitem>
         </varlistentry>
+
+        <varlistentry>
+          <term><option>--bind-user-group=<replaceable>NAME</replaceable></option></term>
+
+          <listitem><para>When used with <option>--bind-user=</option>, includes the specified group as an
+          auxiliary group in the user records of users bound into the virtual machine. Takes a group name.</para>
+
+          <para>Note: This will not check whether the specified groups exist in the virtual machine.</para>
+
+          <para>This operation is only supported in combination with <option>--bind-user=</option>.</para>
+
+          <xi:include href="version-info.xml" xpointer="v259"/></listitem>
+        </varlistentry>
+
       </variablelist>
     </refsect2>
 
index 814895b7818f630895f4cdeae2ad8a82a000c1b7..4cd16d9bc0742b68e5ff1cf87c5fca9f5640e49e 100644 (file)
@@ -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;
index 476536ea2813864c4275bcd2e84787853de7d13d..c0fd1a96d076816e107f8de740556c37e6db4b32 100644 (file)
@@ -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;
index c5537d34d2b5a6a88a2f72ae5da79ee6ecfa99ca..265fe13727709a655243bfe2e2a7801d06e6e1af 100644 (file)
@@ -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);
index f3ee2c28e704b708b6157f41687b1a98a89ba87f..14ae20fbe38f19c113afe389a9a3ad97932759c7 100644 (file)
@@ -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;