"""Executor router — dispatch table over runtime adapters. Security note (F-02 / F-11 remediation): - The previous 6-branch if/elif chain is replaced with a dispatch table (``_RUNTIME_ADAPTERS``). Adding a new runtime is now a one-line dict entry instead of a new branch. - ``run_firecracker`` no longer builds an SSH command string via f-string interpolation; it delegates to the (now arg-list-based) adapter in :mod:`backend.executor.adapters`. """ from __future__ import annotations import subprocess from typing import Any, Callable, Dict, Mapping, Optional # --------------------------------------------------------------------------- # Public dispatch table # --------------------------------------------------------------------------- def execute(action: Mapping[str, Any], node: Mapping[str, Any], spec: Optional[Mapping[str, Any]] = None) -> int: """Dispatch ``action`` to the runtime named by ``spec.execution``. Returns the subprocess exit code (0 = success). Raises :class:`ValueError` if the runtime is unknown — this satisfies MISRA Rule 16.4 (switch shall have default / if-elif must raise on unknown). """ exec_type = (spec or {}).get("execution") or (action or {}).get("runtime") or "host" fn = _RUNTIME_ADAPTERS.get(exec_type) if fn is None: raise ValueError(f"Unknown execution type: {exec_type}") return fn(action, node, spec) # --------------------------------------------------------------------------- # Per-runtime adapters (legacy signature kept for backward compat) # --------------------------------------------------------------------------- def run_distcc(action: Mapping[str, Any], node: Mapping[str, Any], spec: Optional[Mapping[str, Any]]) -> int: cmd = action.get("cmd") or action.get("command") or "" if not cmd: return 2 # distcc takes the compiler invocation as argv; tokenize safely. import shlex return subprocess.call(["distcc", *shlex.split(cmd)]) def run_lxc(action: Mapping[str, Any], node: Mapping[str, Any], spec: Optional[Mapping[str, Any]]) -> int: from backend.executor.adapters import run_lxc as _lxc container = node.get("container") or "default" cmd = action.get("cmd") or action.get("command") or "" if not cmd: return 2 return _lxc(container, cmd) def run_libvirt(action: Mapping[str, Any], node: Mapping[str, Any], spec: Optional[Mapping[str, Any]]) -> int: from backend.executor.adapters import run_libvirt as _libvirt vm = node.get("vm") or "default" cmd = action.get("cmd") or action.get("command") or "" if not cmd: return 2 return _libvirt(vm, cmd) def run_podman(action: Mapping[str, Any], node: Mapping[str, Any], spec: Optional[Mapping[str, Any]]) -> int: from backend.executor.adapters import run_podman as _podman container = node.get("container", "default") cmd = action.get("cmd") or action.get("command") or "" if not cmd: return 2 cwd = action.get("dir") or action.get("cwd") return _podman(container, cmd, cwd=cwd) def run_firecracker(action: Mapping[str, Any], node: Mapping[str, Any], spec: Optional[Mapping[str, Any]]) -> int: """SSH into a Firecracker microVM and run ``action['cmd']``. F-02 fix: no shell=True, no f-string interpolation of untrusted fields. Delegates to :func:`backend.executor.adapters.run_firecracker`, which builds the SSH argv explicitly. """ import os from backend.executor.adapters import run_firecracker as _fc host = node.get("host", "127.0.0.1") fc_config = node.get("firecracker", {}) or {} ssh_key = fc_config.get( "ssh_key", os.environ.get("FESTER_FC_SSH_KEY", "/root/.ssh/fc_builder"), ) ssh_port = fc_config.get("ssh_port", 2222) cmd = action.get("cmd") or action.get("command") or "" if not cmd: return 2 cwd = action.get("dir") or action.get("cwd") return _fc(host, ssh_key, ssh_port, cmd, cwd=cwd) def run_tmux(action: Mapping[str, Any], node: Mapping[str, Any], spec: Optional[Mapping[str, Any]]) -> int: cmd = action.get("cmd") or action.get("command") or "" if not cmd: return 2 import shlex return subprocess.call(["tmux", "new", "-d", *shlex.split(cmd)]) # --------------------------------------------------------------------------- # Dispatch table — single source of truth for runtime lookup # --------------------------------------------------------------------------- _RUNTIME_ADAPTERS: Dict[str, Callable[..., int]] = { "distcc": run_distcc, "lxc": run_lxc, "libvirt": run_libvirt, "podman": run_podman, "firecracker": run_firecracker, "tmux": run_tmux, # 'host' is handled by runtime_router.execute_action directly via # adapters.run_host, but we register it here so the dispatch table # is the single source of truth for "is this runtime known?". "host": lambda action, node, spec: 0, }