fix: WinRM RDP update PowerShell quoting via EncodedCommand (0.20.10)
Broken -Command quotes caused ParserError on remote hosts; run deploy script through base64-encoded PowerShell.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
import socket
|
import socket
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@@ -140,17 +141,37 @@ def run_winrm_logoff(*, target: str, user: str, password: str, session_id: int)
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _encode_powershell(script: str) -> str:
|
||||||
|
return base64.b64encode(script.encode("utf-16-le")).decode("ascii")
|
||||||
|
|
||||||
|
|
||||||
|
def _powershell_literal_path(path: str) -> str:
|
||||||
|
return path.replace("'", "''")
|
||||||
|
|
||||||
|
|
||||||
def _winrm_rdp_update_remote_cmd(script_path: str) -> str:
|
def _winrm_rdp_update_remote_cmd(script_path: str) -> str:
|
||||||
script = script_path.strip()
|
script = script_path.strip()
|
||||||
if script:
|
if script:
|
||||||
return f'powershell.exe -NoProfile -ExecutionPolicy Bypass -File "{script}"'
|
literal = _powershell_literal_path(script)
|
||||||
|
ps = (
|
||||||
|
f"$p = '{literal}'\n"
|
||||||
|
"if (-not (Test-Path -LiteralPath $p)) { throw 'Deploy script not found' }\n"
|
||||||
|
"& $p"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ps = (
|
||||||
|
"$candidates = @(\n"
|
||||||
|
" (Join-Path $env:ProgramData 'LoginMonitor\\Deploy-LoginMonitor.ps1'),\n"
|
||||||
|
" (Join-Path $env:USERPROFILE 'Deploy-LoginMonitor.ps1')\n"
|
||||||
|
")\n"
|
||||||
|
"$p = $candidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1\n"
|
||||||
|
"if (-not $p) { throw 'Deploy-LoginMonitor.ps1 not found' }\n"
|
||||||
|
"& $p"
|
||||||
|
)
|
||||||
|
encoded = _encode_powershell(ps)
|
||||||
return (
|
return (
|
||||||
"powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "
|
"powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass "
|
||||||
'"& { $candidates = @('
|
f"-EncodedCommand {encoded}"
|
||||||
"(Join-Path $env:ProgramData 'LoginMonitor' 'Deploy-LoginMonitor.ps1'),"
|
|
||||||
"(Join-Path $env:USERPROFILE 'Deploy-LoginMonitor.ps1')"
|
|
||||||
'); $p = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1; '
|
|
||||||
"if (-not $p) { throw 'Deploy-LoginMonitor.ps1 not found' }; & $p }'"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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.9"
|
APP_VERSION = "0.20.10"
|
||||||
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""WinRM command builder tests."""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from app.services.winrm_connect import _winrm_rdp_update_remote_cmd
|
||||||
|
|
||||||
|
|
||||||
|
def test_rdp_update_default_uses_encoded_command_without_broken_quotes():
|
||||||
|
cmd = _winrm_rdp_update_remote_cmd("")
|
||||||
|
assert "-EncodedCommand" in cmd
|
||||||
|
assert "-Command" not in cmd
|
||||||
|
assert '"& {' not in cmd
|
||||||
|
|
||||||
|
encoded = cmd.rsplit(" ", 1)[-1]
|
||||||
|
script = base64.b64decode(encoded).decode("utf-16-le")
|
||||||
|
assert "Deploy-LoginMonitor.ps1" in script
|
||||||
|
assert "Join-Path $env:ProgramData" in script
|
||||||
|
|
||||||
|
|
||||||
|
def test_rdp_update_custom_script_uses_encoded_command():
|
||||||
|
cmd = _winrm_rdp_update_remote_cmd(r"C:\ProgramData\LoginMonitor\Deploy-LoginMonitor.ps1")
|
||||||
|
assert "-EncodedCommand" in cmd
|
||||||
|
encoded = cmd.rsplit(" ", 1)[-1]
|
||||||
|
script = base64.b64decode(encoded).decode("utf-16-le")
|
||||||
|
assert r"C:\ProgramData\LoginMonitor\Deploy-LoginMonitor.ps1" in script
|
||||||
|
assert "Test-Path -LiteralPath" in script
|
||||||
|
|
||||||
|
|
||||||
|
def test_rdp_update_custom_script_escapes_single_quotes():
|
||||||
|
cmd = _winrm_rdp_update_remote_cmd(r"C:\Users\O'Brien\Deploy-LoginMonitor.ps1")
|
||||||
|
encoded = cmd.rsplit(" ", 1)[-1]
|
||||||
|
script = base64.b64decode(encoded).decode("utf-16-le")
|
||||||
|
assert "O''Brien" 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.9";
|
export const APP_VERSION = "0.20.10";
|
||||||
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