feat: MeshSafe 文件摆渡安全卫士 v1.0 初始提交
核心功能: - 6 层检测管线: L0 结构分析 → L1 ClamAV → L2 文档深度检测 → L3 关键词匹配 → L4 YARA → L5 PE/二进制 - FastAPI + PostgreSQL + Redis + Docker Compose 架构 - 客户端 HMAC-SHA256 签名鉴权 + 限流 + 幂等性 - 控制台 UI (登录/Dashboard/诊断中心/策略管理/任务列表/测试中心/产品介绍页) - 三层诊断定时器 (error_scan 30s / db_aggregate 30s / container_check 5min) 并发与稳定性修复: - 诊断定时器 Redis 时间槽位锁 (多 worker 同一槽位只有一个执行) - JSONL 存储 fcntl 跨进程排他锁 + 容错解码 + 原子重写 - 移除 SQLite fallback, 统一 PostgreSQL - API_CLIENTS 脱敏为环境变量, .env 已 .gitignore
This commit is contained in:
155
app/services/audit.py
Normal file
155
app/services/audit.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""审计日志服务,带哈希链防篡改。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import get_settings
|
||||
from app.models import AuditLog
|
||||
|
||||
_settings = get_settings()
|
||||
|
||||
|
||||
def _canonical(detail: Dict[str, Any]) -> str:
|
||||
return json.dumps(detail, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
|
||||
|
||||
|
||||
def compute_row_hash(
|
||||
ts: datetime,
|
||||
actor: str,
|
||||
actor_role: str,
|
||||
action: str,
|
||||
target: str,
|
||||
channel: str,
|
||||
client_ip: str,
|
||||
outcome: str,
|
||||
detail: Dict[str, Any],
|
||||
prev_hash: str,
|
||||
) -> str:
|
||||
"""计算审计记录的链式哈希。
|
||||
|
||||
哈希输入包含除 ``row_hash`` 外的全部字段以及前一条记录的哈希,
|
||||
任何字段被修改都会导致链断裂。
|
||||
"""
|
||||
payload = "|".join(
|
||||
[
|
||||
ts.astimezone(timezone.utc).isoformat(),
|
||||
actor,
|
||||
actor_role,
|
||||
action,
|
||||
target,
|
||||
channel,
|
||||
client_ip,
|
||||
outcome,
|
||||
_canonical(detail),
|
||||
prev_hash,
|
||||
]
|
||||
)
|
||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def record(
|
||||
db: Session,
|
||||
*,
|
||||
action: str,
|
||||
actor: str = "system",
|
||||
actor_role: str = "system",
|
||||
target: str = "",
|
||||
channel: str = "",
|
||||
client_ip: str = "",
|
||||
outcome: str = "success",
|
||||
detail: Optional[Dict[str, Any]] = None,
|
||||
) -> AuditLog:
|
||||
"""写入一条审计日志。"""
|
||||
detail = detail or {}
|
||||
|
||||
prev_hash = ""
|
||||
if _settings.audit_hash_chain:
|
||||
last = db.execute(
|
||||
select(AuditLog).order_by(AuditLog.id.desc()).limit(1)
|
||||
).scalar_one_or_none()
|
||||
if last is not None:
|
||||
prev_hash = last.row_hash or ""
|
||||
|
||||
ts = datetime.now(timezone.utc)
|
||||
row_hash = ""
|
||||
if _settings.audit_hash_chain:
|
||||
row_hash = compute_row_hash(
|
||||
ts, actor, actor_role, action, target, channel, client_ip, outcome, detail, prev_hash
|
||||
)
|
||||
|
||||
entry = AuditLog(
|
||||
ts=ts,
|
||||
actor=actor,
|
||||
actor_role=actor_role,
|
||||
action=action,
|
||||
target=target,
|
||||
channel=channel,
|
||||
client_ip=client_ip,
|
||||
outcome=outcome,
|
||||
detail=detail,
|
||||
prev_hash=prev_hash,
|
||||
row_hash=row_hash,
|
||||
)
|
||||
db.add(entry)
|
||||
db.commit()
|
||||
db.refresh(entry)
|
||||
return entry
|
||||
|
||||
|
||||
def verify_chain(db: Session, limit: int = 1000) -> Dict[str, Any]:
|
||||
"""校验最近若干条审计记录的哈希链完整性。"""
|
||||
rows = list(
|
||||
db.execute(select(AuditLog).order_by(AuditLog.id.desc()).limit(limit)).scalars().all()
|
||||
)
|
||||
rows.reverse()
|
||||
|
||||
broken_at: Optional[int] = None
|
||||
prev_hash = ""
|
||||
for row in rows:
|
||||
if row.prev_hash != prev_hash:
|
||||
broken_at = row.id
|
||||
break
|
||||
expected = compute_row_hash(
|
||||
row.ts,
|
||||
row.actor,
|
||||
row.actor_role,
|
||||
row.action,
|
||||
row.target,
|
||||
row.channel,
|
||||
row.client_ip,
|
||||
row.outcome,
|
||||
row.detail or {},
|
||||
row.prev_hash,
|
||||
)
|
||||
if expected != row.row_hash:
|
||||
broken_at = row.id
|
||||
break
|
||||
prev_hash = row.row_hash
|
||||
|
||||
return {
|
||||
"checked": len(rows),
|
||||
"intact": broken_at is None,
|
||||
"broken_at_id": broken_at,
|
||||
"verified_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
|
||||
|
||||
def purge_expired(db: Session) -> int:
|
||||
"""清理超出留存期的审计记录,返回删除条数。
|
||||
|
||||
等保三级要求审计记录留存不少于 180 天,删除动作本身也会记入审计日志。
|
||||
"""
|
||||
threshold = datetime.now(timezone.utc) - timedelta(days=_settings.audit_retention_days)
|
||||
rows = db.execute(select(AuditLog).where(AuditLog.ts < threshold)).scalars().all()
|
||||
for row in rows:
|
||||
db.delete(row)
|
||||
if rows:
|
||||
db.commit()
|
||||
return len(rows)
|
||||
Reference in New Issue
Block a user