镜像扫描只能回答「这个镜像里有没有已知漏洞」,回答不了「这个进程此刻为什么在读取宿主机文件」。运行时安全(Runtime Security)关注的是容器运行起来之后的行为:谁调用了 setns、谁打开了 /proc/*/mem、谁在监听一个非预期的端口。本文从威胁模型出发,讲清 eBPF 如何在内核层采集这些信号,Falco 与 Tetragon 的规则与执行能力,以及告警降噪、性能开销与内核兼容性这些落地时绕不开的问题。
目录
- 1. 运行时安全威胁模型
- 2. eBPF 检测原理与内核钩子
- 3. Falco 规则与告警管道
- 4. Tetragon 与策略执行
- 5. 系统调用与行为基线
- 6. 容器逃逸与提权检测
- 7. 告警降噪与响应编排
- 8. 性能开销与内核兼容性
- 9. 生产最佳实践
1. 运行时安全威胁模型
1.1 攻击链视角
容器攻击通常遵循一条链路:初始入口 → 执行 → 权限提升 → 逃逸 → 横向移动 → 持久化。镜像扫描与准入控制只能覆盖最前面的部分,后面几步全部发生在运行时。
入口:漏洞利用 / 供应链投毒 / 配置错误 / 凭据泄露
执行:下载并运行二进制、反弹 Shell、写入 WebShell
提权:利用 SUID、内核漏洞、错误挂载的 socket
逃逸:privileged 容器、hostPath 挂载、docker.sock 暴露
横向与持久:读取 SA Token、访问 API Server、写入 CronJob
1.2 运行时安全的可观测面
| 观测面 | 数据源 | 能回答什么 |
|---|---|---|
| 系统调用 | eBPF / auditd | 谁在做什么内核操作 |
| 进程谱系 | execve 钩子 | 进程从哪来、由谁派生 |
| 网络连接 | socket 钩子 | 连到哪个外部地址 |
| 文件访问 | openat 钩子 | 读写哪些敏感路径 |
| 容器生命周期 | CRI / kubelet | 容器何时创建与销毁 |
1.3 与准入控制的边界
准入控制是事前拦截(对象还没落库),运行时安全是事中发现与阻断(进程已经在跑)。两者互补:准入能拦住特权容器,但拦不住「合法容器内的合法二进制被滥用」。不要把运行时安全当作准入的替代品,也不要用准入去解决运行时问题。
2. eBPF 检测原理与内核钩子
2.1 为什么是 eBPF
传统方案要么改内核模块(风险高、维护难),要么依赖 auditd(性能差、粒度粗)。eBPF 在内核中安全地运行沙箱程序,通过验证器保证不会崩溃内核,通过 map 与用户态通信,具备三个优势:
低开销:JIT 编译后接近原生性能,可编程过滤减少数据拷贝
可观测:挂载点覆盖系统调用、内核函数、网络栈
可移植:一次编写,内核版本 4.x+ 普遍支持(需 BTF/CO-RE)
2.2 常见挂载点
| 类型 | 挂载点 | 用途 |
|---|---|---|
| kprobe | 任意内核函数 | 跟踪 openat、execve 等 |
| tracepoint | 稳定内核事件点 | 语义稳定,推荐优先 |
| raw_tracepoint | 系统调用入口 | 低开销 syscall 采集 |
| LSM | 安全钩子 | 可实现阻断 |
| cgroup | 容器维度 | 按容器过滤 |
2.3 容器上下文关联
eBPF 看到的是「某个 PID 调用了 openat」,必须关联到「哪个容器、哪个 Pod」。关联链路:
pid -> /proc/<pid>/cgroup -> cgroup id -> container id -> Pod (via CRI)
这就是为什么运行时安全 Agent 通常以 DaemonSet + hostPID + hostNetwork 运行:它需要看到宿主机的全部进程与 cgroup 层级,才能完成这层映射。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: runtime-agent
spec:
template:
spec:
hostPID: true
tolerations:
- operator: Exists
containers:
- name: agent
image: registry.example.com/runtime-agent:v0.9.0
securityContext:
privileged: true
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
Agent 自身是特权组件,必须单独做准入、镜像签名与 RBAC 最小化,否则它本身就是最大的攻击面。
3. Falco 规则与告警管道
3.1 规则结构
Falco 规则由「条件」与「输出」两部分组成,条件基于系统调用事件字段表达。
- rule: 容器内写入敏感目录
desc: 检测向 /etc 或 /bin 写入的行为
condition: >
open_write and container
and fd.name startswith /etc
and not proc.name in (known_package_managers)
output: >
敏感目录写入 (user=%user.name container=%container.name
file=%fd.name command=%proc.cmdline)
priority: WARNING
tags: [filesystem, mitre_persistence]
3.2 常用宏与列表
Falco 提供大量内置宏(container、spawned_process、outbound)与列表(shell_binaries、sensitive_mount)。复用内置宏比手写条件更可靠,因为内置宏已经处理了各种边界情况。
| 宏 / 列表 | 含义 |
|---|---|
| container | 事件发生在容器内(非宿主机) |
| spawned_process | 有新进程被创建 |
| shell_binaries | sh、bash、zsh 等 |
| sensitive_file_names | /etc/shadow、/root/.ssh 等 |
| package_mgmt_binaries | apt、yum、apk 等 |
3.3 告警管道
eBPF 事件 -> Falco 引擎 -> 规则匹配 -> 输出通道
├── stdout(JSON)
├── gRPC(Falco Talon / 自定义消费者)
├── syslog
└── webhook / Kafka / S3
推荐把告警以 JSON 输出到 stdout,由 Fluent Bit 采集后送 Kafka 或 SIEM:
falco:
json_output: true
json_include_output_property: true
priority: notice
buffered_outputs: false
3.4 规则调优三步
第一步:观察。先用默认规则集跑一周,统计各规则触发量。
第二步:收敛。把噪声最大的规则加条件收窄(加 container、加进程白名单)。
第三步:分级。按 priority 分级路由:CRITICAL 立即告警,WARNING 聚合日报。
4. Tetragon 与策略执行
4.1 Falco 与 Tetragon 的定位差异
| 维度 | Falco | Tetragon |
|---|---|---|
| 数据面 | eBPF + 规则引擎 | 纯 eBPF(CO-RE) |
| 表达方式 | Falco 规则 DSL | TracingPolicy CRD |
| 阻断能力 | 需配合 Talon 等 | 原生支持(SIGKILL / 拒绝) |
| 进程谱系 | 弱 | 强(原生进程树) |
| 可观测 | 告警为主 | 全量事件 + 策略 |
核心区别:Falco 以「告警」为中心,Tetragon 以「可观测 + 执行」为中心。
4.2 TracingPolicy 示例
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: block-write-etc
spec:
kprobes:
- call: "fd_install"
syscall: false
args:
- index: 1
type: "file"
selectors:
- matchActions:
- action: Sigkill
matchArgs:
- index: 1
operator: "Prefix"
values: ["/etc/"]
matchActions 支持 Post(仅记录)、Sigkill(杀进程)、Override(改写返回值)、FollowFD 等。阻断能力是 Tetragon 相对 Falco 最大的差异点。
4.3 进程谱系的价值
Tetragon 会记录每个进程的父子关系与容器归属,因此可以回答「这个可疑进程是从哪个入口进程派生出来的」:
kubectl exec -n kube-system ds/tetragon -- \
tetra getevents -o compact --processes nginx
排查反弹 Shell 时,这条谱系链比单条告警有用得多。
5. 系统调用与行为基线
5.1 为什么要基线
静态规则只能覆盖已知攻击模式,行为基线能发现「这个 Pod 第一次访问了它从未访问过的路径」这类异常。基线通常按「工作负载 + 进程」维度建立。
基线维度:namespace / workload / image / 进程名
基线内容:常见系统调用集合、文件路径集合、出网目标集合
异常判定:出现基线外的调用或路径,且频次超阈值
5.2 建立基线的流程
1. 学习期(1~2 周):只记录不告警,采集全量行为
2. 收敛期:剔除低频噪声(如健康检查、日志轮转)
3. 固化期:生成基线规则,进入告警模式
4. 维护期:镜像或版本变更后重新学习对应工作负载
5.3 用 seccomp 做强制基线
基线不仅能告警,还能变成强制约束。用运行时观测到的系统调用集合生成 seccomp profile,是「以观测驱动加固」的典型做法:先采集工作负载实际调用的系统调用集合,再生成 securityContext.seccompProfile(type: Localhost + localhostProfile)挂载到 Pod 上,把基线外的调用直接在内核层拒绝。
6. 容器逃逸与提权检测
6.1 逃逸路径与检测信号
| 逃逸路径 | 检测信号 |
|---|---|
| privileged 容器 | 创建时检测 privileged 标志 |
| hostPath 挂载宿主机根 | 挂载路径含 /、/etc、/var/run |
| 挂载 docker.sock | openat /var/run/docker.sock |
| CAP_SYS_ADMIN 滥用 | mount、pivot_root 系统调用 |
| /proc 逃逸 | 访问 /proc/1/root 或 /proc/*/mem |
| 内核漏洞利用 | 异常的内核函数调用序列 |
6.2 关键检测规则
- rule: 容器内挂载文件系统
desc: 非特权容器不应调用 mount
condition: >
evt.type in (mount, umount2) and container
and not proc.name in (allowed_mount_helpers)
output: 容器内挂载 (container=%container.name cmd=%proc.cmdline)
priority: CRITICAL
- rule: 访问宿主机进程内存
desc: 读取 /proc/<pid>/mem 是典型的逃逸探测
condition: open_read and container and fd.name glob "/proc/*/mem"
output: 进程内存读取 (file=%fd.name cmd=%proc.cmdline)
priority: CRITICAL
6.3 提权检测要点
SUID 二进制执行:proc.name in (sudo, su) 或发现 SUID 位文件被调用
Capability 提升:capset 系统调用携带非预期 capability
命名空间操作:setns、unshare 在业务容器中出现
内核模块加载:init_module / finit_module
注意:这些规则在 CI/CD 构建机、调试容器中会大量误报,必须用命名空间或镜像白名单收敛。
7. 告警降噪与响应编排
7.1 噪声是运行时安全的最大杀手
一个未调优的 Falco 在中等规模集群每天可产生数十万条告警,最终结果是「没人看」。降噪手段按优先级:
| 手段 | 效果 |
|---|---|
| 白名单(进程 + 镜像 + 命名空间) | 消除已知良性行为 |
| 条件收窄(加 container、加路径前缀) | 减少宽泛匹配 |
| 聚合去重(相同规则 + 相同容器) | 把风暴压成一条 |
| 优先级过滤(只发 CRITICAL) | 控制告警量 |
| 基线比对(只报基线外行为) | 从源头减少 |
7.2 响应编排
CRITICAL -> 立即通知(PagerDuty / 企业微信)+ 自动取证(保存进程树、网络连接、文件哈希)
WARNING -> 聚合到日报 + 关联分析
NOTICE -> 仅入库,供事后审计
自动取证比自动阻断更值得优先建设:阻断容易误伤,而取证数据一旦丢失就无法追溯。Tetragon 的 Sigkill 只应在验证充分的规则上启用。
7.3 与响应平台联动
启用 Falco 的 gRPC 输出(grpc.enabled: true,监听本地 unix socket),由响应服务消费事件流,按规则决定:隔离 Pod(打 taint)、吊销 Token、拉取现场快照、通知值班。
8. 性能开销与内核兼容性
8.1 开销来源与量级
| 组件 | 典型 CPU 开销 | 说明 |
|---|---|---|
| eBPF 采集(过滤前) | 1%~3% / 节点 | 与系统调用频率相关 |
| 规则引擎匹配 | 1%~5% | 规则数量与复杂度相关 |
| 用户态事件处理 | 1%~3% | 与告警量相关 |
| 全量事件导出 | 10%+ | 仅调试时开启 |
关键结论:开销主要来自「事件量 × 规则复杂度」,而非 eBPF 本身。在内核侧做过滤(按 cgroup、按系统调用类型)能把开销压到最低。
8.2 降低开销的做法
□ 只挂载需要的钩子,不采集全量系统调用
□ 用 cgroup 过滤,跳过 kube-system 等已知命名空间
□ 关闭调试级事件导出,只保留匹配规则的事件
□ 限制 ring buffer 大小,避免内存膨胀
□ 对高频事件(如 read/write)设置采样
8.3 内核兼容性
要求一:内核版本 >= 4.14(推荐 5.4+)
要求二:BTF 支持(/sys/kernel/btf/vmlinux 存在)以便 CO-RE
要求三:开启 CONFIG_BPF、CONFIG_BPF_SYSCALL、CONFIG_DEBUG_INFO_BTF
# 检查 BTF 是否可用
ls -l /sys/kernel/btf/vmlinux
# 检查内核版本
uname -r
无 BTF 的老内核需要为目标内核编译专用探针,跨节点内核版本不一致时优先选择支持 CO-RE 的发行版(如 Ubuntu 20.04+、RHEL 8.4+)。
9. 生产最佳实践
9.1 落地 Checklist
□ Agent 以 DaemonSet 部署,独立命名空间 + 最小 RBAC
□ 先观察后告警,学习期不少于一周
□ 规则按优先级分级路由,CRITICAL 立即通知
□ 建立进程 + 镜像 + 命名空间三层白名单
□ 采集进程谱系,为取证保留上下文
□ 基线驱动 seccomp profile 加固
□ 监控 Agent 自身资源占用与事件丢弃计数
□ 规则纳入 Git,变更走评审
9.2 常见坑与对策
| 坑 | 现象 | 对策 |
|---|---|---|
| 规则未调优 | 每天数十万告警无人看 | 白名单 + 聚合 + 分级 |
| Agent 无特权 | 采集不到宿主机进程 | hostPID + 必要 capability |
| 内核无 BTF | Agent 启动失败 | 升级内核或用预编译探针 |
| 全量事件导出 | CPU 飙升 | 只导出匹配事件 |
| 只告警不取证 | 事后无法追溯 | 事件触发时保存现场 |
| 阻断规则过激 | 误杀生产进程 | 先 Post 观察,再 Sigkill |
| 忽略 Agent 自身安全 | Agent 成为攻击面 | 镜像签名 + 最小权限 |
小结
运行时安全的本质是在攻击链的中后段建立可观测性与可执行性:eBPF 提供了低开销、覆盖内核全栈的采集能力,Falco 把它变成可读的规则与告警,Tetragon 进一步把它变成可执行策略与进程谱系。落地的最大挑战从来不是技术选型,而是噪声治理——一个未调优的规则集会在两周内被彻底忽略。因此建议:先以观察模式跑满一个学习周期,用白名单与聚合把告警压到值班可承受的量级,再对少数高置信度规则开启阻断。记住:没有基线的运行时安全只是更吵的日志。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。