第 3
篇 把事件送进 ring;本篇钉
过滤分层轴在用户态的一半。读者最常见的误判是:Helm
里配了 denylist,于是认定「集群里已经没有这类事件」。v1.7.0
Events 文档用 Caution 把这句话钉死——allowlist /
denylist 只作用于 JSON 文件 sink;gRPC 与默认的
tetra getevents
仍看到未按这两份名单过滤的事件。
本篇在系列中的位置
篇目 核心内容 第 3 篇 · Sensor 与 Hook 事件从哪类 hook 来 第 4 篇 · 事件导出路径 JSON / gRPC / tetra;allow·deny、field filter、redaction 第 5 篇 · Agent 生命周期 BTF 加载与三条策略路径 系列目录 全部篇目
版本锚定:Tetragon v1.7.0(
docs/.../concepts/events.md;pkg/exporter/exporter.go;pkg/filters/;pkg/fieldfilters/;api/v1/tetragon/events.proto)。JSON 默认路径以 tag 文档为准:/var/run/cilium/tetragon/tetragon.log。gRPC 默认:unix:///var/run/tetragon/tetragon.sock。不以 live 站点的迁仓说明覆盖这两处。无真实节点则不粘贴伪造导出;配置 JSON 来自官方文档示例。
内核 selector(matchArgs 等)在 hook 内丢事件,属于第 7 篇。本篇只讨论 已经通过内核、到达 agent 之后 的分流。
一、三条出口
flowchart LR
ring["Ring to agent"] --> split{"Sink"}
split -->|"JSON allow deny"| json["tetragon.log"]
split -->|"gRPC unfiltered"| grpc["tetra getevents"]
json --> pipe["tetra reading JSON stdin"]
图:与第 1 篇五轴图中的 sink 分流一致。第三条路不是独立内核出口,而是 JSON 文件的消费者。
v1.7.0 Events 文档把观察方式分成 JSON、tetra
CLI、gRPC。工程上要拆成三条可见集合不同的路:
| 出口 | 文档中的用法 | 过滤从哪来 | 默认落点 |
|---|---|---|---|
| 文件 JSON | 容器 export-stdout 日志,或读 agent
写出的文件 |
agent 为 exporter 构造的
GetEventsRequest(allow/deny、可选 field
filter) |
/var/run/cilium/tetragon/tetragon.log,默认轮转压缩 |
| gRPC | 监听地址 --server-address;Helm
tetragon.grpc.address |
该次 GetEventsRequest
自己的 allow/deny/field_filters |
默认
unix:///var/run/tetragon/tetragon.sock |
把 JSON 再喂给 tetra |
kubectl logs ... -c export-stdout -f \| tetra getevents |
已经过文件 sink
过滤;tetra 只做二次显示过滤(进程名、pod 等
CLI 旗标) |
标准输入 |
pkg/exporter/exporter.go 的
Start 调用
server.GetEventsWG,把自身当作 gRPC
流的接收端。NewExporter 从
option.Config.ExportFilename
拆出文件名与目录;rotationInterval < 0
直接报错。Send 里做可选速率限制,再
encoder.Encode 写成 JSON:
// project: cilium/tetragon, tag: v1.7.0
// path: pkg/exporter/exporter.go
// function: (*Exporter).Send
func (e *Exporter) Send(event *tetragon.GetEventsResponse) error {
if e.rateLimiter != nil && !e.rateLimiter.Allow() {
e.rateLimiter.Drop()
rateLimitDropped.Inc()
return nil
}
if err := e.encoder.Encode(event); err != nil {
logger.GetLogger().Warn("Failed to JSON encode", logfields.Error, err)
}
eventsExportedTotal.Inc()
eventsExportTimestamp.Set(float64(event.GetTime().GetSeconds()))
return nil
}因此「JSON 导出」在实现上是 同一套 GetEvents API 的一个客户端,配置来自 daemon 的 export 旗标,而不是另一套 BPF。编码失败只打日志,函数仍返回 nil——文件里缺行时不要只查 denylist。
文档给出的对照命令语义(未在本仓库执行):
# 消费已经按 JSON export 规则过滤过的行
kubectl logs -n kube-system -l app.kubernetes.io/name=tetragon -c export-stdout -f | tetra getevents -o compact
# 容器内直接跑 tetra:走 gRPC,不经过文件 allow/deny
kubectl exec -it -n kube-system ds/tetragon -c tetragon -- tetra getevents -o compact文档在 gRPC 节写明:tetra 没有把 JSON
piped 进来时,就是 gRPC 客户端。Helm 示例里的
localhost:54321 是改地址的方法,不是 v1.7.0
的默认监听。默认 Unix socket 属于 Upgrade notes 与
events.md 共同钉死的行为;第三方客户端若仍连旧
TCP 端口,会表现为「agent 正常、订阅为空」。第 5、12
篇回收地址变更。
EventLogService(eventlogservice.proto)只调整
JSON 文件的 lumberjack
参数(max_size、rotation_interval、max_backups),不改变
gRPC 可见集合。
二、allow / deny 只作用于 file sink
Export filter 是一行一个 JSON 对象。行与行之间 OR,同一对象内字段 AND。仅配置 allowlist 时导出是默认拒绝;denylist 与 allowlist 同时命中时 denylist 优先。两边都不配则 JSON 导出全部事件。
官方文档示例(配置语义,不是事件样本):
{"event_set": ["PROCESS_EXEC", "PROCESS_EXIT"], "namespace": ["foo"]}
{"event_set": ["PROCESS_KPROBE"]}匹配含义:(PROCESS_EXEC|PROCESS_EXIT) ∧ namespace=foo
或 PROCESS_KPROBE。
实现:pkg/filters/filters.go 的
Apply 为
whitelist.MatchOne(ev) && blacklist.MatchNone(ev);空名单视为通过。ParseFilterList
按 JSON 流解码为 tetragon.Filter。
// project: cilium/tetragon, tag: v1.7.0
// path: pkg/filters/filters.go
// function: Apply
func Apply(whitelist, blacklist FilterFuncs, ev *event.Event) bool {
return whitelist.MatchOne(ev) && blacklist.MatchNone(ev)
}events.proto 的
GetEventsRequest 把这两份名单叫做
allow_list /
deny_list,并写明结果是集合差
allow_list - deny_list。同一
Filter 对象内部多个字段是
AND(BuildFilterList 里每行一个
MatchAll);多行之间是
OR(MatchOne)。这与文档「行 OR、行内
AND」一致。
Caution(v1.7.0 events.md
原文语义):上述 denylist / allowlist
只应用于导出成 JSON 文件的事件(file
sink)。它们 不影响经 gRPC API 流出、被
tetra CLI 消费的事件。即使某事件会被 JSON
denylist 丢掉,使用 tetra CLI
时你仍会看到未按该名单过滤的事件。
这不是 CLI 的 bug。JSON exporter 与交互式
tetra 是两个 GetEvents 客户端:前者带 daemon
配置的名单,后者默认不带那份名单。把 JSON 管道进
tetra
时,你看到的是已经按文件规则滤过的子集,再叠加
CLI 自己的 --processes /
--pod。排障时必须先问「这条 tetra
命令有没有 stdin JSON」。
Filter 字段(events.proto @
v1.7.0)与文档表对照如下。写过滤器以 proto
为准:文档表未列全 PROCESS_THROTTLE /
PROCESS_LSM / PROCESS_USDT。
| 字段 | 匹配对象 |
|---|---|
event_set |
EventType 枚举 |
binary_regex /
parent_binary_regex /
ancestor_binary_regex |
RE2,后者要求该事件类型开启 ancestors |
namespace /
namespace_regex |
Pod 命名空间;空字符串匹配非 Pod 进程 |
pod_regex / labels /
container_id /
container_name_regex |
K8s / 容器元数据;labels 永不匹配无
pod 的主机事件 |
in_init_tree |
容器 init 子树,见 第 2 篇 |
cel_expression |
用户态 CEL(可带 k8s IP/CIDR 扩展) |
health_check |
二进制是否像对应 Pod 的 liveness/readiness exec |
pid_set |
PID 及其子孙;注释写明面向测试,生产需
--enable-pid-set-filter |
CEL 这里不是第 7 篇的 CEL-in-BPF
选择器。ancestor_binary_regex 在未开启
ancestors 时,CheckAncestorsEnabled
会让构建过滤器失败。
内核已经 NoPost
的事件不会到达本层。allow/deny
不能把内核丢掉的事件找回来,只能再砍 agent
已经拿到的集合。
三、field filter
Field filter 限制留下哪些 protobuf
字段,不决定事件是否存在。写法同样是一行一个
JSON,fields 用 protobuf field
mask(点分路径,逗号分隔多路径)。action 为
INCLUDE 或 EXCLUDE。可按
event_set
限定类型;invert_event_set 反转该集合。
默认:某事件类型没有任何 field filter
时导出全部字段。一旦对该类型定义了
INCLUDE,未列入的字段默认不再出现。EXCLUDE 与
INCLUDE 冲突时,GetEventsRequest 注释写
exclusion always takes precedence。
文档示例:除 PROCESS_EXEC 外,只保留
process.exec_id 与
process.parent_exec_id:
{"fields":"process.exec_id,process.parent_exec_id", "event_set": ["PROCESS_EXEC"], "invert_event_set": true, "action": "INCLUDE"}pkg/fieldfilters/fields.go 在 JSON
反序列化前把 snake_case 改成 protobuf JSON 期望的
camelCase(fixupFieldFilterString)。Filter
方法只改 event oneof
内部,node_name / time
等公共字段保留。
Field filter 活在
GetEventsRequest.field_filters 里,因此:
- JSON exporter 若配置了 field filter,文件里的对象会缺字段。
- 独立 gRPC 客户端除非自己在请求里带上同一份 filter,否则仍看到完整字段。
- 管道进
tetra的 JSON 已经被 exporter 裁过;tetra不能把被 INCLUDE 丢掉的字段变回来。
这与 allow/deny 同构:都是 每个 GetEvents
客户端一份,不是「内核里已经删了字段」。SIEM
映射失败时先看 field filter 是否把 pod
砍掉,再看策略。
四、redaction 与环境变量
Redaction 针对参数和环境变量里的敏感子串,避免它们经
JSON
导出被带出集群——文档这一节的标题如此。实现却比
allow/deny
更早:initProcessInternalExec(pkg/process/process.go)在写入
process cache 之前调用
fieldfilters.RedactionFilters.Redact(binary, args, envs)。因此
gRPC 客户端拿到的 Process.arguments
同样可能已被打码。排障时不要假设「tetra
看见明文、文件里是 *****」——对 redaction
而言两套 sink 共享 cache 里那份已经改过的字符串。
配置:--redaction-filters 或 Helm
redactionFilters。JSON 对象含
redact(RE2 列表)与可选
binary_regex。只有捕获组被替换成
*****(常量 REDACTION_STR)。文档
Warning:JSON 里反斜杠要再转义。RE2 语法以 Google RE2
为准,不要按 PCRE 去写回溯。
文档示例(配置,非本机事件):
{"redact": ["--password(?:\\s+|=)(\\S*)"]}含义:参数里 --password=foo 变为
--password=*****。按二进制限制:
{"binary_regex": ["(?:^|/)foo$"], "redact": ["-p(?:\\s+|=)(\\S*)"]}环境变量同理,例如
{"redact": ["(?:SSHPASS=)+(\\S+)"]}。
--filter-environment-variables VAR1[,VAR2..]
在 redaction
之前把未点名的环境变量删掉(slices.DeleteFunc)。这是白名单保留,不是打码。未开此旗标且未配
redaction 时,开启 env 采集会把机密送进所有 sink——采集开关在
__base__ 的 ENV_VARS_ENABLED(第 3
篇),本篇管的是已经采集之后如何裁剪。
Redact 对每个 redaction 对象:若配置了
binary_regex 且全部不匹配,则跳过;否则对 argv
与每个 env 字符串做
ReplaceAllStringFunc,只替换捕获组区间。嵌套捕获组若起点落在已打码区间则跳过,避免把
***** 再切碎。RE2 不支持回溯引用的写法在 Go
regexp 上会编译失败,agent
启动时应直接报错,而不是静默不过滤。
RedactionFilter.match 在 proto 里已
deprecated,不要写进新配置。
Redaction 在用户态、在 hook 返回之后。它不阻止内核日志或审计的其他通道看见同一 argv;它只约束 Tetragon 事件对象。TOCTOU 与「参数在用户态被改写」仍属 hook 轴(第 3、10 篇)。
五、失败表象
| 表象 | 先点哪一层 | 不要先做什么 |
|---|---|---|
SIEM(读 tetragon.log)没有,节点上
tetra getevents(无管道)有 |
JSON allow/deny;官方 Caution | 重装 DaemonSet;改 TracingPolicy 符号 |
管道 logs \| tetra 与无管道
tetra 不一致 |
你在比较 file sink 与 gRPC | 认定 tetra 解析 JSON 坏了 |
事件在,但没有 pod /
parent |
field filter INCLUDE;或血缘轴 cache miss | 加大 denylist |
arguments 里是
*****,策略却按明文写的 |
redaction 已改 cache | 在选择器里匹配打码后的字符串却期望明文 |
| 两边都没有事件 | 内核 selector / hook / 加载轴(第 3、6–7 篇) | 只调 export Helm 值 |
| 配置了 CEL export filter 无效果 | 是否配在 JSON exporter 的名单里;gRPC
请求是否带同一 cel_expression |
把它当成内核 CEL-in-BPF |
| 日志文件找不到 | tag 文档路径
/var/run/cilium/tetragon/tetragon.log;Helm
是否改了位置 |
用 live 文档的另一默认目录当 v1.7.0 |
Exporter.Send 在 rateLimiter
不允许时丢弃并计数,返回
nil(不报错给调用方)。这是用户态第二层丢事件,表象与
denylist 相似,指标不同。节流与 cgroup-rate 见第 11 篇。
谱系:McCanne & Jacobson(1993)把过滤放进内核 VM——对应本系列的 selector,不对应 JSON denylist。Garfinkel(NDSS 2003)关心拦截完整性:用户态再滤一层会让「监控面」与「强制执行面」分叉。Tetragon 把分叉写成文档 Caution,而不是藏起来。Höiland-Jørgensen et al.(2018)的 XDP 丢包发生在驱动路径,与 file sink 丢 JSON 行没有同一套计数语义。
争论:导出采样与真实事件完备性。A 侧要
SIEM 体积可控,用 allow/deny 与 field filter 砍字段。B
侧取证要求 gRPC 所见与落盘所见可对齐。v1.7.0
明确选择不对齐:同一
agent、两套客户端配置。可检验命题是:runbook
是否强制「对账必须点名 sink」。若合规审计把
tetra 屏幕当证据,却用 denylist
证明「未发生」,结论不成立。
开放问题:
- 能否在不改变 Caution 语义的前提下,让两个 sink 的差异可被单一 metric 证明——第 12 篇看 exporter 指标,不在本篇编数字。
- redaction 发生在 cache 构造时,对「只想打码 JSON、保留 gRPC 明文」没有开关——这是 v1.7.0 实现边界。
- 内核过滤与用户态过滤的集合差,如何在排障坐标系里变成检查清单——第 13 篇。
六、小结
- 三条出口:JSON 文件、gRPC、把 JSON 喂给
tetra。后一条是文件的消费者。 - allow/deny 只作用于 file sink;官方
Caution 覆盖
tetra默认 gRPC 路径。 - field filter 按 GetEvents 客户端裁字段;管道 JSON 不可恢复。
- redaction 在写入 process cache 时改 argv/env,因此进入所有 sink。
- 默认 JSON 路径是
/var/run/cilium/tetragon/tetragon.log。下一篇回到 agent:程序没挂上时,导出层再干净也没有事件。
七、参考资料
规范 / 官方文档(A)
- Tetragon v1.7.0 Events —
docs/content/en/docs/concepts/events.md@ v1.7.0(JSON 路径、export Caution、field filter、redaction、tetra管道 vs 容器内 gRPC、默认 Unix socket)。
源码(A)
pkg/exporter/exporter.goStart/Send@ v1.7.0pkg/filters/filters.goApply/ParseFilterList@ v1.7.0pkg/fieldfilters/fields.goFieldFilter.Filter;pkg/fieldfilters/redaction.goRedact@ v1.7.0pkg/process/process.goinitProcessInternalExec(env 白名单后 redaction)@ v1.7.0api/v1/tetragon/events.proto(Filter、FieldFilter、GetEventsRequest、RedactionFilter);eventlogservice.proto@ v1.7.0
核心论文
- McCanne & Jacobson, The BSD Packet Filter, USENIX Winter 1993 —— 内核过滤 vs 用户态再滤。
- Garfinkel, Traps and Pitfalls, NDSS 2003 —— 监控面与拦截面分叉。
- Höiland-Jørgensen et al., The eXpress Data Path, CoNEXT 2018 —— 数据面丢包对照,非 JSON sink。
实验 / 工具
tetra getevents与kubectl logs ... | tetra getevents仅写语义与失败模式;未跑对照实验。不伪造 denylist 前后的 JSON。
站内对照
→ 上一篇:Sensor 与 Hook · 系列目录 · 下一篇:Agent 生命周期
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Tetragon / eBPF】Agent 生命周期:BTF、安装形态与三条策略加载路径
钉清 Tetragon v1.7.0 agent 从 BTF/CO-RE 发现到 sensor 挂载的失败表象;对照 DaemonSet 与主机安装;说明默认 gRPC Unix socket 与 kubectl/gRPC/静态文件三条路径如何进入同一 collection map。
【Tetragon / eBPF】TracingPolicy:CRD、三条加载路径与 collectionKey
钉清 TracingPolicy 与 TracingPolicyNamespaced 的差异;给出 v1.7.0 CRD 字段总表(含 fentries);说明 kubectl、gRPC、静态文件如何共用 collectionKey;并写明 Enable/Disable gRPC 在默认配置下返回错误。
【Tetragon / eBPF】运维与导出工程:metrics 更名、JSON 轮转与 SIEM 接线边界
钉死 Tetragon v1.7.0 Upgrade notes:kprobe merge 指标合并为 tetragon_generic_kprobe_merge_total;JSON 轮转只服务文件 sink;默认 gRPC 改为 unix:///var/run/tetragon/tetragon.sock 会打断第三方客户端。不写 Splunk/ELK 菜谱。
【Tetragon / eBPF】运行时安全全景:缺口、五轴坐标系与 16 篇路线
相对 Cilium 数据面、可观测对照与 Falco 口号,钉清 Tetragon v1.7.0 运行时安全内核缺口;定义进程血缘、Sensor/Hook、策略加载、过滤分层、强制执行五条坐标系,给出 16 篇阅读路线。