fix: run tool terminal sessions as container user instead of root
- Remove compose-level user: 0:0 override from manifest_compiler.py so the entrypoint can start as root, fix mount ownership, and drop privileges to the container user internally. - Add get_manifest_container_user() helper to resolve the manifest-declared container user (with uid:gid fallback). - Pass container user through TerminalSession, TerminalManager, and the terminal WebSocket handler so docker exec is invoked with --user <user>. - Update and add unit tests for the manifest compiler and terminal session. - Record the additional root-user fix in the fix-pi-container-mount-permissions OpenSpec change/tasks. Quality gates: pytest tests/unit/ (226 passed), pytest tests/services/test_terminal_manager_multi.py (7 passed), ruff check on changed files (clean), mypy on changed files (clean)
This commit is contained in:
@@ -6,6 +6,7 @@ from src.services.build.manifest_compiler import (
|
||||
compile_compose,
|
||||
compile_dockerfile,
|
||||
compile_entrypoint,
|
||||
get_manifest_container_user,
|
||||
get_manifest_home_dir,
|
||||
)
|
||||
|
||||
@@ -273,13 +274,19 @@ def test_compile_dockerfile_starts_as_root_and_drops_privileges() -> None:
|
||||
entrypoint = compile_entrypoint(manifest)
|
||||
|
||||
assert "USER dev" not in dockerfile
|
||||
assert 'exec runuser -u dev -- /bin/bash -il' in entrypoint
|
||||
assert "exec runuser -u dev -- /bin/bash -il" in entrypoint
|
||||
assert 'exec runuser -u dev -- "$@"' in entrypoint
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_compile_compose_runs_as_root() -> None:
|
||||
"""The compose service must start as root so the entrypoint can fix ownership."""
|
||||
def test_compile_compose_does_not_pin_root_user() -> None:
|
||||
"""The compose service must not override the user to root.
|
||||
|
||||
The Dockerfile intentionally omits USER so the entrypoint starts as root,
|
||||
fixes mount ownership, and drops privileges internally. Setting a
|
||||
compose-level user would pin docker exec sessions to root even after the
|
||||
entrypoint drops privileges.
|
||||
"""
|
||||
manifest = {
|
||||
"base_image": "ubuntu:24.04",
|
||||
"interface_type": "terminal",
|
||||
@@ -296,7 +303,32 @@ def test_compile_compose_runs_as_root() -> None:
|
||||
}
|
||||
compose = compile_compose(manifest, variables)
|
||||
|
||||
assert "user: 0:0" in compose
|
||||
assert "user:" not in compose
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_get_manifest_container_user_returns_name() -> None:
|
||||
"""The container user helper returns the manifest user name."""
|
||||
manifest = {
|
||||
"user": {"name": "dev", "uid": 1000, "gid": 1000},
|
||||
}
|
||||
assert get_manifest_container_user(manifest) == "dev"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_get_manifest_container_user_falls_back_to_uid_gid() -> None:
|
||||
"""When the user name is missing, return uid:gid."""
|
||||
manifest = {
|
||||
"user": {"uid": 1000, "gid": 1000},
|
||||
}
|
||||
assert get_manifest_container_user(manifest) == "1000:1000"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_get_manifest_container_user_returns_none_without_user() -> None:
|
||||
"""When no user is declared, return None."""
|
||||
manifest = {"base_image": "ubuntu:24.04"}
|
||||
assert get_manifest_container_user(manifest) is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -314,7 +346,7 @@ class TestCompileEntrypoint:
|
||||
|
||||
assert 'mkdir -p "$HOME_DIR"' in entrypoint
|
||||
assert 'mkdir -p "$WORKSPACE_TARGET"' in entrypoint
|
||||
assert 'ln -sfn' not in entrypoint
|
||||
assert "ln -sfn" not in entrypoint
|
||||
assert 'WORKSPACE_NAME="${WORKSPACE_NAME:-workspace}"' in entrypoint
|
||||
|
||||
def test_entrypoint_removes_stale_placeholder_directory(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user