5.0 KiB
| name | description | version |
|---|---|---|
| audit-logging | Use when 设计审计日志/审计跟踪(审计独立性、owner.audit角色、防自删)。 | 1.0.0 |
审计日志设计(审计独立性 + 防自删)
为多租户/多用户系统设计全局审计日志模块。核心不是"记录日志",而是审计独立性: 审计员独立于被审计者(管理员),防止管理员篡改/删除自己的操作记录。
核心原则
- 审计独立性:单独
owner.audit角色,审计查看/备份/删除权限仅 audit 角色,owner.superuser也无权(superuser 不会自动放行审计路径)。审计员是独立角色,不是管理员。 - append-only:审计日志只 INSERT 不 UPDATE,禁止修改已写记录。
- 防自删:删除操作留痕永不删除(见下)。
数据表
CREATE TABLE IF NOT EXISTS sd_audit_logs (
id varchar(32) NOT NULL,
user_id varchar(32), username varchar(100),
action varchar(50) NOT NULL, -- 事件白名单
target varchar(200), detail text, -- 操作对象 + 详情
result varchar(10), client_ip varchar(64),
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_action (action), KEY idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
owner.audit 角色 + 判断
role 表 id='owner.audit', orgtypeid='owner', name='audit'(owner.superuser 同理,id 直接是
字符串 'owner.superuser',不是随机 ID)。初始化幂等:INSERT IGNORE INTO role (id,orgtypeid,name) VALUES ('owner.audit','owner','audit')。
判断用户是否审计员查 userrole 表(users 表无 role 字段):
SELECT 1 FROM userrole WHERE userid=${u}$ AND roleid='owner.audit' LIMIT 1
双保险权限(RBAC 层 + 应用层,缺一不可)
审计 API 要两层都卡,因为独立 app 的 RBAC 可能对未注册路径放行(实测新 .dspy 未注册 也能访问):
- RBAC 层:load_path.py 加
PATHS_AUDIT挂 owner.audit(不给 logined/superuser)。 set_role_perm.py 角色名用orgtypeid.name格式,即owner.audit。 - 应用层:审计 .dspy 内部再查 userrole 校验
is_audit_role(),不依赖 RBAC 是否生效。 应用层校验是最终保障,即使 RBAC 放行或缓存未刷新也拦得住。
防自删(删除留痕永不删)
删除操作先写 audit_delete 记录,且 DELETE 语句排除它:
async def delete_audit_logs(sor, user_id, username, before="", client_ip=""):
# 排除 audit_delete 记录,保证删除留痕不被删
wsql = (" WHERE created_at<${b}$ AND action != 'audit_delete'" if before
else " WHERE action != 'audit_delete'")
await audit_log(sor, user_id, username, "audit_delete", target="sd_audit_logs",
detail="删除 before=" + (before or "全部"), client_ip=client_ip) # 先留痕
recs = await sor.sqlExe("SELECT COUNT(*) AS c FROM sd_audit_logs" + wsql, ns)
await sor.sqlExe("DELETE FROM sd_audit_logs" + wsql, ns)
audit_delete 记录累积是审计的代价,可接受(那正是"删除证据"本身)。
审计事件清单(action 白名单)
用集合白名单校验 action,非白名单归 unknown:
- 认证:login / login_fail / logout
- 权限:role_change / perm_change / user_role_change
- 工作环境:work_env_set / org_key_gen / remote_bwrap(远程主机变更是高风险)
- 部署账号:account_create / account_remove / sandbox_run
- 用户机构:user_create / user_disable / user_delete / org_change
- 审计自身:audit_delete / audit_backup
审计写入的接入点
写入是"系统自动"(各模块关键操作时调用 audit_log()),不是用户触发。审计写入失败
不应阻断主流程(try/except 吞掉,仅 logger 记录)。关键接入点:RBAC 变更、工作环境/
远程主机变更、账号生命周期、高危命令执行、登录成败。
Pitfalls
-
client_ip 是可信的(勿误判为可伪造):ahserver
real_ip.py中间件split(',')[-1]取 X-Forwarded-For 最后一段;nginx 用$proxy_add_x_forwarded_for追加真实 IP(非透传$http_x_forwarded_for),且X-real-ip $remote_addr覆盖。故末段恒是 nginx 追加的真实客户端 IP,客户端伪造的 XFF 只出现在前段。前提:生产必须经 nginx($proxy_add_x_forwarded_for);若直连应用端口(无 nginx)才可伪造。 -
information_schema 查询必须限定
table_schema=DATABASE():不限定会查到别的库的同名列 (如把 sage 库的need_audit列误当成当前库的,随后 SQL 报 Unknown column)。 -
审计独立性要实测:撤销 audit 角色后,用 superuser 账号 curl 审计 API,确认返回拒绝—— 只靠 RBAC 注册不够,必须验证应用层
is_audit_role真的拦住了 superuser。 -
审计写入与删除的次序:删除留痕必须在 DELETE 之前写,且 DELETE 排除留痕本身, 否则"删全部"会把刚写的留痕一起删掉。