diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 797f8fa7d..7f4812e7b 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -1092,10 +1092,14 @@ class DockerVM(BaseNode): # as the container-side pass in _fix_permissions). --pull=never keeps # a stale tag reference from turning into a registry pull attempt, # and the create-time image ID (when known) is immune to retagging. + # --user 0:0 overrides a USER baked into the image (e.g. + # ghcr.io/nokia/srlinux runs as "user"): without it the "privileged" + # helper is exactly as unprivileged as the server itself and + # chmod/chown fail with EPERM on files written by other uids. image_ref = self._image_id or self._image try: process = await asyncio.subprocess.create_subprocess_exec( - "docker", "run", "--rm", "--network", "none", "--pull", "never", + "docker", "run", "--rm", "--network", "none", "--pull", "never", "--user", "0:0", "--entrypoint", "/gns3/bin/busybox", "-v", f"{resources_path}:/gns3:ro", "-v", f"{directory}:/target", @@ -2049,7 +2053,7 @@ class DockerVM(BaseNode): pass raise ComputeError( f"Could not delete the node directory '{self.working_dir}': files left owned by " - f"root could not be reclaimed ({e}). Reclaim them manually with: " - f"docker run --rm -v \"{self.working_dir}\":/target --entrypoint /bin/sh " + f"another user could not be reclaimed ({e}). Reclaim them manually with: " + f"docker run --rm --user 0:0 -v \"{self.working_dir}\":/target --entrypoint /bin/sh " f"{self._image} -c 'chown -R {os.getuid()}:{os.getgid()} /target'" ) diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 8d04b5f83..92e790913 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -2093,7 +2093,7 @@ async def test_reclaim_runs_helper_container(vm, tmp_path): assert await vm._reclaim_directory_ownership(str(directory)) is True args, _ = mock_exec.call_args - assert args[:7] == ("docker", "run", "--rm", "--network", "none", "--pull", "never") + assert args[:9] == ("docker", "run", "--rm", "--network", "none", "--pull", "never", "--user", "0:0") assert args[args.index("--entrypoint") + 1] == "/gns3/bin/busybox" assert "/gns3-share:/gns3:ro" in args assert f"{directory}:/target" in args @@ -2149,7 +2149,7 @@ async def test_delete_reports_root_files_when_reclaim_fails(vm): with patch.object(vm, "_reclaim_directory_ownership", new_callable=AsyncioMagicMock, return_value=False): with patch("gns3server.compute.base_node.shutil.rmtree", side_effect=OSError("permission denied")): - with pytest.raises(ComputeError, match="owned by root"): + with pytest.raises(ComputeError, match="owned by another user"): await vm.delete()