From ec5ec622402fa721daaa77762d2b126cce37c456 Mon Sep 17 00:00:00 2001 From: PTah Date: Sat, 20 Jun 2026 19:28:14 +1000 Subject: [PATCH] fix: enforce SAC git URL when updating ssh-monitor agents (0.20.8) Pass REPO_URL/GIT_BRANCH from agent settings over SSH, preflight git remotes on stale hosts, and stop using GitHub origin on routers. --- backend/app/api/v1/hosts.py | 3 +- backend/app/services/agent_update.py | 11 ++++++- backend/app/services/ssh_connect.py | 47 +++++++++++++++++++++++++--- backend/app/version.py | 2 +- backend/tests/test_ssh_connect.py | 14 +++++++-- frontend/src/version.ts | 2 +- 6 files changed, 68 insertions(+), 11 deletions(-) diff --git a/backend/app/api/v1/hosts.py b/backend/app/api/v1/hosts.py index 87303c9..9ef8aa2 100644 --- a/backend/app/api/v1/hosts.py +++ b/backend/app/api/v1/hosts.py @@ -415,10 +415,11 @@ def update_host_agent_via_ssh( agent_cfg = get_effective_agent_update_config(db) repo_url = (agent_cfg.ssh_git_repo_url or "").strip() or None + branch = (agent_cfg.git_branch or "main").strip() or "main" response = _run_ssh_action_on_host( host, cfg, - action=partial(run_ssh_monitor_update, repo_url=repo_url), + action=partial(run_ssh_monitor_update, repo_url=repo_url, git_branch=branch), ) _set_ssh_admin_status(host, response.ok) if response.ok and response.product_version: diff --git a/backend/app/services/agent_update.py b/backend/app/services/agent_update.py index 7c7b870..5130c71 100644 --- a/backend/app/services/agent_update.py +++ b/backend/app/services/agent_update.py @@ -146,6 +146,7 @@ def run_linux_agent_update_fallback( cfg_linux, *, repo_url: str | None = None, + git_branch: str | None = None, ) -> tuple[bool, str, str | None]: if not is_linux_host(host): return False, "Host is not Linux", None @@ -160,12 +161,14 @@ def run_linux_agent_update_fallback( return False, str(exc), None effective_repo = (repo_url or "").strip() or None + effective_branch = (git_branch or "main").strip() or "main" for target in targets: result = run_ssh_monitor_update( target=target, user=cfg_linux.user, password=cfg_linux.password, repo_url=effective_repo, + git_branch=effective_branch, ) last_message = result.message if result.ok: @@ -217,7 +220,13 @@ def execute_agent_update_fallback(db: Session, host: Host) -> tuple[bool, str, s linux_cfg = get_effective_linux_admin_config(db) agent_cfg = get_effective_agent_update_config(db) repo_url = (agent_cfg.ssh_git_repo_url or "").strip() or None - ok, message, version = run_linux_agent_update_fallback(host, linux_cfg, repo_url=repo_url) + branch = (agent_cfg.git_branch or "main").strip() or "main" + ok, message, version = run_linux_agent_update_fallback( + host, + linux_cfg, + repo_url=repo_url, + git_branch=branch, + ) elif product == PRODUCT_RDP or is_windows_host(host): win_cfg = get_effective_win_admin_config(db) ok, message, version = run_windows_agent_update_fallback(host, win_cfg, cfg.win_agent_update_script) diff --git a/backend/app/services/ssh_connect.py b/backend/app/services/ssh_connect.py index 2c41f3a..6ede9fb 100644 --- a/backend/app/services/ssh_connect.py +++ b/backend/app/services/ssh_connect.py @@ -274,18 +274,46 @@ def probe_ssh_monitor_version( return None -def _ssh_monitor_bootstrap_command(repo_url: str) -> str: +def _ssh_monitor_preflight_git_remote(repo_url: str) -> str: + """Align clone remotes with SAC-trusted URL (works even before updater script is refreshed).""" + safe_repo = _shell_single_quote(repo_url.strip()) + repo_dir = _shell_single_quote(f"{SSH_MONITOR_UPDATE_DIR}/{SSH_MONITOR_REPO_NAME}") + return ( + f"if [ -d {repo_dir}/.git ]; then " + f"git -C {repo_dir} remote add kalinamall {safe_repo} 2>/dev/null || " + f"git -C {repo_dir} remote set-url kalinamall {safe_repo}; " + f"if git -C {repo_dir} remote get-url origin >/dev/null 2>&1; then " + f"git -C {repo_dir} remote set-url origin {safe_repo}; " + f"fi; fi" + ) + + +def _ssh_monitor_update_invoke_command( + repo_url: str, + *, + git_branch: str = "main", +) -> str: + """Run installed updater with SAC-trusted git URL (overrides script default / stale origin).""" + safe_repo = _shell_single_quote(repo_url.strip()) + safe_branch = _shell_single_quote((git_branch or "main").strip() or "main") + preflight = _ssh_monitor_preflight_git_remote(repo_url) + return f"{preflight}; REPO_URL={safe_repo} GIT_BRANCH={safe_branch} {SSH_MONITOR_UPDATE_SCRIPT}" + + +def _ssh_monitor_bootstrap_command(repo_url: str, *, git_branch: str = "main") -> str: """Clone ssh-monitor repo and install updater when /opt/scripts/update_ssh_monitor.sh is absent.""" safe_repo = repo_url.replace("\\", "\\\\").replace('"', '\\"') + safe_branch = (git_branch or "main").replace("\\", "\\\\").replace('"', '\\"') return ( f'UPDATE_DIR="{SSH_MONITOR_UPDATE_DIR}";' f'REPO_URL="{safe_repo}";' + f'GIT_BRANCH="{safe_branch}";' f'SCRIPT_NAME="{SSH_MONITOR_REPO_NAME}";' f'INSTALL="{SSH_MONITOR_UPDATE_SCRIPT}";' f'mkdir -p "$UPDATE_DIR" && cd "$UPDATE_DIR" && ' - f'([ -d "$SCRIPT_NAME" ] || git clone "$REPO_URL" "$SCRIPT_NAME") && ' + f'([ -d "$SCRIPT_NAME" ] || git clone -b "$GIT_BRANCH" "$REPO_URL" "$SCRIPT_NAME") && ' f'cp "$UPDATE_DIR/$SCRIPT_NAME/update_ssh_monitor.sh" "$INSTALL" && ' - f'chmod 750 "$INSTALL" && "$INSTALL"' + f'chmod 750 "$INSTALL" && REPO_URL="$REPO_URL" GIT_BRANCH="$GIT_BRANCH" "$INSTALL"' ) @@ -295,9 +323,15 @@ def run_ssh_monitor_update( user: str, password: str, repo_url: str | None = None, + git_branch: str | None = None, ) -> SshCommandResult: script = SSH_MONITOR_UPDATE_SCRIPT effective_repo = (repo_url or SSH_MONITOR_UPDATE_REPO_URL).strip() + effective_branch = (git_branch or "main").strip() or "main" + update_cmd = _ssh_monitor_update_invoke_command( + effective_repo, + git_branch=effective_branch, + ) check_cmd = f"test -x {script} && echo ready || echo missing" probe = run_ssh_command( target=target, @@ -318,7 +352,10 @@ def run_ssh_monitor_update( bootstrapped = False if "missing" in probe.stdout: - bootstrap_cmd = _ssh_monitor_bootstrap_command(effective_repo) + bootstrap_cmd = _ssh_monitor_bootstrap_command( + effective_repo, + git_branch=effective_branch, + ) updated = run_ssh_command( target=target, user=user, @@ -333,7 +370,7 @@ def run_ssh_monitor_update( target=target, user=user, password=password, - remote_cmd=script, + remote_cmd=update_cmd, command_timeout_sec=900, need_root=True, ) diff --git a/backend/app/version.py b/backend/app/version.py index 94d49ee..a49a568 100644 --- a/backend/app/version.py +++ b/backend/app/version.py @@ -1,5 +1,5 @@ """Единый источник версии SAC (API, health, логи, OpenAPI).""" APP_NAME = "Security Alert Center" -APP_VERSION = "0.20.7" +APP_VERSION = "0.20.8" APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}" diff --git a/backend/tests/test_ssh_connect.py b/backend/tests/test_ssh_connect.py index 6858128..2841d6d 100644 --- a/backend/tests/test_ssh_connect.py +++ b/backend/tests/test_ssh_connect.py @@ -232,7 +232,12 @@ def test_run_ssh_monitor_update_runs_script(monkeypatch): stdout="ready\n", ) if calls == 2: - assert kwargs["remote_cmd"] == "/opt/scripts/update_ssh_monitor.sh" + cmd = kwargs["remote_cmd"] + assert "git -C" in cmd + assert "kalinamall" in cmd + assert "REPO_URL=" in cmd + assert "git.kalinamall.ru" in cmd + assert "update_ssh_monitor.sh" in cmd assert kwargs.get("need_root") is True return ssh_connect.SshCommandResult( ok=True, @@ -251,7 +256,12 @@ def test_run_ssh_monitor_update_runs_script(monkeypatch): monkeypatch.setattr(ssh_connect, "run_ssh_command", fake_run) - result = run_ssh_monitor_update(target="ubabuba", user="root", password="pw") + result = run_ssh_monitor_update( + target="ubabuba", + user="root", + password="pw", + repo_url="https://git.kalinamall.ru/PapaTramp/ssh-monitor.git", + ) assert result.ok is True assert result.agent_version == "2.1.0-SAC" assert calls == 3 diff --git a/frontend/src/version.ts b/frontend/src/version.ts index b2a9200..aa01130 100644 --- a/frontend/src/version.ts +++ b/frontend/src/version.ts @@ -1,4 +1,4 @@ /** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */ export const APP_NAME = "Security Alert Center"; -export const APP_VERSION = "0.20.7"; +export const APP_VERSION = "0.20.8"; export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;