64 lines
2.2 KiB
Python
64 lines
2.2 KiB
Python
"""
|
|
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
|