原文:
zh.annas-archive.org/md5/901f31c65e11db9dd25e51adeba7505a
译者:飞龙
前言
现代计算堆栈的出现彻底改变了我们对安全性的看法。在旧的数据中心时代,安全从业者把软件应用程序看作是中世纪城堡:保护它们需要建造高墙和小而精密的入口。现代基于云的软件看起来更像是一个繁华的现代城市:人们在其内部自由移动,并跨越其界限消费和提供服务,买卖、建设和修复事物。
正如今天的城市规划师所知道的那样,仅有大墙和守卫的入口是不足以保护一个城市的。更好的方法包括广泛而细化的可见性:例如一个安全摄像头网络,以及能够查看它们的录像并实时响应它们捕捉到的任何威胁。
本书讲述的是现代应用程序的安全性,使用业界广泛接受的开源工具,被称为云原生堆栈的“安全摄像头”:Falco。Falco 是一个云原生运行时安全项目,旨在通过实时检测意外行为、入侵和数据窃取来保护在云中运行的软件。它是 Kubernetes 和云基础设施的事实上的威胁检测引擎,被无数用户部署在从单机测试环境到全球最大的计算环境之一的各种场景中。我们将教会你如何通过检测工作负载和它们所操作的云基础设施中的威胁和配置错误来保护应用程序在运行时的安全性。
本书有一个非常实际的目标:为您提供在任何规模的环境中成功部署运行时安全性所需的知识,使用 Falco。当您读完本书时,您将对 Falco 的工作原理有扎实的理解:您将能够在任何环境中安装它,调整其性能,根据您的需求自定义它,收集和解释其数据,甚至扩展它。
本书适合谁?
我们主要为希望在其现代计算环境中生产中实施运行时安全性和威胁检测的安全操作员和架构师编写了本书。然而,我们设计本书,即使是对领域中有限或没有经验的读者也很友好。因此,我们只要求您熟悉最重要的云计算服务、容器和 Kubernetes 即可。
我们还将涵盖像规模部署、优化和规则编写等更高级的主题,即使是专家用户也会觉得有用。因此,即使您熟悉运行时安全性,或者已经在使用 Falco,本书也将帮助您提升水平。书的后半部分需要了解像 Go 这样的编程语言的基础知识。希望扩展或自定义 Falco 的开发人员将在这里找到很大的价值。最后,我们将本书的最后一章针对那些考虑成为 Falco 贡献者的人——我们希望能激励您加入他们!
概述
本书分为四个部分,按照递增复杂性的顺序组织,每个部分都建立在前一个部分的基础上。为了帮助你了解,让我们来看看每个部分的内容。
第一部分:基础知识
第一部分介绍了 Falco 的定义及其功能。在这里,我们将教你 Falco 背后的基本概念,并指导你完成首次本地部署:
-
第一章,“介绍 Falco”,概述了 Falco 的定义,包括其功能的高级视图以及各个组件的简介描述。本章包括了 Falco 的简要历史以及启发它的工具的介绍。
-
第二章,“在本地机器上开始使用 Falco”,指导你如何在本地 Linux 系统上安装单个 Falco 实例的过程。本章包括如何运行 Falco 并生成你的第一个通知输出的说明。
第二部分:Falco 的架构
第二部分将教你了解 Falco 架构及其内部工作的复杂性:
-
第三章,“理解 Falco 的架构”,深入探讨了 Falco 传感器的细节、数据收集过程及其涉及的组件。本章带给你的架构理解将成为本书其余部分的基础。
-
第四章,“数据源”,介绍了 Falco 中可以使用的两种主要数据源:系统调用和插件。我们解释了这些数据源产生的数据内容,数据如何收集以及 Falco 收集栈与替代方法的比较。
-
第五章,“数据增强”,涵盖了 Falco 用于增强其收集的数据的技术。增强包括向收集的数据添加上下文信息的层级;例如,容器 ID、Kubernetes 标签或云提供商标签。本章解释了如何配置 Falco 以收集增强元数据以及如何自定义它以添加你自己的元数据。
-
第六章,“字段和过滤器”,涵盖了 Falco 中最重要的概念之一——过滤引擎及其基础字段。本章结构化地介绍了语言语法(包括操作符)和字段作为参考。
-
第七章,“Falco 规则”,介绍了规则及其语法,包括列表和宏等结构,这些在定制 Falco 时会经常用到。
-
第八章,“输出框架”,描述了 Falco 用于将通知传递到输出通道的机制以及 Falco 可用的通道,并教你如何配置和使用它们。
第三部分:在生产环境中运行 Falco
第 III 部分 是严肃的 Falco 用户的参考手册。本书的这一部分将教会您在任何环境中部署、配置、运行和调优 Falco 的所有知识:
-
第九章,“安装 Falco”,介绍了在生产环境中安装 Falco 的方法,提供了详细说明。
-
第十章,“配置和运行 Falco”,详细介绍了 Falco 的配置系统如何运作。本章将帮助您理解和使用 Falco 的设置,包括命令行选项、环境变量、配置文件和规则文件。
-
第十一章,“使用 Falco 进行云安全”,提供了云安全的概述,然后深入讲解了使用 Falco 的 CloudTrail 插件进行 AWS 威胁检测的具体细节。采用实用的方法,并提供了在您的环境中使用 Falco 设置云安全的清晰完整说明。
-
第十二章,“消费 Falco 事件”,关注于您如何处理 Falco 的检测结果。涵盖了帮助您处理 Falco 输出的工具,如 falco-explorer 和 Falcosidekick,并帮助您了解哪些 Falco 事件对观察和分析是有用的,以及如何处理它们。
第四部分:扩展 Falco
第 IV 部分 是开发者的参考,涵盖了扩展 Falco 的方法:
-
第十三章,“编写 Falco 规则”,讲述了定制和扩展 Falco 检测的方法。您将学习如何编写新规则以及调整现有规则以满足您的需求。除了规则编写的基础知识外,本章还涵盖了噪声减少、性能优化和标记等高级主题。
-
第十四章,“Falco 开发”,介绍了如何与 Falco 的源代码一起工作。首先概述了代码库,然后深入探讨了两种扩展 Falco 的重要方法:使用 gRPC API 和插件框架。本章包含了几个示例,供您作为编程探险的基础。
-
第十五章,“如何贡献”,讨论了 Falco 社区并展示了如何为其做出贡献。如果您在读完全书后和我们一样对 Falco 深感兴奋,这是理想的阅读材料!
本书使用的约定
本书中使用以下排版约定:
斜体
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
等宽字体
用于命令行输入和程序清单,以及段落内用于引用命令和程序元素,如变量或函数名称、数据类型和环境变量。
**常量宽度粗体**
显示用户应该按字面意义输入的命令或其他文本。在程序清单中偶尔用于突出感兴趣的文本。
*常数宽度斜体*
显示应由用户提供的值或由上下文确定的值替换的文本。
提示
此元素表示提示或建议。
注意
此元素表示一般注释。
警告
此元素指示警告或注意事项。
使用代码示例
来自第十四章的代码示例可在https://oreil.ly/practical-cloud-native-security-falco-code下载。
如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com。
本书旨在帮助您完成工作。一般情况下,如果本书提供了示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分,否则您无需征得我们的许可。例如,编写一个使用本书中几个代码片段的程序不需要许可。出售或分发包含 O’Reilly 书籍示例的 CD-ROM 需要许可。引用本书并引用示例代码回答问题不需要许可。将本书中大量示例代码整合到您产品的文档中需要许可。
我们感谢您的提及,但并不要求署名。通常包括标题、作者、出版商和 ISBN。例如:“Practical Cloud Native Security with Falco,由 Loris Degioanni 和 Leonardo Grasso 编写(O’Reilly)。版权所有 2022 年 O’Reilly Media, Inc.,978-1-098-11857-0。”
如果您认为您对代码示例的使用超出了合理使用范围或上述许可,请随时联系我们:permissions@oreilly.com。
O’Reilly 在线学习
超过 40 年来,O’Reilly Media 为提供技术和商业培训、知识和见解,帮助公司取得成功。
我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专长。O’Reilly 的在线学习平台为您提供按需访问的实时培训课程、深入的学习路径、交互式编码环境,以及来自 O’Reilly 和其他 200 多个出版商的广泛的文本和视频。更多信息,请访问http://oreilly.com。
如何联系我们
请将有关本书的评论和问题发送给出版商:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-998-9938(美国或加拿大)
-
707-829-0515(国际或本地)
-
707-829-0104(传真)
我们为本书准备了一个网页,列出勘误、示例和任何额外信息。您可以访问https://oreil.ly/practical-cloud-native-security-falco。
发送电子邮件至bookquestions@oreilly.com,以评论或提出关于本书的技术问题。
获取有关我们的书籍和课程的新闻和信息,请访问https://oreilly.com。
在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media
在 Twitter 上关注我们:https://twitter.com/oreillymedia
在 YouTube 上关注我们:https://www.youtube.com/oreillymedia
致谢
首先,我们要由衷感谢 Falco 社区:那些投入无数小时运营和发展项目的维护者们,他们怀着无比的激情;那些大小贡献者,每天让 Falco 变得更好;那些给予 Falco 机会并提供宝贵反馈的采纳者和倡导者。毫无疑问,Falco 是你们的爱和才华的结晶,如果这本书能展示你们的不凡工作,将是我们的荣幸。
还要感谢 Cloud Native Computing Foundation,为 Falco 提供一个良好的家园并支持其成长。
我们还要感谢在写作过程中帮助和支持我们的人们:特别是我们的项目经理 Tammy Yue 和我们的 O’Reilly 编辑 Sarah Grey。你们不仅非常专业和乐于助人,而且极为亲切、建设性和耐心。能够与你们合作是一种真正的愉悦。
最后,这本书得益于我们共同工作的公司 Sysdig 的支持才得以问世。我们真心感激能够为一个不仅理解而且积极支持开源,并且认同我们对未来安全性的开放信念的组织工作。
Leonardo
有一天,我和 Loris 正在交谈时,他提议我们一起写一本书。所以在这里,我首先要感谢他。和他一起开展这个想法是我人生中最具挑战性但同时也最有趣的事情之一。我们会再做一次吗?作为第一次作者,写这本书对我来说是一次令人难以置信的新冒险,没有家人的帮助和爱,这是不可能的。因此,我要感谢我闪耀而心爱的 Ada,她一直支持我,并且赐给我我们的小米开罗。我也要感谢我们的小男孩,在他的妈妈完成写作后立刻出生。还有我的玛~(读作“玛蒂尔德”,我们的小猫在我写作时陪伴我轻声呼噜),他们在这段旅程中以耐心和欢乐陪伴着我。
最后但同样重要的是,我还要由衷感谢我的父母、姐妹和叔伯们。他们一直相信我,支持我,并在我需要时帮助我。没有他们,我无法走到今天。
Loris
我要感谢我生命中的爱人 Stacey,我的妻子,感谢她对我所做的一切的耐心支持。感谢你在这本书的制作过程中没有让我挨饿、淹死或者遭受其他伤害。
我还要感谢我的三个孩子,Josephine、Vincenzo 和 August,你们给我生活的每一分钟带来了幸福,包括参与这本出版物的时间。你们频繁的问题和打扰使得写作本书更具挑战性,但也更加愉快。我期待着你们长大后将要出版的书籍。
我要感谢我的父母,在我事业起步之前和之后一直支持我。如果没有你们多年前种下并用爱和慷慨浇灌的种子,今天我不会在这里撰写这篇序言。
这本书如果没有我的合著者 Leo,将不可能问世。我们俩花了大量时间一起完成这项工作,每一分钟都是愉快、富有建设性和乐趣的。Leo,我期待着未来与你一起投入更多有趣和雄心勃勃的项目中。
第一部分:基础部分
第一章:介绍 Falco
这本书的第一章的目标是解释什么是 Falco。别担心,我们会循序渐进!我们首先将看看 Falco 的功能,包括其功能的高层视图和每个组件的简介描述。我们将探讨启发 Falco 并至今指导其发展的设计原则。然后,我们将讨论你可以用 Falco 做什么,以及它所不涵盖的领域,以及你可以用其他工具更好地完成的任务。最后,我们将提供一些历史背景来全面看待这些事情。
Falco 简介
在最高层面上,Falco 非常直接:通过在分布式基础设施中安装多个sensors来部署它。每个传感器收集数据(从本地机器或通过某些 API 通信),对其运行一组规则,并在发生问题时通知你。图 1-1 展示了其工作原理的简化图表。
图 1-1 Falco 的高层架构
你可以把 Falco 想象成你基础设施的安全摄像头网络:你在关键位置放置传感器,它们观察发生的情况,并在检测到有害行为时通知你。对于 Falco 来说,不良行为由社区为你创建和维护的一组规则定义,你可以根据需要自定义或扩展。由你的 Falco 传感器群生成的警报理论上可以留在本地机器上,但实际上它们通常会导出到一个集中的收集器。为了集中警报收集,你可以使用通用安全信息与事件管理(SIEM)工具或类似 Falcosidekick 的专业工具。(我们将在第十二章中详细介绍警报收集。)
现在让我们深入了解 Falco 架构,探索其主要组件,从传感器开始。
传感器
图 1-2 展示了 Falco 传感器的工作原理。
图 1-2 Falco 传感器架构
传感器包括一个引擎,具有两个输入:数据源和一组规则。传感器对来自数据源的每个事件应用规则。当规则匹配事件时,将生成输出消息。非常直接明了,对吧?
数据源
每个传感器能够从多个来源收集输入数据。最初,Falco 被设计为专门处理系统调用,迄今仍然是其最重要的数据源之一。我们将在第三章和第四章中详细介绍系统调用,但现在您可以将其视为运行程序用于与外部世界交互的方式。打开或关闭文件,建立或接收网络连接,从磁盘或网络读取和写入数据,执行命令,使用管道或其他类型的进程间通信与其他进程通信等都是系统调用的使用示例。
Falco 通过在 Linux 操作系统(OS)的内核中进行仪器化来收集系统调用。它可以通过两种不同的方式实现:部署内核模块(即可安装在操作系统内核中以扩展内核功能的可执行代码片段)或使用称为 eBPF 的技术,该技术允许在 OS 内部安全执行操作。我们将在第四章中广泛讨论内核模块和 eBPF。
利用这些数据,Falco 可以极大地增强对基础设施中发生的一切事情的可见性。以下是 Falco 可以为您检测到的一些示例:
-
特权升级
-
访问敏感数据
-
所有权和模式更改
-
意外的网络连接或套接字变化
-
未经授权的程序执行
-
数据外泄
-
合规性违规
Falco 还可以扩展到除系统调用之外的其他数据源(我们将在整本书中为您展示示例)。例如,Falco 可以实时监视您的云日志,并在云基础设施发生问题时通知您。以下是它可以为您检测到的一些更多示例:
-
当用户未经多因素身份验证登录时
-
当云服务配置被修改时
-
当有人访问 Amazon Web Services(AWS)S3 存储桶中的一个或多个敏感文件时
Falco 经常添加新的数据源,因此我们建议查看网站和Slack 频道以了解最新信息。
规则
规则告诉 Falco 引擎如何处理来自源的数据。它们允许用户以紧凑且易读的格式定义策略。Falco 预装了一套全面的规则,涵盖主机、容器、Kubernetes 和云安全,您可以轻松创建自己的规则来自定义它。我们将在第七章和第十三章中详细讨论规则;在阅读完本书后,您将成为规则的专家。以下是一个示例来激发您的兴趣:
- rule: shell_in_container
desc: shell opened inside a container
condition: spawned_process and container.id != host and proc.name = bash
output: shell in a container (user=%user.name container_id=%container.id)
Source: syscall
priority: WARNING
此规则检测容器内启动 bash shell 的情况,这在不可变的基于容器的基础设施中通常不是一件好事。规则的核心条目是条件,告诉 Falco 要查看什么,以及输出,即条件触发时 Falco 将告诉您的内容。正如您所见,条件和输出都作用于字段,这是 Falco 的核心概念之一。条件是一个布尔表达式,它结合了对字段与值的检查(本质上是一个过滤器)。输出是文本和字段名称的组合,其值将在通知中打印出来。其语法类似于编程语言中的print
语句。
这是否让你想起像 tcpdump 或 Wireshark 这样的网络工具?很有眼光:它们是 Falco 的重要灵感来源。
数据丰富
丰富的数据源和灵活的规则引擎帮助使 Falco 成为一个强大的运行时安全工具。此外,来自各种提供者的元数据丰富了其检测能力。
当 Falco 告诉您发生了某事,例如系统文件已被修改,通常您需要更多信息来理解问题的原因和范围。是哪个进程做的?这发生在容器中吗?如果是,容器和镜像的名称是什么?发生这种情况的服务/命名空间是什么?这是在生产环境还是开发环境中进行的更改?这是由 root 用户做出的改变吗?
Falco 的数据丰富引擎通过构建环境状态来帮助回答所有这些问题,包括运行中的进程和线程、它们打开的文件、它们所在的容器和 Kubernetes 对象等。所有这些状态对 Falco 的规则和输出都是可访问的。例如,您可以轻松地将规则范围限定为仅在生产环境或特定服务中触发。
输出通道
每当触发规则时,对应的引擎会发出输出通知。在最简单的配置中,引擎将通知写入标准输出(正如你可以想象的那样,通常不是很有用)。幸运的是,Falco 提供了复杂的输出路由方式,可以将输出定向到多个地方,包括日志收集工具、像 S3 这样的云存储服务,以及像 Slack 和电子邮件这样的通信工具。其生态系统包括一个名为 Falcosidekick 的精彩项目,专门设计用于将 Falco 连接到世界上,并使输出收集变得轻松(详见第十二章了解更多信息)。
容器和更多
Falco 专为现代云原生应用程序设计,因此在容器、Kubernetes 和云端具有出色的开箱即用支持。由于本书是关于云原生安全的,我们将主要关注这一点,但请记住,Falco 并不局限于在云中运行的容器和 Kubernetes。您绝对可以将其用作主机安全工具,它的许多预加载规则可以帮助您保护 Linux 服务器群。Falco 还对网络检测有很好的支持,允许您检查连接、IP 地址、端口、客户端和服务器的活动,并在它们展示不良或意外/非典型行为时收到警报。
Falco 的设计原则
现在您了解了 Falco 的功能后,让我们谈谈它为什么会成为现在这个样子。当您开发一个非常复杂的软件时,重要的是专注于正确的使用案例并优先考虑最重要的目标。有时候这意味着要接受一些权衡。Falco 也不例外。它的开发受到一组核心原则的指导。在本节中,我们将探讨为什么选择了这些原则以及它们如何影响 Falco 的架构和功能集。了解这些原则将帮助您判断 Falco 是否适合您的使用案例,并帮助您充分利用它。
专为运行时优化
Falco 引擎旨在在您的服务和应用程序运行时检测威胁。当它检测到不良行为时,Falco 应该立即(最多几秒钟内)向您发出警报,以便您能够立即获得信息并做出反应,而不是在几分钟或几小时后才做出反应。
这一设计原则体现在三个重要的架构选择中。首先,Falco 引擎被设计为流式引擎,能够在数据到达时快速处理数据,而不是存储数据然后再处理。其次,它被设计为独立评估每个事件,而不是根据事件序列生成警报;这意味着即使可以,也不把相关事件作为主要目标,事实上是不鼓励的。第三,Falco 尽可能在数据源附近评估规则。如果可能的话,在处理数据之前避免传输信息,并倾向于在端点部署更丰富的引擎。
适用于生产环境
您应该能够在任何环境中部署 Falco,包括生产环境,稳定性和低开销至关重要。它不应该使您的应用崩溃,并且应该尽可能地减少对其性能的影响。
这一设计原则影响数据收集架构,特别是当 Falco 运行在具有多个进程或容器的端点时。Falco 的驱动程序(内核模块和 eBPF 探针)经历了多次迭代和多年测试,以保证其性能和稳定性。通过接入操作系统内核来收集数据,而不是对被监控的进程/容器进行仪表化,确保您的应用程序不会因 Falco 中的错误而崩溃。
Falco 引擎采用 C++ 编写,并采用多种手段来降低资源消耗。例如,它避免处理读取或写入磁盘或网络数据的系统调用。在某些方面,这是一种限制,因为它阻止用户创建检查有效负载内容的规则,但这也确保 CPU 和内存消耗保持较低水平,这更为重要。
无意图仪表化
Falco 设计用于观察应用程序行为,而无需用户重新编译应用程序、安装库或重建带监控钩子的容器。在现代容器化环境中,这非常重要,因为对每个组件应用更改将需要不切实际的工作量。它还确保 Falco 能够看到每个进程和容器,无论其来源、由谁运行或存在多长时间。
优化以在边缘运行
与其他策略引擎(例如 OPA)相比,Falco 明确设计为具有分布式、多传感器架构。其传感器设计轻量、高效且可移植,并能在各种环境中运行。它可以部署在物理主机、虚拟机或容器中。Falco 二进制文件适用于多个平台,包括 ARM。
避免移动和存储大量数据
大多数当前市场上的威胁检测产品基于向集中式 SIEM 工具发送大量事件,然后在收集的数据上执行分析。Falco 围绕着一个非常不同的原则设计:尽可能靠近端点执行检测,并只向集中式收集器发送警报。这种方法导致解决方案在执行复杂分析方面略显能力不足,但操作简单、成本效益更高,并且在水平方向上具有很好的扩展性。
可扩展
谈到规模,Falco 的另一个重要设计目标是应该能够扩展以支持全球最大的基础设施。如果你可以运行它,Falco 应该能够保护它。正如我们刚才描述的,保持有限的状态和避免集中存储是这一目标的重要组成部分。边缘计算也是一个重要因素,因为分发规则评估是实现 Falco 工具真正水平扩展的唯一方法。
可扩展性的另一个关键部分是端点仪表化。Falco 的数据收集堆栈不使用诸如旁路、库链接或进程仪表化等技术。原因是所有这些技术的资源利用率随要监视的容器、库或进程数量增加而增长。繁忙的机器有许多容器、库和进程——对于这些技术来说太多了,但它们只有一个操作系统内核。在内核中捕获系统调用意味着您只需要一个 Falco 传感器每台机器,无论这台机器有多大活动量。这使得在具有大量活动的大型主机上运行 Falco 成为可能。
真实的
使用系统调用作为数据源的另一个好处?系统调用永远不会撒谎。Falco 难以逃避,因为它用于收集数据的机制非常难以禁用或规避。如果您试图逃避或规避它,您将留下 Falco 可以捕获的痕迹。
坚固的默认设置,丰富的可扩展性
另一个关键的设计目标是尽量减少从 Falco 中提取价值所需的时间。您只需安装它即可完成这一目标;除非您有高级需求,否则不需要进行定制。
尽管如此,每当确实需要定制时,Falco 提供了灵活性。例如,您可以通过丰富而表达性强的语法创建新规则,开发和部署扩展检测范围的新数据源,并将 Falco 集成到您想要的通知和事件收集工具中。
简单
简单性是支持 Falco 的最后一个设计选择,但也是最重要的选择之一。Falco 规则语法设计为紧凑、易读和易学习。在可能的情况下,Falco 规则条件应该适合一行。任何人,不仅仅是专家,都应该能够编写新规则或修改现有规则。如果这降低了语法的表达能力也没关系:Falco 的业务是提供高效的安全规则引擎,而不是完整的领域特定语言。对于那些,有更好的工具。
简单性还体现在扩展 Falco 以警报新数据源并与新云服务或容器类型集成的过程中,这只需编写任何语言的插件,包括 Go、C 和 C++。Falco 可以轻松加载这些插件,您可以使用它们来支持新的数据源或新的字段以用于规则中。
使用 Falco 可以做什么
Falco 在运行时和实时检测威胁、入侵和数据盗窃方面表现突出。它在传统基础设施上运行良好,但在支持容器、Kubernetes 和云基础设施方面表现出色。它保护工作负载(进程、容器、服务)和基础设施(主机、虚拟机、网络、云基础设施和服务)。它被设计为轻量级、高效和可扩展,适用于开发和生产环境。它可以检测多类威胁,但如果您需要更多功能,可以自定义它。它还有一个支持并不断增强的活跃社区。
Falco 的局限性
没有单一工具能解决所有问题。了解 Falco 不能做什么与知道何时使用它同样重要。与任何工具一样,都有权衡。首先,Falco 不是通用的策略语言:它不提供完整编程语言的表达能力,也不能在不同引擎间执行相关性。相反,其规则引擎设计为在您基础设施的多个地方高频应用相对无状态的规则。如果您寻找强大的集中式策略语言,我们建议您查看OPA。
其次,Falco 并非设计用来将其收集的数据存储在集中式存储库中,以便您可以对其进行分析。规则验证在端点执行,只有警报会发送到集中位置。如果您专注于高级分析和大数据查询,我们建议您使用市场上提供的众多日志收集工具之一。
最后,出于效率考虑,Falco 不会检查网络有效载荷。因此,它不适合实施第 7 层(L7)安全策略。传统的基于网络的入侵检测系统(IDS)或 L7 防火墙更适合这种用例。
背景和历史
本书的作者们是 Falco 历史的一部分,这一最后部分展示了我们的记忆和观点。如果您只对操作 Falco 感兴趣,可以跳过本章的其余部分。但是,我们认为了解 Falco 的起源可以为其架构提供有用的背景,最终帮助您更好地使用它。此外,这也是一个有趣的故事!
网络数据包:BPF、libpcap、tcpdump 和 Wireshark
在 1990 年代末互联网高潮时期,计算机网络变得极为流行。对于观察、故障排除和保护它们的需求也同样增长。然而,许多运营商当时无法承担那时所有都是商业化且非常昂贵的网络可见性工具。因此,很多人在黑暗中摸索。
很快,全球各地的团队开始致力于解决这一问题。有些解决方案涉及扩展现有操作系统,以增加数据包捕获功能:换句话说,将现成的计算机工作站转换为可以放置在网络上并收集其他工作站发送或接收的所有数据包的设备。伯克利数据包过滤器(BPF)是这样一种解决方案,由加州大学伯克利分校的 Steven McCanne 和 Van Jacobson 开发,旨在扩展 BSD 操作系统内核。如果您使用 Linux,您可能熟悉 eBPF,这是一个可以安全地在 Linux 内核中执行任意代码的虚拟机(“e”代表“扩展”)。eBPF 是 Linux 内核中最热门的现代功能之一。经过多年的改进,它已经发展成为一种非常强大和灵活的技术,但它最初只是 BSD Unix 的一个可编程数据包捕获和过滤模块。
BPF 随附一个名为libpcap的库,任何程序都可以使用它来捕获原始网络数据包。其可用性引发了大量的网络和安全工具。基于libpcap的第一个工具是一个命令行网络分析器,名为 tcpdump,它仍然是几乎所有 Unix 发行版的一部分。然而,在 1998 年,推出了一个基于 GUI 的开源协议分析器,名为 Ethereal(2006 年更名为 Wireshark)。它成为了行业标准的数据包分析工具,至今仍然如此。
tcpdump、Wireshark 和许多其他流行的网络工具共同之处在于能够访问丰富、准确和可信的数据源,并且可以以非侵入式的方式进行收集:即原始网络数据包。在继续阅读时,请牢记这个概念!
Snort 与基于数据包的运行时安全
类似 tcpdump 和 Wireshark 的内省工具是 BPF 数据包捕获堆栈的自然早期应用。然而,人们很快开始在数据包的用例上展开创意。例如,1998 年,Martin Roesch 发布了一个开源网络入侵检测工具,名为 Snort。Snort 是一个规则引擎,处理从网络捕获的数据包。它拥有一套大量的规则,可以通过检查数据包、它们包含的协议和它们携带的有效负载来检测威胁和不受欢迎的活动。它启发了类似工具如 Suricata 和 Zeek 的创建。
像 Snort 这样的工具之所以强大,是因为它们能够在应用程序运行时验证网络和应用程序的安全性。这一点很重要,因为它提供了实时保护,而对运行时行为的关注使得可以基于尚未公开的漏洞检测到威胁。
网络数据包危机
你刚刚看到了什么使网络数据包成为可见性、安全性和故障排除的数据源。基于它们的应用推动了几个成功的行业。然而,出现了趋势,侵蚀了数据包作为真相来源的有用性:
-
在云等环境中,收集数据包变得越来越复杂,特别是在访问路由器和网络基础设施受限的情况下。
-
加密和网络虚拟化使提取有价值信息更具挑战性。
-
容器和类似 Kubernetes 的编排器的兴起使基础设施更具弹性。与此同时,可靠地收集网络数据变得更加复杂。
这些问题在 2010 年代初期变得明显,随着云计算和容器的流行。再次出现了一个令人兴奋的新生态,但没有人确切知道如何进行故障排除和安全保护。
以系统调用作为数据源:sysdig
这就是你的作者介入的地方。我们发布了一个名为 sysdig 的开源工具,我们受到一系列问题的启发而建立它:如何最好地为现代云原生应用程序提供可见性?我们能否将基于数据包捕获的工作流应用于这个新世界?最佳的数据源是什么?
sysdig 最初专注于从操作系统内核收集系统调用。系统调用是丰富的数据源,甚至比数据包更丰富,因为它们不仅关注网络数据:它们包括文件 I/O、命令执行、进程间通信等等。它们是云原生环境的比数据包更好的数据源,因为它们可以从内核中收集,适用于容器和云实例。此外,收集它们简单、高效且最小化侵入。
sysdig 最初由三个独立的组件组成。
-
内核捕获探针(提供内核模块和 eBPF 两种版本)
-
一组库以便于开发捕获程序
-
具有解码和过滤功能的命令行工具
换句话说,它是将 BPF 堆栈移植到系统调用。sysdig 的设计旨在支持最流行的网络数据包工作流程:跟踪文件、简单过滤、可脚本化等等。从一开始,我们还包括了与 Kubernetes 和其他编排器的本地集成,旨在使它们在现代环境中有用。sysdig 立即在社区中非常流行,验证了技术方法的有效性。
Falco
那么下一个逻辑步骤会是什么呢?你猜对了:一个类似于 Snort 的系统调用工具!我们认为,在 sysdig 库之上实现一个灵活的规则引擎,将是一种可靠和高效地检测现代应用程序异常行为和入侵的强大工具——本质上是应用于系统调用的 Snort 方法,设计用于在云中工作。
因此,Falco 就这样诞生了。第一个(相当简单的)版本于 2016 年底发布,包括大部分重要组件,如规则引擎。Falco 的规则引擎受到 Snort 的启发,但设计用于处理更丰富和更通用的数据集,并插入到 sysdig 库中。它提供了一组相对较小但有用的规则。这个最初版本的 Falco 主要是单机工具,没有分布式部署的能力。我们将其作为开源发布,因为我们看到社区对它有广泛需求,并且当然也是因为我们热爱开源!
扩展到 Kubernetes
随着工具的发展和社区的接受,Falco 的开发人员将其扩展到新的适用领域。例如,在 2018 年,我们添加了 Kubernetes 审计日志作为数据源。这一功能允许 Falco 访问由审计日志产生的事件流,并在事件发生时检测配置错误和威胁。
创建这一功能需要我们改进引擎,这使得 Falco 更加灵活,更适合更广泛的用例。
加入云原生计算基金会
2018 年,Sysdig 将 Falco 贡献给了云原生计算基金会(CNCF)作为一个沙箱项目。CNCF 是现代云计算基础的许多重要项目的归属地,例如 Kubernetes、Prometheus、Envoy 和 OPA。对我们团队而言,将 Falco 纳入 CNCF 是将其发展为真正社区驱动的努力的一种方式,以确保其能够与云原生栈的其余部分无缝集成,并为其提供长期支持。在 2021 年,通过将 sysdig 内核模块、eBPF 探针和库贡献给 CNCF,将这一努力扩展为 Falco 组织的一个子项目。完整的 Falco 栈现在掌握在一个中立和关怀的社区手中。
插件和云
随着时间的推移和 Falco 的成熟,有几点变得很明显。首先,其复杂的引擎、高效的特性和易于部署使其适用于远不止基于系统调用的运行时安全。其次,随着软件变得越来越分布式和复杂,运行时安全变得至关重要,以立即检测到威胁,无论是预期的还是意外的。最后,我们相信世界需要一种一致和标准化的方式来处理运行时安全。特别是,对于一种可以在一个统一的方式中保护工作负载(进程、容器、服务、应用程序)和基础设施(主机、网络、云服务)的解决方案有巨大的需求。
因此,Falco 演进的下一个步骤是添加模块化、灵活性和对更多跨不同领域的数据源的支持。例如,在 2021 年,添加了一个新的插件基础设施,允许 Falco 访问数据源,如云提供商日志,以检测配置错误、未经授权的访问、数据窃取等等。
一个漫长的旅程
Falco 的故事跨越了二十多年,并连接了许多人、发明和一开始看起来并不相关的项目。在我们看来,这个故事充分展示了开源的魅力:成为贡献者让你能够向之前的聪明人学习,建立在他们创新的基础上,并以创造性的方式连接社区。
第二章:在本地计算机上开始使用 Falco
现在您已经了解了 Falco 提供的可能性,试试它是了解它的更好方式吗?在本章中,您将发现在本地计算机上安装和运行 Falco 是多么简单。我们将逐步引导您完成这个过程,介绍和分析核心概念和功能。我们将生成一个 Falco 将为我们检测到的事件,模拟恶意行为,并向您展示如何阅读 Falco 的通知输出。我们将通过介绍一些可管理的方法来定制您的安装,来结束本章。
在本地计算机上运行 Falco
虽然 Falco 不是一个典型的应用程序,但在本地计算机上安装和运行它非常简单——您只需要一个 Linux 主机或虚拟机和一个终端。要安装的两个组件是:用户空间程序(命名为falco)和驱动程序。驱动程序用于收集系统调用,这是 Falco 的一种可能的数据源之一。为简单起见,本章将仅专注于系统调用捕获。
注意
您将在第三章了解更多有关可用驱动程序及其为何需要它们来配置系统的信息,并在第四章中探索替代数据源。目前,您只需知道默认驱动程序已实现为 Linux 内核模块,足以收集系统调用并开始使用 Falco。
如您将在第八章中看到的,有几种方法可用于安装这些组件。但在本章中,我们选择使用二进制软件包。它适用于几乎任何 Linux 发行版,并且没有自动化:您可以亲自操作它的组件。二进制软件包包括falco程序、falco-driver-loader脚本(一个帮助您安装驱动程序的实用工具)和许多其他必需的文件。您可以从The Falco Project的官方网站下载此软件包,那里还有关于安装它的详细信息。那么,让我们开始吧!
下载和安装二进制软件包
Falco 的二进制软件包以 GNU zip(gzip)压缩的单个 tarball 分发。tarball 文件名为falco-<x.y.z>-.tar.gz,其中x.y.z是 Falco 版本的版本号,是软件包的目标架构(例如x86_64)。
所有可用的软件包都列在 Falco 的“下载”页面上。您可以获取二进制软件包的 URL 并在本地下载,例如使用curl
:
$ curl -L -O \
https://download.falco.org/packages/bin/x86_64/falco-0.32.0-x86_64.tar.gz
下载 tarball 后,解压缩和解压它非常简单:
$ tar -xvf falco-0.32.0-x86_64.tar.gz
您刚刚提取的 tarball 内容旨在直接复制到本地文件系统的根目录(即*/*),无需任何特殊的安装过程。要复制它,请以 root 身份运行此命令:
$ sudo cp -R falco-0.32.0-x86_64/* /
现在您已经准备好安装驱动程序了。
安装驱动程序
系统调用是 Falco 的默认数据源。要对 Linux 内核进行仪表化并收集这些系统调用,需要一个驱动程序:即 Linux 内核模块或者 eBPF 探针。驱动程序需要针对 Falco 将运行的特定版本和配置的内核进行构建。幸运的是,Falco 项目为绝大多数常见的 Linux 发行版提供了成千上万的预构建驱动程序,可下载各种内核版本。如果你的发行版和内核版本尚无预构建驱动程序,你在上一节安装的文件中也包含了内核模块和 eBPF 探针的源代码,因此也可以在本地构建驱动程序。
这听起来可能很多,但你刚刚安装的 falco-driver-loader 脚本可以完成所有这些步骤。在使用该脚本之前,你只需安装几个必要的依赖项:
-
动态内核模块支持(dkms)
-
GNU make
-
Linux 内核头文件
根据你使用的包管理器,实际的包名称可能会有所不同;但是,它们并不难找。一旦安装了这些包,你就可以以 root 权限运行 falco-driver-loader 脚本了。如果一切顺利,脚本的输出应该类似于这样:
$ sudo falco-driver-loader
* Running falco-driver-loader for: falco version=0.32.0, driver version=39ae...
* Running falco-driver-loader with: driver=module, compile=yes, download=yes
...
* Looking for a falco module locally (kernel 5.18.1-arch1-1)
* Trying to download a prebuilt falco module from https://download.falco.org/...
curl: (22) The requested URL returned error: 404
Unable to find a prebuilt falco module
* Trying to dkms install falco module with GCC /usr/bin/gcc
此输出包含一些有用的信息。第一行报告了正在安装的 Falco 和驱动程序的版本。随后的行告诉我们,该脚本将尝试下载一个预构建驱动程序,以便安装一个内核模块。如果预构建的驱动程序不可用,Falco 将尝试在本地构建它。输出的其余部分显示了通过 DKMS 构建和安装模块的过程,并最终显示模块已被安装和加载。
启动 Falco
要启动 Falco,你只需以 root 权限运行它:^(1)
$ sudo falco
Mon Jun 6 16:08:29 2022: Falco version 0.32.0 (driver version 39ae7d404967...
Mon Jun 6 16:08:29 2022: Falco initialized with configuration file /etc/fa...
Mon Jun 6 16:08:29 2022: Loading rules from file /etc/falco/falco_rules.yaml:
Mon Jun 6 16:08:29 2022: Loading rules from file /etc/falco/falco_rules.loc...
Mon Jun 6 16:08:29 2022: Starting internal webserver, listening on port 8765
注意配置和规则文件的路径。我们将在第九章和第十三章更详细地讨论这些内容。最后一行显示了一个 Web 服务器已经启动;这是因为 Falco 提供了一个健康检查端点,你可以用它来测试它是否正常运行。
提示
在本章中,为了让你习惯,我们只是将 Falco 作为一个交互式 shell 进程运行;因此,简单的 Ctrl-C 就足以结束进程。在本书的整个过程中,我们将向你展示安装和运行它的不同和更复杂的方法。
一旦 Falco 打印出这些启动信息,它就可以在加载的规则集中满足条件时发出通知了。现在,你可能看不到任何通知(假设你的系统上没有运行恶意软件)。在下一节中,我们将生成一个可疑事件。
生成事件
有数百万种生成事件的方法。在系统调用的情况下,实际上,许多事件在进程运行时连续发生。然而,要看到 Falco 的实际效果,我们必须关注可以触发警报的事件。正如您会回忆的,Falco 预装了一套规则,涵盖了最常见的安全场景。它使用规则来表达不希望的行为,因此我们需要选择一个规则作为目标,然后通过在系统内模拟恶意操作来触发它。
在本书的过程中,特别是在第十三章,您将了解规则的完整解剖,如何解释和编写使用 Falco 规则语法的条件,以及条件和输出中支持的字段。暂时让我们简要回顾一下规则是什么,并通过考虑一个真实的例子来解释其结构:
- rule: Write below binary dir
desc: an attempt to write to any file below a set of binary directories
condition: >
bin_dir and evt.dir = < and open_write
output: >
File below a known binary directory opened for writing
(user=%user.name user_loginuid=%user.lo command=%proc.cmdline
file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline
gparent=%proc.aname[2] container_id=%container.id
image=%container.image.repository)
priority: ERROR
source: syscall
规则声明是一个 YAML 对象,有几个键。第一个键rule
在ruleset(一个或多个包含规则定义的 YAML 文件)中唯一标识规则。第二个键desc
允许规则的作者简要描述规则将检测到什么。condition
键,可以说是最重要的一个,允许使用一些简单的语法来表达安全断言。各种布尔和比较运算符可以与fields(保存收集的数据)结合使用,以仅过滤相关事件。在此示例规则中,evt.dir
是用于过滤的字段。支持的字段和过滤器在第六章中有更详细的介绍。
只要条件为假,就不会发生任何事情。当条件为真时,断言得到满足,然后将立即触发警报。警报将包含一个信息性消息,由规则的作者使用规则声明的output
键定义。priority
键的值也将被报告。警报的内容将在下一节中更详细地介绍。
condition
的语法还可以利用一些更多的构造,比如list
和macro
,这些可以与规则集中定义的规则一起使用。顾名思义,list是可以在不同规则中重复使用的项目列表。类似地,macros是可重用的条件片段。为了完整起见,这里是在Write below binary dir规则的condition
键中使用的两个宏(bin_dir
和open_write
):
- macro: bin_dir
condition: fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
- macro: open_write
condition: >
evt.type in (open,openat,openat2)
and evt.is_open_write=true
and fd.typechar='f'
and fd.num>=0
在运行时,当加载规则时,宏会展开。因此,我们可以想象最终的规则条件将类似于:
evt.type in (open,openat,openat2)
and evt.is_open_write=true
and fd.typechar='f'
and fd.num>=0
and evt.dir = <
and fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
条件广泛使用字段。在这个例子中,你可以轻松识别条件中的哪些部分是字段(evt.type
、evt.is_open_write
、fd.typechar
、evt.dir
、fd.num
和fd.directory
),因为它们后面跟着比较运算符(如=
、>=
、in
)。字段名称包含一个点(.
),因为具有类似上下文的字段被分组在一起形成类。点之前的部分代表类(例如,evt
和fd
是类)。
尽管您可能尚未彻底理解条件的语法,但目前无需理解。您只需要知道,在符合条件的目录(如*/bin*)下创建文件(这意味着打开文件进行写入)应该足以触发规则的条件。让我们试一试。
首先,启动加载我们目标规则的 Falco。Write below binary dir 规则包含在*/etc/falco/falco_rules.yaml*中,默认启动 Falco 时会加载它,所以你无需手动复制。只需打开一个终端并运行:
$ sudo falco
其次,在*/bin*目录中创建文件以触发规则。这样做的一种简单方法是打开另一个终端窗口,并输入:
$ sudo touch /bin/surprise
现在,如果你返回运行 Falco 的第一个终端,你应该会在日志中找到一行(即,Falco 发出的警报),看起来像以下内容:
16:52:09.350818073: Error File below a known binary directory opened for writing
(user=root user_loginuid=1000 command=touch /bin/surprise file=/bin/surprise
parent=sudo pcmdline=sudo touch /bin/surprise gparent=zsh container_id=host
image=<NA>)
Falco 抓住了我们!幸运的是,这正是我们想要发生的。(我们将在下一节详细查看此输出。)
规则让我们告诉 Falco 我们想要观察哪些安全策略(由condition
键表达),以及我们希望在违反策略时接收哪些信息(由output
键指定)。每当事件符合规则定义的条件时,Falco 就会发出一个警报(输出一行文本),因此如果您再次运行相同的命令,将会触发新的警报。
在尝试完这个示例之后,为什么不自己测试一些其他规则呢?为了方便起见,Falcosecurity 组织提供了一个名为event-generator的工具。这是一个简单的命令行工具,不需要任何特殊的安装步骤。你可以下载最新版本并解压到任意位置。它附带了一系列与默认 Falco 规则集中许多规则匹配的事件。例如,要生成符合Read sensitive file untrusted规则条件的事件,你可以在终端窗口中输入以下内容:
$ ./event-generator run syscall.ReadSensitiveFileUntrusted
警告
请注意,此工具可能会更改您的系统。例如,由于此工具的目的是重现真实的恶意行为,某些操作会修改文件和目录,如*/bin*、/etc和*/dev*。在使用之前,请确保您充分理解此工具及其选项的目的。正如在线文档建议的那样,在容器中运行 event-generator 更加安全。
解释 Falco 的输出
让我们更仔细地查看我们实验产生的警报通知,看看它包含了哪些重要信息:
16:52:09.350818073: Error File below a known binary directory opened for writing
(user=root user_loginuid=1000 command=touch /bin/surprise file=/bin/surprise
parent=sudo pcmdline=sudo touch /bin/surprise gparent=zsh container_id=host
image=<NA>)
这显然复杂的一行实际上只包含三个主要元素,它们之间由空格分隔:时间戳、严重级别和消息。让我们分别来看看每一个:
时间戳
直觉上,第一个元素是时间戳(后跟冒号:16:52:09.350818073:
)。那是事件生成的时间。默认情况下,它以本地时区显示,并包括纳秒。如果您愿意,可以配置 Falco 以 ISO 8601 格式显示时间,包括日期、纳秒和时区偏移(在 UTC 中)。
严重级别
第二个元素指示了警报的严重性(例如,Error
),如规则中的priority
键所指定的那样。它可以采用以下值之一(从最严重到最不严重):Emergency
、Alert
、Critical
、Error
、Warning
、Notice
、Informational
或Debug
。Falco 允许我们通过指定我们想要接收警报的最小严重级别来过滤那些对我们不重要的警报,从而减少输出的噪声。默认情况下是debug
,意味着默认包含所有严重级别,但我们可以通过修改*/etc/falco/falco.yaml*配置文件中priority
参数的值来更改这一点。例如,如果我们将此参数的值更改为notice
,那么我们将不会收到具有priority
等于INFORMATIONAL
或DEBUG
的规则的警报。
消息
最后也是最关键的元素是消息。这是一个根据output
键指定的格式生成的字符串。它的特点在于使用占位符,Falco 引擎稍后将这些占位符替换为事件数据,我们马上就会看到。
通常,规则的output
键以简短的文本描述开头,以便识别问题的类型(例如,File below a known binary directory opened for writing
)。然后它包含一些占位符(例如,%user.name
),这些占位符在输出时将用实际值(例如,root
)填充。您可以轻松识别占位符,因为它们以%
符号开头,后跟事件支持的字段之一。这些字段可以在 Falco 规则的condition
键和output
键中都使用。
这一功能的美妙之处在于,您可以为每个安全策略设置不同的输出格式。这立即为您提供了与违规相关的最相关信息,而无需导航数百个字段。
尽管这种文本格式可能包含你所需的所有信息,并且适合许多其他程序使用,但并非输出的唯一选项 - 你可以通过简单更改配置参数指示 Falco 以 JSON 格式输出通知。JSON 输出格式具有易于消费者解析的优点。启用后,Falco 将为每个警报生成一个 JSON 行输出,格式如下,我们进行了格式化以提高可读性:
{
"output": "11:55:33.844042146: Error File below a known binary directory...",
"priority": "Error",
"rule": "Write below binary dir",
"time": "2021-09-13T09:55:33.844042146Z",
"output_fields": {
"container.id": "host",
"container.image.repository": null,
"evt.time": 1631526933844042146,
"fd.name": "/bin/surprise",
"proc.aname[2]": "zsh",
"proc.cmdline": "touch /bin/surprise",
"proc.pcmdline": "sudo touch /bin/surprise",
"proc.pname": "sudo",
"user.loginuid": 1000,
"user.name": "root"
}
}
此输出格式报告与之前相同的文本消息。此外,每条信息都分隔到不同的 JSON 属性中。你可能还注意到了一些额外的数据:例如,这次包含了规则标识符 ("rule": "Write below binary dir"
)。
要立即尝试,请在启动 Falco 时简单地传递以下标志作为命令行参数,以覆盖默认配置:
$ sudo falco -o json_output=true
或者,你可以编辑 /etc/falco/falco.yaml 并将 json_output
设置为 true
。这将使每次启动 Falco 时都启用 JSON 格式,而无需标志。
自定义你的 Falco 实例
启动 Falco 时,它会加载几个文件。特别是,它首先加载主配置文件(也是唯一的配置文件),如启动日志所示:
Falco initialized with configuration file /etc/falco/falco.yaml
Falco 默认在 /etc/falco/falco.yaml 查找其配置文件。这就是提供的配置文件所安装的位置。如果需要,你可以在运行 Falco 时使用 -c
命令行参数指定另一个配置文件的路径。无论你选择哪个文件位置,配置文件必须是一个主要包含一组键值对的 YAML 文件。让我们看看一些可用的配置选项。
规则文件
最重要的选项之一,也是在提供的配置文件中首次找到的选项,是要加载的规则文件列表:
rules_file:
- /etc/falco/falco_rules.yaml
- /etc/falco/falco_rules.local.yaml
- /etc/falco/rules.d
尽管名称如此(为了向后兼容性),rules_file
允许你指定多个条目,每个条目可以是规则文件或包含规则文件的目录。如果条目是文件,Falco 将直接读取它。如果是目录,Falco 将读取该目录中的每个文件。
这里顺序很重要。文件按照呈现的顺序加载(目录内的文件按字母顺序加载)。用户可以通过简单地在列表中稍后出现的文件中覆盖它们来自定义预定义规则。例如,假设你想关闭 Write below binary dir 规则,该规则包含在 /etc/falco/falco_rules.yaml 中。你只需编辑 /etc/falco/falco_rules.local.yaml(它在列表中出现在该文件之后,并且旨在添加本地覆盖),并写入:
- rule: Write below binary dir
enabled: false
输出通道
有一组选项可控制 Falco 提供的可用输出通道,允许您指定安全通知的发送位置。此外,您可以同时启用多个选项。您可以在配置文件(/etc/falco/falco.yaml)中轻松识别它们,因为它们的键都以_output
结尾。
默认情况下,仅启用了两个输出通道:stdout_output
指示 Falco 将警报消息发送到标准输出,syslog_output
将其发送到系统日志守护程序。它们的配置为:
stdout_output:
enabled: true
syslog_output:
enabled: true
Falco 提供了几种其他高级内置输出通道。例如:
file_output:
enabled: false
keep_alive: false
filename: ./events.txt
当启用file_output
时,Falco 还将其警报写入由子键filename
指定的文件。
其他输出通道允许你以复杂的方式消耗警报并与第三方集成。例如,如果你想将 Falco 输出传递给本地程序,你可以使用:
program_output:
enabled: false
keep_alive: false
program: mail -s "Falco Notification" someone@example.com
一旦启用此功能,Falco 将为每个警报执行程序并将其内容写入程序的标准输出。您可以将program
子键设置为任何有效的 shell 命令,因此这是展示您喜欢的单行命令的绝佳机会。
如果你只需要与 webhook 集成,更方便的选项是使用http_output
输出通道:
http_output:
enabled: false
url: http://some.url
对于每个警报,将发送一个简单的 HTTP POST 请求到指定的url
。这使得将 Falco 连接到其他工具变得非常简单,例如 Falcosidekick,后者将警报转发到 Slack、Teams、Discord、Elasticsearch 和许多其他目的地。
最后但同样重要的是,Falco 配备了一个 gRPC API 和相应的输出grpc_output
。启用 gRPC API 和 gRPC 输出通道允许您连接到 falco-exporter*,后者将指标导出到 Prometheus。
注意
Falcosidekick 和 falco-exporter 是您可以在Falcosecurity GitHub 组织下找到的开源项目。在第十二章,您将再次遇到这些工具,并学习如何处理输出。
结论
本章向您展示了如何在本地机器上安装和运行 Falco 作为一个游乐场。您看到了一些生成事件的简单方法,并学习了如何解码输出。然后,我们看了如何使用配置文件自定义 Falco 的行为。加载和扩展规则是指导 Falco 保护对象的主要方法。同样,配置输出通道使我们能够以满足需求的方式消耗通知。
有了这些知识,你可以自信地开始尝试使用 Falco。本书的其余部分将扩展你在这里学到的内容,并最终帮助你完全掌握 Falco。
^(1) Falco 需要以 root 权限运行,以操作收集系统调用的驱动程序。然而,也存在替代方法。例如,你可以从 Falco 的 “Running” 页面 学习如何在容器中以最小特权原则运行 Falco。
第二部分:Falco 的架构
第三章:理解 Falco 的架构
欢迎来到本书的 Part II!在 Part I 中,您了解了 Falco 的定义及其功能。您还高层次地了解了其架构,将其安装在您的计算机上,并进行了试用。现在是时候提升您的水平了!
在本书的这一部分(从第三章到第八章),我们将深入探讨 Falco 的内部工作原理。您将更详细地了解其架构,包括其主要组件以及数据在这些组件之间的流动方式。我们将展示 Falco 如何与操作系统的内核和云日志接口以收集数据,并且如何通过上下文和元数据丰富这些数据。第六章 将介绍字段和过滤器这一重要主题,而第七章 将让您更加熟悉 Falco 规则。我们将通过讨论输出框架,Falco 的关键部分,来结束 Part II。
您真的需要了解 Falco 的内部结构才能操作它吗?答案通常是人生中常见的“这要看情况”。如果您的目标只是在其默认配置下部署 Falco 并向老板展示它正在运行,那么您可能可以跳过本书的这一部分。然而,这样做会使某些事情变得困难,而其他事情则变得不可能。例如,在第 III 和 IV 部分中,我们将讨论:
-
解读 Falco 的输出
-
确定警报是否可能是误报
-
优化 Falco 以优先考虑准确性而不是噪音
-
精确地调整 Falco 以适应您的环境
-
自定义和扩展 Falco
所有这些任务都要求您真正理解 Falco 及其架构背后的核心概念,这正是我们在这里要帮助您实现的。
真正的安全从不是琐碎的。它需要一种超越表面理解的投入。但是这种投入通常会百倍回报,因为它可能决定了您的软件是否会受到攻击,以及您的公司是否因错误的原因出现在新闻中。
假设我们已经说服了您,让我们开始吧。图 3-1 描述了典型 Falco 传感器部署的主要组件。
图 3-1。典型 Falco 传感器部署的高级架构
在 GitHub 上的 Falcosecurity 组织 中,代码级别组织的架构反映在 图 3-1 中。在这个粒度级别上,主要组件包括:
Falco 库
Falco libraries,或“libs”,负责收集传感器将处理的数据。它们还管理状态并为收集的数据提供多层次的丰富化。
插件
插件通过额外的数据源扩展了传感器的功能。例如,插件使得 Falco 能够使用 AWS CloudTrail 和 Kubernetes 审计日志作为数据源。
Falco
这是包括规则引擎在内的主传感器可执行文件。
Falcosidekick
Falcosidekick负责路由通知并连接传感器与外部世界。
在图 3-1 中的组件中,Falco 和 Falco libs 是必需的并且始终安装,而 Falcosidekick 和插件是可选的;您可以根据部署策略和需求进行安装。
Falco 和 Falco Libraries:数据流视图
让我们来看看刚才描述的两个最重要的组件,即 Falco 库和 Falco,并探索它们的数据流和关键模块。
正如图 3-2 所示,系统调用是数据的核心来源之一。这些由 Falco 的两个驱动程序之一在操作系统的内核中捕获:内核模块和eBPF(扩展伯克利数据包过滤器)探针。
图 3-2. 传感器数据流和主要模块
收集的系统调用流入 Falco 核心库的第一个库libscap,该库还可以接收来自插件的数据,并为上层提供一个通用接口。数据然后传递到另一个关键库libsinsp进行解析和增强。接下来,数据传递给规则引擎进行评估。Falco 接收规则引擎的输出并生成相应的通知,可以选择性地发送到 Falcosidekick。
相当直接了,对吧?图 3-3 提供了关于每个模块功能的更多细节,在接下来的章节中,我们将更深入地探讨它们。
图 3-3. 传感器主要模块的关键角色
驱动程序
系统调用是 Falco 的原始数据源,直到今天它们仍然是最重要的。收集系统调用是 Falco 能够以非常精细和高效的方式跟踪进程、容器和用户行为核心。可靠和高效的系统调用收集需要在操作系统内核内部执行驱动程序,因此需要一个在操作系统内部运行的驱动程序。如前所述,Falco 提供了两种这样的驱动程序:内核模块和 eBPF 探针。
这两个组件提供相同的功能,并以互斥的方式部署:如果部署了内核模块,则无法运行 eBPF 探针,反之亦然。那么它们有什么区别呢?
内核模块与 Linux 内核的任何版本兼容,包括较旧的版本。此外,它需要更少的资源来运行,因此在关心 Falco 尽可能小的开销时应使用它。
另一方面,eBPF 探针仅在 Linux 的较新版本上运行,从内核版本 4.11 开始。其优势在于更安全,因为其代码在执行之前会严格由操作系统验证。这意味着即使它包含错误,理论上也不会导致系统崩溃。与内核模块相比,它还能更好地防止可能危及你运行它的机器的安全漏洞。因此,在大多数情况下,eBPF 探针是你应该选择的选项。还要注意,某些环境——特别是基于云的托管容器化环境——禁止加载操作系统内核中的内核模块。在这种环境中,eBPF 探针是你唯一的选择。
内核模块和 eBPF 探针都承担一组非常重要的任务:
捕获系统调用
驱动程序的首要责任是捕获系统调用。这是通过一个称为tracepoints的内核设施完成的,并且经过了大量优化,以最小化对被监视应用程序性能的影响。
系统调用打包
然后,驱动程序将系统调用信息编码到一个传输缓冲区中,使用一种 Falco 堆栈其余部分可以轻松高效解析的格式。
零拷贝数据传输
最后,驱动程序负责将这些数据高效地从内核传输到用户级,libscap将在那里接收它。事实上,我们应该称这种方式为高效地不传输数据,因为内核模块和 eBPF 探针都设计成零拷贝架构,将数据缓冲区映射到用户级内存,使libscap能够访问原始数据,而无需复制或传输它。
在第四章中,你将学习有关驱动程序的所有必要知识,包括它们的架构、功能和使用场景。
插件
插件是一种简单地向 Falco 添加额外数据源而无需重新构建它的方法。插件实现了一个接口,将事件馈送到 Falco,类似于内核模块和 eBPF 探针所做的事情。然而,插件不仅限于捕获系统调用:它们可以向 Falco 馈送任何类型的数据,包括日志和 API 事件。
Falco 拥有几个强大的插件,扩展了其范围。例如,CloudTrail 插件从 AWS CloudTrail 中摄取 JSON 日志,并允许 Falco 在你的云基础设施中发生危险事件时向你发出警报。插件可以用任何语言编写,但有 Go 和 C++的软件开发工具包(SDK)可用,这使得用这些语言编写插件更容易。我们将在第四章和第十一章中更详细地讨论插件。
libscap
名称libscap代表“系统捕获库”,清楚地提示了它的目的。libscap是数据进入 Falco 处理流水线之前经过的门户。让我们看看libscap为我们做了哪些主要工作。
管理数据源
libscap 库包含了控制内核模块和 eBPF 探针的逻辑,包括它们的加载、启动和停止捕获,以及读取它们生成的数据。它还包括加载、管理和运行插件的逻辑。
libscap 的设计旨在向堆栈的上层导出通用的捕获源抽象。这意味着无论您如何收集数据(内核模块、eBPF 探针、插件),使用libscap的程序都将拥有一致的方式来枚举和控制数据源,启动和停止捕获,并接收捕获的事件,而不必担心与这些不同输入源进行接口的细微差别。
支持跟踪文件
libscap 中另一个极为重要的功能是支持跟踪文件。如果您曾经使用 Wireshark 或 tcpdump 创建或打开过 PCAP 文件,我们相信您一定了解跟踪文件的概念是多么有用(和强大!)。如果还不了解,请允许我们解释。
除了捕获和解码网络流量外,协议分析器(如 Wireshark 和 tcpdump)允许您将捕获的网络数据包“转储”到跟踪文件中。跟踪文件包含每个数据包的副本,以便稍后分析该网络段的活动。您还可以与他人共享它或过滤其内容以隔离相关信息。
跟踪文件通常被称为 PCAP 文件,这个名称源自用于编码其中数据的*.pcap*文件格式(这是一种开放、标准化的格式,全球所有网络工具都能理解)。这使得在计算机网络中关键的“现在捕获,以后分析”的工作流程变得无穷无尽。
许多 Falco 用户并不了解,Falco 支持使用*.pcap*格式的跟踪文件。这个功能非常强大,并且在您积累更多经验时,绝对应该成为您工具库的一部分。例如,当您撰写新规则时,跟踪文件无价。
我们将详细讨论如何利用跟踪文件,例如在第四章和第十三章,但现在让我们通过两个简单的步骤来激发您的兴趣,教您如何创建跟踪文件并让 Falco 读取它。为此,我们需要介绍一个名为 sysdig 的命令行工具。您将在第四章更多地了解 sysdig,但现在我们将它作为一个简单的跟踪文件生成器使用。
步骤 1:创建跟踪文件
按照安装说明在您的 Linux 主机上安装 sysdig。安装完成后,在命令行上运行以下命令,指示 sysdig 捕获主机生成的所有系统调用,并将它们写入名为testfile.scap的文件:
$ sudo sysdig -w testfile.scap
等待几秒钟,确保您的机器正在工作,然后按 Ctrl-C 停止 sysdig。
现在,您拥有了主机活动几秒钟快照的快照。让我们看看它包含了什么:
$ sysdig -r testfile.scap
1 17:41:13.628568857 0 prlcp (4358) < write res=0 data=.N;.n...
2 17:41:13.628573305 0 prlcp (4358) > write fd=6(<p>pipe:[43606]) size=1
3 17:41:13.628588359 0 prlcp (4358) < write res=1 data=.
4 17:41:13.609136030 3 gmain (2935) < poll res=0 fds=
5 17:41:13.609146818 3 gmain (2935) > write fd=4(<e>) size=8
6 17:41:13.609149203 3 gmain (2935) < write res=8 data=........
7 17:41:13.609151765 3 gmain (2935) > read fd=7(<i>) size=4096
8 17:41:13.609153301 3 gmain (2935) < read res=-11(EAGAIN) data=
9 17:41:13.626956525 0 Xorg (3214) < epoll_wait res=1
10 17:41:13.626964759 0 Xorg (3214) > setitimer
11 17:41:13.626966955 0 Xorg (3214) < setitimer
12 17:41:13.626969972 0 Xorg (3214) > recvmsg fd=42(<u>@/tmp/.X11-unix/X0)
13 17:41:13.626976118 0 Xorg (3214) < recvmsg res=28 size=28 data=....E..... ...
14 17:41:13.626992585 0 Xorg (3214) > writev fd=42(<u>@/tmp/.X11-unix/X0) size=32
15 17:41:13.627013409 0 Xorg (3214) < writev res=32 data=...7E.............. ...
...
我们稍后会详细介绍此输出的格式,但您可能已经注意到,这是由像 Xorg、gmain 和 prlcp 这样的系统工具在空闲时在此计算机上运行的大量后台输入/输出(I/O)活动。
第 2 步:使用 Falco 处理跟踪文件
把跟踪文件想象成带我们回到过去:您在特定时间点拍摄了主机的快照,现在可以跟踪在该时间周围生成的主机系统调用,详细观察每个进程。使用 Falco 处理跟踪文件很容易,让您快速查看在那段时间内是否发生了任何安全违规。以下是其输出的示例:
$ falco -e testfile.scap
Wed Sep 29 18:04:00 2021: Falco version 0.30.0
Wed Sep 29 18:04:00 2021: Falco initialized with configuration file /etc/falco
/falco.yaml
Wed Sep 29 18:04:00 2021: Loading rules from file /etc/falco/falco_rules.yaml:
Wed Sep 29 18:04:00 2021: Reading system call events from file: testfile.scap
Events detected: 0
Rule counts by severity:
Triggered rules by rule name:
Syscall event drop monitoring:
- event drop detected: 0 occurrences
- num times actions taken: 0
幸运的是,看起来我们很安全。当编写或单元测试规则时,这种一致且回溯式的 Falco 运行方式非常有用。我们将在第十三章详细讨论它,那时我们将深入研究编写 Falco 规则的内容。
收集系统状态
系统状态收集是一个与捕获系统调用密切相关的重要任务。内核模块和 eBPF 探针产生原始系统调用,缺少 Falco 所需的一些重要上下文。
让我们来看一个例子。一个非常常见的系统调用是read
,顾名思义,它从文件描述符中读取数据到缓冲区中。这是read
的原型:
ssize_t read(int fd, void *buf, size_t count);
它有三个输入:数字文件描述符标识符、要填充的缓冲区和缓冲区大小。它返回在缓冲区中写入的数据量。
文件描述符类似于操作系统内核中对象的 ID:它可以指示文件、网络连接(具体来说是套接字)、管道的端点、互斥量(用于进程同步)、定时器或其他几种类型的对象。
当编写 Falco 规则时,知道文件描述符编号并不是很有用。作为用户,我们更喜欢考虑文件或目录名称,或者可能是连接的 IP 地址和端口,而不是文件描述符编号。libscap帮助我们做到这一点。当 Falco 启动时,libscap从操作系统内部的多种来源(例如*/proc* Linux 文件系统)获取大量数据。它使用这些数据构建一组表,可以将加密的数字(例如文件描述符、进程 ID 等)解析为逻辑实体及其详细信息,这对人类来说更容易使用。
这个功能是为什么 Falco 的语法比大多数类似工具更具表现力和可用性的一部分。本书中你会经常听到一个主题,即没有上下文的粒度数据是无用的。这为你提供了这个意思的一个提示。接下来我们将深入探讨另一个重要的 Falco 库:libsinsp。
libsinsp
libsinsp代表“系统检查库”。这个库利用libscap生成的数据流,对其进行丰富,并提供了许多高级原语来处理它。让我们首先探索它最重要的功能,即状态引擎。
状态引擎
正如我们在前一节中所指出的,当 Falco 启动时,libscap构建了一组表,用于将低级标识符(如文件描述符号)转换为高级可操作信息,如 IP 地址和文件名。这很棒,但如果一个程序在 Falco 启动后打开一个文件会怎么样呢?例如,在 Unix 中一个非常常见的系统调用是open
,它接受两个输入参数,文件名和一些标志,并返回一个标识新打开文件的文件描述符:
int open(const char *pathname, int flags);
在实践中,open
像许多其他系统调用一样,会创建一个新的文件描述符,有效地改变调用它的进程的状态。如果一个进程在 Falco 启动后调用open
,它的新文件描述符将不会出现在状态表中,Falco 也不会知道如何处理该描述符。然而,请考虑这一点:open
是一个系统调用。更普遍地说,系统调用总是用于创建、销毁或修改文件描述符。还要记住,Falco libs 捕获每个进程的所有系统调用。
特别是libsinsp有逻辑来检查每个改变状态的系统调用,并根据系统调用的参数更新状态表。换句话说,它跟踪整个机器的活动,以保持状态与底层操作系统同步。此外,它以一种准确地支持容器的方式进行操作。libsinsp将这些不断更新的信息保存在分层结构中。这个结构(图 3-4)从进程表开始,每个条目包含文件描述符表等信息。
这些准确的、不断更新的状态表是 Falco 数据增强的核心,进而是规则引擎的关键构建块。
图 3-4. libsinsp 状态层次结构
事件解析
状态引擎需要大量的逻辑来理解系统调用并解析其参数。这正是 libsinsp 的 事件解析器 所做的。状态跟踪利用事件解析,但也用于其他目的。例如,它从系统调用或其他数据源中提取有用的参数,使它们可供规则引擎使用。它还整理和重构可以分布在多个收集消息中的缓冲区,从而更容易地从 Falco 规则中解码其内容。
过滤器
过滤是 Falco 中最重要的概念之一,而且它在 libsinsp 中得到了完全实现。过滤器 是一个布尔表达式,它将多个检查联系在一起,每个检查都将一个过滤字段与常量值进行比较。当我们查看规则时,过滤器的重要性显而易见。(事实上,它如此重要,以至于我们专门在 第六章 中全面讨论了它。)让我们看一下这里显示的简单规则:
- rule: shell_in_container
desc: shell opened inside a container
condition: container.id != host and proc.name = bash
output: shell in a container (user=%user.name container_id=%container.id)
priority: WARNING
规则中的 condition
部分是 libsinsp 的过滤器。我们的示例中的条件检查容器 ID 不是 host
,并且进程名称是 bash
。每个满足这两个条件的捕获系统调用都将触发该规则。
libsinsp 负责定义和实现与系统调用相关的过滤字段。它还包含评估过滤器并告诉我们是否应触发规则的引擎,因此可以说 libsinsp 是 Falco 的核心,这一点并非言过其实。
输出格式化
如果我们再次看一下示例规则,我们可以看到 output
部分使用了类似于 condition
部分的语法:
output: shell in a container (user=%user.name container_id=%container.id)
触发规则时,Falco 打印的内容就是输出——是的,在此部分中您可以通过在字段名前加上 %
字符来使用 condition
部分中同样可以使用的过滤字段。libsinsp 具有解析这些字段并创建最终输出字符串的逻辑。值得一提的是,如果您成为编写条件过滤器的专家,您也将掌握输出字符串的技能!
libsinsp 的另一重要事项
到目前为止,您可能已经看到 Falco 很多逻辑都在 libsinsp 中。这是有意为之的。Falco 的开发人员认识到了其数据收集堆栈的价值(以及其优雅性),并意识到它可以成为许多其他工具的基础。这就是 libsinsp 存在的原因。它位于强大的 Falco 收集堆栈(包括驱动程序、插件和 libscap)之上,并以可重复使用的方式添加了 Falco 逻辑的最重要部分。更重要的是,libsinsp 包含了从容器、虚拟机、Linux 主机和云基础设施收集安全和取证数据所需的一切。它稳定、高效且文档完善。
还有几个其他开源和商业工具是基于 libsinsp 构建的。如果你想编写一个,或者只是好奇想了解更多,我们建议你从 falcosecurity/libs 仓库 开始。
规则引擎
Falco 规则引擎是你在运行 Falco 时互动的组件。以下是规则引擎负责的一些事务:
-
载入 Falco 规则文件
-
解析文件中的规则
-
根据本地规则文件对规则应用本地定制(例如追加和覆盖)。
-
使用 libsinsp 编译每个规则的条件和输出。
-
当规则触发时执行适当的操作,包括输出结果。
由于 libscap 和 libsinsp 的强大功能,规则引擎简单且相对独立于堆栈的其他部分。
结论
现在你知道了 Falco 的内部构成及其组件之间的关系,你已经在掌握它的路上了!在接下来的章节中,我们将更深入地探讨本章介绍的一些组件和概念。
第四章:数据源
在本章中,我们将深入研究操作系统内核和 Falco 数据收集堆栈。您将了解 Falco 如何捕获喂入其规则引擎的不同类型事件,其数据收集过程如何与替代方法进行比较,以及为什么它被构建成现在这样。到本章结束时,您将充分了解细节,能够选择并部署适合您需求的正确驱动程序和插件。
首要任务是了解 Falco 中可以使用的数据源。Falco 的数据源可以分为两大类:系统调用和插件。系统调用是 Falco 的原始数据源。它们来自操作系统的内核,并提供对进程、容器、虚拟机和主机活动的可见性。Falco 使用它们来保护工作负载和应用程序。第二类数据源,插件,相对较新:2022 年添加了支持。插件将各种输入连接到 Falco,如云日志和 API。
Falco 先前支持 Kubernetes 审计日志作为第三个独立的数据源类型;然而,从 Falco 0.32 开始,这个数据源已被重新实现为插件,因此我们不会在本章涵盖它。
系统调用
正如我们已经多次提到的,系统调用是 Falco 的一个重要数据源,也是使其独特的关键因素之一。但系统调用究竟是什么?让我们从维基百科的高层次定义开始:
在计算机领域,系统调用(通常缩写为 syscall)是计算机程序向其所在操作系统的内核请求服务的编程方式。这可能涉及硬件相关的服务(例如访问硬盘驱动器或访问设备的摄像头)、创建和执行新进程,以及与内核的核心服务进行通信,例如进程调度。
让我们详细解释一下。在最高抽象层次上,计算机由运行各种软件的硬件组成。然而,在现代计算中,程序直接在硬件上运行的情况极为罕见。相反,在绝大多数情况下,程序在操作系统之上运行。Falco 的驱动程序专门针对驱动云和现代数据中心的操作系统 Linux。
操作系统是一种旨在进行和支持其他软件执行的软件。除了许多其他功能外,操作系统还负责:
-
进程调度
-
管理内存
-
中介硬件访问
-
实现网络连接
-
处理并发性
显然,几乎所有这些功能都需要向运行在操作系统之上的程序公开,以便它们可以做一些有用的事情。显然,软件公开功能的最佳方式是提供一个 应用程序编程接口 (API):一组客户程序可以调用的函数。这几乎就是系统调用的作用:与操作系统交互的 API。
等等,为什么几乎?
嗯,操作系统是一种独特的软件,你不能像调用库一样直接调用它。操作系统在称为特权模式的分离执行模式中运行,与用户模式(用于执行常规进程,即运行程序的上下文)隔离开来。这种分离使得调用操作系统变得更加复杂。在某些 CPU 中,您通过触发中断来调用系统调用。然而,在大多数现代 CPU 中,您需要使用特定的 CPU 指令。如果我们排除这种额外的复杂性,可以说系统调用就是访问操作系统功能的 API。它们有很多,每个系统调用都有自己的输入参数和返回值。
每个程序,无一例外,都广泛而持续地使用系统调用接口来处理任何非纯计算的事务:读取输入,生成输出,访问磁盘,网络通信,运行新程序等等。这意味着,正如你所能想象的那样,观察系统调用能够提供每个进程活动的非常详细的图像。
操作系统开发者长期以来一直将系统调用接口视为稳定的 API。这意味着,即使内核内部发生了巨大变化,您也可以期望它保持不变。这一点非常重要,因为它确保了时间和执行环境的一致性,使系统调用 API 成为收集可靠安全信号的理想选择。例如,Falco 规则可以引用特定系统调用,并假定在任何 Linux 发行版上使用它们都可以正常工作。
例子
Linux 提供了 许多 系统调用——超过 300 个。要完整讲解它们几乎是不可能且非常无聊的,所以我们就不细说了。但是,我们确实想给你一些可用系统调用类型的概念。
表 4-1 包括了一些对像 Falco 这样的安全工具最相关的系统调用类别。对于每个类别,表中列出了代表性系统调用的示例。你可以通过在 Linux 终端或浏览器的搜索栏中输入 **man 2 *X***
来查找每个系统调用的更多信息,其中 ***X***
是系统调用的名称。
表 4-1. 重要的系统调用类别
类别 | 示例 |
---|---|
文件 I/O | open , creat , close , read , write , ioctl , link , unlink , chdir , chmod , stat , seek , mount , rename , mkdir , rmdir |
网络 | socket , bind , connect , listen , accept , sendto , recvfrom , getsockopt , setsockopt , shutdown |
进程间通信 | pipe , futex , inotify_add_watch , eventfd , semop , semget , semctl , msgctl |
进程管理 | clone , execve , fork , nice , kill , prctl , exit , setrlimit , setpriority , capset |
内存管理 | brk , mmap , mprotect , mlock , madvise |
用户管理 | setuid , getuid , setgid , getgid |
系统 | sethostname , setdomainname , reboot , syslog , uname , swapoff , init_module , delete_module |
提示
如果您有兴趣查看完整的 Linux 系统调用列表,请在 Linux 终端或搜索引擎中键入**man syscalls**
。这将显示官方 Linux 手册页面,其中包含系统调用的全面列表,并带有超链接,以深入了解其中的许多内容。此外,软件工程师 Filippo Valsorda 在他的个人主页上提供了一个清晰组织且可搜索的列表。
观察系统调用
考虑到系统调用对于 Falco 和一般运行时安全性是多么重要,学习如何捕获、观察和解释它们至关重要。这是一项有价值的技能,在许多情况下都会很有用。我们将向您展示两种不同的工具,您可以用它们来实现这个目的:strace 和 sysdig。
strace
strace 是一个工具,在几乎每台运行 Unix 兼容操作系统的机器上都可以找到。它的最简单用法是运行一个程序,它会将程序发出的每个系统调用打印到标准错误输出。换句话说,只需将**strace**
添加到任意命令行的开头,你就能看到该命令行生成的所有系统调用:
$ strace echo hello world
execve("/bin/echo", ["echo", "hello", "world"], 0x7ffc87eed490 /* 32 vars */) = 0
brk(NULL) = 0x558ba22bf000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=121726, ...}) = 0
mmap(NULL, 121726, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f289009c000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0" ...
fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) ...
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) ...
mprotect(0x7f288fc87000, 2097152, PROT_NONE) = 0
mmap(0x7f288fe87000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED| ...
mmap(0x7f288fe8d000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED| ...
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f289009b540) = 0
mprotect(0x7f288fe87000, 16384, PROT_READ) = 0
mprotect(0x558ba2028000, 4096, PROT_READ) = 0
mprotect(0x7f28900ba000, 4096, PROT_READ) = 0
munmap(0x7f289009c000, 121726) = 0
brk(NULL) = 0x558ba22bf000
brk(0x558ba22e0000) = 0x558ba22e0000
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=3004224, ...}) = 0
mmap(NULL, 3004224, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f288f7c2000
close(3) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
write(1, "hello world\n", 12hello world
) = 12
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
请注意,strace 的输出模仿 C 语法,看起来像一系列函数调用的流,每行末尾的=
符号后面加上返回值。例如,看看write
系统调用(加粗)将字符串“hello world”输出到标准输出(文件描述符 1)。它返回成功写入的字节数 12
。请注意,在write
系统调用返回之前,“hello world”字符串已经打印到标准输出,并且 strace 在屏幕上打印其返回值。
使用 strace 的第二种方法是通过在命令行上指定进程 ID(PID)指向运行中的进程:
$ sudo strace -p`pidof vi`
strace: Process 16472 attached
select(1, [0], [], [0], NULL) = 1 (in [0])
read(0, "\r", 250) = 1
select(1, [0], [], [0], {tv_sec=0, tv_usec=0}) = 0 (Timeout)
select(1, [0], [], [0], {tv_sec=0, tv_usec=0}) = 0 (Timeout)
write(1, "\7", 1) = 1
select(1, [0], [], [0], {tv_sec=4, tv_usec=0}) = 0 (Timeout)
select(1, [0], [], [0], NULL
**^C**
strace: Process 16472 detached
<detached ...>
strace 有一些优点和一些缺点。它得到了广泛的支持,因此它要么已经可用,要么可以轻松安装。它的使用也很简单,在需要检查单个进程时非常理想,因此非常适合调试用途。
至于缺点,strace 仅对单个进程进行仪器化,这使得它不适合检查整个系统的活动或者当你没有特定的起点进程时使用。此外,strace 基于 ptrace 进行系统调用收集,这使得它在生产环境中非常慢且不适用。当你附加 strace 时,应该预期进程会显著减速(有时减速数倍)。
sysdig
我们在第三章中介绍了 sysdig 的追踪文件。sysdig 比 strace 更复杂,并包含几个高级功能。虽然这可能使它有点难以使用,但好消息是 sysdig 与 Falco 的数据模型、输出格式和过滤语法相同——因此你可以在 sysdig 中应用你在 Falco 中学到的很多内容,反之亦然。
首先要记住的是,与 strace 不同,你不需要像对待单个进程那样指定 sysdig。相反,只需运行它,它将捕获机器上内外的每个系统调用:
$ sudo sysdig
1 17:41:13.628568857 0 prlcp (4358) < write res=0 data=.N;.n...
2 17:41:13.628573305 0 prlcp (4358) > write fd=6(<p>pipe:[43606]) size=1
4 17:41:13.609136030 3 gmain (2935) < poll res=0 fds=
5 17:41:13.609146818 3 gmain (2935) > write fd=4(<e>) size=8
6 17:41:13.609149203 3 gmain (2935) < write res=8 data=........
9 17:41:13.626956525 0 Xorg (3214) < epoll_wait res=1
10 17:41:13.626964759 0 Xorg (3214) > setitimer
11 17:41:13.626966955 0 Xorg (3214) < setitimer
通常这会造成很大噪音,且不太有用,所以你可以通过使用过滤器来限制 sysdig 显示的内容。sysdig 接受与 Falco 相同的过滤语法(顺便说一句,这使它成为一个测试和调试 Falco 规则的强大工具)。以下是一个例子,我们将 sysdig 限制为仅捕获名为“cat”的进程的系统调用:
$ sudo sysdig proc.name=cat & cat /etc/hosts
47190 14:40:39.913809700 12 cat (377163.377163) < execve res=0 exe=cat
args=/etc/hosts. tid=377163(cat) pid=377163(cat) ptid=5860(zsh) cwd=
fdlimit=1024 pgft_maj=0 pgft_min=60 vm_size=424 vm_rss=4 vm_swap=0 comm=cat
cgroups=cpuset=/user.slice.cpu=/user.slice.cpuacct=/.io=/user.slice.memory=
/user.slic... env=SYSTEMD_EXEC_PID=3558.GJS_DEBUG_TOPICS=JS ERROR;JS
LOG.SESSION_MANAGER=local/... tty=34817 pgid=377163(cat) loginuid=1000 flags=0
47194 14:40:39.913846153 12 cat (377163.377163) > brk addr=0
47196 14:40:39.913846951 12 cat (377163.377163) < brk res=55956998C000
vm_size=424 vm_rss=4 vm_swap=0
47205 14:40:39.913880404 12 cat (377163.377163) > arch_prctl
47206 14:40:39.913880871 12 cat (377163.377163) < arch_prctl
47207 14:40:39.913896493 12 cat (377163.377163) > access mode=4(R_OK)
47208 14:40:39.913900922 12 cat (377163.377163) < access res=-2(ENOENT)
name=/etc/ld.so.preload
47209 14:40:39.913903872 12 cat (377163.377163) > openat dirfd=-100(AT_FDCWD)
name=/etc/ld.so.cache flags=4097(O_RDONLY|O_CLOEXEC) mode=0
47210 14:40:39.913914652 12 cat (377163.377163) < openat
fd=3(<f>/etc/ld.so.cache) dirfd=-100(AT_FDCWD) name=/etc/ld.so.cache
flags=4097(O_RDONLY|O_CLOEXEC) mode=0 dev=803
这个输出需要比 strace 更详细的解释。sysdig 打印的字段包括:
-
增量事件编号
-
事件时间戳
-
CPU ID
-
命令名称
-
进程 ID 和线程 ID(TID),用点号分隔
-
事件方向(
>
表示 进入,<
表示 退出) -
事件类型(对于我们来说,这是系统调用的名称)
-
系统调用参数
与 strace 不同,sysdig 为每个系统调用打印了 两 行:进入 行是系统调用开始时生成的,退出 行是系统调用返回时打印的。如果你需要确定系统调用运行时间或找出卡在系统调用中的进程,这种方法非常有效。
还要注意,默认情况下,sysdig 除了打印进程 ID 外还打印线程 ID。线程 是操作系统和 sysdig 的核心执行单元。多个线程可以存在于同一个进程或命令中,并共享资源,比如内存。TID 是在跟踪机器上执行活动时跟踪执行活动的基本标识符。你可以通过查看 TID 号码或使用如下命令行来过滤掉噪音:
$ sysdig thread.tid=1234
这将仅保留线程 1234 的执行流。
线程存在于进程内部,进程通过进程 ID 进行标识。在平均 Linux 系统上运行的大部分进程都是单线程的,在这种情况下,thread.tid
和proc.pid
是相同的。通过proc.pid
进行过滤很有用,可以观察线程在进程内部的交互情况。
追踪文件
正如在 Chapter 3 中所学到的,您可以指示 sysdig 将其捕获的系统调用保存到跟踪文件中,例如:
$ sudo sysdig -w testfile.scap
您可能想要使用过滤器来控制文件大小。例如:
$ sudo sysdig -w testfile.scap proc.name=cat
当读取跟踪文件时,您也可以使用过滤器:
$ sysdig -r testfile.scap proc.name=cat
sysdig 的过滤器非常重要,我们将专门为它们撰写一整章(Chapter 6)。
我们建议您使用 sysdig 并探索 Linux 中常见程序的活动。这将在稍后创建或解释 Falco 规则时非常有帮助。
捕获系统调用
好的,系统调用很酷,我们需要捕获它们。那么最好的方法是什么?
在本章的前面部分,我们描述了系统调用是如何将执行流从运行中的进程转移到操作系统内核的。直觉上,如图 Figure 4-1 所示,可以在两个地方捕获系统调用:在运行的进程中或操作系统内核中。
图 4-1. 系统调用捕获选项
在运行的进程中捕获系统调用通常涉及修改进程或其某些库以进行某种形式的仪器化。由于 Linux 中大多数程序使用 C 标准库,也称为 glibc,来执行系统调用,使得仪器化它非常吸引人。因此,有大量工具和框架用于修改 glibc(和其他系统库)以进行仪器化。这些技术可以是静态的,改变库的源代码并重新编译它,或者是动态的,在目标进程的地址空间中找到其位置并插入钩子。
注意
捕获系统调用的另一种方法是利用操作系统的调试工具。例如,strace 使用一个名为 ptrace 的工具,^(1) 这个工具是像 GNU 调试器(gdb)这样工具的基础。
第二种选择涉及在系统调用转移到操作系统后拦截其执行。这需要在操作系统内核中运行一些代码。这往往更加棘手和风险更高,因为在内核中运行代码需要提升的权限。在内核中运行的任何内容都有可能控制机器、其进程、其用户和其硬件。因此,内核中运行的任何错误可能导致重大的安全风险、数据损坏或在某些情况下甚至机器崩溃。这就是为什么许多安全工具选择仪器化选项 1 并在用户级别内捕获系统调用的原因。
Falco 则相反:它完全基于内核的仪器化。选择背后的理由可以总结为三个词:准确性、性能和可伸缩性。让我们依次探讨每一个。
准确性
用户级别的工具技术,特别是在glibc层面工作的技术,有几个主要问题。首先,一个有动机的攻击者可以通过避开使用glibc来规避它们!你并不一定需要使用库来发出系统调用,攻击者可以很容易地编写一系列简单的 CPU 指令,完全绕过glibc的检测。这不是好事。
更糟糕的是,有一些主要类别的软件根本不加载glibc。例如,在容器中非常常见的静态链接的 C 程序,在编译时导入glibc函数并将它们嵌入到可执行文件中。对于这些程序,你无法替换或修改库。Go 语言编写的程序也是如此,它有自己静态链接的系统调用接口库。
内核级别的捕获不受这些限制。它支持任何语言、任何堆栈和任何框架,因为系统调用的收集发生在所有库和抽象层之下。这意味着内核级别的工具更难以被攻击者规避。
性能
一些用户级别的捕获技术,比如使用ptrace
,因为产生了大量的上下文切换,所以有很大的开销。每个系统调用都需要被单独传递到一个独立的进程,这就需要在进程之间来回“乒乓”。这非常非常慢,甚至影响到在生产环境中使用这种技术,因为对被检测进程的影响太大是不可接受的。
glibc基础的捕获确实可以更有效率,但是对于基本操作如事件时间戳的捕获仍然引入了高开销。相比之下,内核级别的捕获不需要任何上下文切换,并且可以从内核中收集所有必要的上下文信息,比如时间戳。这使得它比任何其他技术都要快得多,因此最适合生产环境。
可伸缩性
如其名,进程级别的捕获要求对每个单独的进程“做某事”。这个“某事”可能有所不同,但它仍然引入了与观察到的进程数量成正比的开销。而使用内核级别的工具则不会出现这种情况。看看图 4-2。
如果你在正确的位置插入内核工具,可以有一个单一的工具插点(在图 4-2 中标记为 2),无论有多少进程在运行。这不仅确保了最大效率,还确保你永远不会遗漏任何事情,因为没有进程能逃过内核级别的捕获。
图 4-2. 系统调用捕获的可伸缩性,进程级别与内核
那么,稳定性和安全性呢?
我们提到过,内核级仪器化更为微妙,因为一个 bug 可能会导致严重问题。您可能会想,“选择基于内核仪器化的 Falco 这样的工具,而不是基于用户级仪器化的产品,是否会增加额外的风险?”
实际上并不是这样。首先,内核级仪器化受益于有文档记录、稳定的挂钩接口,而像基于glibc的捕获方法则不够干净且内在风险更高。它们可能不会导致机器崩溃,但绝对可以导致被检测的进程崩溃,结果通常是糟糕的。除此之外,像 eBPF 这样的技术大大降低了在内核中运行代码的风险,使内核级仪器化即使对于风险规避的用户也是可行的。
内核级仪器化方法
我们希望我们已经说服了您,无论何时可以使用,内核仪器化都是运行时安全的最佳选择。现在的问题是,实施它的最佳机制是什么?在不同可用方法中,两种对于像 Falco 这样的工具是相关的:内核模块或 eBPF 探针。让我们看看这两种方法。
内核模块
可加载内核模块是可以在运行时加载到内核中的代码片段。在 Linux(以及许多其他操作系统)的历史上,模块被广泛用于使内核具有可扩展性、高效性和更小的体积。
内核模块扩展了内核的功能,无需重新启动系统。它们通常用于实现设备驱动程序、网络协议和文件系统。内核模块使用 C 语言编写,并针对特定内核进行编译。换句话说,不可能在一台机器上编译模块然后在另一台机器上使用它(除非它们具有完全相同的内核)。当用户不再需要时,内核模块也可以被卸载以节省内存。
Linux 长期支持内核模块,因此它们甚至与非常旧的 Linux 版本兼容。它们还可以广泛访问内核,这意味着它们几乎没有什么限制可以做什么。这使它们成为 Falco 等运行时安全工具所需的详细信息的收集的绝佳选择。由于它们使用 C 语言编写,内核模块也非常高效,因此在性能重要时是一个不错的选择。
如果您想查看在您的 Linux 系统上加载的模块列表,请使用以下命令:
$ sudo lsmod
eBPF
正如第一章中提到的,eBPF 是伯克利数据包过滤器(BPF)的“下一代”。BPF 于 1992 年为 BSD 操作系统设计用于网络数据包过滤,今天仍然被像 Wireshark 这样的工具使用。BPF 的创新之处在于能够在操作系统内核中执行任意代码。然而,由于这种代码在机器上拥有几乎无限的特权,因此这可能存在潜在风险,必须谨慎使用。
图 4-3 显示了 BPF 如何在内核中安全地运行任意数据包过滤器。
图 4-3. BPF 过滤器部署步骤
让我们来看看这里描述的步骤:
-
用户在诸如 Wireshark 的程序中输入过滤器(例如,
port 80
)。 -
过滤器被输入到编译器中,编译器将其转换为虚拟机的字节码。这在概念上类似于编译 Java 程序,但在使用 BPF 时,程序和虚拟机(VM)指令集都简单得多。例如,我们的
port 80
过滤器在编译后变成了这样:(000) ldh [12] (001) jeq #0x86dd jt 2 jf 10 (002) ldb [20] (003) jeq #0x84 jt 6 jf 4 (004) jeq #0x6 jt 6 jf 5 (005) jeq #0x11 jt 6 jf 23 (006) ldh [54] (007) jeq #0x50 jt 22 jf 8 (008) ldh [56] (009) jeq #0x50 jt 22 jf 23 (010) jeq #0x800 jt 11 jf 23 (011) ldb [23] (012) jeq #0x84 jt 15 jf 13 (013) jeq #0x6 jt 15 jf 14 (014) jeq #0x11 jt 15 jf 23 (015) ldh [20] (016) jset #0x1fff jt 23 jf 17 (017) ldxb 4*([14]&0xf) (018) ldh [x + 14] (019) jeq #0x50 jt 22 jf 20 (020) ldh [x + 16] (021) jeq #0x50 jt 22 jf 23 (022) ret #262144 (023) ret #0
-
为了防止编译的过滤器造成损害,它在注入内核之前由验证器分析。验证器检查字节码,并确定过滤器是否具有危险属性(例如,导致过滤器永不返回的无限循环,消耗大量内核 CPU)。
-
如果过滤器代码不安全,验证器将拒绝它,并向用户返回错误,停止加载过程。如果验证器满意,字节码将传递给虚拟机,虚拟机将其针对每个传入的数据包运行。
eBPF 是 BPF 的更新版本(也更为强大),于 2014 年添加到 Linux,并首次随内核版本 3.18 一同发布。eBPF 将 BPF 的概念提升到新的水平,提供更高效的性能,并利用更新的硬件。最重要的是,通过内核各处的钩子,eBPF 使得除了简单的数据包过滤外,还能支持跟踪、性能分析、调试和安全等用例。它本质上是一个通用的代码执行虚拟机,保证其运行的程序不会造成损害。
这里是 eBPF 相比经典 BPF 引入的一些改进:
-
更先进的指令集,意味着 eBPF 可以运行更复杂的程序。
-
即时(JIT)编译器。经典 BPF 是解释执行的,而验证通过的 eBPF 程序会被转换为本机 CPU 指令。这意味着它们可以以接近本机 CPU 速度运行得更快。
-
能够编写真正的 C 程序,而不仅仅是简单的数据包过滤器。
-
一个成熟的库集,允许您从诸如 Go 之类的语言控制 eBPF。
-
运行子程序和辅助函数的能力。
-
安全访问多个内核对象。eBPF 程序可以安全地“窥视”内核结构以收集信息和上下文,这对于像 Falco 这样的工具非常重要。
-
映射 的概念,可以用来高效、轻松地与用户级交换数据的内存区域。
-
更复杂的验证器,使得 eBPF 程序在保持安全性的同时可以做更多事情。
-
在内核中运行的位置更多,使用诸如 tracepoints、kprobes、uprobes、Linux 安全模块钩子和用户态静态定义跟踪(USDT)等设施,而不仅限于网络堆栈。
eBPF 技术正在迅速发展,并迅速成为扩展 Linux 内核的标准方法。eBPF 脚本灵活安全,运行速度极快,非常适合捕获运行时活动。
Falco 驱动程序
Falco 提供了两种不同的驱动程序实现,分别实现了我们刚才描述的两种方法:一个是内核模块,另一个是 eBPF 探针。这两种实现具有相同的功能,在使用 Falco 时可以互换。因此,我们可以描述它们的工作方式,而不必专注于特定的实现。
高级捕获流程显示在图 4-4 中。
图 4-4. 驱动程序的捕获流程
Falco 驱动程序用于捕获系统调用的方法包括图中标记的三个主要步骤:
-
内核设施称为追踪点截获了系统调用的执行。追踪点使得可以在操作系统内核中的特定位置插入一个钩子,以便每次内核执行到达该点时调用一个回调函数。Falco 驱动程序为系统调用安装了两个追踪点:一个用于系统调用进入内核,另一个用于系统调用退出内核并将控制返回给调用进程。
-
当在追踪点回调函数中时,驱动程序将系统调用参数“打包”到共享内存缓冲区中。在此阶段,系统调用也会被时间戳标记,并且从操作系统中收集额外的上下文信息(例如线程 ID,或者某些套接字系统调用的连接详情)。这个阶段需要非常高效,因为直到驱动程序的追踪点回调函数返回之前,系统调用才不能被执行。
-
共享缓冲区现在包含系统调用数据,并且 Falco 可以通过libscap直接访问它(在第 3 章介绍)。在此阶段不复制任何数据,从而最大程度地减少 CPU 利用率并优化缓存一致性。
在使用 Falco 进行系统调用捕获时需要记住几件事情。第一点是,系统调用在缓冲区中的打包方式是灵活的,并不一定反映原始调用的参数。在某些情况下,驱动程序跳过不需要的参数以最大化性能。在其他情况下,驱动程序添加包含状态、有用上下文或额外信息的字段。例如,Falco 中的 clone
事件包含许多字段,增加了有关新创建进程的信息,如环境变量。
第二点需要记住的是,即使系统调用是驱动程序捕获的数据中最重要的来源,但它们并不是唯一的来源。使用追踪点,驱动程序还钩入内核中的其他地方(如调度程序),以捕获上下文切换和信号传递。看一下这个命令:
$ sysdig evt.type=switch
这行代码显示通过上下文切换追踪点捕获的事件。
您应该使用哪个驱动程序?
如果您不确定应该使用哪个驱动程序,请参考以下简单指南:
-
在您有高 I/O 负载并且关心保持尽可能低的仪器化开销时,请使用内核模块。内核模块的开销比 eBPF 探针低,在生成大量系统调用的机器上,它对运行中的进程的性能影响较小。很难估计内核模块的性能会比 eBPF 探针好多少,因为这取决于进程产生多少系统调用,但是期望在每秒生成大量系统调用的磁盘或网络密集型工作负载中能够明显感觉到差异。
-
当您需要支持早于 Linux 版本 4.12 的内核时,也应使用内核模块。
-
在所有其他情况下,请使用 eBPF 探针。
就是这样!
在容器内捕获系统调用
基于跟踪点的内核级捕获的优势在于它可以看到运行在机器上的任何东西,无论是容器内还是容器外。它没有任何漏网之鱼。而且部署简单,无需在被监视的容器内运行任何东西,也不需要 sidecars。
图 4-5 展示了如何在容器化环境中部署 Falco,其中包含一个简化的图表,显示了基于不同容器运行时的机器上运行三个容器(标记为 1、2 和 3)。
图 4-5. 在容器化环境中部署 Falco
在这种情况下,Falco 通常作为一个容器安装。像 Kubernetes 这样的编排器使得在每个主机上部署 Falco 变得容易,使用诸如 DaemonSets 和 Helm charts 等工具。
当 Falco 容器启动时,它会将驱动程序安装在操作系统中。一旦安装完成,驱动程序可以看到任何容器中任何进程的系统调用,无需进一步的用户操作,因为所有这些系统调用都通过相同的跟踪点。驱动程序中的高级逻辑可以将每个捕获的系统调用归因于其容器,以便 Falco 始终知道哪个容器生成了系统调用。Falco 还从容器运行时获取元数据,便于创建依赖于容器标签、镜像名称和其他元数据的规则。(Falco 还包括基于 Kubernetes 元数据的进一步增强,我们将在下一章讨论。)
运行 Falco 驱动程序
现在您对它们的工作原理有了一个概念,让我们看看如何在本地机器上部署和使用两个 Falco 驱动程序。(如果您想在生产环境中安装 Falco,请参阅第九章和第十章。)
内核模块
默认情况下,Falco 使用内核模块运行,因此如果要使用它作为驱动程序,则不需要额外的步骤。只需运行 Falco,它将加载内核模块。如果要卸载内核模块并加载不同版本,例如因为您构建了自己的定制模块,请使用以下命令:
$ sudo rmmod falco
$ sudo insmod *path/to/your/module/falco.ko*
eBPF 探针
要在 Falco 中启用 eBPF 支持,您需要设置FALCO_BPF_PROBE
环境变量。如果将其设置为空值(FALCO_BPF_PROBE=""
),Falco 将从*~/.falco/falco-bpf.o*加载 eBPF 探针。否则,您可以显式指定 eBPF 探针所在的路径:
export FALCO_BPF_PROBE="*path/to/your/ebpf/probe/falco-bpf.o*"
设置环境变量后,只需正常运行 Falco,它将使用 eBPF 探针。
提示
要确保 Falco 的 eBPF 探针(以及任何其他 eBPF 程序)具有最佳性能,请确保您的内核启用了CONFIG_BPF_JIT
并且net.core.bpf_jit_enable
设置为1
。这会在内核中启用 BPF JIT 编译器,显著加快 eBPF 程序的执行速度。
在无法访问内核的环境中使用 Falco:pdig
在可能的情况下,总是优先选择内核仪器化。但如果您想在禁止访问内核的环境中运行 Falco 会怎样?这在托管容器环境(如 AWS Fargate)中很常见。在这种环境中,安装内核模块不是选项,因为云提供商会阻止它。
对于这些情况,Falco 开发人员实现了一个称为pdig的用户级仪器驱动程序。它建立在 ptrace 之上,因此使用与 strace 相同的方法。与 strace 类似,pdig 可以以两种方式运行:可以运行您在命令行上指定的程序,也可以附加到正在运行的进程。无论哪种方式,pdig 都会以产生与 Falco 兼容的事件流的方式对进程及其子进程进行仪器化。
请注意,与 strace 类似,pdig 需要您为容器运行时启用CAP_SYS_PTRACE
。确保以此能力启动您的容器,否则 pdig 将无法工作。
eBPF 探针和内核模块在全局主机级别工作,而 pdig 在进程级别工作。这可能会使容器仪器化更具挑战性。幸运的是,pdig 可以跟踪已仪器化进程的子进程。这意味着使用 pdig 运行容器的入口点将允许您捕获该容器中任何进程生成的每个系统调用。
pdig 的最大限制是性能。ptrace 功能广泛,但会对仪器化进程引入大量开销。pdig 采用了几种技巧来减少这种开销,但仍比内核级别的 Falco 驱动程序慢得多。
使用 pdig 运行 Falco
您可以像使用 strace 一样,使用要跟踪的进程的路径(以及参数,如果有)。以下是一个示例:
$ pdig [-a] curl https://example.com/
-a
选项启用了完整的过滤器,提供了更丰富的系统调用仪表化集合。出于性能原因,您可能不希望在 Falco 中使用此选项。
您还可以使用 -p
选项附加到正在运行的进程:
$ pdig [-a] -p 1234
要观察任何效果,您需要在单独的进程中运行 Falco。使用 -u
命令行标志:
$ falco -u
这将启用用户空间的仪器化。
Falco 插件
除了系统调用,Falco 还可以收集和处理许多其他类型的数据,例如应用程序日志和云活动流。让我们通过探索此功能的基础机制来结束本章:Falco 的插件框架。
插件是扩展 Falco 输入的一种模块化、灵活的方式。任何人都可以使用它们将新的数据源(本地或远程)添加到 Falco 中。图 4-6 显示了插件在 Falco 捕获堆栈中的位置:它们是 libscap 的输入,并作为捕获系统调用时驱动程序的替代品。
插件实现为符合文档化 API 的共享库。它们允许您添加新的事件源,并使用过滤表达式和 Falco 规则对其进行评估。它们还允许您定义可以从事件中提取信息的新字段。
图 4-6. Falco 插件
插件架构概念
插件是动态共享库(Unix 中的 .so 文件,Windows 中的 .dll 文件),导出 C 调用约定函数。Falco 动态加载这些库并调用导出的函数。插件使用语义化版本控制进行版本管理,以减少回归和兼容性问题。它们可以用任何语言编写,只要导出所需函数即可。首选用于编写插件的语言是 Go,其次是 C/C++。
插件包括两个主要功能模块,也称为 能力:
事件溯源
此功能用于实现新的事件源。事件源可以通过 next
方法“打开”和“关闭”事件流,并将事件返回给 libscap。换句话说,它用于向 Falco 提供新的“内容”。
字段提取
字段提取专注于从其他插件或核心库生成的事件中生成字段。字段是 Falco 规则的基本组成部分,因此暴露新字段相当于扩展 Falco 规则的适用范围到新的领域。例如,JSON 解析就是一个例子,插件可以从任意 JSON 负载中提取字段。关于字段的更多信息,请参见 第六章。
单个插件可以同时提供事件溯源功能、字段提取功能,或者两者兼具。通过实现插件 API 接口中的特定函数来导出这些功能。
为了更容易编写插件,有处理内存管理和类型转换细节的 Go 和 C++ SDK。它们提供了一种简化的方式来实现插件,而不必处理构成插件 API 的所有底层函数的细节。
为了保护 Falco 和其他消费者免受损坏数据的影响,库将尽一切可能验证来自插件的数据。但基于性能考虑,插件是受信任的,并且因为它们与 Falco 在相同的线程和地址空间中运行,可能会导致程序崩溃。Falco 假定作为用户的你可以控制,确保只加载或打包了你审查过的插件。
Falco 如何使用插件
Falco 根据 falco.yaml 中的配置加载插件。截至 2022 年夏季,本书印刷时,如果加载了源插件,则仅处理来自该插件的事件,并且禁用系统调用捕获。此外,运行中的 Falco 实例只能使用一个插件。如果在单台机器上希望 Falco 从多个插件或从插件和驱动程序中收集数据,则需要运行多个 Falco 实例,并为每个实例使用不同的来源。^(3)
Falco 通过 falco.yaml 中的 plugins
属性配置插件。以下是一个示例:
plugins:
- name: cloudtrail
library_path: libcloudtrail.so
init_config: "..."
open_params: "..."
load_plugins: [cloudtrail]
falco.yaml 中的 plugins
属性定义了 Falco 可以加载的插件集合,而 load_plugins
属性控制 Falco 启动时加载哪些插件。
插件加载的机制在 libscap 中实现,并利用操作系统的动态库功能。^(4) 插件加载代码还确保:
-
插件有效(即,它导出了预期的符号集)。
-
插件的 API 版本号与插件框架兼容。
-
对于给定事件源,一次只能加载一个源插件。
-
如果为给定事件源加载了源和提取器插件的混合体,则导出字段具有不会跨插件重叠的唯一名称。
最新的 Falco 插件列表可以在 插件仓库 中找到,属于 Falcosecurity GitHub 组织。截至本文写作时,Falcosecurity 组织正式维护 CloudTrail、GitHub、Okta、Kubernetes 审计日志和 JSON 的插件。除此之外,还有第三方插件可用于 seccomp 和 Docker。
如果你有兴趣编写自己的插件,你可以在 第十四章 找到所有需要知道的内容。如果你迫不及待,只想看代码,你可以在插件库中找到所有当前可用插件的源代码。
结论
恭喜您完成了一个充满大量信息的丰富章节!您在这里学到的内容是理解和操作 Falco 的核心。这也构成了一个坚实的架构基础,每次在 Linux 上运行或部署安全工具时都会很有用。
接下来,您将了解如何向捕获的数据添加上下文,从而使 Falco 的功能更加强大。
^(1) 运行 **man 2 ptrace**
以获取更多信息。
^(2) 更多信息,请参阅 Mathieu Desnoyer 的文章 “Using the Linux Kernel Tracepoints”。
^(3) 请注意,Falco 的开发人员正在努力消除这一限制。因此,将来 Falco 将能够同时从多个插件接收数据或捕获系统调用并同时使用插件。
^(4) 在 Unix 系统中使用 dlopen/dlsym
或在 Windows 中使用 LoadLibrary/GetProcAddress
加载动态库。
第五章:数据丰富化
Falco 的架构允许您从不同的数据源捕获事件,正如您所了解的那样。此过程提供原始数据,可能非常丰富,但如果不与正确的上下文配对,对运行时安全性并不是非常有用。这就是为什么 Falco 首先提取,然后用上下文信息丰富原始数据,以便规则作者可以轻松使用。通常,我们将这些信息称为事件元数据。获取元数据可能是一个复杂的任务,而有效获取它更加复杂。
您已经看到libscap中的系统状态收集功能和libsinsp中实现的状态引擎(在第三章讨论过),这些都是这项活动的核心,但还有更多内容等待探索。在本章中,我们将深入探讨 Falco 堆栈的设计方面,帮助您更好地理解数据丰富化的工作方式。特别是,我们将展示libsinsp用于系统调用事件获取系统、容器和 Kubernetes 元数据的高效分层方法。这使您能够根据您的用例访问您需要的不同上下文信息,例如容器的 ID 或发生可疑事件的 Pod 的名称。最后,我们将展示插件如何实现其自己的数据丰富化机制,为 Falco 的另一个主要数据源打开无限的可能性。
理解 Syscalls 的数据丰富化
理解数据丰富化的工作原理将帮助您充分理解 Falco 的机制。此外,虽然数据丰富化通常可以即插即用,但 Falco 支持的每个上下文都有自己的实现,可能需要特定的配置。了解实现细节将有助于您进行故障排除和优化 Falco。
Falco中的数据丰富化指的是通过解码原始数据或从补充源收集事件元数据,并将其提供给规则引擎的过程。然后,您可以将此元数据用作规则条件和输出格式化的字段。Falco 将收集的元数据组织成一组字段类别,因此您可以轻松识别它们所属的上下文。(您可以在第六章找到支持的字段的完整列表,或者如果您的 Falco 安装在手边,可以键入**falco --list**
来查找。)
数据丰富化的一个重要例子是使用系统调用作为数据源,您在第四章已经了解过。由于 syscalls 对每个应用程序都至关重要,它们几乎发生在每个上下文中。然而,直接由 syscall 提供的信息如果没有上下文是没有用处的,因此收集和连接周围信息变得至关重要。
表 5-1 显示了 Falco 为系统调用收集的不同类别的元数据以及每个数据丰富层关联的字段类别。
表 5-1. 系统调用的上下文元数据
上下文 | 元数据 | 字段类别 |
---|
| 操作系统 | 进程和线程 文件描述符
用户和组
网络接口 | proc
, thread
, fd
, fdlist
, user
, group
|
| 容器 | ID 和名称 类型
镜像名称
特权
挂载点
健康检查 | container
|
| Kubernetes | 命名空间 Pod
复制控制器
服务
副本集
部署 | k8s
|
数据丰富过程在用户空间中进行,涉及 Falco 堆栈的多个组件。最重要的是,每次规则引擎请求时必须立即提供元数据。因此,尝试从其他互补来源即时收集元数据是不可行的,因为这样做可能会阻塞规则引擎和传入事件的整个流程。
因此,数据丰富涉及两个不同的阶段。第一个阶段通过大量收集 Falco 启动时存在的数据来初始化本地状态,第二个阶段则在 Falco 运行时持续更新本地状态。拥有本地状态允许 Falco 立即提取元数据。这种设计在所有实现层中都是共享的,你将在以下章节中了解到。
操作系统元数据
正如你在第三章学到的,libscap 和 libsinsp 协同工作,提供创建和更新由多个状态表组成的分层结构中的上下文信息所需的所有基础设施(如果需要复习,请参见图 3-4)。这些表包括关于以下信息的信息:
-
进程和线程
-
文件描述符
-
用户和组
-
网络接口
从高层次来看,收集系统信息的机制相对简单。在启动时,libscap 的任务之一是扫描进程信息伪文件系统,或称为procfs,它为 Linux 内核数据结构提供用户空间接口,并包含初始化状态表所需的大部分信息。它还使用标准 C 库提供的函数来收集系统信息(不在*/proc*中可用),这些函数最终从底层操作系统获取数据(例如,getpwent
和 getgrent
分别用于用户和组列表,以及 getifaddrs
用于网络接口列表)。此时,初始化阶段完成。
提示
libscap 和 libsinsp 依赖于主机的 procfs 来访问主机的系统信息。这在 Falco 运行在主机上时是默认的,因为它可以直接访问主机的 /proc。然而,在容器中运行 Falco 时,容器内的 /proc 指的是不同的命名空间。在这种情况下,你可以通过 HOST_ROOT
环境变量配置 libscap 来从另一路径读取信息。如果设置了 HOST_ROOT
,libscap 将使用其值作为查找系统路径的基路径。例如,在容器中运行 Falco 时,通常的方法是将主机的 /proc 挂载到容器内的 /host/proc 并设置 HOST_ROOT
为 /host。通过这种设置,libscap 将从 /host/proc 读取信息,因此它将使用主机的 procfs 提供的信息。
之后,libsinsp 通过其状态引擎发挥作用(见 图 5-1)。它通过检查驻留在内核空间的驱动程序提供的持续捕获的系统调用流来更新表格。在初始化阶段之后,Falco 将不需要进行任何系统调用或从 Linux 内核获取更新。这种方法的双重好处是不会在系统中创建噪音并且对性能影响很低。此外,这种技术使 libsinsp 能够以低延迟发现系统变化,从而使 Falco 能够作为流式引擎(其设计的一个重要目标)运行。
最后需要注意的是,libsinsp 在将事件传递给规则引擎之前更新状态表。这确保了当条件或输出需要元数据时,它始终可用且一致。然后,你可以在你在 表格 5-1 中看到的字段类集合中找到系统元数据:proc
、thread
、fd
、fdlist
、user
和 group
。
这组信息表示了使规则作者能够使用系统调用事件的基本元数据。想想看:你如何在规则中使用数值文件描述符?使用文件名要好得多!
图 5-1. 初始化阶段之前(1)和之后(2)的系统状态收集
这个数据丰富层产生的系统信息(即状态表)也是收集容器级上下文信息所必需的。接下来我们会详细看一下这些信息。
容器元数据
额外的基本上下文信息存储在容器运行时层中。容器运行时 是可以在主机操作系统上运行容器的软件组件。通常负责管理容器映像和在系统上运行的容器的生命周期。它还负责管理与每个运行中容器相关的一组信息,并将该信息提供给其他应用程序。
因为 Falco 是一个云原生运行时安全工具,它需要能够处理容器信息。为了实现这一目标,libsinsp 与最常用的容器运行时环境合作,包括 Docker、Podman 和与 CRI 兼容的运行时(如 containerd 和 CRI-O)。
当 libsinsp 在主机上找到一个运行的容器运行时时,几乎在所有情况下,容器元数据增强功能都能够立即正常工作。例如,libsinsp 尝试使用 Docker 的 Unix 套接字 /var/run/docker.sock;如果存在,则 libsinsp 将自动连接并开始抓取容器元数据。libsinsp 对 Podman 和 containerd 也是如此。对于其他与 CRI 兼容的运行时,你需要使用 --cri
命令行标志将套接字路径传递给 Falco(例如,对于 CRI-O,你需要传递 /var/run/crio/crio.sock
)。
提示
如果设置了环境变量HOST_ROOT
,libsinsp 将使用其值作为查找这些 Unix 套接字时的基本路径。例如,当在容器中运行 Falco 时,通常会设置 HOST_ROOT=/host
并将 /var/run/docker.sock 挂载到容器内的 /host/var/run/docker.sock。
无论使用哪种容器运行时,在初始化时,libsinsp 都会请求所有运行中容器的列表,并用它来初始化内部缓存。同时,libsinsp 更新运行进程和线程的状态表,将每个进程和线程与其相应的容器 ID(如果有)关联起来。
libsinsp 通过使用来自驱动程序的系统调用流来处理后续更新(类似于处理系统信息)。由于容器信息始终与进程关联,libsinsp 跟踪所有新的进程和线程。当检测到一个新进程或线程时,它会在内部缓存中查找相应的容器 ID。如果容器 ID 不在缓存中,libsinsp 将查询容器运行时以收集丢失的数据。
最终,发生在容器中的每个系统调用生成的事件都有一个进程或线程 ID,映射到一个容器 ID,并因此映射到容器元数据(如 图 5-2 所示)。因此,当规则引擎需要此元数据时,libsinsp 会从状态表中查找并返回系统信息以及容器元数据。你可以在字段类 container
中找到可用的容器元数据,这些数据可以用于条件和输出格式化。
图 5-2. libsinsp 状态层次结构中的容器信息
注意,字段 container.id
可以包含容器 ID 或特殊值 host
。这个特殊值表示事件未发生在容器内。条件 container.id != host
是表达仅在容器上下文中应用规则的常见方式。
在最终的数据增强层中,Falco 收集与系统调用相关联的 Kubernetes 元数据。我们接下来将看看它是如何工作的。
Kubernetes 元数据
Kubernetes 是云原生计算基金会的旗舰项目,是一个用于管理工作负载和服务的开源平台。它引入了许多新概念,使得管理和扩展集群更加容易,并且是当今最流行的容器编排系统。
Kubernetes 的一个关键特性是将您的应用程序封装在称为 Pods 的对象中,每个 Pod 包含一个或多个容器。Pods 是短暂的对象,您可以快速部署和轻松复制。Kubernetes 中的 Services 是一种抽象,允许您将一组 Pods 暴露为单个网络服务。最后,Kubernetes 允许您将这些和许多其他对象安排到 namespaces 中,这些对象允许将单个集群分区为多个虚拟集群。
虽然这些概念极大地便于管理和自动化集群,但它们也引入了关于应用程序在何处以及如何运行的一系列上下文信息。了解这些信息至关重要,因为如果不知道事件发生的地点(例如,在哪个命名空间或 Pod 中),知道 Kubernetes 集群中发生了某事并没有什么用处。Falco 收集的信息包括容器镜像名称、Pod 名称、命名空间、标签、注释和暴露的服务名称,以尽可能准确地展示您的部署和应用。这对于运行时警报和保护基础设施至关重要,因为您通常更关心显示异常行为的服务或部署,而不是获取容器 ID 或其他难以关联的信息。作为云原生工具,Falco 可以轻松获取此元数据并将其附加到事件中。
与前几节中看到的操作系统和容器元数据收集机制类似,此功能允许 Falco 通过添加 Kubernetes 元数据来丰富系统调用事件。要完全支持 Kubernetes,您必须通过向 Falco 传递两个命令行选项来选择加入:
--k8s-api
(或简写为 -k
)
这通过连接到指定的 API 服务器(例如,http://admin:password@127.0.0.1:8080
)来启用 Kubernetes 支持。
--k8s-api-cert
(或简写为 -K
)
这提供了证书材料来对用户进行身份验证,并(可选地)验证 Kubernetes API 服务器的身份。
更多详细信息请参阅第十章。
提示
当 Falco 在 Pod 中运行时,Kubernetes 会将该信息注入到容器中,因此您只需要设置:
-k https://$(KUBERNETES_SERVICE_HOST)
-K /var/run/secrets/kubernetes.io/serviceaccount/token
大多数安装方法使用此策略自动获取这些值。
一旦配置了 Kubernetes 支持,libsinsp 将从 Kubernetes 获取所有必要的数据,以创建和维护集群状态的本地副本。但是,与从主机本地获取元数据的其他丰富机制不同,libsinsp 必须连接到 Kubernetes API 服务器(通常是远程端点)以获取集群信息。由于这种差异,实现设计需要考虑性能和可扩展性问题。
典型的 Falco 部署(如 图 5-3 所示)在集群中的每个节点上运行一个 Falco 传感器。在启动时,每个传感器连接到 API 服务器以收集集群数据并在本地构建初始状态。从此以后,每个传感器将使用 Kubernetes watch API 定期更新本地状态。
图 5-3. 使用 DaemonSet 实现的 Falco 部署,以确保所有节点运行一个 Pod 的副本
由于 Falco 传感器在集群中分布(每个节点一个),并从 API 服务器获取数据 —— 因为从 Kubernetes 收集某些资源类型可能导致响应巨大,严重影响 API 服务器和 Falco —— libsinsp 具有避免拥塞的机制。首先,在下载每个数据块之间等待一段时间。Falco 允许您通过更改 /etc/falco/falco.yaml 中的一个值来调整该等待时间以及几个其他参数。
更重要的是,可以仅从 API 服务器请求针对目标节点的相关元数据。这非常有用,因为 Falco 的架构是分布式的,所以每个传感器只需从事件发生的节点获取数据。如果您希望在具有数千个节点的集群上扩展 Falco,则这种优化至关重要。要启用此功能,请在 Falco 命令行参数中添加 --k8s-node
标志,并将当前节点名称作为值传递。通常可以通过 Kubernetes 的 Downward API 轻松获取此名称。^(3)
如果不包括 --k8s-node
标志,libsinsp 仍然能够从 Kubernetes 获取数据,但每个 Falco 传感器将不得不请求整个集群的数据。这可能会在大型集群上引入性能损失,因此我们强烈不建议这样做。(您将在 第三部分 了解更多关于在生产 Kubernetes 集群上运行 Falco 的内容。)
当 Kubernetes 元数据可用时,您将在 k8s
字段类中找到它们。Falco 的许多默认规则在其条件中包含 k8s
字段。当使用 -pk
命令行选项运行 Falco 时,Falco 会自动将最关键的 Kubernetes 元数据附加到所有通知的输出中,如下所示,我们已对其进行了美化以提高可读性(更多信息请参阅 “输出设置”):
15:29:40.515013896: Notice System user ran an interactive command
(user=bin user_loginuid=-1 command=login container_id=46c99eea62a8
image=docker.io/library/nginx)
k8s.ns=default k8s.pod=my-app-84d64cb8fb-zmxgz
container=46c99eea62a8
这个输出是刚学习到的复杂机制的结果,它允许你获取准确和上下文化的信息,以立即识别刚刚发生的事件及其位置。
到目前为止,我们只讨论了 Falco 对系统调用数据丰富化的过程。尽管这对大多数用户可能是最相关的信息,但你应该知道,Falco 还提供了自定义丰富化机制。接下来我们快速看一下如何实现这些机制。
使用插件进行数据丰富化
插件可以通过添加新的数据源和定义新的字段来扩展 Falco,以描述如何使用这些新事件。正如你在第四章中所了解到的,一个提供字段提取能力的插件可以处理其他插件或核心库提供的事件。虽然现在可能还不明显,但具有这种能力的插件已具备提供自定义数据丰富化机制的一切条件。首先,它可以从任何数据源接收数据。其次,它可以定义新的字段。基本上,它允许插件作者实现逻辑来返回这些字段的值,从而潜在地提供额外的元数据。这打开了实现自定义数据丰富化的可能性。
当这样一个插件运行时,libsinsp 会为每个传入的事件调用插件函数进行字段提取。该函数接收事件的原始数据负载和规则引擎所需的字段列表。插件 API 接口并不对使提取过程工作施加其他限制。尽管数据丰富化在上述流程中是可能的,但插件作者仍需考虑使用情况的所有影响;例如,插件将需要管理本地状态和随后的更新。因此,提取字段和丰富化事件完全取决于插件作者。API 仅提供了基本工具。
第十四章 展示了如何实现一个插件。如果你对此感兴趣,我们建议先阅读下一章关于字段和过滤器,这样你就能更全面地了解数据提取的工作方式。
结论
本章展示了 Falco 内部工作的方式,以提供丰富的元数据。Falco 将这些元数据作为字段提供,你可以在规则的条件中使用这些字段。继续阅读,了解如何使用字段仅过滤那些真正与你需求相关的事件。
^(1) 在较早的 Falco 版本中,Kubernetes 审计日志是一个内置的数据源。从 Falco 0.32 开始,这个数据源已被重构为一个插件。
^(2) 容器运行时接口(CRI) 是由 Kubernetes 引入的插件接口,允许 kubelet 使用任何实现 CRI 的容器运行时。
^(3) 下行 API 允许容器在不使用 Kubernetes API 服务器的情况下获取有关自身或集群的信息。它可以通过环境变量暴露当前节点名称,这个名称可以在 Falco 命令行参数中使用。
第六章:字段和过滤器
终于是时候将你在前几章学到的所有理论付诸实践了。在本章中,你将学习 Falco 过滤器的相关知识:它们是什么,如何工作以及如何使用它们。
过滤器是 Falco 的核心。它们也是一个强大的调查工具,可以在多种其他工具中使用,比如 sysdig。因此,我们预期你即使在完成本书后,也会经常回来查阅本章内容——因此我们设计它可以用作参考。例如,它包含了过滤语言提供的所有运算符和数据类型的表格,设计用于快速查询,以及 Falco 最有用字段的详细文档列表。本章的内容几乎每次你编写 Falco 规则时都会派上用场,因此确保将其加入书签!
什么是过滤器?
让我们从一个半正式的定义开始:
Falco 中的过滤器是一个包含一系列比较的条件,这些比较由布尔运算符连接。每个比较评估从输入事件中提取的字段与一个常量之间的关系运算符。过滤器中的比较按从左到右的顺序进行评估,但可以使用括号定义优先级。过滤器应用于输入事件,并返回一个布尔结果,指示事件是否与过滤器匹配。
哎呀。这个描述非常枯燥并且有些复杂。但如果我们借助一些例子来解释,你会发现并不难。让我们从第一句开始:
Falco 中的过滤器是一个包含一系列比较的条件,这些比较由布尔运算符连接。
这只是意味着一个过滤器看起来像这样:
A = B and not C != D
换句话说,如果你可以在任何编程语言中编写一个if
条件,过滤器语法看起来会非常熟悉。接下来的句子是:
每个比较评估从输入事件中提取的字段与一个常量之间的关系运算符。
这告诉我们,Falco 的过滤语法基于字段的概念,我们将在本章后面详细描述。字段名称采用点语法,并出现在每个比较的左侧。右侧是将与字段进行比较的常量值。以下是一个示例:
proc.name = emacs or proc.pid != 1234
继续下一个:
过滤器中的比较按从左到右的顺序进行评估,但可以使用括号定义优先级。
这意味着你可以使用括号来组织你的过滤器。例如:
proc.name = emacs or (proc.name = vi and container.name=redis)
同样,这与在你喜爱的编程语言中使用逻辑表达式内部的括号完全相同。现在是最后一句:
过滤器应用于输入事件,并返回一个布尔结果,指示事件是否与过滤器匹配。
当您在 Falco 规则中指定一个过滤器时,该过滤器将应用于每个输入事件。例如,如果您使用 Falco 的驱动程序之一,则过滤器将应用于每个系统调用。过滤器评估系统调用并返回布尔值:true
表示事件满足过滤器(我们说过滤器匹配事件),而false
表示过滤器拒绝或丢弃事件。例如,此过滤器:
proc.name = emacs or proc.name = vi
匹配(返回true
),由名为emacs
或vi
的进程生成的每个系统调用。
这基本上就是您需要在高层次上了解的全部。现在让我们深入了解细节。
过滤语法参考
从语法角度来看,正如我们提到的,编写 Falco 过滤器非常类似于在任何编程语言中编写if
条件,因此如果您具有基本的编程经验,不应该期望有任何重大的惊喜。然而,有一些区域是特定于您在 Falco 中进行匹配类型的。本节详细讨论了语法,为您提供了全面的图片。
关系运算符
表 6-1 提供了所有可用关系运算符的参考,包括每个运算符的示例。
表 6-1. Falco 的关系运算符
运算符 | 描述 | 示例 |
---|---|---|
= , != | 一般的相等性/不等性运算符。可用于所有类型的字段。 | proc.name = emacs |
<= , < , >= , > | 数字比较运算符。仅可用于数字字段。 | evt.buflen > 100 |
contains | 仅可用于字符串字段。对字段值进行区分大小写的字符串搜索,如果字段值包含指定常量则返回true 。 | fd.filename 包含 passwd |
icontains | 类似于contains ,但不区分大小写。 | user.name 包含 john |
bcontains | 类似于contains ,但允许您在二进制缓冲区上执行检查。 | evt.buf bcontains DEADBEEF |
startswith | 仅可用于字符串字段。如果指定的常量与字段值的开头匹配则返回true 。 | fd.directory 开始于 "/etc" |
bstartswith | 类似于startswith ,但允许您在二进制缓冲区上执行检查。 | evt.buf bstartswith DEADBEEF |
endswith | 仅可用于字符串字段。如果指定的常量与字段值的结尾匹配则返回true 。 | fd.filename 结尾为 ".key" |
in | 将字段值与多个常量进行比较,如果其中一个或多个常量等于字段值则返回true 。可用于所有字段,包括数字字段和字符串字段。 | proc.name 在 (vi, emacs) |
intersects | 当具有多个值的字段包含至少一个与提供的常量之一匹配的值时返回true 。 | ka.req.pod.volumes.hostpath 交集 (/proc, /var/run/docker.sock) |
| pmatch
| 如果常量之一是字段值的前缀,则返回 true
。注意:pmatch
可用作 in
运算符的替代方案,并且在大量常数的情况下执行效果更好,因为它在内部实现为前缀树,而不是多个比较。 | fd.name pmatch (/var/run, /etc, /lib, /usr/lib)
fd.name = /var/run/docker
成功,因为 /var/run
是 /var/run/docker
的前缀。
fd.name = /boot
不能成功,因为没有任何常数是 /boot
的前缀。
fd.name = /var
不能成功,因为没有任何常数是 /var
的前缀。 |
exists | 如果输入事件存在给定字段,则返回 true 。 | evt.res exists |
---|---|---|
glob | 根据 Unix shell 通配符模式将给定字符串与字段值匹配。有关详细信息,请在终端中输入 **man 7 glob** 。 | fd.name glob '/home/*/.ssh/*' |
逻辑运算符
您可以在 Falco 过滤器中使用的逻辑运算符非常直接,不包含任何意外。表 6-2 列出了它们并提供了示例。
表格 6-2. Falco 的逻辑运算符
运算符 | 示例 |
---|---|
and | proc.name = emacs and proc.cmdline contains myfile.txt |
or | proc.name = emacs or proc.name = vi |
not | not proc.name = emacs |
字符串和引用
字符串常量可以不使用引号指定:
proc.name = emacs
引号可以用于括起包含空格或特殊字符的字符串。单引号和双引号都被接受。例如:
proc.name = "my process" or proc.name = 'my process'
这意味着您可以在字符串中包含引号:
evt.buffer contains '"'
字段
如您所见,Falco 过滤器并不复杂。但是,它们非常灵活和强大。这种强大来自于您可以在过滤条件中使用的字段。Falco 为您提供访问多种字段的权限,每个字段公开了 Falco 捕获的输入事件的属性。由于字段非常重要,让我们看看它们是如何工作和组织的。然后我们将讨论何时以及使用哪些字段。
参数字段与丰富字段
字段将输入事件的属性公开为类型化值。例如,字段可以是字符串(如进程名称)或数字(如进程 ID)。
在最高级别上,Falco 提供了两类字段。第一类包括通过解析输入事件获得的字段。系统调用参数,例如 open
系统调用的文件名或 read
系统调用的缓冲区参数,都是此类字段的示例。您可以使用以下语法访问这些字段,其中 *X*
是要访问的参数的名称:
evt.arg.*X*
或者,其中 *N*
是参数的位置:
evt.arg[*N*]
例如:
evt.arg.name = /etc/passwd
evt.arg[1] = /etc/passwd
要了解特定事件类型支持哪些参数,请使用 sysdig。sysdig 中事件的输出行将显示所有参数及其名称。
第二类别包括从libsinsp捕获系统调用和其他事件时执行的丰富化过程中派生的字段,详见第五章。Falco 导出许多字段,这些字段公开了libsinsp的线程和文件描述符表的内容,为从驱动程序接收的事件添加了丰富的上下文。
为了帮助您理解它是如何工作的,让我们以proc.cwd
字段为例。对于 Falco 捕获的每个系统调用,此字段包含发出系统调用的进程的当前工作目录。如果您想捕获当前在特定目录内运行的所有进程生成的系统调用,这非常方便;例如:
proc.cwd = /tmp
进程的工作目录不是系统调用的一部分,因此要公开此字段,需要跟踪进程的工作目录,并将其附加到进程生成的每个系统调用中。这反过来涉及四个步骤:
-
当一个进程启动时,收集其工作目录,并将其存储在线程表中的进程条目中。
-
跟踪进程何时更改其工作目录(通过拦截和解析
chdir
系统调用),并相应地更新线程表条目。 -
解析每个系统调用的线程 ID,以识别相应的线程表条目。
-
返回线程表条目的
cwd
值。
libsinsp做了所有这些工作,这意味着proc.cwd
字段可用于每个系统调用,而不仅仅是像chdir
这样与目录相关的调用。Falco 为向您公开此字段所做的大量工作令人印象深刻!
基于丰富化的过滤非常强大,因为它允许您根据并非包含在系统调用本身中但对安全策略非常有用的属性来过滤系统调用(以及任何其他事件)。例如,以下过滤器允许您捕获读取或写入*/etc/passwd*的系统调用:
evt.is_io=true and fd.name=/etc/passwd
即使这些系统调用最初不包含任何有关文件名的信息(它们操作文件描述符),它们也能正常工作。箱中提供的数百种基于丰富化的字段是 Falco 如此强大和多功能的主要原因。
强制字段与可选字段
一些字段存在于每个输入事件中,无论事件类型或族群如何,您都能保证找到它们。此类字段的示例包括evt.ts
、evt.dir
和evt.type
。
然而,大多数字段是可选的,并且仅存在于某些输入事件类型中。通常情况下,你不需要担心这一点,因为不存在的字段会在不生成错误的情况下仅评估为false
。例如,以下检查将对所有没有名为name
的参数的事件评估为false
:
evt.arg.name contains /etc
但在某些情况下,您可能想要显式检查字段是否存在。一个原因是解决像evt.arg.name != /etc
这样的模糊性,以确定对于没有名为name
的参数的事件,是否返回true
或false
。您可以通过使用exists
关系运算符来回答这类问题:
evt.arg.name exists and evt.arg.name != /etc
字段类型
字段具有类型,用于验证值并确保过滤器的语法正确性。看下面的过滤器:
proc.pid = hello
Falco 和 sysdig 将使用以下错误拒绝这个:
filter error at position 16: hello is not a valid number
之所以会发生这种情况是因为proc.pid
字段的类型为INT64
,所以其值必须是整数。类型系统还允许 Falco 通过理解字段背后的含义来改善某些字段的渲染。例如,evt.arg.res
的类型是ERRNO
,默认情况下是一个数字。然而,可能时,Falco 会将其解析为一个错误代码字符串(如EAGAIN
),从而提高字段的可读性和可用性。
当我们研究关系运算符时,我们注意到一些与大多数编程语言中的运算符非常相似,而其他一些则是 Falco 过滤器独有的。字段类型也是如此。表 6-3 列出了您在 Falco 过滤器字段中可能遇到的类型。
表 6-3. 字段类型
类型 | 描述 |
---|---|
INT8 , INT16 , INT32 , INT64 , UINT8 , UINT16 , UINT32 , UINT64 , DOUBLE | 像您喜欢的编程语言中的数值类型。 |
CHARBUF | 可打印字符缓冲区。 |
BYTEBUF | 一个原始字节缓冲区,不适合打印。 |
ERRNO | 一个INT64 值,可能时,会被解析为错误代码。 |
FD | 一个INT64 值,可能时,会被解析为文件描述符的值。例如,对于文件,这会被解析为文件名;对于套接字,这会被解析为 TCP 连接元组。 |
PID | 一个INT64 值,可能时,会被解析为进程名称。 |
FSPATH | 包含相对或绝对文件系统路径的字符串。 |
SYSCALLID | 一个 16 位系统调用 ID。可能时,该值会被解析为系统调用名称。 |
SIGTYPE | 一个 8 位信号编号,可能时,会被解析为信号名称(例如SIGCHLD )。 |
RELTIME | 一个相对时间,精确到纳秒级,呈现为可读字符串。 |
ABSTIME | 绝对时间间隔。 |
PORT | 一个 TCP/UDP 端口。可能时,会被解析为协议名称。 |
L4PROTO | 一个 1 字节的 IP 协议类型。可能时,会解析为 L4 协议名称(TCP, UDP)。 |
BOOL | 一个布尔值。 |
IPV4ADDR | 一个 IPv4 地址。 |
DYNAMIC | 表示字段类型根据上下文可变。用于像evt.rawarg 这样的通用字段。 |
FLAGS8 , FLAGS16 , FLAGS32 | 标志字(即,使用二进制编码的一组标志作为数字)。在可能的情况下,将其转换为可读字符串(例如,O_RDONLY|O_CLOEXEC )。字符串的解析取决于上下文,因为事件可以注册自己的标志值。因此,例如,lseek 系统调用事件的标志将转换为SEEK_END ,SEEK_CUR 等值,而sockopt 的标志将转换为SOL_SOCKET ,SOL_TCP 等等。 |
UID | 当可能时,将 Unix 用户 ID 解析为用户名。 |
GID | 当可能时,将 Unix 组 ID 解析为组名。 |
IPADDR | 一个 IPv4 或 IPv6 地址。 |
IPNET | 一个 IPv4 或 IPv6 网络。 |
MODE | 用于表示文件模式的 32 位位掩码。 |
您如何找出要使用的字段的类型?最好的方法是使用 Falco 的--list
和-v
选项调用:
$ falco --list -v
这将打印字段的完整列表,包括每个条目的类型信息。
使用字段和过滤器
现在您已经了解了过滤器和字段,让我们看看如何在实践中使用它们。我们将重点放在 Falco 和 sysdig 上。
Falco 中的字段和过滤器
字段和过滤器是 Falco 规则的核心。字段用于表达规则的条件,既是条件的一部分,也是输出的一部分。为了演示如何使用它们,我们将制定我们自己的规则。
假设我们希望 Falco 在每次尝试更改文件权限并使其对其他用户可执行时通知我们。发生这种情况时,我们想知道已更改的文件的名称,文件的新模式以及导致问题的用户的名称。我们还想知道模式更改尝试是否成功。
这是规则:
- rule: File Becoming Executable by Others
desc: Attempt to make a file executable by other users
condition: >
(evt.type=chmod or evt.type=fchmod or evt.type=fchmodat)
and evt.arg.mode contains S_IXOTH
output: >
attempt to make a file executable by others
(file=%evt.arg.filename mode=%evt.arg.mode user=%user.name
failed=%evt.failed)
priority: WARNING
condition
部分是指定规则过滤器的地方。
文件模式,包括可执行位,是使用chmod
系统调用或其变体进行更改的。因此,过滤器的第一部分选择了类型为chmod
,fchmod
或fchmodat
的事件:
evt.type=chmod or evt.type=fchmod or evt.type=fchmodat
现在我们已经有了正确的系统调用,我们想要接受仅设置了“其他”可执行位的子集。阅读chmod
手册页显示我们需要检查的标志是S_IXOTH
。我们通过使用contains
操作符来确定其是否存在:
evt.arg.mode contains S_IXOTH
将这两个片段组合并使用and
得到完整的过滤器。简单!
现在,让我们将注意力集中在规则的output
部分。这是我们告诉 Falco 当规则条件返回true
时在屏幕上打印什么的地方。您会注意到,这只是一个类似于printf
的字符串,其中混合了常规文本和字段,这些字段的值将在最终消息中解析:
attempt to make a file executable by others (file=%evt.arg.filename
mode=%evt.arg.mode user=%user.name failed=%evt.failed)
您唯一需要记住的是,在输出字符串中需要使用%
字符作为字段名的前缀;否则,它们将仅被视为字符串的一部分。
是时候让您尝试一下了!将前述规则保存在名为ch6.yaml的文件中。之后,在终端中运行以下命令行:
$ sudo falco -r ch6.yaml
然后,在另一个终端中,运行以下两个命令:
$ echo test > test.txt
$ chmod o+x test.txt
这是您将在 Falco 终端中获得的输出:
17:26:43.796934201: Warning attempt to make a file executable by others
(file=/home/loris/test.txt mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP
|S_IRGRP|S_IXUSR|S_IWUSR|S_IRUSR user=root failed=false)
恭喜,您刚刚执行了自己的 Falco 检测!请注意evt.arg.mode
和evt.failed
如何以人类可读的方式显示,即使在内部它们是数字。这显示了过滤器/字段类型系统的强大功能。
sysdig 中的字段和过滤器
在第四章中提供了 sysdig 的简介(如果您需要复习,请参见“sysdig”)。这里我们将特别看看 sysdig 中如何使用过滤器和字段。
虽然 Falco 基于规则的概念并在规则匹配时通知用户,sysdig 专注于调查、故障排除和威胁狩猎工作流程。在 sysdig 中,您可以使用过滤器来限制输入,并且(可选地)使用字段格式化来控制输出。这两者的结合为调查提供了极大的灵活性。
在 sysdig 中,过滤器是在命令行末尾指定的:
$ sudo sysdig proc.name=echo
使用-p
命令行标志提供输出格式化,并使用与我们刚刚在讨论 Falco 输出时描述的相同的printf
-类似语法:
$ sudo sysdig -p"type:%evt.type proc:%proc.name" proc.name=echo
请记住的一件重要事情是,当使用-p
标志时,sysdig 只会为所有指定过滤器存在的事件打印输出行。因此,这个命令:
$ sudo sysdig -p"%evt.res %proc.name"
仅为具有返回值和进程名称的事件打印一行,例如跳过所有系统调用“enter”事件。如果您关心查看所有事件,请在格式字符串的开头放置星号(*
):
$ sudo sysdig -p"*%evt.res %proc.name"
当字段缺失时,它将显示为<NA>
。
当使用-p
未指定格式时,sysdig 以标准格式显示输入事件,方便地包括所有参数和参数名,用于每个系统调用。以下是一个openat
系统调用的 sysdig 输出行示例,其中以粗体突出显示系统调用参数以提高可见性:
4831 20:50:01.473556825 2 cat (865.865) < openat fd=7(<f>/tmp/myfile.txt)
dirfd=-100(AT_FDCWD) name=/tmp/myfile.txt flags=1(O_RDONLY) mode=0 dev=4
每个参数都可以使用evt.arg
语法在过滤器中使用:
$ sudo sysdig evt.arg.name=/tmp/myfile.txt
作为更高级的示例,让我们将我们在前一节为 Falco 创建的文件被其他人设置为可执行规则转换为 sysdig 命令行:
$ sudo sysdig -p"attempt to make a file executable by others \
(file=%evt.arg.filename mode=%evt.arg.mode user=%user.name \
failed=%evt.failed)" \
"(evt.type=chmod or evt.type=fchmod or evt.type=fchmodat) \
and evt.arg.mode contains S_IXOTH"
这展示了在创建新规则时如何将 sysdig 作为开发工具使用的简便性。
Falco 的最有用字段
本节介绍了按类别组织的一些最重要的 Falco 字段的精选列表。您可以在编写过滤器时将此列表作为参考。要获取包括所有插件字段的完整列表,请在命令行中使用以下命令:
$ falco --list -v
一般情况
列在表 6-4 中的字段适用于每个事件,并包括事件的一般属性。
表 6-4. evt
过滤器类字段
字段名 | 描述 |
---|---|
evt.num | 事件编号。 |
evt.time | 事件时间戳,包括纳秒部分的字符串。 |
evt.dir | 事件方向;可以是 > 表示进入事件,或 < 表示退出事件。 |
evt.type | 事件名称(例如 open )。 |
evt.cpu | 发生此事件的 CPU 编号。 |
evt.args | 所有事件参数,聚合为单个字符串。 |
evt.rawarg | 事件参数之一,按名称指定(例如 evt.rawarg.fd )。 |
evt.arg | 事件参数之一,按名称或编号指定。某些事件(如返回代码或文件描述符)将在可能时转换为文本表示(例如 evt.arg.fd 或 evt.arg[0] )。 |
evt.buffer | 事件的二进制数据缓冲区(例如 read、recvfrom 等)。在过滤器中使用 contains 来搜索 I/O 数据缓冲区。 |
evt.buflen | 具有二进制数据缓冲区的事件的缓冲区长度,如 read 、recvfrom 等。 |
evt.res | 事件返回值,作为字符串。如果事件失败,则结果是错误代码字符串(例如 ENOENT );否则,结果是字符串 SUCCESS 。 |
evt.rawres | 事件返回值,作为数字(例如 -2 )。用于范围比较时很有用。 |
evt.failed | 对于返回错误状态的事件为 true 。 |
Processes
此类中的字段包含关于进程和线程的所有信息。 Table 6-5 中的信息主要来自内存中 libsinsp 构建的进程表。
Table 6-5. proc
过滤器类字段
字段名 | 描述 |
---|---|
proc.pid | 生成事件的进程 ID。 |
proc.exe | 第一个命令行参数(通常是可执行文件名或自定义名称)。 |
proc.name | 生成事件的可执行文件的名称(不包括路径)。 |
proc.args | 启动生成事件进程时传递的命令行参数。 |
proc.env | 生成事件的进程的环境变量。 |
proc.cwd | 事件的当前工作目录。 |
proc.ppid | 生成事件的进程的父进程 PID。 |
proc.pname | 生成事件进程的父进程的名称(不包括路径)。 |
proc.pcmdline | 生成事件进程的父进程的完整命令行(proc.name + proc.args )。 |
proc.loginshellid | 当前进程祖先中最老的 shell 的 PID(如果存在)。此字段可用于区分不同的用户会话,并与像 spy_user 这样的凿子一起使用。 |
thread.tid | 生成事件的线程 ID。 |
thread.vtid | 生成事件的线程 ID,在其当前 PID 命名空间中可见。 |
proc.vpid | 生成事件的进程 ID,在其当前 PID 命名空间中可见。 |
proc.sid | 生成事件的进程的会话 ID。 |
proc.sname | 当前进程的会话领导者的名称。这要么是具有pid=proc.sid 的进程,要么是具有与当前进程相同会话 ID 的最年长的祖先。 |
proc.tty | 进程的控制终端。对于没有终端的进程,这是0 。 |
文件描述符
表 6-6 列出了与文件描述符相关的字段,这些字段是 I/O 的基础。包含有关文件和目录、网络连接、管道和其他类型的进程间通信的详细信息的字段都可以在这个类中找到。
表 6-6. fd
过滤类字段
字段名 | 描述 |
---|---|
fd.num | 标识文件描述符的唯一编号。 |
fd.typechar | 文件描述符的类型,以单个字符表示。可以是f 表示文件,4 表示 IPv4 套接字,6 表示 IPv6 套接字,u 表示 Unix 套接字,p 表示管道,e 表示 eventfd,s 表示 signalfd,l 表示 eventpoll,i 表示 inotify,或o 表示未知。 |
fd.name | 文件描述符的完整名称。如果是文件,则此字段包含完整路径。如果是套接字,则此字段包含连接元组。 |
fd.directory | 如果文件描述符是文件,则包含它的目录。 |
fd.filename | 如果文件描述符是文件,则是不带路径的文件名。 |
fd.ip | (仅过滤) 匹配文件描述符的 IP 地址(客户端或服务器)。 |
fd.cip | 客户端的 IP 地址。 |
fd.sip | 服务器的 IP 地址。 |
fd.lip | 本地 IP 地址。 |
fd.rip | 远程 IP 地址。 |
fd.port | (仅过滤) 匹配文件描述符的端口(客户端或服务器)。 |
fd.cport | 对于 TCP/UDP 文件描述符,客户端的端口。 |
fd.sport | 对于 TCP/UDP 文件描述符,服务器的端口。 |
fd.lport | 对于 TCP/UDP 文件描述符,本地端口。 |
fd.rport | 对于 TCP/UDP 文件描述符,远程端口。 |
fd.l4proto | 套接字的 IP 协议。可以是tcp ,udp ,icmp 或raw 。 |
用户和用户组
表 6-7 列出了user
和group
过滤类中的字段。
表 6-7. user
和group
过滤类字段
字段名 | 描述 |
---|---|
user.uid | 用户的 ID |
user.name | 用户的名称 |
group.gid | 用户组的 ID |
group.name | 用户组的名称 |
容器
container
类中的字段(表 6-8)可用于与容器相关的一切,包括获取 ID、名称、标签和挂载。
表 6-8. container
过滤类字段
字段名 | 描述 |
---|---|
container.id | 容器 ID。 |
container.name | 容器名称。 |
container.image | 容器镜像名称(例如,Docker 中的falcosecurity/falco:latest )。 |
container.image.id | 容器镜像 ID(例如,6f7e2741b66b )。 |
container.privileged | 运行为特权的容器为 true ,否则为 false 。 |
container.mounts | 一组以空格分隔的挂载信息。列表中每个项的格式为 *<source>*:*<dest>*:*<mode>*:*<rdrw>*:*<propagation>* 。 |
container.mount | 单个挂载的信息,由编号(例如,container.mount[0] )或挂载源(例如,container.mount[/usr/local] )指定。路径名可以是通配符(例如,container.mount[/usr/local/*] ),在这种情况下,将返回第一个匹配的挂载。信息的格式为 *<source>*:*<dest>*:*<mode>*:*<rdrw>*:*<propagation>* 。如果没有指定索引或匹配提供的源的挂载,则返回字符串 "none" 而不是 NULL 值。 |
container.image.repository | 容器镜像仓库(例如,falcosecurity/falco )。 |
container.image.tag | 容器镜像标签(例如,stable ,latest )。 |
container.image.digest | 容器镜像注册表摘要(例如,sha256:d977378f890d445c15e51795296e4e5062f109ce6da83e0a355fc4ad8699d27 )。 |
Kubernetes
当 Falco 配置为与 Kubernetes API 服务器接口时,可以使用此类中的字段(列在 Table 6-9)来获取有关 Kubernetes 对象的信息。
Table 6-9. k8s
过滤器类字段
字段名称 | 描述 |
---|---|
k8s.pod.name | Kubernetes Pod 名称。 |
k8s.pod.id | Kubernetes Pod ID。 |
k8s.pod.label | Kubernetes Pod 标签(例如,k8s.pod.label.foo )。 |
k8s.rc.name | Kubernetes ReplicationController 名称。 |
k8s.rc.id | Kubernetes ReplicationController ID。 |
k8s.rc.label | Kubernetes ReplicationController 标签(例如,k8s.rc.label.foo )。 |
k8s.svc.name | Kubernetes 服务名称。可能返回多个值,已连接。 |
k8s.svc.id | Kubernetes Service ID。可能返回多个值,已连接。 |
k8s.svc.label | Kubernetes Service 标签(例如,k8s.svc.label.foo )。可能返回多个值,已连接。 |
k8s.ns.name | Kubernetes 命名空间名称。 |
k8s.ns.id | Kubernetes 命名空间 ID。 |
k8s.ns.label | Kubernetes 命名空间标签(例如,k8s.ns.label.foo )。 |
k8s.rs.name | Kubernetes ReplicaSet 名称。 |
k8s.rs.id | Kubernetes ReplicaSet ID。 |
k8s.rs.label | Kubernetes ReplicaSet 标签(例如,k8s.rs.label.foo )。 |
k8s.deployment.name | Kubernetes 部署名称。 |
k8s.deployment.id | Kubernetes 部署 ID。 |
k8s.deployment.label | Kubernetes 部署标签(例如,k8s.rs.label.foo )。 |
CloudTrail
当配置 CloudTrail 插件时,可以使用 cloudtrail
类中的字段(列在 Table 6-10)来构建 AWS 检测的过滤器和格式化程序。
Table 6-10. cloudtrail
过滤器类字段
字段名称 | 描述 |
---|---|
ct.error | 事件的错误代码。如果没有错误,则为 "" 。 |
ct.src | CloudTrail 事件的来源(在 JSON 中为 eventSource )。 |
ct.shortsrc | CloudTrail 事件的来源(在 JSON 中为 eventSource ),不包括 .amazonaws.com 后缀。 |
ct.name | CloudTrail 事件的名称(在 JSON 中为 eventName )。 |
ct.user | CloudTrail 事件的用户(在 JSON 中为 userIdentity.userName )。 |
ct.region | CloudTrail 事件的区域(在 JSON 中为 awsRegion )。 |
ct.srcip | 生成事件的 IP 地址(在 JSON 中为 sourceIPAddress )。 |
ct.useragent | 生成事件的用户代理(在 JSON 中为 userAgent )。 |
ct.readonly | 如果事件仅读取信息(例如 DescribeInstances ),则为 true ;如果事件修改状态(例如 RunInstances 、CreateLoadBalancer ),则为 false 。 |
s3.uri | S3 URI (s3://*<bucket>*/*<key>* )。 |
s3.bucket | S3 事件的存储桶名称。 |
s3.key | S3 的键名。 |
ec2.name | EC2 实例的名称,通常存储在实例标签中。 |
Kubernetes 审计日志
关于 Kubernetes 审计日志的字段(列在 表 6-11 中)在配置了 k8saudit 插件时可用。k8saudit 插件负责将 Falco 与 Kubernetes 审计日志设施接口化。插件导出的字段可用于监视多种类型的 Kubernetes 活动。
Table 6-11. k8saudit
过滤器类字段
Field name | 描述 |
---|---|
ka.user.name | 执行请求的用户名称 |
ka.user.groups | 用户所属的组 |
ka.verb | 正在执行的操作 |
ka.uri | 从客户端发送到服务器的请求 URI |
ka.uri.param | URI 中给定查询参数的值(例如,当 uri=/foo?key=val 时,ka.uri.param[key] 是 val ) |
ka.target.name | 目标对象的名称 |
ka.target.namespace | 目标对象的命名空间 |
ka.target.resource | 目标对象的资源 |
ka.req.configmap.name | 当请求对象指向 ConfigMap 时,ConfigMap 的名称 |
ka.req.pod.containers.image | 当请求对象指向 Pod 时,容器的镜像 |
ka.req.pod.containers.privileged | 当请求对象指向 Pod 时,所有容器的 privileged 标志的值 |
ka.req.pod.containers .add_capabilities | 当请求对象指向 Pod 时,在运行容器时添加的所有能力 |
ka.req.role.rules | 当请求对象指向角色或集群角色时,与角色关联的规则 |
ka.req.role.rules.verbs | 当请求对象指向角色或集群角色时,与角色规则关联的动词 |
ka.req.role.rules .resources | 当请求对象指向角色或集群角色时,与角色规则关联的资源 |
ka.req.service.type | 当请求对象涉及服务时,服务类型 |
ka.resp.name | 响应对象的名称 |
ka.response.code | 响应代码 |
ka.response.reason | 响应原因(通常仅在失败时出现) |
结论
恭喜,你现在已经是一个过滤专家了!此时,你应该能够阅读和理解 Falco 规则,并且离能够编写自己的规则更近了一步。在下一章中,我们将专注于 Falco 的输出。
第七章:Falco 规则
第 3(ch03.xhtml#understanding_falcoapostr)至第 6(ch06.xhtml#fields_and_filters)章为您全面展示了 Falco 的架构,描述了大多数重要的概念,这些概念是一位认真使用 Falco 用户所需了解的。剩下的部分要覆盖的是最重要的之一:规则。规则是 Falco 的核心。您已经多次遇到它们,但是本章将以更正式和全面的方式讨论这个主题,为您提供在您阅读本书后续部分时所需的基础。
注意
本章介绍规则是什么及其语法。目标是为您提供理解和使用它们所需的所有知识,而不是教您编写自己的规则。编写您自己的规则将在本书的第四部分(特别是在第十三章(ch13.xhtml#writing_falco_rules)中)介绍。
Falco 的设计简单直观,规则语法和语义也不例外。规则文件很直观,您很快就能理解。让我们从基础知识开始。
介绍 Falco 规则文件
Falco 规则告诉 Falco 应该做什么。它们通常打包在规则文件中,在启动时由 Falco 读取。规则文件是一个 YAML 文件,可以包含一个或多个规则,每个规则都是 YAML 主体中的一个节点。
Falco 包含一组默认规则文件,通常位于 /etc/falco。如果没有命令行选项启动 Falco,则默认规则文件会自动加载。这些文件由社区策划并随每个新版本的 Falco 更新。
启动时,Falco 会告诉您已加载了哪些规则文件:
$ sudo falco
Mon Jun 6 17:09:22 2022: Falco version 0.32.0 (driver version
39ae7d40496793cf3d3e7890c9bbdc202263836b)
Mon Jun 6 17:09:22 2022: Falco initialized with configuration file
/etc/falco/falco.yaml
Mon Jun 6 17:09:22 2022: Loading rules from file /etc/falco/falco_rules.yaml:
Mon Jun 6 17:09:22 2022: Loading rules from file
/etc/falco/falco_rules.local.yaml:
通常,您会希望加载自己的规则文件而不是默认文件。您可以通过两种不同的方式实现这一点。第一种方法涉及使用 -r
命令行选项:
$ sudo falco -r book_rules_1.yaml -r book_rules_2.yaml
Mon Jun 6 17:10:17 2022: Falco version 0.32.0 (driver version
39ae7d40496793cf3d3e7890c9bbdc202263836b)
Mon Jun 6 17:10:17 2022: Falco initialized with configuration file
/etc/falco/falco.yaml
Mon Jun 6 17:10:17 2022: Loading rules from file book_rules_1.yaml:
Mon Jun 6 17:10:17 2022: Loading rules from file book_rules_2.yaml:
而第二种方法涉及修改 Falco 配置文件的 rules_file
部分(通常位于 /etc/falco/falco.yaml),默认情况下如下所示:
rules_file:
- /etc/falco/falco_rules.yaml
- /etc/falco/falco_rules.local.yaml
- /etc/falco/rules.d
您可以在此部分添加、删除或修改条目,以控制 Falco 加载哪些规则文件。
请注意,使用这两种方法,您可以指定一个目录而不是单个文件。例如:
$ sudo falco -r ~/my_rules_directory
和:
rules_file:
- /home/john/my_rules_directory
这很方便,因为它允许您通过简单修改目录内容而无需重新配置 Falco,从而添加和删除规则文件。
正如我们提到的,Falco 的默认规则文件通常安装在 /etc/falco 下。该目录包含对 Falco 在不同环境中正常运行至关重要的文件。表 7-1(#falcoapostrophes_default_rules_files)概述了其中最重要的文件。
表 7-1。Falco 的默认规则文件
文件名 | 描述 |
---|---|
falco_rules.yaml | 这是 Falco 的主要规则文件,包含宿主和容器的基于系统调用的官方规则集。 |
falco_rules.local.yaml | 这是您可以添加自己的规则,或创建覆盖以修改现有规则,而无需污染 falco_rules.yaml 的位置。第十三章将详细介绍规则的创建和覆盖。 |
rules.available/application_rules.yaml | 该文件包含针对常见应用程序(如 Cassandra 和 Mongo)的规则。由于此规则集往往噪音较大,默认情况下是禁用的。 |
k8s_audit_rules.yaml | 该文件包含通过访问 Kubernetes 审计日志来检测威胁和错误配置的规则。此规则集默认未启用;要使用它,您需要启用它并配置 Falco Kubernetes Audit Events 插件。 |
aws_cloudtrail_rules.yaml | 该文件包含通过访问 AWS CloudTrail 日志流来执行检测的规则。此规则集默认未启用;要使用它,您需要启用它并配置 Falco CloudTrail 插件,如我们将在第十一章中解释的那样。 |
rules.d | 此空目录包含在默认的 Falco 配置中。这意味着您可以将文件添加到此目录(或在此目录中创建符号链接到您的规则文件),Falco 将自动加载它们。 |
默认情况下,Falco 加载了两个这些文件:falco_rules.yaml 和 falco_rules.local.yaml。此外,它挂载了 rules.d 目录,您可以使用它来扩展规则集,而无需更改命令行或配置文件。
Falco 规则文件的解剖
现在你已经从外部了解了规则文件的外观,是时候了解其中的内容了。规则文件中的 YAML 可以包含三种不同类型的节点:rules、macros 和 lists。让我们看看这些结构是什么,以及它们在规则文件中扮演的角色。
规则
规则声明了 Falco 检测。在前几章中你已经看到了几个例子,但作为提醒,规则有两个主要目的:
-
声明一个条件,当满足时将通知用户
-
定义条件满足时向用户报告的输出消息
这里有一个例子规则,来自第六章:
- rule: File Becoming Executable by Others
desc: Attempt to make a file executable by other users
condition: >
(evt.type=chmod or evt.type=fchmod or evt.type=fchmodat)
and evt.arg.mode contains S_IXOTH
output: >
attempt to make a file executable by others
(file=%evt.arg.filename mode=%evt.arg.mode user=%user.name
failed=%evt.failed)
priority: WARNING
source: syscall
tags: [filesystem, book]
当有尝试更改文件权限使其可被其他用户执行时,此规则将通知我们。
正如您在上面的例子中看到的那样,一个规则包含多个键。一些键是必需的,而其他一些是可选的。表 7-2 包含您可以在规则中使用的字段的详尽列表。
表 7-2. 规则字段
键 | 必需 | 描述 |
---|---|---|
rule | 是 | 描述规则并唯一标识它的简短句子。 |
desc | 是 | 更详细地描述规则检测内容的长描述。 |
condition | Yes | 规则条件。这是一个过滤表达式,在 第六章 中描述了语法,指定触发规则所需满足的条件。 |
output | Yes | Falco 触发规则时发出的类似 printf 的消息。 |
priority | Yes | 规则触发时生成的警报的优先级。Falco 使用类似 syslog 的优先级,因此此键接受以下值:EMERGENCY 、ALERT 、CRITICAL 、ERROR 、WARNING 、NOTICE 、INFORMATIONAL 和 DEBUG 。 |
source | No | 应用规则的数据源。如果不存在此键,则假定源为 syscall 。每个插件定义其自己的源类型,可以用作此键的值。例如,对于包含基于 CloudTrail 插件字段的条件/输出的规则,请使用 aws_cloudtrail 。 |
enabled | No | 可以选择禁用规则的布尔键。禁用的规则在引擎中不加载,并且在 Falco 运行时不需要任何资源。如果缺少此键,则假定 enabled 为 true 。 |
tags | No | 与该规则相关联的标签列表。标签有多种用途,包括轻松选择要加载的规则和分类 Falco 生成的警报。我们将在本章后面讨论标签。 |
warn_evttypes | No | 当设置为 false 时,此标志禁用有关此规则缺少事件类型检查的警告。当 Falco 加载规则时,除了验证其语法外,还会运行多个检查以确保规则符合基本性能标准。如果您知道自己在做什么,并且特别想创建一个不符合此类标准的规则,此标志将阻止 Falco 抱怨。默认情况下,此标志的值为 true 。 |
skip-if-unknown-filter | No | 如果将此标志设置为 true ,则使 Falco 在当前版本的规则引擎不接受字段时,静默跳过此规则。如果未设置此标志或设置为 false ,当遇到无法解析的规则时,Falco 将打印错误并退出。 |
规则中的关键字段是 condition
和 output
。第六章 对它们进行了广泛讨论,因此如果您尚未这样做,我们建议您参考该章节以获取概述。
宏
默认 Falco 规则集中广泛使用宏。它们使得将规则的部分分离为独立且可重复使用的实体成为可能。您可以将宏视为已分离并可以按名称引用的条件片段。为了探索这个概念,让我们回到前面的示例,并尝试使用宏来模块化它:
- rule: File Becoming Executable by Others
desc: Attempt to make a file executable by other users
condition: >
(evt.type=chmod or evt.type=fchmod or evt.type=fchmodat)
and evt.arg.mode contains S_IXOTH
output: >
attempt to make a file executable by others
(file=%evt.arg.filename mode=%evt.arg.mode user=%user.name
failed=%evt.failed)
priority: WARNING
看一下条件:我们将事件类型与三种不同的系统调用进行匹配,因为内核提供了三种不同的系统调用来更改文件权限。实际上,这三种系统调用都是 chmod
的变体,基本上使用相同的参数来检查。我们可以通过将这种复杂性隔离到宏中,使得相同的条件更易读:
- macro: chmod
condition: (evt.type=chmod or evt.type=fchmod or evt.type=fchmodat)
- rule: File Becoming Executable by Others
desc: attempt to make a file executable by other users
condition: chmod and evt.arg.mode contains S_IXOTH
output: >
attempt to make a file executable by others
(file=%evt.arg.filename mode=%evt.arg.mode user=%user.name
failed=%evt.failed)
priority: WARNING
注意条件现在更短更易读。此外,现在我们可以在其他规则中重用 chmod
宏,简化所有规则并使它们保持一致。更重要的是,如果我们想要添加另一个 Falco 应检查的 chmod
系统调用,我们只需更改一个地方(即宏),而不是多个规则。
宏帮助我们保持规则集的清洁、模块化和可维护性。
列表
类似宏一样,在 Falco 的默认规则集中大量使用列表。列表是可以从规则集的其他部分包含的项目集合。例如,列表可以被规则、宏甚至其他列表包含。宏和列表的区别在于前者实际上是一个条件,并且被解析为过滤表达式。另一方面,列表更类似于编程语言中的数组。
继续前面的例子,更好的写法如下:
- list: chmod_syscalls
items: [chmod, fchmod, fchmodat]
- macro: chmod
condition: (evt.type in (chmod_syscalls))
- rule: File Becoming Executable by Others
desc: attempt to make a file executable by other users
condition: chmod and evt.arg.mode contains S_IXOTH
output: >
attempt to make a file executable by others
(file=%evt.arg.filename mode=%evt.arg.mode user=%user.name
failed=%evt.failed)
这次有何不同?首先,我们已将 chmod
宏更改为使用 in
运算符而不是进行三个单独的比较。这不仅更高效,还让我们有机会将三个系统调用分离到一个列表中。列表方法非常适合规则维护,因为它允许我们将值隔离到类似数组的表示中,清晰紧凑,如果需要可以轻松覆盖(有关列表覆盖的更多信息,请参阅 第十三章)。
规则标记
标签 是将标签分配给规则的概念。如果您熟悉像 AWS 或 Kubernetes 这样的现代云计算环境,您就知道它们允许您向资源附加标签。这样做可以让您更轻松地管理这些资源,作为组而不是个体。标签将相同的理念带入到 Falco 规则中:它允许您像对待牲畜而不是宠物一样处理规则。
例如,这是默认 Falco 规则集中的一条规则:
- rule: Launch Privileged Container
desc: >
Detect the initial process started in a privileged container.
Exceptions are made for known trusted images.
condition: >
container_started and container
and container.privileged=true
and not falco_privileged_containers
and not user_privileged_containers
output: >
Privileged container started
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline
%container.info image=%container.image.repository:%container.image.tag)
priority: INFO
tags: [container, cis, mitre_privilege_escalation, mitre_lateral_movement]
注意规则有多个标签,有些标签指示规则适用于什么(例如,container
),而其他标签将其映射到合规框架,如 CIS 和 MITRE ATT&CK。
Falco 允许您使用标签控制加载哪些规则。这通过两个命令行标志 -T
和 -t
实现。操作方法如下:
-
使用
-T
禁用具有特定标签的规则。例如,要跳过所有具有k8s
和cis
标签的规则,可以这样运行 Falco:$ sudo falco -T k8s -T cis
-
使用
-t
实现相反的目的;即仅运行具有指定标签的规则。例如,要仅运行具有k8s
和cis
标签的规则,可以使用以下命令行:$ sudo falco -t k8s -T cis
-T
和 -t
都可以在命令行上指定多次。
你可以使用任何你想要的标签来装饰你的规则。但是,默认规则集是基于一个统一的标签集合进行标准化的。根据官方 Falco 文档,Table 7-3 展示了这个标准标签集是什么。
Table 7-3. 默认规则标签
Tag | 用途 |
---|---|
file | 与读写文件和访问文件系统相关的规则 |
software_mgmt | 与软件包管理(rpm、dpkg 等)或安装新软件相关的规则 |
process | 与进程、命令执行和进程间通信(IPC)相关的规则 |
database | 与数据库相关的规则 |
host | 适用于虚拟和物理机器,但不适用于容器的规则 |
shell | 适用于启动 shell 和执行 shell 操作的规则 |
container | 适用于容器而不适用于主机的规则 |
k8s | 与 Kubernetes 相关的规则 |
users | 适用于用户、组和身份管理的规则 |
network | 检测网络活动的规则 |
cis | 涵盖 CIS 基准的部分规则 |
mitre_* | 包含 MITRE ATT&CK 框架的规则(这是一个包括多个标签的类别:mitre_execution 、mitre_persistence 、mitre_privilege_escalation 等) |
声明预期的引擎版本
如果你用文本编辑器打开一个 Falco 规则文件,通常你会看到的第一行是一个类似这样的声明:
- required_engine_version: 9
声明最低所需引擎版本是可选的,但非常重要,因为它有助于确保你运行的 Falco 版本能够正确支持其中的规则。规则集中使用的一些字段可能在较旧版本的 Falco 中不存在,或者某些规则可能需要最近才添加的系统调用。如果版本声明不正确,规则文件可能无法加载,甚至更糟糕的是,可能加载但会产生不正确的结果。如果规则文件需要比 Falco 支持的版本更高的引擎版本,Falco 将报告错误并拒绝启动。
类似地,规则文件可以通过 required_plugin_versions
顶级字段声明它们兼容的插件版本。这个字段也是可选的;如果你不包含它,将不会执行任何插件兼容性检查,并且你可能会看到与刚刚描述的类似的行为。required_plugin_versions
的语法如下:
- required_plugin_versions:
- name: *`<plugin_name>`*
version: *`<x.y.z>`*
...
在 required_plugin_versions
下面,你需要指定一个对象列表,每个对象都有两个属性:name
和 version
。如果加载了一个插件,并且在 required_plugin_versions
中找到了对应的条目,则加载的插件版本必须与 version
属性兼容 semver-compatible。
预装的 Falco 默认规则文件都有版本号。别忘了在你的每个规则文件中也这样做!
替换、追加和禁用规则
Falco 预装了丰富且不断增长的规则集,涵盖了许多重要的用例。但是,在许多情况下,您可能会发现定制默认规则集会带来好处。例如,您可能希望减少某些规则的噪声,或者您可能对扩展一些 Falco 检测的范围感兴趣,以更好地匹配您的环境。
处理这些情况的一种方法是编辑默认的规则文件。一个重要的教训是,你不必这样做。实际上,你不应该这样做——Falco 提供了一种更灵活的方式来定制规则,旨在使您的更改可维护并在发布中重复使用。让我们看看这是如何工作的。
替换宏、列表和规则
替换列表、宏或规则只是重新声明它的事情。第二次声明可以在同一文件中,也可以在加载原始声明文件之后的另一个文件中。
让我们通过一个例子来看看这是如何工作的。以下规则检测文本编辑器是否已作为 root 打开(正如我们都知道的那样,人们应该避免这样做):
- list: editors
items: [vi, nano]
- macro: editor_started
condition: (evt.type = execve and proc.name in (editors))
- rule: Text Editor Run by Root
desc: the root user opened a text editor
condition: editor_started and user.name=root
output: the root user started a text editor (cmdline=%proc.cmdline)
priority: WARNING
如果我们将此规则保存在名为 rulefile.yaml 的规则文件中,我们可以通过在 Falco 中加载该文件来测试规则:
$ sudo falco -r rulefile.yaml
每次我们以 root 身份运行 vi 或 nano 时,规则都会触发。
现在假设我们想要更改规则以支持不同的文本编辑器集合。我们可以创建第二个规则文件,命名为 editors.yaml,并按以下方式填充它:
- list: editors
items: [emacs, subl]
注意我们如何重新定义了 editors
列表的内容,用 emacs
和 subl
替换了原始命令名称。现在我们只需在原始规则文件后加载 editors.yaml:
$ sudo falco -r rulefile.yaml -r editors.yaml
Falco 将接受 editors
的第二个定义,并在以 root 身份运行 emacs 或 subl 时生成警报,但不会在 vi 或 nano 上运行。本质上,我们已经替换了列表的内容。
这个技巧在宏和规则中的工作方式与列表完全相同。
追加到宏、列表和规则
让我们继续使用相同的文本编辑器规则示例。但是这次,假设我们想要在编辑器列表中追加其他名称,而不是完全替换整个列表。机制是相同的,但增加了append
关键字。以下是语法:
- list: editors
items: [emacs, subl]
append: `true`
我们可以将此列表保存在名为 additional_editors.yaml 的文件中。现在,如果我们运行以下命令行:
$ sudo falco -r rulefile.yaml -r editors.yaml
Falco 将检测到 vi、nano、emacs 和 subl 的根执行。
您也可以(使用相同的语法)追加到宏和规则。但是,有几件事情需要牢记:
-
对于规则,只能追加到条件。尝试追加到其他键,如
output
,将被忽略。 -
记住,追加到条件只是将新文本附加到其末尾,所以要注意歧义。
例如,假设我们通过追加条件来扩展我们示例中的规则,如下所示:
- rule: Text Editor Run by Root
condition: or user.name = loris
append: `true`
完整的规则条件将变为:
condition: editor_started and user.name=root or user.name = loris
这个条件显然是模棱两可的。当用户root
或loris
打开文本编辑器时,规则会触发吗?还是说只有当root
打开文本编辑器,并且loris
执行任何命令时才会触发?为了避免这种歧义,并使您的规则文件更易读,您可以在原始条件中使用括号。
禁用规则
您经常会遇到需要禁用规则集中一个或多个规则的情况,例如因为它们太嘈杂或者对您的环境来说不相关。Falco 提供了不同的方法来执行此操作。我们将涵盖其中的两种方法:使用命令行和覆盖enabled
标志。
从命令行禁用规则
实际上,Falco 提供了两种通过命令行禁用规则的方法。第一种方法,在本章前面讨论规则标记时已经提到,涉及使用-T
标志。作为复习,您可以使用-T
来禁用具有给定标记的规则。可以在命令行上多次使用-T
来禁用多个标记。例如,要跳过所有具有k8s
标记、cis
标记或两者都有的规则,可以像这样运行 Falco:
$ sudo falco -T k8s -T cis
从命令行禁用规则的第二种方式是使用-D
标志。-D *<substring>*
会禁用所有名称中包含*<substring>*
的规则。与-T
类似,-D
可以多次使用,并带有不同的参数。
如果您通过官方 Helm 图表部署 Falco,则可以将这些参数指定为 Helm 图表值(extraArgs
)。
通过覆盖enabled
标志禁用规则
您可能还记得在表 7-2 中提到的一个可选规则字段叫做enabled
。作为复习,这是我们在本章前面对其进行文档化的方式:
可选地用于禁用规则的布尔键。禁用的规则在引擎加载时不会被加载,并且在运行 Falco 时不需要任何资源。如果缺少此键,则假定
enabled
为true
。
可以通过常规机制将enabled
打开或关闭,来覆盖规则。例如,如果您想在*/etc/falco/falco_rules.yaml中禁用用户管理二进制规则*,可以在*/etc/falco/falco_rules.local.yaml*中添加以下内容:
- rule: User mgmt binaries
enabled: `false`
结论
看,这并不难!在这一点上,您应该能够阅读并理解 Falco 规则,并且离编写自己的规则更近了一步。我们将在书的第四部分中重点讨论规则编写,特别是在第十三章中。我们的下一步将是全面了解 Falco 输出。