审计、数据保留与变更治理
正在读取学习记录…
安全团队需要回答:某次工具请求是谁发起的、按哪一版策略允许或拒绝、记录还保留多久。正常记录可以写为“第9天,u2,允许,策略1”。排查时先找请求与决策,避免把整段用户消息、邮箱和密钥复制进日志。
审计是保留能够追踪行为与决策的记录。数据保留是为这些记录设定生命周期:什么时候继续存、什么时候到期清理。变更治理是让策略修改经过复核,并留下版本与责任人,让历史决定可以对照当时的规则解释。
示例用整数“天”替代真实时间,第10天只保留最近七天的事件。真实服务应选定时间基准、时区和保留起点,考虑调查冻结及合法留存要求。
记录内容、链和保留窗口怎样相连
每条事件从白名单参数构建:时间、主体、决定、策略版本。摘要是由内容算出的固定长度指纹;prev 保存前一条指纹,把相邻记录连接起来。verify 同时重新计算内容摘要和核对前后连接,能发现当前列表里的内容改动和乱序。
摘要链要配合受控存储与外部可信检查点。拥有整份日志写权限的人可以重算所有摘要;删除尾部也不会被当前列表自检发现。生产场景可采用独立写入身份、访问控制、外部签名检查点或不可变存储,以保存完整性的参照。
retain 用年龄筛选窗口,年龄等于保留天数时到期,未来日期也不会进入当前窗口。它重建一个新链,便于独立检查保留片段;旧链的摘要连续性在窗口切换时结束。实际系统要记录清理批次、窗口边界与旧检查点,才能解释为什么旧事件消失。
| 要回答的问题 | 使用字段或规则 | 例子 |
|---|---|---|
| 谁做了什么决定 | actor、decision | u2 / allow |
| 当时用哪版策略 | policy_version | 1 |
| 当前内容有没有变化 | digest、prev | 改决定后校验 False |
| 是否仍在保留窗口 | 0 ≤ 年龄 < days | 第9天在第10天仍保留 |
| 谁能改保留规则 | 另一复核人、版本递增 | 版本1变版本2 |
在空目录保存并运行两个文件
在 PowerShell 输入以下命令创建练习目录,并进入目录。目录创建好后,在编辑器中新建下面两份文件并保存到这个目录。
New-Item -ItemType Directory -Force l8-audit
Set-Location l8-auditmacOS 终端:
mkdir -p l8-audit
cd l8-audit先保存 audit_demo.py。它包含前面解释的内容校验以及正常和边界输入。
"""审计事件使用字段白名单、策略版本与保留规则。"""
import hashlib
import json
from dataclasses import dataclass
RETENTION = 7
@dataclass(frozen=True)
class Policy:
version: int
days: int
def policy_change(old, days, actor, reviewer):
if days <= 0 or actor == reviewer:
raise ValueError("需正数保留期和另一复核人")
return Policy(old.version + 1, days)
def add_event(events, day, actor, decision, policy):
# 从显式参数构造记录,避免把原始请求或秘密整包写入日志。
event = {"day": day, "actor": actor, "decision": decision,
"policy_version": policy.version,
"prev": events[-1]["digest"] if events else "START"}
payload = json.dumps(event, sort_keys=True, ensure_ascii=False).encode()
event["digest"] = hashlib.sha256(payload).hexdigest()
events.append(event)
def verify(events):
previous = "START"
for event in events:
payload = {key: value for key, value in event.items() if key != "digest"}
digest = hashlib.sha256(json.dumps(
payload, sort_keys=True, ensure_ascii=False).encode()).hexdigest()
if event["prev"] != previous or event["digest"] != digest:
return False
previous = event["digest"]
return True
def retain(events, today, policy):
# 年龄等于保留期时到期;新窗口重建链,原摘要无法继续串联。
kept = [event for event in events if 0 <= today - event["day"] < policy.days]
rebuilt = []
for event in kept:
add_event(rebuilt, event["day"], event["actor"], event["decision"],
Policy(event["policy_version"], policy.days))
return rebuilt
def main():
policy = Policy(1, RETENTION)
events = []
add_event(events, 1, "u1", "deny", policy)
add_event(events, 9, "u2", "allow", policy)
print("完整链 ->", verify(events))
events[1]["decision"] = "deny"
print("改动记录 ->", verify(events))
events[1]["decision"] = "allow"
kept = retain(events, 10, policy)
print("保留窗口 ->", [event["day"] for event in kept])
updated = policy_change(policy, 3, "editor", "reviewer")
print("策略变更 ->", updated.version, updated.days)
if __name__ == "__main__":
main()再保存 test_audit.py。测试通过程序的公开函数检查正常路径与拒绝路径;测试文件和主程序放在同一目录,才能按文件名导入。
import unittest
# 固定良性输入检查正常、拒绝和边界路径,不连接外部服务。
from audit_demo import Policy, add_event, policy_change, retain, verify
class AuditTests(unittest.TestCase):
def setUp(self):
self.policy = Policy(1, 7)
self.events = []
add_event(self.events, 1, "u1", "deny", self.policy)
add_event(self.events, 9, "u2", "allow", self.policy)
def test_chain(self):
self.assertTrue(verify(self.events))
def test_mutation(self):
self.events[1]["decision"] = "deny"
self.assertFalse(verify(self.events))
def test_reorder(self):
self.assertFalse(verify(list(reversed(self.events))))
def test_retention_boundary(self):
add_event(self.events, 7, "u3", "allow", self.policy)
kept = retain(self.events, 8, self.policy)
self.assertEqual([event["day"] for event in kept], [7])
self.assertTrue(verify(kept))
def test_minimal_fields(self):
self.assertEqual(set(self.events[0]),
{"day", "actor", "decision", "policy_version", "prev", "digest"})
def test_policy_review(self):
self.assertEqual(policy_change(self.policy, 3, "a", "b"), Policy(2, 3))
for days, reviewer in ((0, "b"), (3, "a")):
with self.assertRaises(ValueError):
policy_change(self.policy, days, "a", reviewer)
if __name__ == "__main__":
unittest.main()返回同一个终端,先列出当前目录内容,确认两个文件名都以 .py 结尾,然后运行主程序。
ls
python -X utf8 audit_demo.py程序输出:
完整链 -> True
改动记录 -> False
保留窗口 -> [9]
策略变更 -> 2 3完整链校验为 True;修改第二条决定后变为 False。恢复字段后,七天保留窗口留下第9天事件。把保留天数变更为3,策略版本递增为2。示例在内存中筛选与返回结果,不会删除磁盘上的日志文件;生产清理还需要覆盖副本、备份和检索索引,并遵循相应保留制度。
运行测试:
python -X utf8 -m unittest -v test_audit.py末尾应显示 Ran 6 tests 和 OK。若提示找不到模块,先用 dir 核对目录与文件名。
改一处,观察决定怎样变化
把 RETENTION = 7 改成 RETENTION = 10,重新运行主程序。第三行由 保留窗口 -> [9] 变成 保留窗口 -> [1, 9]:第1天到第10天的年龄为9,小于新的10天窗口。恢复7。
python -X utf8 audit_demo.py恢复原值后,重新运行测试。
日志越来越多时先收敛字段与生命周期
| 现象 | 检查位置 | 修正动作 |
|---|---|---|
| 日志含秘密或全文请求 | 记录函数入参与字段 | 白名单构建,脱敏后再写入 |
| 摘要一致却少了事件 | 外部检查点或序列范围 | 对照预期最后序号/检查点,不能仅自检 |
| 保留窗口删掉调查资料 | 冻结或例外留存状态 | 在清理前检查冻结规则与授权 |
| 历史决定无法解释 | 事件策略版本与历史配置 | 保存可检索的配置版本和变更记录 |
| 新日志失败但工具继续成功 | 写入可靠性与故障流程 | 根据动作风险决定阻断、重试或受控降级 |
代码的 policy_change 只验证正数和不同复核人,生产变更服务要从已认证身份取得两人的角色,并记录变更原因、生效范围和回滚版本。别把名字不同当成真实职责分离。保留期也要先验证再部署,避免一次错误配置清理掉必要记录。
OWASP 日志指南讨论记录内容、敏感数据排除、访问控制与日志生命周期。日志完整性和保留治理需要存储系统、权限及运营流程共同实现。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“审计、数据保留与变更治理”,哪种做法更稳妥?
对应知识点:L8-audit
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L8-audit
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L8-audit
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L8-audit
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。