fix: set SSH key ownership to container user with mode 600

- Mount SSH keys as bind (not ro) so docker exec --user root can chown
- Add apply_ssh_permissions() to permission_fixer.py
- Call apply_ssh_permissions() after container start for all instance types
- Derive container user from home_dir (/root → root, /home/user → user)
- Tests: apply_ssh_permissions unit tests + start_instance integration tests

Quality gates: pytest 236 passed (6 pre-existing failures), tsc --noEmit clean
This commit is contained in:
Alex Blank
2026-05-29 14:04:52 +02:00
parent ceaed9af66
commit 03d22c4d06
4 changed files with 374 additions and 3 deletions
+29 -3
View File
@@ -75,7 +75,7 @@ from src.services.manifest_compiler import (
merge_with_config,
resolve_base,
)
from src.services.permission_fixer import apply_mount_permissions
from src.services.permission_fixer import apply_mount_permissions, apply_ssh_permissions
from src.services.readiness_probe import execute_probe
from src.services.ssh_keys import cleanup_ssh_key_files, prepare_ssh_key_files
@@ -1415,7 +1415,7 @@ async def start_instance(
{
"source": ssh_dir,
"target": ssh_target,
"type": "ro",
"type": "bind",
}
)
logger.debug(
@@ -1493,7 +1493,7 @@ async def start_instance(
{
"source": ssh_dir,
"target": "/root/.ssh",
"type": "ro",
"type": "bind",
}
)
logger.debug(
@@ -1651,6 +1651,32 @@ async def start_instance(
result["error"],
)
# Fix SSH key ownership/permissions inside the container
if instance.ssh_key_ids and instance.container_id:
container_user = (
"root"
if home_dir == "/root"
else home_dir[6:] if home_dir.startswith("/home/") else "root"
)
ssh_target = os.path.join(home_dir, ".ssh")
logger.debug(
"Applying SSH permissions for user %s on %s in instance %s",
container_user,
ssh_target,
instance.id,
)
ssh_perm_result = apply_ssh_permissions(
instance.container_id,
ssh_target,
container_user,
)
if not ssh_perm_result["success"]:
logger.warning(
"SSH permission fix failed for instance %s: %s",
instance.id,
ssh_perm_result["error"],
)
# Execute readiness probe if configured
tool_type = await session.get(ToolType, instance.tool_type_id)
if tool_type and instance.container_id:
+63
View File
@@ -104,6 +104,69 @@ def apply_mount_permissions(
return results
def apply_ssh_permissions(
container_id: str,
ssh_target: str,
container_user: str,
timeout: int = 10,
) -> dict[str, Any]:
"""Fix SSH directory ownership and permissions in a running container.
Runs chown and chmod on the ~/.ssh directory so the container user
can use the keys (SSH requires the private key to be owned by the
user with mode 600).
Args:
container_id: Docker container ID or name.
ssh_target: Absolute path to the .ssh directory inside the container.
container_user: The container user that should own the keys.
timeout: Max seconds per docker exec command.
Returns:
Result dict with keys: success, error.
"""
result: dict[str, Any] = {"success": True, "error": None}
try:
# Ensure directory is owned by the container user
_run_in_container(
container_id,
["chown", "-R", f"{container_user}:{container_user}", ssh_target],
timeout,
)
# Set directory permissions
_run_in_container(
container_id,
["chmod", "700", ssh_target],
timeout,
)
# Set private key permissions
_run_in_container(
container_id,
[
"sh",
"-c",
f"find {ssh_target} -name 'id_*' -type f -exec chmod 600 {{}} +",
],
timeout,
)
logger.debug(
"Applied SSH permissions for user %s on %s in container %s",
container_user,
ssh_target,
container_id,
)
except PermissionFixError as exc:
result["success"] = False
result["error"] = str(exc)
logger.warning(
"SSH permission fix failed for container %s (target=%s): %s",
container_id,
ssh_target,
exc,
)
return result
class PermissionFixError(Exception):
"""Raised when a permission fix command fails."""