diff --git a/bot_bottle/agent_provider.py b/bot_bottle/agent_provider.py index 006e4eb..c3d94d0 100644 --- a/bot_bottle/agent_provider.py +++ b/bot_bottle/agent_provider.py @@ -267,6 +267,8 @@ class AgentProvider(ABC): # runtime, after every backend's copy/export path has completed. git_xdg_dir = f"{plan.guest_home}/.config/git" repair = bottle.exec( + f"chown node:node {shlex.quote(plan.guest_home)} && " + f"chmod 755 {shlex.quote(plan.guest_home)} && " f"mkdir -p {shlex.quote(git_xdg_dir)} && " f"chown -R node:node {shlex.quote(f'{plan.guest_home}/.config')} && " f"chmod -R u+rwX,go+rX {shlex.quote(f'{plan.guest_home}/.config')}", diff --git a/bot_bottle/backend/firecracker/util.py b/bot_bottle/backend/firecracker/util.py index cf30a18..0d9aad8 100644 --- a/bot_bottle/backend/firecracker/util.py +++ b/bot_bottle/backend/firecracker/util.py @@ -373,6 +373,12 @@ mount -o remount,rw / 2>/dev/null # scratch dirs there — git worktrees, build temp, `git init /tmp/...`, etc. mkdir -p /tmp && chmod 1777 /tmp +# Rootfs export also maps the image's original owners to the unprivileged +# host build uid. That uid is not guaranteed to be node's uid in the guest; +# restore the home-directory boundary before any SSH provisioning runs. +chown node:node /home/node 2>/dev/null || true +chmod 755 /home/node 2>/dev/null || true + # Install the per-bottle SSH pubkey from the kernel cmdline. KEY=$(sed -n 's/.*bb_pubkey=\([^ ]*\).*/\1/p' /proc/cmdline | base64 -d 2>/dev/null) if [ -n "$KEY" ]; then diff --git a/tests/unit/test_docker_provision_git_user.py b/tests/unit/test_docker_provision_git_user.py index c6e8e8d..a889002 100644 --- a/tests/unit/test_docker_provision_git_user.py +++ b/tests/unit/test_docker_provision_git_user.py @@ -135,6 +135,8 @@ class TestProvisionGitUser(unittest.TestCase): if "/home/node/.config/git" in call.args[0] ) self.assertEqual("root", user) + self.assertIn("chown node:node /home/node", script) + self.assertIn("chmod 755 /home/node", script) self.assertIn("mkdir -p /home/node/.config/git", script) self.assertIn("chown -R node:node /home/node/.config", script) self.assertIn("chmod -R u+rwX,go+rX /home/node/.config", script) diff --git a/tests/unit/test_firecracker_backend.py b/tests/unit/test_firecracker_backend.py index 5da1c77..d72d795 100644 --- a/tests/unit/test_firecracker_backend.py +++ b/tests/unit/test_firecracker_backend.py @@ -61,6 +61,12 @@ class TestNetpoolSlots(unittest.TestCase): class TestNetpoolRenderers(unittest.TestCase): + def test_guest_init_restores_node_home_boundary(self): + from bot_bottle.backend.firecracker import util + + self.assertIn("chown node:node /home/node", util._GUEST_INIT) + self.assertIn("chmod 755 /home/node", util._GUEST_INIT) + def test_nixos_module_is_non_invasive(self): # The NixOS module must NOT flip the host firewall backend or # hand interfaces to systemd-networkd; it brings the pool up via