Files
ssh-jumphost/app/recordings/recorder.py.bak_1788208152
2026-09-02 20:30:44 +02:00

64 lines
2.2 KiB
Plaintext

"""
Session-Aufzeichnung mit Hash-Verkettung (siehe Konzept 6.5).
Jede Session schreibt eine eigene JSONL-Datei unter settings.recordings_dir.
Jede Zeile verkettet sich mit der vorherigen (gleiches Prinzip wie das
Audit-Log, app/security/audit.py), damit nachtraegliche Manipulation der
Aufzeichnung erkennbar ist.
"""
from __future__ import annotations
import hashlib
import json
import time
from pathlib import Path
from app.config import settings
GENESIS_HASH = "0" * 64
class SessionRecorder:
def __init__(self, session_id: int) -> None:
self.session_id = session_id
self.path = settings.recordings_dir / f"session_{session_id}.jsonl"
self._prev_hash = GENESIS_HASH
self._start_ts = time.time()
self._fh = open(self.path, "a", encoding="utf-8")
try:
self.path.chmod(0o600)
except OSError:
pass
def record(self, direction: str, data: str) -> None:
"""direction: 'input' (Tastatureingabe) oder 'output' (Terminal-/RDP-Ausgabe)."""
offset = round(time.time() - self._start_ts, 4)
entry = {"t": offset, "dir": direction, "data": data}
entry_json = json.dumps(entry, ensure_ascii=False, sort_keys=True)
entry_hash = hashlib.sha256((self._prev_hash + "|" + entry_json).encode()).hexdigest()
line = json.dumps({"entry": entry, "prev_hash": self._prev_hash, "hash": entry_hash})
self._fh.write(line + "\n")
self._fh.flush()
self._prev_hash = entry_hash
def close(self) -> None:
if not self._fh.closed:
self._fh.close()
def verify_recording(path: Path) -> bool:
prev_hash = GENESIS_HASH
with open(path, encoding="utf-8") as fh:
for line in fh:
if not line.strip():
continue
row = json.loads(line)
if row["prev_hash"] != prev_hash:
return False
entry_json = json.dumps(row["entry"], ensure_ascii=False, sort_keys=True)
expected = hashlib.sha256((prev_hash + "|" + entry_json).encode()).hexdigest()
if expected != row["hash"]:
return False
prev_hash = row["hash"]
return True