diff --git a/backend/app/services/winrm_connect.py b/backend/app/services/winrm_connect.py index cd81cf6..fe5b310 100644 --- a/backend/app/services/winrm_connect.py +++ b/backend/app/services/winrm_connect.py @@ -35,8 +35,28 @@ def resolve_windows_host_target(host: Host) -> str: return targets[0] +def _netbios_name_variants(name: str | None) -> list[str]: + """Короткое имя ПК (NetBIOS), как Enter-PSSession -ComputerName Andrisonova-PC.""" + text = (name or "").strip() + if not text: + return [] + short = text.split(".")[0].split("\\")[0].strip() + if not short: + return [] + if short.casefold() == text.casefold(): + return [short] + return [short, text] + + +def _looks_like_computer_name(value: str) -> bool: + text = value.strip() + if not text or " " in text: + return False + return len(text) <= 63 + + def iter_winrm_targets(host: Host) -> list[str]: - """WinRM + NTLM: сначала имя машины (SPN), затем IPv4.""" + """WinRM + NTLM: сначала имя хоста (как Enter-PSSession -ComputerName), IP — последним.""" if not is_windows_host(host): raise HostNotWindowsError("Host is not Windows") @@ -45,17 +65,24 @@ def iter_winrm_targets(host: Host) -> list[str]: def add(value: str | None) -> None: text = (value or "").strip() - if not text or text in seen: + if not text or text.casefold() in seen: return - seen.add(text) + seen.add(text.casefold()) ordered.append(text) + for variant in _netbios_name_variants(host.hostname): + add(variant) + inventory = host.inventory if isinstance(host.inventory, dict) else {} computer_name = inventory.get("computer_name") if isinstance(computer_name, str): - add(computer_name) - add(host.hostname) - add(host.display_name) + for variant in _netbios_name_variants(computer_name): + add(variant) + + if host.display_name and _looks_like_computer_name(host.display_name): + for variant in _netbios_name_variants(host.display_name): + add(variant) + add(host.ipv4) return ordered @@ -104,8 +131,8 @@ def test_winrm_connection( hint = "" if "credentials were rejected" in detail.lower(): hint = ( - " Проверьте логин (B26\\user), пароль и что учётка — admin на целевом ПК; " - "при подключении по IP NTLM часто отклоняет creds — используйте имя хоста." + " Проверьте логин (B26\\user), пароль и права admin на ПК; " + "подключайтесь по имени хоста (как Enter-PSSession -ComputerName), не по IP." ) return WinRmTestResult( ok=False, diff --git a/backend/app/version.py b/backend/app/version.py index 1ea9fb5..fac61a3 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.10.3" +APP_VERSION = "0.10.4" APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}" diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py index 241c941..5ce03fb 100644 --- a/backend/tests/test_health.py +++ b/backend/tests/test_health.py @@ -4,6 +4,6 @@ from app.version import APP_NAME, APP_VERSION, APP_VERSION_LABEL def test_version_constants(): - assert APP_VERSION == "0.10.3" + assert APP_VERSION == "0.10.4" assert APP_NAME == "Security Alert Center" - assert APP_VERSION_LABEL == "Security Alert Center v.0.10.3" + assert APP_VERSION_LABEL == "Security Alert Center v.0.10.4" diff --git a/backend/tests/test_win_admin_settings.py b/backend/tests/test_win_admin_settings.py index 64d9c78..3d50748 100644 --- a/backend/tests/test_win_admin_settings.py +++ b/backend/tests/test_win_admin_settings.py @@ -4,6 +4,7 @@ from unittest.mock import patch from app.models.ui_settings import UI_SETTINGS_ROW_ID, UiSettings from app.services.win_admin_settings import normalize_win_admin_user +from app.services.winrm_connect import iter_winrm_targets def test_normalize_win_admin_user(): @@ -11,6 +12,21 @@ def test_normalize_win_admin_user(): assert normalize_win_admin_user(r"B26\papatramp") == r"B26\papatramp" +def test_iter_winrm_targets_hostname_before_ip(): + from app.models import Host + + host = Host( + hostname="Andrisonova-PC", + os_family="windows", + product="rdp-login-monitor", + ipv4="192.168.1.50", + inventory={"computer_name": "Andrisonova-PC"}, + ) + targets = iter_winrm_targets(host) + assert targets[0] == "Andrisonova-PC" + assert targets[-1] == "192.168.1.50" + + def test_get_win_admin_settings_env_default(jwt_headers, client, monkeypatch): monkeypatch.setenv("SAC_WIN_ADMIN_USER", "") monkeypatch.setenv("SAC_WIN_ADMIN_PASSWORD", "") diff --git a/frontend/package.json b/frontend/package.json index a4e4e8e..e362dda 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "sac-ui", "private": true, - "version": "0.10.3", + "version": "0.10.4", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/version.ts b/frontend/src/version.ts index 0371eb5..6ffd3ea 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.10.3"; +export const APP_VERSION = "0.10.4"; export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;