fix: friendly 502 handling, 2 uvicorn workers, file bundle cache (0.20.18)

This commit is contained in:
2026-06-20 20:01:46 +10:00
parent 4d9a9e7e2b
commit f3a8952947
8 changed files with 63 additions and 40 deletions
+28 -28
View File
@@ -1,26 +1,19 @@
"""Short-lived in-memory RDP agent bundles for WinRM clients to download."""
"""Short-lived RDP agent bundles for WinRM clients to download."""
from __future__ import annotations
import io
import re
import secrets
import time
import zipfile
from pathlib import Path
from threading import Lock
TTL_SECONDS = 600
_UTF8_BOM = b"\xef\xbb\xbf"
_BOM_SUFFIXES = {".ps1", ".txt"}
_lock = Lock()
_entries: dict[str, tuple[bytes, float]] = {}
def _prune_expired(now: float) -> None:
expired = [token for token, (_, expires_at) in _entries.items() if expires_at <= now]
for token in expired:
_entries.pop(token, None)
_TOKEN_RE = re.compile(r"^[A-Za-z0-9_-]{16,128}$")
TTL_SECONDS = 600
BUNDLE_DIR = Path("/tmp/sac-rdp-bundles")
def _bundle_file_bytes(path: Path) -> bytes:
@@ -43,27 +36,34 @@ def build_rdp_bundle_zip(repo_dir: Path, filenames: tuple[str, ...]) -> bytes:
return buffer.getvalue()
def _prune_expired_bundles(now: float | None = None) -> None:
if not BUNDLE_DIR.is_dir():
return
cutoff = (now or time.time()) - TTL_SECONDS
for path in BUNDLE_DIR.glob("*.zip"):
try:
if path.stat().st_mtime < cutoff:
path.unlink(missing_ok=True)
except OSError:
continue
def register_rdp_bundle_zip(zip_bytes: bytes) -> str:
token = secrets.token_urlsafe(32)
expires_at = time.time() + TTL_SECONDS
with _lock:
_prune_expired(time.time())
_entries[token] = (zip_bytes, expires_at)
BUNDLE_DIR.mkdir(parents=True, exist_ok=True)
_prune_expired_bundles()
(BUNDLE_DIR / f"{token}.zip").write_bytes(zip_bytes)
return token
def get_rdp_bundle_zip(token: str) -> bytes | None:
text = (token or "").strip()
if not text:
if not _TOKEN_RE.fullmatch(text):
return None
now = time.time()
with _lock:
_prune_expired(now)
entry = _entries.get(text)
if entry is None:
return None
data, expires_at = entry
if expires_at <= now:
_entries.pop(text, None)
return None
return data
path = BUNDLE_DIR / f"{text}.zip"
if not path.is_file():
return None
if time.time() - path.stat().st_mtime > TTL_SECONDS:
path.unlink(missing_ok=True)
return None
return path.read_bytes()
+1 -1
View File
@@ -1,5 +1,5 @@
"""Единый источник версии SAC (API, health, логи, OpenAPI)."""
APP_NAME = "Security Alert Center"
APP_VERSION = "0.20.17"
APP_VERSION = "0.20.18"
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
+4 -2
View File
@@ -70,7 +70,8 @@ def test_custom_deploy_script_escapes_single_quotes():
assert "O''Brien" in script
def test_rdp_bundle_zip_adds_utf8_bom_for_ps1(tmp_path: Path):
def test_rdp_bundle_zip_adds_utf8_bom_for_ps1(tmp_path: Path, monkeypatch):
monkeypatch.setattr("app.services.rdp_bundle_delivery.BUNDLE_DIR", tmp_path)
repo = tmp_path / "repo"
repo.mkdir()
(repo / "Deploy-LoginMonitor.ps1").write_text("# test", encoding="utf-8")
@@ -83,7 +84,8 @@ def test_rdp_bundle_zip_adds_utf8_bom_for_ps1(tmp_path: Path):
assert data.startswith(_UTF8_BOM)
def test_rdp_bundle_zip_roundtrip(tmp_path: Path):
def test_rdp_bundle_zip_roundtrip(tmp_path: Path, monkeypatch):
monkeypatch.setattr("app.services.rdp_bundle_delivery.BUNDLE_DIR", tmp_path)
repo = tmp_path / "repo"
repo.mkdir()
for name in RDP_BUNDLE_REQUIRED: