77 lines
2.7 KiB
Python
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)
|