审计日志的数据结构:字段、时间戳、源、目标、行为、结果
好,咱们开始啃第三块硬骨头。
上一章讲了日志收集的几种模式,但收上来的日志长什么样?如果每套系统的日志格式都随心所欲,那审计基本没法干。我在刚入行那会儿,接过一个客户的活,他们十几套系统的日志字段对不上,硬生生花了三周做字段映射。从那以后,我养成了一个习惯:先定标准,再谈分析。
核心六元组:一条日志的骨架
一条合格的审计日志,说白了就是由六个固定字段组成的「事件快照」。你想想看,你在监控系统里看到一条告警,你最想知道什么?谁干的、在哪儿干的、干了什么、结果如何、什么时候干的。这五个问题,正好对应五个核心字段:时间戳、源、目标、行为、结果。再加上一个用于归类的日志类型,就成了我现在要讲的「六元组」。
| 字段 | 英文/别名 | 必填 | 典型内容(举例) |
|---|---|---|---|
| 时间戳 | Timestamp | 是 | 2025-04-01T14:23:10.882Z |
| 源 | Source / Actor | 是 | user:zhangsan / ip:192.168.1.10 |
| 目标 | Target / Object | 是 | file:/data/report.pdf / db:orders |
| 行为 | Action / Operation | 是 | DELETE / DOWNLOAD / LOGIN |
| 结果 | Result / Status | 是 | SUCCESS / FAILURE / DENIED |
| 日志类型 | EventType / Category | 强烈建议 | ACCESS / AUTH / CONFIG |
核心原则:六元组缺一个,这条日志的「证据链」就断了一环。我见过不少厂商的日志只记了IP和操作,压根没写是哪个用户,这等于白记。
时间戳:一切事件的锚点
时间戳是日志里最容易被忽视,但坑最深的一个字段。为什么?
精度问题。秒级精度在高并发场景下根本不够用。我记得有一次排查一个微服务的调用链,调用量每秒上千次,结果两条日志的时间戳一模一样,根本分不清先后顺序。后来我们统一把时间戳改成了毫秒级。相信我,如果你的系统吞吐量很高,直接用微秒或纳秒,别犹豫。
时区问题。这是老生常谈的坑了。我曾经吃过一个大亏:收集来的日志,有的用UTC,有的用了GMT+8,还有的干脆没带时区信息。排障的时候对着日志傻眼,以为事件发生在凌晨三点,其实是下午三点。我的建议是——日志里统一用UTC+ISO 8601格式,显示的时候再转本地时间。
// 推荐格式(ISO 8601)
2025-04-01T14:23:10.882Z // UTC,带时区Z
2025-04-01T22:23:10.882+08:00 // 东八区,偏移量
// 不推荐格式
2025-04-01 14:23:10 // 既无Z也无偏移,完全没法判断时区
避坑指南:我曾经在项目中见过设备时间偏差达到7分钟的情况,排查了一圈发现是设备NTP服务挂了没人管。记住:没有对齐的时间戳,审计日志就是一堆废纸。
源与目标:谁对谁做了什么
「源」是动作的发起方,可能是用户、进程、IP、服务账号。「目标」是动作的承受方,可能是文件、数据库、API端点、配置项。这两个字段必须充分描述,不能只写一个ID。我个人的习惯是,源字段至少包含用户标识+来源IP,目标字段至少包含资源类型+资源路径。
举个实际的例子,登录失败事件:
// 不够完整的源/目标
"subject": "user_001"
"object": "portal"
// 我建议的写法
"subject": { "username": "user_001", "ip": "10.0.0.5", "type": "human" }
"object": { "resource": "portal", "id": "svc_portal_01", "type": "application" }
你想想看,如果只有个user_001,事后你怎么确定是谁在什么地点操作的?把信息写全,后续做用户行为分析时,连ETL清洗的功夫都省了。
行为与结果:动词要规范,状态要明确
行为(Action)应该是一个严格的动词枚举,而不是自由文本。我见过的反面教材:一条日志写的action是「用户操作」,另一条写的是「修改配置」,还有一条写的是「不小心点了删除」。这种日志在报表聚合时根本没法用。
建议的做法:定义一套统一的动作词表。比如:
- 访问类:LOGIN, LOGOUT, READ, DOWNLOAD
- 变更类:CREATE, UPDATE, DELETE, UPLOAD
- 权限类:GRANT, REVOKE, ADD_USER, REMOVE_USER
结果(Result)我这里要特别说一句。很多人只记成功,不记失败,这是大忌。你想想看,一个攻击者试了100次密码,99次失败了,第1次成功了。如果你只记录了那1次成功登录,那攻击者的扫描行为就完全隐身了。所以,结果字段必须包含三种状态:SUCCESS / FAILURE / DENIED。DENIED是系统主动拒绝,FAILURE是操作执行失败了(比如数据库写入异常)。
小技巧:在结果里带一个错误码或具体原因。比如FAILURE后面跟个 err:"WRONG_PASSWORD",比光写个FAILURE有用得多。我习惯把错误原因字段叫 reasonCode 或 failCause。
扩展字段与元数据
六元组是骨架,但真正让日志「丰满」起来的是扩展字段。比如:
- 会话ID(SessionId):能把同一次登录到登出的所有操作串起来。
- 请求ID(RequestId / TraceId):分布式系统中的全链路追踪利器。
- 原始日志(RawLog):有时候标准化字段丢失了上下文,保留原始文本可以回溯。
我自己在写日志规范时,会把字段分为三组:必填(六元组)、建议(会话ID、请求ID)、可选(负载数据、原始日志)。
知识体系:六元组的关系
下面这张图展示了审计日志核心字段的关联方式。每个审计事件都是一个六元组的实例,缺一不可。你可以把它当作一个审计事件的「DNA」。
一个完整的日志示例
上面讲了不少理论,我们来拼一个完整的样子。假设用户zhangsan从IP 10.0.0.5通过SSH登录了服务器web-01,结果成功。那么标准化的审计日志大概长这样:
{
"timestamp": "2025-04-01T14:23:10.882Z",
"source": {
"user": "zhangsan",
"ip": "10.0.0.5",
"type": "human",
"sessionId": "abc123"
},
"target": {
"resource": "server",
"name": "web-01",
"type": "host"
},
"action": "LOGIN",
"result": "SUCCESS",
"eventType": "AUTH",
"metadata": {
"protocol": "SSH",
"port": 22
}
}
重点回顾:不同场景下的「源」和「目标」会变化。比如写文件操作,源是进程或用户,目标是文件。API调用时,源是调用方IP,目标是端点URL。理解这个映射关系,无论面对什么系统,你都能快速拆解出其六元组。
如何定义你自己的日志规范
每个公司的基础设施不一样,但我建议你遵循这样一个流程来制定规范:
- 盘点所有日志源:应用、数据库、网络设备、云服务,一个都不能漏。
- 统一字段名称:不要一个系统叫user,另一个叫username,第三个叫subject。我见过最夸张的,一个日志里同时出现了user和actor两个字段指代不同用户,直接看懵了。
- 定义枚举值范围:Action和Result用有限的词表,不要自由书写。
- 增加上下文:比如带上请求ID、客户端版本、用户代理。
- 写一份规范文档:发布给所有开发团队,做成一个在线字典,随时查。
一个建议:别一次性把所有字段定死。我习惯的做法是先定六元组为核心,第一版只要求这六个字段必须存在。等大家习惯了,再慢慢加扩展字段。步子太大容易扯着蛋。
嗯,这一章内容就到这儿。数据结构的规范,是整个审计体系的奠基石。下一节我们会把这个标准套用到实际场景中,看看如何从零开始搭建一套合规的审计日志采集系统。
(本章结束)