Various --bind-user= fixes (#39498)

This commit is contained in:
Yu Watanabe
2025-10-31 20:43:54 +09:00
committed by GitHub
11 changed files with 164 additions and 28 deletions
+50
View File
@@ -5,12 +5,16 @@
#include "sd-json.h"
#include "alloc-util.h"
#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "io-util.h"
#include "log.h"
#include "nspawn.h"
#include "machine-bind-user.h"
#include "nspawn-bind-user.h"
#include "strv.h"
#include "user-record.h"
#include "group-record.h"
#include "path-util.h"
@@ -67,6 +71,40 @@ static int write_and_symlink(
return 0;
}
static int write_membership(const char *root, const char *user, const char *group) {
int r;
assert(user);
assert(group);
_cleanup_free_ char *membership = strjoin(user, ":", group, ".membership");
if (!membership)
return log_oom();
_cleanup_free_ char *p = path_join("/run/host/userdb/", membership);
if (!p)
return log_oom();
_cleanup_close_ int fd = chase_and_open(
p,
root,
CHASE_PREFIX_ROOT|CHASE_NO_AUTOFS,
O_WRONLY|O_CREAT|O_CLOEXEC,
/* ret_path= */ NULL);
if (fd < 0)
return log_error_errno(errno, "Failed to create %s: %m", p);
r = userns_chown_at(fd, /* fname= */ NULL, /* uid= */ 0, /* gid= */ 0, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to adjust access mode of '%s': %m", p);
r = loop_write(fd, "{}\n", SIZE_MAX);
if (r < 0)
return log_error_errno(r, "Failed to write empty JSON object into %s: %m", p);
return 0;
}
int bind_user_setup(const MachineBindUserContext *c, const char *root) {
static const UserRecordLoadFlags strip_flags = /* Removes privileged info */
USER_RECORD_LOAD_MASK_PRIVILEGED|
@@ -130,6 +168,12 @@ int bind_user_setup(const MachineBindUserContext *c, const char *root) {
if (r < 0)
return r;
STRV_FOREACH(u, stripped_group->members) {
r = write_membership(root, *u, stripped_group->group_name);
if (r < 0)
return r;
}
/* Third, write out user shadow data. i.e. extract privileged info from user record */
r = user_record_clone(d->payload_user, shadow_flags, &shadow_user);
if (r < 0)
@@ -161,6 +205,12 @@ int bind_user_setup(const MachineBindUserContext *c, const char *root) {
0);
if (r < 0)
return r;
STRV_FOREACH(g, stripped_user->member_of) {
r = write_membership(root, stripped_user->user_name, *g);
if (r < 0)
return r;
}
}
return 1;
+25 -2
View 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;
@@ -1733,7 +1752,7 @@ static int in_child_chown(void) {
return IN_SET(arg_userns_mode, USER_NAMESPACE_PICK, USER_NAMESPACE_FIXED);
}
static int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int flags) {
int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int flags) {
assert(fd >= 0 || fd == AT_FDCWD);
if (!in_child_chown())
@@ -1756,6 +1775,9 @@ static int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int
return -EOVERFLOW;
}
if (isempty(fname))
flags |= AT_EMPTY_PATH;
return RET_NERRNO(fchownat(fd, strempty(fname), uid, gid, flags));
}
@@ -4020,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;
+1
View File
@@ -3,6 +3,7 @@
#include "shared-forward.h"
int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int flags);
int userns_lchown(const char *p, uid_t uid, gid_t gid);
int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid);
int make_run_host(const char *root);
+4
View 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;
+1
View 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);
+32 -18
View File
@@ -18,6 +18,7 @@
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
#include "io-util.h"
#include "log.h"
#include "main-func.h"
#include "mkdir.h"
@@ -1196,6 +1197,31 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) {
return r;
}
static int write_membership(int dir_fd, const char *dir, const char *user, const char *group) {
int r;
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(dir);
assert(user);
assert(group);
_cleanup_free_ char *membership = strjoin(user, ":", group, ".membership");
if (!membership)
return log_oom();
_cleanup_close_ int fd = openat(dir_fd, membership, O_WRONLY|O_CREAT|O_CLOEXEC, 0644);
if (fd < 0)
return log_error_errno(errno, "Failed to create %s/%s: %m", dir, membership);
r = loop_write(fd, "{}\n", SIZE_MAX);
if (r < 0)
return log_error_errno(r, "Failed to write empty JSON object into %s/%s: %m", dir, membership);
log_info("Installed %s/%s from credential.", dir, membership);
return 0;
}
static int load_credential_one(
int credential_dir_fd,
const char *name,
@@ -1430,27 +1456,15 @@ static int load_credential_one(
if (ur)
STRV_FOREACH(g, ur->member_of) {
_cleanup_free_ char *membership = strjoin(ur->user_name, ":", *g);
if (!membership)
return log_oom();
_cleanup_close_ int fd = openat(*userdb_dir_fd, membership, O_WRONLY|O_CREAT|O_CLOEXEC, 0644);
if (fd < 0)
return log_error_errno(errno, "Failed to create %s: %m", membership);
log_info("Installed %s/%s from credential.", userdb_dir, membership);
r = write_membership(*userdb_dir_fd, userdb_dir, ur->user_name, *g);
if (r < 0)
return r;
}
else
STRV_FOREACH(u, gr->members) {
_cleanup_free_ char *membership = strjoin(*u, ":", gr->group_name);
if (!membership)
return log_oom();
_cleanup_close_ int fd = openat(*userdb_dir_fd, membership, O_WRONLY|O_CREAT|O_CLOEXEC, 0644);
if (fd < 0)
return log_error_errno(errno, "Failed to create %s: %m", membership);
log_info("Installed %s/%s from credential.", userdb_dir, membership);
r = write_membership(*userdb_dir_fd, userdb_dir, *u, gr->group_name);
if (r < 0)
return r;
}
if (ur && user_record_disposition(ur) == USER_REGULAR) {
+21 -1
View 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;