|
|
|
@@ -20,7 +20,8 @@ _CLIXML_PROGRESS_NOISE = frozenset(
|
|
|
|
|
)
|
|
|
|
|
_CLIXML_MARKER = "#< CLIXML"
|
|
|
|
|
_CYRILLIC_RE = re.compile(r"[\u0400-\u04FF]")
|
|
|
|
|
RDP_REMOTE_STAGING = r"C:\ProgramData\RDP-login-monitor\_sac_staging"
|
|
|
|
|
RDP_REMOTE_STAGING_DIRNAME = "sac-rdp-staging"
|
|
|
|
|
RDP_LEGACY_STAGING = r"C:\ProgramData\RDP-login-monitor\_sac_staging"
|
|
|
|
|
RDP_BUNDLE_FILES = (
|
|
|
|
|
"Login_Monitor.ps1",
|
|
|
|
|
"Sac-Client.ps1",
|
|
|
|
@@ -165,12 +166,17 @@ def _winrm_failure_detail(stdout: str, stderr: str, exit_code: int) -> str:
|
|
|
|
|
stdout_plain = _clixml_to_plain(stdout)
|
|
|
|
|
stderr_plain = _clixml_to_plain(stderr)
|
|
|
|
|
|
|
|
|
|
for text in (stderr_plain, stdout_plain):
|
|
|
|
|
for line in text.splitlines():
|
|
|
|
|
stripped = line.strip()
|
|
|
|
|
if stripped.startswith("ERROR:"):
|
|
|
|
|
return stripped[6:].strip() or stripped
|
|
|
|
|
|
|
|
|
|
for candidate in (stderr_plain, stdout_plain):
|
|
|
|
|
if candidate and _CLIXML_MARKER not in candidate:
|
|
|
|
|
line = candidate.strip().splitlines()[0][:2000]
|
|
|
|
|
if line.startswith("ERROR:"):
|
|
|
|
|
return line[6:].strip() or line
|
|
|
|
|
return line
|
|
|
|
|
lines = [line.strip() for line in candidate.splitlines() if line.strip()]
|
|
|
|
|
if lines:
|
|
|
|
|
return lines[-1][:2000]
|
|
|
|
|
|
|
|
|
|
if stdout_plain:
|
|
|
|
|
return stdout_plain[:2000]
|
|
|
|
@@ -380,52 +386,67 @@ def _custom_deploy_body(script_path: str) -> str:
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _prepare_staging_body(staging_path: str) -> str:
|
|
|
|
|
literal = _powershell_literal_path(staging_path)
|
|
|
|
|
def _rdp_staging_setup_ps() -> str:
|
|
|
|
|
dirname = _powershell_literal_path(RDP_REMOTE_STAGING_DIRNAME)
|
|
|
|
|
legacy = _powershell_literal_path(RDP_LEGACY_STAGING)
|
|
|
|
|
return (
|
|
|
|
|
f"$staging = '{literal}'\n"
|
|
|
|
|
"if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }\n"
|
|
|
|
|
"New-Item -ItemType Directory -Path $staging -Force | Out-Null\n"
|
|
|
|
|
"Write-Output \"Staging ready: $staging\"\n"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _deploy_from_staging_body(staging_path: str) -> str:
|
|
|
|
|
literal = _powershell_literal_path(staging_path)
|
|
|
|
|
return (
|
|
|
|
|
f"$staging = '{literal}'\n"
|
|
|
|
|
"$deploy = Join-Path $staging 'Deploy-LoginMonitor.ps1'\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"
|
|
|
|
|
"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"
|
|
|
|
|
f"$staging = Join-Path $env:TEMP '{dirname}'\n"
|
|
|
|
|
f"$legacyStaging = '{legacy}'\n"
|
|
|
|
|
"if (Test-Path -LiteralPath $legacyStaging) {\n"
|
|
|
|
|
" Remove-Item -LiteralPath $legacyStaging -Recurse -Force -ErrorAction SilentlyContinue\n"
|
|
|
|
|
"}\n"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _prepare_staging_body() -> str:
|
|
|
|
|
return (
|
|
|
|
|
_rdp_staging_setup_ps()
|
|
|
|
|
+ "if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }\n"
|
|
|
|
|
+ "New-Item -ItemType Directory -Path $staging -Force | Out-Null\n"
|
|
|
|
|
+ "Write-Output \"Staging ready: $staging\"\n"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _deploy_from_staging_body() -> str:
|
|
|
|
|
return (
|
|
|
|
|
_rdp_staging_setup_ps()
|
|
|
|
|
+ "$deploy = Join-Path $staging 'Deploy-LoginMonitor.ps1'\n"
|
|
|
|
|
+ "if (-not (Test-Path -LiteralPath $staging)) { throw 'Staging directory missing' }\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"
|
|
|
|
|
+ "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"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _bundle_download_url(token: str) -> str:
|
|
|
|
|
base = get_settings().sac_public_url.rstrip("/")
|
|
|
|
|
settings = get_settings()
|
|
|
|
|
base = (settings.sac_agent_bundle_base_url or settings.sac_public_url).rstrip("/")
|
|
|
|
|
return f"{base}/api/v1/agent/rdp-bundle/{token}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _download_bundle_body(staging_path: str, bundle_url: str) -> str:
|
|
|
|
|
staging = _powershell_literal_path(staging_path)
|
|
|
|
|
def _download_bundle_body(bundle_url: str) -> str:
|
|
|
|
|
url = _powershell_literal_path(bundle_url)
|
|
|
|
|
return (
|
|
|
|
|
f"$staging = '{staging}'\n"
|
|
|
|
|
f"$url = '{url}'\n"
|
|
|
|
|
"$zip = Join-Path $staging 'sac-rdp-bundle.zip'\n"
|
|
|
|
|
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\n"
|
|
|
|
|
"Write-Output \"Downloading bundle: $url\"\n"
|
|
|
|
|
"Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing\n"
|
|
|
|
|
"Add-Type -AssemblyName System.IO.Compression.FileSystem\n"
|
|
|
|
|
"[System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $staging)\n"
|
|
|
|
|
"Remove-Item -LiteralPath $zip -Force\n"
|
|
|
|
|
"Write-Output \"Bundle extracted to $staging\"\n"
|
|
|
|
|
_rdp_staging_setup_ps()
|
|
|
|
|
+ "if (-not (Test-Path -LiteralPath $staging)) { throw 'Staging directory missing' }\n"
|
|
|
|
|
+ f"$url = '{url}'\n"
|
|
|
|
|
+ "$zip = Join-Path $env:TEMP ('sac-rdp-bundle-' + [Guid]::NewGuid().ToString() + '.zip')\n"
|
|
|
|
|
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\n"
|
|
|
|
|
+ "Write-Output \"Downloading bundle: $url\"\n"
|
|
|
|
|
+ "Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing\n"
|
|
|
|
|
+ "if (-not (Test-Path -LiteralPath $zip)) { throw 'Bundle download produced no file' }\n"
|
|
|
|
|
+ "$zipSize = (Get-Item -LiteralPath $zip).Length\n"
|
|
|
|
|
+ "if ($zipSize -lt 100) { throw \"Bundle too small ($zipSize bytes)\" }\n"
|
|
|
|
|
+ "Add-Type -AssemblyName System.IO.Compression.FileSystem\n"
|
|
|
|
|
+ "[System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $staging)\n"
|
|
|
|
|
+ "Remove-Item -LiteralPath $zip -Force -ErrorAction SilentlyContinue\n"
|
|
|
|
|
+ "Write-Output \"Bundle extracted to $staging\"\n"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -486,7 +507,6 @@ def run_winrm_rdp_monitor_update(
|
|
|
|
|
target=target,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
staging = RDP_REMOTE_STAGING
|
|
|
|
|
zip_bytes = build_rdp_bundle_zip(repo_dir, RDP_BUNDLE_FILES)
|
|
|
|
|
if not zip_bytes:
|
|
|
|
|
return WinRmCmdResult(
|
|
|
|
@@ -496,12 +516,13 @@ def run_winrm_rdp_monitor_update(
|
|
|
|
|
)
|
|
|
|
|
bundle_token = register_rdp_bundle_zip(zip_bytes)
|
|
|
|
|
bundle_url = _bundle_download_url(bundle_token)
|
|
|
|
|
staging_label = f"%TEMP%\\{RDP_REMOTE_STAGING_DIRNAME}"
|
|
|
|
|
|
|
|
|
|
prep = run_winrm_ps(
|
|
|
|
|
target=target,
|
|
|
|
|
user=user,
|
|
|
|
|
password=password,
|
|
|
|
|
script=_wrap_powershell_body(_prepare_staging_body(staging)),
|
|
|
|
|
script=_wrap_powershell_body(_prepare_staging_body()),
|
|
|
|
|
timeout_sec=120,
|
|
|
|
|
)
|
|
|
|
|
if not prep.ok:
|
|
|
|
@@ -511,7 +532,7 @@ def run_winrm_rdp_monitor_update(
|
|
|
|
|
target=target,
|
|
|
|
|
user=user,
|
|
|
|
|
password=password,
|
|
|
|
|
script=_wrap_powershell_body(_download_bundle_body(staging, bundle_url)),
|
|
|
|
|
script=_wrap_powershell_body(_download_bundle_body(bundle_url)),
|
|
|
|
|
timeout_sec=300,
|
|
|
|
|
)
|
|
|
|
|
if not download.ok:
|
|
|
|
@@ -531,10 +552,10 @@ def run_winrm_rdp_monitor_update(
|
|
|
|
|
target=target,
|
|
|
|
|
user=user,
|
|
|
|
|
password=password,
|
|
|
|
|
script=_wrap_powershell_body(_deploy_from_staging_body(staging)),
|
|
|
|
|
script=_wrap_powershell_body(_deploy_from_staging_body()),
|
|
|
|
|
timeout_sec=900,
|
|
|
|
|
)
|
|
|
|
|
header = f"SAC served bundle from git; client downloaded to {staging}"
|
|
|
|
|
header = f"SAC served bundle from git; client staging {staging_label}"
|
|
|
|
|
stdout = "\n\n".join(
|
|
|
|
|
part.strip()
|
|
|
|
|
for part in [header, prep.stdout, download.stdout, deploy.stdout]
|
|
|
|
|