Files
water-management-system/src/security/audit.py
T

77 lines
2.7 KiB
Python

"""安全审计:操作日志记录、查询与不可篡改链式校验。"""
from __future__ import annotations
import hashlib
import json
import threading
import time
import uuid
from dataclasses import dataclass, field, asdict
from typing import Optional
@dataclass
class AuditEntry:
action: str # 操作类型,如 user.create / billing.refund
actor: str # 操作人
target: str = "" # 操作对象
detail: str = "" # 附加说明
ip: str = ""
entry_id: str = field(default_factory=lambda: uuid.uuid4().hex)
ts: float = field(default_factory=time.time)
prev_hash: str = ""
entry_hash: str = ""
def canonical(self) -> str:
d = asdict(self)
d.pop("entry_hash", None)
return json.dumps(d, sort_keys=True, ensure_ascii=False)
class AuditLogger:
"""线程安全的内存审计日志(哈希链防篡改),生产可替换为 DB 存储。"""
def __init__(self) -> None:
self._entries: list[AuditEntry] = []
self._lock = threading.Lock()
def log(self, action: str, actor: str, target: str = "", detail: str = "", ip: str = "") -> AuditEntry:
with self._lock:
prev = self._entries[-1].entry_hash if self._entries else "GENESIS"
entry = AuditEntry(action=action, actor=actor, target=target, detail=detail, ip=ip,
prev_hash=prev)
entry.entry_hash = hashlib.sha256(entry.canonical().encode("utf-8")).hexdigest()
self._entries.append(entry)
return entry
def query(self, *, actor: Optional[str] = None, action: Optional[str] = None,
since: Optional[float] = None, until: Optional[float] = None) -> list[AuditEntry]:
with self._lock:
result = list(self._entries)
if actor is not None:
result = [e for e in result if e.actor == actor]
if action is not None:
result = [e for e in result if e.action == action]
if since is not None:
result = [e for e in result if e.ts >= since]
if until is not None:
result = [e for e in result if e.ts <= until]
return result
def verify_chain(self) -> bool:
"""校验哈希链完整性;任何条目被篡改都会返回 False"""
with self._lock:
entries = list(self._entries)
prev = "GENESIS"
for e in entries:
if e.prev_hash != prev:
return False
if hashlib.sha256(e.canonical().encode("utf-8")).hexdigest() != e.entry_hash:
return False
prev = e.entry_hash
return True
def __len__(self) -> int:
return len(self._entries)