- .vault-tools/trading-plattform-pruefung/LIESMICH.md - .vault-tools/trading-plattform-pruefung/bremse_echtlauf.py - .vault-tools/trading-plattform-pruefung/browser_w.py - .vault-tools/trading-plattform-pruefung/generalprobe_echtlauf.py - .vault-tools/trading-plattform-pruefung/ha_aktiv_setzen.py - .vault-tools/trading-plattform-pruefung/handelstag_auswertung.py - .vault-tools/trading-plattform-pruefung/hausmelder_echtlauf.py - .vault-tools/trading-plattform-pruefung/killswitch_simulation.py - .vault-tools/trading-plattform-pruefung/killswitch_simulation_mutanten.py - .vault-tools/trading-plattform-pruefung/latenz_auswertung.py - .vault-tools/trading-plattform-pruefung/s1_alpaca_lesen.py - .vault-tools/trading-plattform-pruefung/s5_echtlauf.py - … und 2 weitere Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
217 lines
8.9 KiB
Python
217 lines
8.9 KiB
Python
"""Gegenmessung zu Paket S5 gegen eine laufende Probe-Instanz: dieselben Angriffe, die der Sicherheitspruefer gemessen hat.
|
|
|
|
Koordinator-Pruefcode (WSL). Gemessen wird gegen 127.0.0.1:<port>; die Instanz laeuft aus einer Kopie mit
|
|
Broker-Attrappe, Wegwerf-Register und Wegwerf-Zugang. Nichts davon beruehrt den Betrieb.
|
|
Aufruf: python s5_echtlauf.py <port> <ablage-der-probe> <code-baum>
|
|
"""
|
|
|
|
import base64
|
|
import hashlib
|
|
import hmac
|
|
import http.client
|
|
import json
|
|
import os
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from pathlib import Path
|
|
|
|
import requests
|
|
|
|
PORT = int(sys.argv[1])
|
|
ABLAGE = Path(sys.argv[2])
|
|
CODE = Path(sys.argv[3])
|
|
BASIS = f"http://127.0.0.1:{PORT}"
|
|
KOPF = {"Origin": BASIS}
|
|
z = json.loads((ABLAGE / "zugang.json").read_text(encoding="utf-8"))
|
|
BEN, PW, GEH = z["benutzer"], z["passwort"], z["geheimnis"]
|
|
befunde = []
|
|
|
|
|
|
def code(versatz=0):
|
|
s = base64.b32decode(GEH)
|
|
mac = hmac.new(s, struct.pack(">Q", int(time.time()) // 30 + versatz), hashlib.sha1).digest()
|
|
v = mac[-1] & 0x0F
|
|
return f"{(struct.unpack('>I', mac[v:v + 4])[0] & 0x7FFFFFFF) % 1_000_000:06d}"
|
|
|
|
|
|
def melde_an():
|
|
s = requests.Session()
|
|
s.trust_env = False
|
|
a = s.post(f"{BASIS}/api/anmeldung", json={"benutzer": BEN, "passwort": PW, "code": code()}, headers=KOPF, timeout=60)
|
|
return s, a.status_code
|
|
|
|
|
|
def naechster_schritt():
|
|
time.sleep(30.5 - (time.time() % 30))
|
|
|
|
|
|
def server_pid():
|
|
aus = subprocess.run(["pgrep", "-f", "start_probe_w.py"], capture_output=True, text=True).stdout.split()
|
|
return int(aus[0]) if aus else None
|
|
|
|
|
|
def speicher_mib(pid):
|
|
werte = {}
|
|
for zeile in Path(f"/proc/{pid}/status").read_text().splitlines():
|
|
if zeile.startswith(("VmRSS", "VmHWM")):
|
|
name, wert = zeile.split(":")
|
|
werte[name] = int(wert.split()[0]) / 1024
|
|
return werte
|
|
|
|
|
|
def pruefe(text, ok):
|
|
print((" OK " if ok else " BEFUND ") + text)
|
|
if not ok:
|
|
befunde.append(text)
|
|
|
|
|
|
pid = server_pid()
|
|
print(f"Server-PID {pid}, Speicher zu Beginn {speicher_mib(pid)}")
|
|
|
|
print("== 1: 422 spiegelt die Eingabe nicht mehr")
|
|
lang = "P" * 300
|
|
a = requests.post(f"{BASIS}/api/anmeldung", json={"benutzer": BEN, "passwort": lang, "code": "123456"}, headers=KOPF, timeout=10)
|
|
pruefe(f"HTTP {a.status_code}, Passwort im Koerper: {lang in a.text}", a.status_code == 422 and lang not in a.text)
|
|
print(f" Koerper: {a.text[:200]}")
|
|
|
|
print("== 2: Rumpf 512 MiB mit content-length, unangemeldet")
|
|
vor = speicher_mib(pid)
|
|
t0 = time.perf_counter()
|
|
verbindung = http.client.HTTPConnection("127.0.0.1", PORT, timeout=60)
|
|
verbindung.putrequest("POST", "/api/anmeldung")
|
|
verbindung.putheader("Origin", BASIS)
|
|
verbindung.putheader("Content-Type", "application/json")
|
|
verbindung.putheader("Content-Length", str(512 * 1024 * 1024))
|
|
verbindung.endheaders()
|
|
gesendet = 0
|
|
status = None
|
|
try:
|
|
block = b"x" * (1024 * 1024)
|
|
for _ in range(512):
|
|
verbindung.send(block)
|
|
gesendet += len(block)
|
|
except OSError as ausnahme:
|
|
print(f" Senden brach nach {gesendet / 1024 / 1024:.0f} MiB ab: {type(ausnahme).__name__}")
|
|
try:
|
|
antwort = verbindung.getresponse()
|
|
status = antwort.status
|
|
antwort.read()
|
|
except Exception as ausnahme: # noqa: BLE001
|
|
print(f" Antwort nicht lesbar: {type(ausnahme).__name__}")
|
|
verbindung.close()
|
|
nach = speicher_mib(pid)
|
|
print(f" Status {status}, {time.perf_counter() - t0:.2f} s, gesendet {gesendet / 1024 / 1024:.0f} MiB, Speicher {vor} -> {nach}")
|
|
pruefe("413 und Spitzenspeicher waechst um weniger als 50 MiB", status == 413 and nach["VmHWM"] - vor["VmHWM"] < 50)
|
|
|
|
print("== 3: Rumpf ohne content-length (chunked), unangemeldet")
|
|
vor = speicher_mib(pid)
|
|
t0 = time.perf_counter()
|
|
verbindung = http.client.HTTPConnection("127.0.0.1", PORT, timeout=60)
|
|
verbindung.putrequest("POST", "/api/anmeldung")
|
|
verbindung.putheader("Origin", BASIS)
|
|
verbindung.putheader("Content-Type", "application/json")
|
|
verbindung.putheader("Transfer-Encoding", "chunked")
|
|
verbindung.endheaders()
|
|
gesendet = 0
|
|
status = None
|
|
try:
|
|
block = b"y" * (256 * 1024)
|
|
for _ in range(800):
|
|
verbindung.send(hex(len(block))[2:].encode() + b"\r\n" + block + b"\r\n")
|
|
gesendet += len(block)
|
|
verbindung.send(b"0\r\n\r\n")
|
|
except OSError as ausnahme:
|
|
print(f" Senden brach nach {gesendet / 1024 / 1024:.1f} MiB ab: {type(ausnahme).__name__}")
|
|
try:
|
|
antwort = verbindung.getresponse()
|
|
status = antwort.status
|
|
antwort.read()
|
|
except Exception as ausnahme: # noqa: BLE001
|
|
print(f" Antwort nicht lesbar: {type(ausnahme).__name__}")
|
|
verbindung.close()
|
|
nach = speicher_mib(pid)
|
|
print(f" Status {status}, {time.perf_counter() - t0:.2f} s, gesendet {gesendet / 1024 / 1024:.1f} MiB, Speicher {vor} -> {nach}")
|
|
pruefe("413 (oder Abbruch der Verbindung) und Spitzenspeicher waechst um weniger als 50 MiB",
|
|
status in (413, None) and nach["VmHWM"] - vor["VmHWM"] < 50)
|
|
|
|
print("== 4: Killswitch unter 40 parallelen teuren Kerzenabrufen")
|
|
sitz, st = melde_an()
|
|
print(f" Anmeldung HTTP {st}")
|
|
cookie = "; ".join(f"{c.name}={c.value}" for c in sitz.cookies)
|
|
ds = sitz.get(f"{BASIS}/api/markt/datensaetze", timeout=120).json().get("datensaetze", [])
|
|
intraday = [d for d in ds if d.get("intraday")]
|
|
if not intraday:
|
|
print(" kein Intraday-Datensatz im Katalog -- Lastmessung nicht moeglich")
|
|
befunde.append("Lastmessung nicht moeglich")
|
|
else:
|
|
bt = intraday[0]["bar_type"]
|
|
teuer = f"{BASIS}/api/markt/kerzen?bar_type={bt}&aufloesung_min=60&anzahl=3000"
|
|
t0 = time.perf_counter()
|
|
einzel = requests.get(teuer, headers={"Cookie": cookie}, timeout=600)
|
|
print(f" ein einzelner teurer Abruf: HTTP {einzel.status_code}, {time.perf_counter() - t0:.1f} s")
|
|
t0 = time.perf_counter()
|
|
sitz.post(f"{BASIS}/api/killswitch/anhalten", json={"grund": "grundlinie"}, headers=KOPF, timeout=60)
|
|
print(f" Grundlinie POST /api/killswitch/anhalten ohne Last: {time.perf_counter() - t0:.2f} s")
|
|
laeuft = [True]
|
|
messung = {"gesund": [], "anhalten": []}
|
|
|
|
def miss():
|
|
while laeuft[0]:
|
|
t = time.perf_counter()
|
|
try:
|
|
requests.get(f"{BASIS}/api/gesund", timeout=300)
|
|
messung["gesund"].append(time.perf_counter() - t)
|
|
except Exception: # noqa: BLE001
|
|
messung["gesund"].append(-1.0)
|
|
t = time.perf_counter()
|
|
try:
|
|
r = sitz.post(f"{BASIS}/api/killswitch/anhalten", json={"grund": "lastprobe"}, headers=KOPF, timeout=300)
|
|
messung["anhalten"].append((time.perf_counter() - t, r.status_code))
|
|
except Exception: # noqa: BLE001
|
|
messung["anhalten"].append((-1.0, 0))
|
|
time.sleep(0.5)
|
|
|
|
faden = threading.Thread(target=miss, daemon=True)
|
|
faden.start()
|
|
t0 = time.perf_counter()
|
|
with ThreadPoolExecutor(max_workers=40) as pool:
|
|
codes = list(pool.map(lambda _: requests.get(teuer, headers={"Cookie": cookie}, timeout=900).status_code, range(40)))
|
|
dauer = time.perf_counter() - t0
|
|
laeuft[0] = False
|
|
faden.join(timeout=310)
|
|
zeiten = [w for w, _ in messung["anhalten"]]
|
|
print(f" 40 Kerzenabrufe: {dauer:.1f} s, Status {sorted(set(codes))}")
|
|
print(f" anhalten waehrend der Last: n={len(zeiten)}, max {max(zeiten):.2f} s, Status {sorted({s for _, s in messung['anhalten']})}")
|
|
print(f" gesund waehrend der Last: n={len(messung['gesund'])}, max {max(messung['gesund']):.2f} s")
|
|
pruefe("Killswitch antwortet unter Last in unter 5 s (vorher gemessen: 118 s)", 0 <= max(zeiten) < 5.0)
|
|
|
|
print("== 5: ueberlanger Grund landet nicht im Register")
|
|
db = ABLAGE / "registry.db"
|
|
vorher = db.stat().st_size
|
|
a = sitz.post(f"{BASIS}/api/killswitch/anhalten", json={"grund": "G" * (1024 * 1024)}, headers=KOPF, timeout=120)
|
|
print(f" grund mit 1 MiB -> HTTP {a.status_code}, registry.db {vorher} -> {db.stat().st_size} Bytes")
|
|
pruefe("abgelehnt (413 oder 422), Register waechst nicht", a.status_code in (413, 422) and db.stat().st_size - vorher < 65536)
|
|
|
|
print("== 6: Passwortwechsel beendet die Sitzung")
|
|
pruefe(f"Sitzung gilt vor dem Wechsel (HTTP {sitz.get(f'{BASIS}/api/uebersicht', timeout=60).status_code})", True)
|
|
sys.path.insert(0, str(CODE))
|
|
os.chdir(CODE)
|
|
from trading.dashboard.zugang import Benutzer, lies_benutzer, schreibe_benutzer, setze_passwort # noqa: E402
|
|
|
|
bestand = lies_benutzer(ABLAGE / "zugang.toml")
|
|
bestand[BEN] = Benutzer(name=BEN, passwort=setze_passwort("ein-neues-passwort-2026"), totp_geheimnis=bestand[BEN].totp_geheimnis)
|
|
schreibe_benutzer(ABLAGE / "zugang.toml", bestand)
|
|
time.sleep(1.0)
|
|
danach = sitz.get(f"{BASIS}/api/uebersicht", timeout=60).status_code
|
|
pruefe(f"alte Sitzung nach dem Passwortwechsel: HTTP {danach}", danach == 401)
|
|
halt = sitz.post(f"{BASIS}/api/killswitch/anhalten", json={"grund": "nach-wechsel"}, headers=KOPF, timeout=60).status_code
|
|
pruefe(f"Killswitch mit der alten Sitzung: HTTP {halt}", halt == 401)
|
|
|
|
print(f"Speicher am Ende {speicher_mib(pid)}")
|
|
print(f"{len(befunde)} Befund(e): {befunde}")
|
|
sys.exit(2 if befunde else 0)
|