审计日志(二):Compliance settings(合规性审计设置)
上一篇聊审计日志的时候,我留了个尾巴说“默认事件集根本不够用”,这次直接把这部分摊开讲。如果你在处理审计需求时发现默认的authentication_failed、access_denied两条日志实在太单薄,连“谁在什么时候成功访问了哪个索引”都查不到,那八成就是没把 Compliance settings(合规性审计设置)打开。
先说清楚这东西解决什么问题:默认情况下,Elasticsearch 只记录“被拒绝”的访问和“认证失败”的事件。但合规审计偏偏要求你反过来——把“被允许”的访问、“认证成功”的操作、run_as模拟身份等全部记下来。这不是简单的改两个参数,而是要理解整个审计事件家族的分类逻辑、各事件之间的关联关系,以及如何在日志量和合规要求之间找到一个平衡点。这篇文章就是把这套机制拆开揉碎,配合可直接抄走的配置示例和字段解读,让你看完就能把合规审计跑起来。
1. 为什么默认审计日志满足不了合规场景
1.1 默认事件集的盲区在哪里
ES 默认审计日志的记录项非常克制,从 6.x 到 8.x 基本保持了同样的保守风格:只记录authentication_failed和access_denied两类事件。如果一个人反复用错误密码登录,你会看到一堆失败日志;如果一个应用试图读取它没权限的索引,你也会看到拒绝日志。但真正的合规审计要求往往不止于此。
我之前给一家业务线梳理过需求,对方安全团队提出的问题十分典型:某个高权限账号在某个时间段内访问了哪些索引?某个服务账号成功执行了什么操作?这两个需求看起来简单,但默认审计日志一条都回答不了。因为成功访问没有日志,成功认证也没有日志,run_as模拟身份更是完全没有痕迹。等出了数据安全事件再去翻日志,你会发现能看到的只有一长串拒绝记录,而那恰恰不是你最需要的东西。
更麻烦的是,这类“找不到日志”的问题在排障时极具迷惑性。你第一反应往往是“审计没开”,检查之后发现xpack.security.audit.enabled确实是true。然后你会开始怀疑是不是用户配置有问题,是不是索引名写错,是不是网络层做了什么特殊处理——折腾一圈之后才猛然意识到,根本不是功能没开,而是默认事件集太窄,压根没打算记录成功类的行为。
1.2 Compliance 设置的本质:把“所有关键行为”纳入视野
Compliance settings 之所以叫“合规”,是因为它的设计目标直接对标审计标准中“可追溯、完整性、不可抵赖”这几个原则。开启合规事件组之后,审计系统会从“只记录异常”切换到“全量记录关键行为”,涵盖认证、授权、连接、模拟身份、系统级访问和匿名拒绝这几个维度。
我理解它的核心价值在于:你不必再为了等一条成功访问的日志而反复清洗数据、复现操作。开启之后,每一笔关键操作都会留痕,日志的角色从“异常报警器”变成了“完整证据链”。实践中配置也很简单,关键在xpack.security.audit.logfile.events.include这个参数里加一个compliance值,或者手动列出一组具体的事件类型。这两种方式的区别在于控制粒度,后面会详细展开。
这里要明确一个容易混淆的点:compliance不是指开启了某个“合规模式”开关,而是指把一堆预设的事件类型打包成一个组。你可以理解为系统提供了一个“审计事件全家桶”,里面包含了认证成功、认证失败、连接建立、连接拒绝、访问允许、访问拒绝、模拟身份、系统访问、匿名拒绝等所有核心事件。如果你只需要其中一个子集,也可以不选“全家桶”,逐个列出需要的事件名即可。
2. 核心配置参数逐项拆解
2.1 总开关:xpack.security.audit.enabled
无论你打算记录什么类型的事件,前提都是把审计总开关打开:
xpack.security.audit.enabled: true这个参数只能在elasticsearch.yml中配置,改完之后必须重启节点才能生效。它不是集群动态设置,不能用PUT _cluster/settings热更新,这一点在变更流程里要特别注意。如果是在容器环境里部署,通常会用环境变量或配置挂载的方式注入,改完配置之后要走一遍滚动重启的流程。
生产环境里我建议在配置变更前先确认目标节点是否已经承载流量,尽量用滚动重启方式逐节点更新。如果你管理的集群规模比较大,一次重启几十个节点,建议先把配置改好、提交变更单、安排低峰窗口,避免在业务高峰期间引入不必要的风险。
注意:集群里任意节点的审计设置作用于该节点本地。如果集群里各节点配置不一致,不同节点产生的审计日志内容可能有差异。合规场景下要保证集群配置统一。
2.2 事件集合:events.include / events.exclude
这是最核心的两个参数,也是这次讨论的重头戏。它们都支持在elasticsearch.yml里配置,也可以通过集群设置 API 动态更新。先看基本的 include 配置:
xpack.security.audit.logfile.events.include: - compliance如果不想用compliance这个“全家桶”,可以按需列出具体事件类型。不同版本支持的事件名略有差异,但以下是常见稳定的事件类型:
| 事件名 | 触发时机 | 典型用途 |
|---|---|---|
access_granted | 用户成功访问索引/文档 | 追溯谁成功读取或写入了数据 |
access_denied | 用户访问被拒绝 | 发现越权扫描和未授权访问尝试 |
authentication_success | 认证成功 | 确认账号登录过程和来源 |
authentication_failure | 认证失败 | 定位暴力破解和异常登录行为 |
connection_granted | 传输层连接被允许 | 排查连接层面的异常行为 |
connection_denied | 传输层连接被拒绝 | 分析网络层阻断和限制策略 |
run_as_granted | 某用户以另一个身份执行操作 | 追踪借用身份的完整链路 |
run_as_denied | 模拟身份被拒绝 | 发现越权模拟行为 |
system_access_granted | 系统内部操作被允许 | 跟踪节点间内部请求 |
anonymous_access_denied | 匿名访问被拒绝 | 识别匿名请求的来源与目标 |
我的习惯是:如果团队对日志量没有硬性限制,直接使用compliance全家桶,简单省心;如果明确只想关心安全事件,比如只想知道谁越权、谁没通过认证,那就手动配置access_denied和authentication_failure即可。
exclude参数的优先级高于include,也就是说即使某个事件被 include 包含了,只要在 exclude 里排除了,就不会输出。实操中可以这样用:既然compliance把所有事件都打开了,但你对connection_granted这类连接层面的日志不感兴趣,就可以在 exclude 里把它排掉。
xpack.security.audit.logfile.events.include: - compliance xpack.security.audit.logfile.events.exclude: - connection_granted - connection_denied这个组合非常常见,既能保留完整的认证和访问记录,又能避免连接事件刷屏,算是一个比较实用的折中方案。
2.3 请求详情与设置快照:emit_request_details / emit_settings
这两个参数名字里都带 emit,但作用完全不同,很多人容易搞混。
xpack.security.audit.logfile.events.emit_request_details决定审计日志中是否输出原始请求体(request body)。比如一个_search请求,开启之后日志里会带上查询的 JSON 内容;不开启则只有请求目标和行为类型。从排障角度看,这个字段非常有用——你不仅能知道“谁在什么时候访问了什么索引”,还能看到“他到底发了什么请求”。但从隐私和安全角度看,请求体里常会携带过滤条件中的敏感数据,比如身份证号、手机号、具体业务字段值,一旦日志泄露,这些数据也会跟着暴露。
生产环境要不要开emit_request_details,我强烈建议先跟安全团队做一次评估。如果业务要求保留完整操作记录,而且日志存储经过加密、访问受控,那么开启也合理;如果没有明确的合规诉求,宁可不开,或者只对审计专用的索引进行短期采样。这个参数支持动态更新,因此可以随时调整,不必频繁重启。
xpack.security.audit.logfile.events.emit_settings则是在每条审计事件中额外附加一份“当前审计设置快照”。它不会改变具体行为,只是帮助你判断这条日志是根据哪套配置产出的。调试阶段我会建议开启,尤其在对比不同节点配置差异时非常方便。但上线长期运行后建议关闭,因为每条日志额外带上配置信息会徒增存储。
2.4 用户过滤与噪音控制:ignore_users
合规审计要覆盖所有用户,但现实中总有一些“技术用户”没必要记,比如健康检查探活账号、跨节点内部通信的账号、Kibana 系统用户。如果不做过滤,你会被这些高频技术请求刷屏,真正的用户操作日志反而被淹没。
xpack.security.audit.logfile.events.ignore_users专门处理这种情况。它接受一个用户列表,命中这些用户的行为完全不记录审计日志。比如我常用的配置:
xpack.security.audit.logfile.events.ignore_users: - kibana_system - logstash_system - beats_system - monitoring_user注意,这个过滤是“完全跳过”,连authentication_failure都不记。如果某个用户既是技术账号,又疑似被暴力破解,你把它加进ignore_users就可能把攻击痕迹也过滤掉了。所以,添加进 ignore 列表之前,确认这个账号确实只用于内部系统交互,而不是业务账号。
3. 实操:从配置到验证一次走通
3.1 最小可用配置示例
下面给出一套可以直接用于测试环境的最小配置。它在elasticsearch.yml里开启审计,记录全部合规事件,输出请求详情,同时排除连接层面的噪音。
xpack.security.audit.enabled: true xpack.security.audit.logfile.events.include: - compliance xpack.security.audit.logfile.events.exclude: - connection_granted - connection_denied xpack.security.audit.logfile.events.emit_request_details: true xpack.security.audit.logfile.events.emit_settings: true这套配置放在单节点测试环境里跑非常合适。如果你是在生产集群中启用,我会建议先把emit_request_details和emit_settings都设成false,观察一两天日志量之后再做增量调整。
3.2 动态更新与验证方法
除了xpack.security.audit.enabled必须本地配置之后重启之外,其他几个和事件集、输出格式相关的参数基本都是动态的。用集群设置 API 就可以直接调整:
PUT _cluster/settings { "persistent": { "xpack.security.audit.logfile.events.include": ["compliance"], "xpack.security.audit.logfile.events.exclude": ["connection_granted", "connection_denied"], "xpack.security.audit.logfile.events.emit_request_details": true } }执行完这个请求之后,正常情况下当前节点会立刻开始按照新配置产出审计日志。但“立刻”不等于“必定成功”,因为集群设置 API 本身在分发配置时有几秒延迟,而且如果某个参数名写错了,API 会给出校验异常。我建议执行之后别急着验证,等 10 秒左右再查日志,让配置在节点间充分收敛。
验证方法也很简单,在 Kibana 的 Dev Tools 里随便执行一个查询,然后去日志目录看审计文件。只有真实产生新的审计记录,才说明配置真的生效了。
3.3 读懂审计日志的字段
配置生效之后,你会看到类似下面这样的一条 JSON 审计日志。不同版本字段名略有出入,但总体结构一致:
{ "type": "audit", "timestamp": "2024-06-18T10:23:45Z", "node.id": "q1w2e3r4", "node.name": "es-data-01", "event.type": "access_granted", "user.name": "alice", "user.realm": "native", "user.run_by.name": "admin", "user.run_by.realm": "native", "action": "search", "request.method": "POST", "request.path": "/sales/_search", "request.body": "{\"query\":{\"term\":{\"status\":\"paid\"}}}", "origin.type": "rest", "origin.address": "10.0.0.8:52314", "indices": ["sales"] }逐字段解释一下:
event.type是核心行为标记,access_granted表示这次访问被许可了。user.name是实际执行操作的用户,user.run_by.name表示真正发起调用的人,比如管理员用run_as模拟了alice的身份,那user.name是alice,user.run_by.name是admin。action表示动作类型,search对应查询,create_index对应建索引。request.path和indices指出了操作落在哪个索引上,这两项是定位数据访问痕迹的关键。origin.address记录了来源 IP 和端口,做网络侧溯源时很有用。
我第一次看到这种完整结构时,觉得它把关键信息都铺开了,比默认的寥寥两行拒绝日志有用太多。特别是user.run_by.name这个字段,在追踪“谁用别人的身份做了什么”这条链路上极其关键,几乎是为权限代理类场景量身定做的。
提示:如果日志里某些字段缺失,大概率是
emit_request_details配置为false,导致request.body不输出,或者节点名输出被关闭。需要依据实际配置判断,不要直接认为是系统故障。
4. 典型事件与业务场景对照
4.1 认证成功与失败:登录链路的完整画像
开启 compliance 之后,认证层的事件会非常清晰地展示出来。我调试过一个场景:某应用凌晨时段频繁登录失败,看默认审计只能看到一堆authentication_failure,来源 IP 也比较零散。开启合规事件后,authentication_success和authentication_failure成对出现,再结合origin.address字段,就能很快还原出攻击者先试探密码、成功后立刻执行敏感操作的完整画像。
authentication_failure事件里通常还会带上返回给客户端的错误原因,比如failed to authenticate,这对区分“密码错误”和“用户不存在”非常有帮助。如果监控规则里能看到这两种细分失败原因的数量比例,甚至能推断出攻击者是否在做用户枚举。
4.2 访问授权与拒绝:越权访问一目了然
access_granted和access_denied是最直观的合规审计事件。access_granted记录用户成功访问了哪些索引,能直接回答之前那家业务线提出的“这个高权限账号到底查了什么数据”。
举一个我自己的排查案例。有一次业务方反馈数据被异常导出,怀疑某个账号访问了本不该看的索引。开启合规审计之后,我直接按用户和索引过滤日志,几下就把信息拼出来了:某个低权限业务账号在一次run_as模拟过程中访问了财务索引,虽然那一次请求恰好落在索引权限边缘,没有触发默认的拒绝日志,但 compliance 事件把它记录得清清楚楚。这就是成功事件和模拟身份事件组合在一起的威力。
另一个值得注意的是access_denied并不只发生在权限不足时,索引不存在、类型不匹配等也可能会触发类似记录。排查时要结合action和indices字段一起分析,别看到denied就断定是权限问题。
4.3 run_as 与连接事件:藏在背后的一层信息
run_as事件在有多租户或委托操作场景里特别重要。默认审计日志中,run_as_granted和run_as_denied这类事件几乎不会出现。但合规审计打开后,它们能揭示完整的身份链。
举个例子,管理员账号admin用run_as模拟成普通用户alice去执行操作。此时审计日志里会出现user.name: alice、user.run_by.name: admin这样的组合。如果你只关注user.name,很容易认为这只是一次普通的alice操作,但加上run_by字段后,你能看清这其实是管理员代劳的。顺着这条链去追,谁在什么时候借了谁的身份、干了什么,一清二楚。
connection_granted和connection_denied则更多服务于网络层调试。但生产环境里大量节点间传输会带来海量日志,所以我通常建议在 include 全家桶的前提下,用 exclude 把这类连接事件排除掉。如果确实需要排查跨节点通信问题,建议临时把连接事件加回来,问题定位后立刻再排除掉。
5. 常见问题与排查技巧
5.1 配置了 compliance 但日志没变
这是最常遇到的问题,通常有以下几种原因:
第一,xpack.security.audit.enabled没有设为true。注意审计总开关和事件集是两层配置,后者不会自动打开前者。很多人在elasticsearch.yml里只加了 include 参数而忘了开总开关,自然没有任何输出。
第二,事件集配置格式写错。YAML 里如果写成xpack.security.audit.logfile.events.include: compliance这种单值形式,部分版本会解析失败。建议使用列表格式:
xpack.security.audit.logfile.events.include: - compliance第三,ignore_users把你想观察的账号过滤掉了。排查时可以临时把ignore_users清空再测试,排除干扰。
第四,日志文件中审计事件和普通日志混在一起,或者输出到了独立文件。需要确认日志目录里是否存在类似<cluster_name>_audit.json的文件,如果只盯着elasticsearch.log找,可能会漏掉审计专用文件。
5.2 日志量暴涨,磁盘告警
开启 compliance 之后日志量显著上升是正常现象。最典型的场景是把整个compliance全家桶打开后,每一笔访问都记,日志量可能比默认多出几倍甚至十几倍。
应对方式分三步走:
一是评估需求,只保留必要事件。比如只关注认证和访问行为,那就只 include 这几种:
xpack.security.audit.logfile.events.include: - access_granted - access_denied - authentication_success - authentication_failure二是用exclude排除高噪音字段,比如把连接事件排除掉。
三是用ignore_users过滤技术账号。健康检查探活账号的请求频率极高,如果不排除,它能以一己之力占据大半审计日志。
如果磁盘空间确实紧张,也可以调整审计日志的轮转策略。ES 的审计日志默认滚动策略与其他日志一致,可以在 log4j2 配置中调整单个文件大小和保留数量。比如设置每个文件 100MB 保留 5 个备份,就是一个相对保守的组合。
5.3 审计日志里出现敏感数据
emit_request_details开启后,审计日志里可能出现请求体中的敏感字段。有些查询会携带客户信息、纯数字标识等业务数据,一旦日志被不当转发或泄露,就不只是技术问题了。
我的建议是:除非明确要求保留请求原貌,否则保持emit_request_details: false。如果确实需要记录某些索引的查询详情,可以在查询层面对敏感字段做过滤,或者缩短日志保留周期,避免长期积压。
另外,审计日志本身要考虑访问控制。把日志文件权限限制到 Elasticsearch 运行用户和必要的安全运维人员,不要把日志目录对业务人员开放。归档到对象存储时也要基于最小授权原则,不要让非相关人员能够直接读取。
5.4 审计日志轮转与长期存储
合规场景下审计日志往往需要保留半年甚至更久,这带来的存储压力比业务日志更大。我见过不少团队前期只想着开审计,没有设计存储方案,最后日志把磁盘撑爆了又急忙设置清理策略,结果反而丢了关键的合规记录。
比较好的做法是把审计日志当作独立的数据流管理。 ES 本身支持把审计日志输出到另一个索引或另一套日志采集系统中,具体方式取决于你的架构。如果只是本地文件,那就依赖系统级的 logrotate 或 logstash/Filebeat 做采集转发。长期保留的话,建议定期归档到冷存储,并在归档前做一次脱敏检查,移除不必要的请求体内容。这样既满足合规要求,又不会让热节点磁盘成为瓶颈。
6. 一些实操体会
合规审计设置本身并不难,难点在于“配置粒度”和“日志治理”。我踩过几次坑之后养成的习惯是:上线前先在小集群开启完整的compliance家族,观察一周日志量,再针对性地排除高噪音事件。这样做虽然前期有点折腾,但总比开了很久之后发现关键事件没记上、或者磁盘被日志塞满要好得多。
如果你现在还在为“为什么成功访问没有审计日志”而困扰,大概率就是缺了compliance这套设置。照着上面的配置跑一遍,把事件列表和字段含义理解透,再去结合自己的业务场景做裁剪,基本就能满足大部分合规审计需求了。