Add user: support to compose services
ComposeService::user/group (compose_file.{h,cpp}) parse a "user[:group]"
key, split on the first ':' the same way an image's own declared USER is
split (oci_image.cpp). start_compose_services() passes both straight
through to run_mounted_container()'s existing user/group parameters, which
already fall back to the image's own declared user when unset -- the same
default -r/--run itself has when --user isn't given.
Verified end to end on the real target machine (root, via the scoped doas
rule), checked via `ps -eo pid,ppid,uid,cmd` (not -x/--exec, see below):
the actual sandboxed command runs as the resolved uid/gid, matching plain
-r --user's own already-working behavior.
Also recorded in TODO.md: verifying this surfaced a real but unrelated
bug in resolve_namespace_pid() (sandbox_process.cpp), which -x/--exec's
own default-identity resolution uses -- it stops at bwrap's own pid-1
namespace supervisor instead of walking one level deeper to the real
(correctly priv-dropped) target, so `-x/--exec <pid> -- id` with no
explicit --user misreports root for a session that's actually running as
a non-root user the whole time. Not a regression from this change and not
fixed here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
@@ -125,6 +125,50 @@ TEST_CASE("compose file: container_name -- optional, and duplicates across servi
|
||||
CHECK_FALSE(load_compose_file(colliding_with_name).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("compose file: user -- optional, with and without a group, split the same way as an image's own USER",
|
||||
"[unit]") {
|
||||
ScratchXdgDirs scratch;
|
||||
|
||||
auto unset = write_compose(scratch.path(),
|
||||
"services:\n"
|
||||
" web:\n"
|
||||
" image: busybox:latest\n");
|
||||
auto loaded_unset = load_compose_file(unset);
|
||||
REQUIRE(loaded_unset.has_value());
|
||||
CHECK_FALSE(loaded_unset->services[0].user.has_value());
|
||||
CHECK_FALSE(loaded_unset->services[0].group.has_value());
|
||||
|
||||
auto user_only = write_compose(scratch.path(),
|
||||
"services:\n"
|
||||
" web:\n"
|
||||
" image: busybox:latest\n"
|
||||
" user: \"1000\"\n");
|
||||
auto loaded_user_only = load_compose_file(user_only);
|
||||
REQUIRE(loaded_user_only.has_value());
|
||||
REQUIRE(loaded_user_only->services[0].user.has_value());
|
||||
CHECK(*loaded_user_only->services[0].user == "1000");
|
||||
CHECK_FALSE(loaded_user_only->services[0].group.has_value());
|
||||
|
||||
auto user_and_group = write_compose(scratch.path(),
|
||||
"services:\n"
|
||||
" web:\n"
|
||||
" image: busybox:latest\n"
|
||||
" user: \"1000:1000\"\n");
|
||||
auto loaded_both = load_compose_file(user_and_group);
|
||||
REQUIRE(loaded_both.has_value());
|
||||
REQUIRE(loaded_both->services[0].user.has_value());
|
||||
CHECK(*loaded_both->services[0].user == "1000");
|
||||
REQUIRE(loaded_both->services[0].group.has_value());
|
||||
CHECK(*loaded_both->services[0].group == "1000");
|
||||
|
||||
auto empty_user = write_compose(scratch.path(),
|
||||
"services:\n"
|
||||
" web:\n"
|
||||
" image: busybox:latest\n"
|
||||
" user: \"\"\n");
|
||||
CHECK_FALSE(load_compose_file(empty_user).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("compose file: command -- list form used as-is, scalar form wrapped in sh -c", "[unit]") {
|
||||
ScratchXdgDirs scratch;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user