fix: harden SAC security per audit (0.4.15)

Close audit findings: anti-spoof client IP for login rate limit, JWT/CORS
enforce on startup, SSH host-key verification and sudo via stdin, optional
WinRM HTTPS, SSE httpOnly cookie auth, ingest body limit, ILIKE escaping,
and HMAC API key hashing with legacy SHA-256 fallback.
This commit is contained in:
PTah
2026-07-07 19:32:12 +10:00
parent 939fe122c5
commit 80320ae698
29 changed files with 452 additions and 160 deletions
+53 -15
View File
@@ -1,4 +1,4 @@
"""SSH connectivity and remote commands for Linux hosts."""
"""SSH connectivity and remote commands for Linux hosts."""
from __future__ import annotations
@@ -6,6 +6,7 @@ import re
import socket
from dataclasses import dataclass
from app.config import get_settings
from app.models import Host
SSH_MONITOR_UPDATE_SCRIPT = "/opt/scripts/update_ssh_monitor.sh"
@@ -187,20 +188,55 @@ def _shell_single_quote(value: str) -> str:
def _remote_shell_command(
user: str,
remote_cmd: str,
password: str,
*,
need_root: bool = False,
login_shell: bool = True,
) -> str:
"""Build remote shell command. Sudo only when need_root and user is not root."""
) -> tuple[str, bool]:
"""Build remote shell command. Returns (command, needs_sudo_password_on_stdin)."""
safe_cmd = _shell_single_quote(remote_cmd)
bash_flag = "lc" if login_shell else "c"
if user == "root" or not need_root:
return f"bash -{bash_flag} {safe_cmd}"
return f"bash -{bash_flag} {safe_cmd}", False
return f"sudo -S -p '' bash -{bash_flag} {safe_cmd}", True
safe_pw = _shell_single_quote(password)
inner = f"printf '%s\\n' {safe_pw} | sudo -S -p '' bash -{bash_flag} {safe_cmd}"
return f"bash -{bash_flag} {_shell_single_quote(inner)}"
def _configure_ssh_client(client, target: str) -> None:
import paramiko
settings = get_settings()
if settings.sac_ssh_auto_add_host_key:
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
return
client.set_missing_host_key_policy(paramiko.RejectPolicy())
client.load_system_host_keys()
known_hosts = (settings.sac_ssh_known_hosts_file or "").strip()
if known_hosts:
try:
client.load_host_keys(known_hosts)
except OSError:
pass
def _exec_remote_command(
client,
*,
shell_cmd: str,
password: str,
needs_sudo_password: bool,
command_timeout_sec: int,
) -> tuple[int, str, str]:
if needs_sudo_password:
stdin, stdout, stderr = client.exec_command(shell_cmd, timeout=command_timeout_sec, get_pty=True)
stdin.write(f"{password}\n")
stdin.flush()
stdin.channel.shutdown_write()
else:
_stdin, stdout, stderr = client.exec_command(shell_cmd, timeout=command_timeout_sec)
exit_code = stdout.channel.recv_exit_status()
out_text = _truncate_output(stdout.read().decode("utf-8", errors="replace"))
err_text = _truncate_output(stderr.read().decode("utf-8", errors="replace"))
return exit_code, out_text, err_text
def run_ssh_command(
@@ -224,10 +260,9 @@ def run_ssh_command(
except ImportError as exc:
raise RuntimeError("paramiko is not installed on SAC server") from exc
shell_cmd = _remote_shell_command(
shell_cmd, needs_sudo_password = _remote_shell_command(
user,
remote_cmd,
password,
need_root=need_root,
login_shell=login_shell,
)
@@ -237,8 +272,8 @@ def run_ssh_command(
for attempt in range(1, _SSH_CONNECT_ATTEMPTS + 1):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
_configure_ssh_client(client, target)
_connect_ssh_client(
client,
target=target,
@@ -246,10 +281,13 @@ def run_ssh_command(
password=password,
connect_timeout_sec=connect_timeout_sec,
)
_stdin, stdout, stderr = client.exec_command(shell_cmd, timeout=command_timeout_sec)
exit_code = stdout.channel.recv_exit_status()
out_text = _truncate_output(stdout.read().decode("utf-8", errors="replace"))
err_text = _truncate_output(stderr.read().decode("utf-8", errors="replace"))
exit_code, out_text, err_text = _exec_remote_command(
client,
shell_cmd=shell_cmd,
password=password,
needs_sudo_password=needs_sudo_password,
command_timeout_sec=command_timeout_sec,
)
break
except paramiko.AuthenticationException:
return SshCommandResult(