fix: decode WinRM Cyrillic from CP866 and show deploy.log UTF-8 (0.20.17)
This commit is contained in:
@@ -19,7 +19,7 @@ _CLIXML_PROGRESS_NOISE = frozenset(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
_CLIXML_MARKER = "#< CLIXML"
|
_CLIXML_MARKER = "#< CLIXML"
|
||||||
|
_CYRILLIC_RE = re.compile(r"[\u0400-\u04FF]")
|
||||||
RDP_REMOTE_STAGING = r"C:\ProgramData\RDP-login-monitor\_sac_staging"
|
RDP_REMOTE_STAGING = r"C:\ProgramData\RDP-login-monitor\_sac_staging"
|
||||||
RDP_BUNDLE_FILES = (
|
RDP_BUNDLE_FILES = (
|
||||||
"Login_Monitor.ps1",
|
"Login_Monitor.ps1",
|
||||||
@@ -128,6 +128,39 @@ def _clixml_to_plain(text: str) -> str:
|
|||||||
return without
|
return without
|
||||||
|
|
||||||
|
|
||||||
|
def _decode_text_score(text: str) -> int:
|
||||||
|
cyrillic = len(_CYRILLIC_RE.findall(text))
|
||||||
|
suspicious = len(re.findall(r"\?{3,}", text))
|
||||||
|
return cyrillic * 5 - text.count("\ufffd") * 20 - suspicious * 15 - max(0, text.count("?") - cyrillic) * 2
|
||||||
|
|
||||||
|
|
||||||
|
def _decode_winrm_bytes(data: bytes) -> str:
|
||||||
|
if not data:
|
||||||
|
return ""
|
||||||
|
for encoding in ("utf-8-sig", "utf-8"):
|
||||||
|
try:
|
||||||
|
text = data.decode(encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
break
|
||||||
|
score = _decode_text_score(text)
|
||||||
|
if text.isascii() or score > 0:
|
||||||
|
return text
|
||||||
|
best_score = -10_000
|
||||||
|
best_text = ""
|
||||||
|
for encoding in ("cp866", "cp1251"):
|
||||||
|
try:
|
||||||
|
text = data.decode(encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
continue
|
||||||
|
score = _decode_text_score(text)
|
||||||
|
if score > best_score:
|
||||||
|
best_score = score
|
||||||
|
best_text = text
|
||||||
|
if best_text:
|
||||||
|
return best_text
|
||||||
|
return data.decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
|
||||||
def _winrm_failure_detail(stdout: str, stderr: str, exit_code: int) -> str:
|
def _winrm_failure_detail(stdout: str, stderr: str, exit_code: int) -> str:
|
||||||
stdout_plain = _clixml_to_plain(stdout)
|
stdout_plain = _clixml_to_plain(stdout)
|
||||||
stderr_plain = _clixml_to_plain(stderr)
|
stderr_plain = _clixml_to_plain(stderr)
|
||||||
@@ -213,8 +246,8 @@ def run_winrm_ps(
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return WinRmCmdResult(ok=False, message=f"WinRM error ({target}): {exc}", target=target)
|
return WinRmCmdResult(ok=False, message=f"WinRM error ({target}): {exc}", target=target)
|
||||||
|
|
||||||
stdout = (result.std_out or b"").decode("utf-8", errors="replace")
|
stdout = _decode_winrm_bytes(result.std_out or b"")
|
||||||
stderr = (result.std_err or b"").decode("utf-8", errors="replace")
|
stderr = _decode_winrm_bytes(result.std_err or b"")
|
||||||
exit_code = int(result.status_code)
|
exit_code = int(result.status_code)
|
||||||
stdout, stderr = _sanitize_winrm_streams(stdout, stderr)
|
stdout, stderr = _sanitize_winrm_streams(stdout, stderr)
|
||||||
return _finalize_winrm_run(
|
return _finalize_winrm_run(
|
||||||
@@ -260,8 +293,8 @@ def run_winrm_cmd(
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return WinRmCmdResult(ok=False, message=f"WinRM error ({target}): {exc}", target=target)
|
return WinRmCmdResult(ok=False, message=f"WinRM error ({target}): {exc}", target=target)
|
||||||
|
|
||||||
stdout = (result.std_out or b"").decode("utf-8", errors="replace")
|
stdout = _decode_winrm_bytes(result.std_out or b"")
|
||||||
stderr = (result.std_err or b"").decode("utf-8", errors="replace")
|
stderr = _decode_winrm_bytes(result.std_err or b"")
|
||||||
exit_code = int(result.status_code)
|
exit_code = int(result.status_code)
|
||||||
stdout, stderr = _sanitize_winrm_streams(stdout, stderr)
|
stdout, stderr = _sanitize_winrm_streams(stdout, stderr)
|
||||||
return _finalize_winrm_run(
|
return _finalize_winrm_run(
|
||||||
@@ -292,6 +325,9 @@ def _powershell_script_prelude() -> str:
|
|||||||
return (
|
return (
|
||||||
"$ProgressPreference = 'SilentlyContinue'\n"
|
"$ProgressPreference = 'SilentlyContinue'\n"
|
||||||
"$InformationPreference = 'SilentlyContinue'\n"
|
"$InformationPreference = 'SilentlyContinue'\n"
|
||||||
|
"$utf8NoBom = New-Object System.Text.UTF8Encoding $false\n"
|
||||||
|
"[Console]::OutputEncoding = $utf8NoBom\n"
|
||||||
|
"$OutputEncoding = $utf8NoBom\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -338,8 +374,14 @@ def _deploy_from_staging_body(staging_path: str) -> str:
|
|||||||
f"$staging = '{literal}'\n"
|
f"$staging = '{literal}'\n"
|
||||||
"$deploy = Join-Path $staging 'Deploy-LoginMonitor.ps1'\n"
|
"$deploy = Join-Path $staging 'Deploy-LoginMonitor.ps1'\n"
|
||||||
"if (-not (Test-Path -LiteralPath $deploy)) { throw \"Deploy-LoginMonitor.ps1 missing in staging\" }\n"
|
"if (-not (Test-Path -LiteralPath $deploy)) { throw \"Deploy-LoginMonitor.ps1 missing in staging\" }\n"
|
||||||
|
"$logPath = Join-Path $env:ProgramData 'RDP-login-monitor\\Logs\\deploy.log'\n"
|
||||||
"Write-Output \"Running: $deploy -SourceShareRoot $staging\"\n"
|
"Write-Output \"Running: $deploy -SourceShareRoot $staging\"\n"
|
||||||
"powershell.exe -NoProfile -ExecutionPolicy Bypass -File $deploy -SourceShareRoot $staging\n"
|
"powershell.exe -NoProfile -ExecutionPolicy Bypass -File $deploy -SourceShareRoot $staging\n"
|
||||||
|
"if (Test-Path -LiteralPath $logPath) {\n"
|
||||||
|
" Write-Output ''\n"
|
||||||
|
" Write-Output '--- deploy.log ---'\n"
|
||||||
|
" Get-Content -LiteralPath $logPath -Encoding UTF8 | Select-Object -Last 60\n"
|
||||||
|
"}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""Единый источник версии SAC (API, health, логи, OpenAPI)."""
|
"""Единый источник версии SAC (API, health, логи, OpenAPI)."""
|
||||||
|
|
||||||
APP_NAME = "Security Alert Center"
|
APP_NAME = "Security Alert Center"
|
||||||
APP_VERSION = "0.20.16"
|
APP_VERSION = "0.20.17"
|
||||||
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from app.services.winrm_connect import (
|
|||||||
WinRmCmdResult,
|
WinRmCmdResult,
|
||||||
_clixml_to_plain,
|
_clixml_to_plain,
|
||||||
_custom_deploy_body,
|
_custom_deploy_body,
|
||||||
|
_decode_winrm_bytes,
|
||||||
_deploy_from_staging_body,
|
_deploy_from_staging_body,
|
||||||
_download_bundle_body,
|
_download_bundle_body,
|
||||||
_prepare_staging_body,
|
_prepare_staging_body,
|
||||||
@@ -23,6 +24,25 @@ from app.services.winrm_connect import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_decode_winrm_bytes_reads_russian_cp866_console():
|
||||||
|
sample = "Деплой: корень шары".encode("cp866")
|
||||||
|
decoded = _decode_winrm_bytes(sample)
|
||||||
|
assert "Деплой" in decoded
|
||||||
|
assert "шары" in decoded
|
||||||
|
|
||||||
|
|
||||||
|
def test_decode_winrm_bytes_handles_utf8_multibyte():
|
||||||
|
text = "\u0414\u0435\u043f\u043b\u043e\u0439: \u043a\u043e\u0440\u0435\u043d\u044c \u0448\u0430\u0440\u044b"
|
||||||
|
assert _decode_winrm_bytes(text.encode("utf-8")) == text
|
||||||
|
|
||||||
|
|
||||||
|
def test_deploy_from_staging_includes_deploy_log_tail():
|
||||||
|
script = _deploy_from_staging_body(RDP_REMOTE_STAGING)
|
||||||
|
assert "deploy.log" in script
|
||||||
|
assert "Get-Content" in script
|
||||||
|
assert "-Encoding UTF8" in script
|
||||||
|
|
||||||
|
|
||||||
def test_prepare_staging_recreates_remote_dir():
|
def test_prepare_staging_recreates_remote_dir():
|
||||||
script = _prepare_staging_body(RDP_REMOTE_STAGING)
|
script = _prepare_staging_body(RDP_REMOTE_STAGING)
|
||||||
assert "Remove-Item" in script
|
assert "Remove-Item" in script
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
|
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
|
||||||
export const APP_NAME = "Security Alert Center";
|
export const APP_NAME = "Security Alert Center";
|
||||||
export const APP_VERSION = "0.20.16";
|
export const APP_VERSION = "0.20.17";
|
||||||
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;
|
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user