Falco-云原生安全实践指南-全-

Falco 云原生安全实践指南(全)

原文:zh.annas-archive.org/md5/901f31c65e11db9dd25e51adeba7505a

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

现代计算堆栈的出现彻底改变了我们对安全性的看法。在旧的数据中心时代,安全从业者把软件应用程序看作是中世纪城堡:保护它们需要建造高墙和小而精密的入口。现代基于云的软件看起来更像是一个繁华的现代城市:人们在其内部自由移动,并跨越其界限消费和提供服务,买卖、建设和修复事物。

正如今天的城市规划师所知道的那样,仅有大墙和守卫的入口是不足以保护一个城市的。更好的方法包括广泛而细化的可见性:例如一个安全摄像头网络,以及能够查看它们的录像并实时响应它们捕捉到的任何威胁。

本书讲述的是现代应用程序的安全性,使用业界广泛接受的开源工具,被称为云原生堆栈的“安全摄像头”: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 对象,有几个键。第一个键ruleruleset(一个或多个包含规则定义的 YAML 文件)中唯一标识规则。第二个键desc允许规则的作者简要描述规则将检测到什么。condition键,可以说是最重要的一个,允许使用一些简单的语法来表达安全断言。各种布尔和比较运算符可以与fields(保存收集的数据)结合使用,以仅过滤相关事件。在此示例规则中,evt.dir是用于过滤的字段。支持的字段和过滤器在第六章中有更详细的介绍。

只要条件为假,就不会发生任何事情。当条件为真时,断言得到满足,然后将立即触发警报。警报将包含一个信息性消息,由规则的作者使用规则声明的output键定义。priority键的值也将被报告。警报的内容将在下一节中更详细地介绍。

condition的语法还可以利用一些更多的构造,比如listmacro,这些可以与规则集中定义的规则一起使用。顾名思义,list是可以在不同规则中重复使用的项目列表。类似地,macros是可重用的条件片段。为了完整起见,这里是在Write below binary dir规则的condition键中使用的两个宏(bin_diropen_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.typeevt.is_open_writefd.typecharevt.dirfd.numfd.directory),因为它们后面跟着比较运算符(如=>=in)。字段名称包含一个点(.),因为具有类似上下文的字段被分组在一起形成。点之前的部分代表类(例如,evtfd是类)。

尽管您可能尚未彻底理解条件的语法,但目前无需理解。您只需要知道,在符合条件的目录(如/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键所指定的那样。它可以采用以下值之一(从最严重到最不严重):EmergencyAlertCriticalErrorWarningNoticeInformationalDebug。Falco 允许我们通过指定我们想要接收警报的最小严重级别来过滤那些对我们不重要的警报,从而减少输出的噪声。默认情况下是debug,意味着默认包含所有严重级别,但我们可以通过修改/etc/falco/falco.yaml配置文件中priority参数的值来更改这一点。例如,如果我们将此参数的值更改为notice,那么我们将不会收到具有priority等于INFORMATIONALDEBUG的规则的警报。

消息

最后也是最关键的元素是消息。这是一个根据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 编译每个规则的条件和输出。

  • 当规则触发时执行适当的操作,包括输出结果。

由于 libscaplibsinsp 的强大功能,规则引擎简单且相对独立于堆栈的其他部分。

结论

现在你知道了 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.tidproc.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 过滤器部署步骤

让我们来看看这里描述的步骤:

  1. 用户在诸如 Wireshark 的程序中输入过滤器(例如,port 80)。

  2. 过滤器被输入到编译器中,编译器将其转换为虚拟机的字节码。这在概念上类似于编译 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
    
  3. 为了防止编译的过滤器造成损害,它在注入内核之前由验证器分析。验证器检查字节码,并确定过滤器是否具有危险属性(例如,导致过滤器永不返回的无限循环,消耗大量内核 CPU)。

  4. 如果过滤器代码不安全,验证器将拒绝它,并向用户返回错误,停止加载过程。如果验证器满意,字节码将传递给虚拟机,虚拟机将其针对每个传入的数据包运行。

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 驱动程序用于捕获系统调用的方法包括图中标记的三个主要步骤:

  1. 内核设施称为追踪点截获了系统调用的执行。追踪点使得可以在操作系统内核中的特定位置插入一个钩子,以便每次内核执行到达该点时调用一个回调函数。Falco 驱动程序为系统调用安装了两个追踪点:一个用于系统调用进入内核,另一个用于系统调用退出内核并将控制返回给调用进程。

  2. 当在追踪点回调函数中时,驱动程序将系统调用参数“打包”到共享内存缓冲区中。在此阶段,系统调用也会被时间戳标记,并且从操作系统中收集额外的上下文信息(例如线程 ID,或者某些套接字系统调用的连接详情)。这个阶段需要非常高效,因为直到驱动程序的追踪点回调函数返回之前,系统调用才不能被执行。

  3. 共享缓冲区现在包含系统调用数据,并且 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 接口中的特定函数来导出这些功能。

为了更容易编写插件,有处理内存管理和类型转换细节的 GoC++ 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 立即提取元数据。这种设计在所有实现层中都是共享的,你将在以下章节中了解到。

操作系统元数据

正如你在第三章学到的,libscaplibsinsp 协同工作,提供创建和更新由多个状态表组成的分层结构中的上下文信息所需的所有基础设施(如果需要复习,请参见图 3-4)。这些表包括关于以下信息的信息:

  • 进程和线程

  • 文件描述符

  • 用户和组

  • 网络接口

从高层次来看,收集系统信息的机制相对简单。在启动时,libscap 的任务之一是扫描进程信息伪文件系统,或称为procfs,它为 Linux 内核数据结构提供用户空间接口,并包含初始化状态表所需的大部分信息。它还使用标准 C 库提供的函数来收集系统信息(不在/proc中可用),这些函数最终从底层操作系统获取数据(例如,getpwentgetgrent 分别用于用户和组列表,以及 getifaddrs 用于网络接口列表)。此时,初始化阶段完成。

提示

libscaplibsinsp 依赖于主机的 procfs 来访问主机的系统信息。这在 Falco 运行在主机上时是默认的,因为它可以直接访问主机的 /proc。然而,在容器中运行 Falco 时,容器内的 /proc 指的是不同的命名空间。在这种情况下,你可以通过 HOST_ROOT 环境变量配置 libscap 来从另一路径读取信息。如果设置了 HOST_ROOTlibscap 将使用其值作为查找系统路径的基路径。例如,在容器中运行 Falco 时,通常的方法是将主机的 /proc 挂载到容器内的 /host/proc 并设置 HOST_ROOT/host。通过这种设置,libscap 将从 /host/proc 读取信息,因此它将使用主机的 procfs 提供的信息。

之后,libsinsp 通过其状态引擎发挥作用(见 图 5-1)。它通过检查驻留在内核空间的驱动程序提供的持续捕获的系统调用流来更新表格。在初始化阶段之后,Falco 将不需要进行任何系统调用或从 Linux 内核获取更新。这种方法的双重好处是不会在系统中创建噪音并且对性能影响很低。此外,这种技术使 libsinsp 能够以低延迟发现系统变化,从而使 Falco 能够作为流式引擎(其设计的一个重要目标)运行。

最后需要注意的是,libsinsp 在将事件传递给规则引擎之前更新状态表。这确保了当条件或输出需要元数据时,它始终可用且一致。然后,你可以在你在 表格 5-1 中看到的字段类集合中找到系统元数据:procthreadfdfdlistusergroup

这组信息表示了使规则作者能够使用系统调用事件的基本元数据。想想看:你如何在规则中使用数值文件描述符?使用文件名要好得多!

图 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_ROOTlibsinsp 将使用其值作为查找这些 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),由名为emacsvi的进程生成的每个系统调用。

这基本上就是您需要在高层次上了解的全部。现在让我们深入了解细节。

过滤语法参考

从语法角度来看,正如我们提到的,编写 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

进程的工作目录不是系统调用的一部分,因此要公开此字段,需要跟踪进程的工作目录,并将其附加到进程生成的每个系统调用中。这反过来涉及四个步骤:

  1. 当一个进程启动时,收集其工作目录,并将其存储在线程表中的进程条目中。

  2. 跟踪进程何时更改其工作目录(通过拦截和解析chdir系统调用),并相应地更新线程表条目。

  3. 解析每个系统调用的线程 ID,以识别相应的线程表条目。

  4. 返回线程表条目的cwd值。

libsinsp做了所有这些工作,这意味着proc.cwd字段可用于每个系统调用,而不仅仅是像chdir这样与目录相关的调用。Falco 为向您公开此字段所做的大量工作令人印象深刻!

基于丰富化的过滤非常强大,因为它允许您根据并非包含在系统调用本身中但对安全策略非常有用的属性来过滤系统调用(以及任何其他事件)。例如,以下过滤器允许您捕获读取或写入/etc/passwd的系统调用:

evt.is_io=true and fd.name=/etc/passwd

即使这些系统调用最初不包含任何有关文件名的信息(它们操作文件描述符),它们也能正常工作。箱中提供的数百种基于丰富化的字段是 Falco 如此强大和多功能的主要原因。

强制字段与可选字段

一些字段存在于每个输入事件中,无论事件类型或族群如何,您都能保证找到它们。此类字段的示例包括evt.tsevt.direvt.type

然而,大多数字段是可选的,并且仅存在于某些输入事件类型中。通常情况下,你不需要担心这一点,因为不存在的字段会在不生成错误的情况下仅评估为false。例如,以下检查将对所有没有名为name的参数的事件评估为false

evt.arg.name contains /etc

但在某些情况下,您可能想要显式检查字段是否存在。一个原因是解决像evt.arg.name != /etc这样的模糊性,以确定对于没有名为name的参数的事件,是否返回truefalse。您可以通过使用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&#124;O_CLOEXEC)。字符串的解析取决于上下文,因为事件可以注册自己的标志值。因此,例如,lseek 系统调用事件的标志将转换为SEEK_ENDSEEK_CUR等值,而sockopt的标志将转换为SOL_SOCKETSOL_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系统调用或其变体进行更改的。因此,过滤器的第一部分选择了类型为chmodfchmodfchmodat的事件:

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.modeevt.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.fdevt.arg[0])。
evt.buffer 事件的二进制数据缓冲区(例如 read、recvfrom 等)。在过滤器中使用 contains 来搜索 I/O 数据缓冲区。
evt.buflen 具有二进制数据缓冲区的事件的缓冲区长度,如 readrecvfrom 等。
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 协议。可以是tcpudpicmpraw

用户和用户组

表 6-7 列出了usergroup过滤类中的字段。

表 6-7. usergroup过滤类字段

字段名 描述
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​.privi⁠leged 运行为特权的容器为 true,否则为 false
con⁠tainer​.mounts 一组以空格分隔的挂载信息。列表中每个项的格式为 *<source>*:*<dest>*:*<mode>*:*<rdrw>*:*<propagation>*
container.mount 单个挂载的信息,由编号(例如,container.mount[0])或挂载源(例如,con⁠tainer.mount[/usr/local])指定。路径名可以是通配符(例如,container.mount[/usr/local/*]),在这种情况下,将返回第一个匹配的挂载。信息的格式为 *<source>*:*<dest>*:*<mode>*:*<rdrw>*:*<propagation>*。如果没有指定索引或匹配提供的源的挂载,则返回字符串 "none" 而不是 NULL 值。
container.image​.reposi⁠tory 容器镜像仓库(例如,falcosecurity/falco)。
con⁠tainer.image​.tag 容器镜像标签(例如,stablelatest)。
con⁠tainer.image​.digest 容器镜像注册表摘要(例如,sha256:d977378f890d445c15e51795296​e4e5062f109ce6da83e0a355fc4ad8699d27)。

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.deploy⁠ment​.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;如果事件修改状态(例如 RunInstancesCreateLoadBalancer),则为 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​.privi⁠leged 当请求对象指向 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.yamlfalco_rules.local.yaml。此外,它挂载了 rules.d 目录,您可以使用它来扩展规则集,而无需更改命令行或配置文件。

Falco 规则文件的解剖

现在你已经从外部了解了规则文件的外观,是时候了解其中的内容了。规则文件中的 YAML 可以包含三种不同类型的节点:rulesmacroslists。让我们看看这些结构是什么,以及它们在规则文件中扮演的角色。

规则

规则声明了 Falco 检测。在前几章中你已经看到了几个例子,但作为提醒,规则有两个主要目的:

  1. 声明一个条件,当满足时将通知用户

  2. 定义条件满足时向用户报告的输出消息

这里有一个例子规则,来自第六章:

- 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 的优先级,因此此键接受以下值:EMERGENCYALERTCRITICALERRORWARNINGNOTICEINFORMATIONALDEBUG
source No 应用规则的数据源。如果不存在此键,则假定源为 syscall。每个插件定义其自己的源类型,可以用作此键的值。例如,对于包含基于 CloudTrail 插件字段的条件/输出的规则,请使用 aws_cloudtrail
enabled No 可以选择禁用规则的布尔键。禁用的规则在引擎中不加载,并且在 Falco 运行时不需要任何资源。如果缺少此键,则假定 enabledtrue
tags No 与该规则相关联的标签列表。标签有多种用途,包括轻松选择要加载的规则和分类 Falco 生成的警报。我们将在本章后面讨论标签。
warn_evttypes No 当设置为 false 时,此标志禁用有关此规则缺少事件类型检查的警告。当 Falco 加载规则时,除了验证其语法外,还会运行多个检查以确保规则符合基本性能标准。如果您知道自己在做什么,并且特别想创建一个不符合此类标准的规则,此标志将阻止 Falco 抱怨。默认情况下,此标志的值为 true
skip-if-unknown-filter No 如果将此标志设置为 true,则使 Falco 在当前版本的规则引擎不接受字段时,静默跳过此规则。如果未设置此标志或设置为 false,当遇到无法解析的规则时,Falco 将打印错误并退出。

规则中的关键字段是 conditionoutput。第六章 对它们进行了广泛讨论,因此如果您尚未这样做,我们建议您参考该章节以获取概述。

默认 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 禁用具有特定标签的规则。例如,要跳过所有具有 k8scis 标签的规则,可以这样运行 Falco:

    $ sudo falco -T k8s -T cis
    
  • 使用 -t 实现相反的目的;即仅运行具有指定标签的规则。例如,要仅运行具有 k8scis 标签的规则,可以使用以下命令行:

    $ 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_executionmitre_persistencemitre_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 下面,你需要指定一个对象列表,每个对象都有两个属性:nameversion。如果加载了一个插件,并且在 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 列表的内容,用 emacssubl 替换了原始命令名称。现在我们只需在原始规则文件后加载 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

这个条件显然是模棱两可的。当用户rootloris打开文本编辑器时,规则会触发吗?还是说只有当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 时不需要任何资源。如果缺少此键,则假定enabledtrue

可以通过常规机制将enabled打开或关闭,来覆盖规则。例如,如果您想在/etc/falco/falco_rules.yaml中禁用用户管理二进制规则,可以在/etc/falco/falco_rules.local.yaml中添加以下内容:

- rule: User mgmt binaries
  enabled: `false`

结论

看,这并不难!在这一点上,您应该能够阅读并理解 Falco 规则,并且离编写自己的规则更近了一步。我们将在书的第四部分中重点讨论规则编写,特别是在第十三章中。我们的下一步将是全面了解 Falco 输出。

第八章:输出框架

在前几章中,你学到了 Falco 如何收集事件(其输入)以及如何处理它们以使你能够接收重要的安全通知(其输出)。在这个处理管道的最后,Falco 的一个关键部分——输出框架——使其能够将这些通知(也称为警报)传递到正确的位置。我们将其称为框架,因为其模块化设计提供了你需要的一切,可以将通知传递到任何你希望的目的地。在本章中,你将学习输出框架的工作原理以及如何配置和扩展它。

Falco 的输出架构

输出框架是本书这部分描述的事件处理管道的最后一部分。Falco 的用户空间程序在内部实现了核心机制,但外部工具可以扩展它。它的工作是及时将通知传递到正确的目的地。每当上游事件(由驱动程序、插件或任何 Falco 支持的其他输入源产生)满足规则条件时,规则引擎会要求输出框架向下游消费者发送通知,这可以是环境中的任何其他程序或系统(或仅仅是你自己)。

传送警报的过程涉及两个不同的阶段,如图 8-1 所示。

在第一阶段中,处理程序接收事件数据和事件触发规则的信息。它使用提供的信息准备通知,并根据规则的output键格式化文本表示。然后,为了防止输出目的地阻塞运行在主执行线程中的处理管道,处理程序将通知推入并发队列中^(1)。推送操作是非阻塞的,因此处理管道无需等待通知消费者拉取通知;它可以继续执行其工作而不受干扰。事实上,Falco 需要尽快执行这个阶段,以便处理管道可以处理下一个事件。

图 8-1。Falco 中传送通知的两个阶段

在队列的另一端,等待弹出通知的输出工作者(运行在单独的执行线程中)。这时第二阶段开始。一旦输出工作者收到通知,它立即将该通知扩散到所有配置的输出通道。输出通道(或简称输出)是输出框架的一部分,允许 Falco 将警报转发到目的地。每个输出通道实现了通知特定类别的警报消费者的实际逻辑。例如,一些消费者希望将通知写入文件,而其他人则更喜欢将其发布到 Web 终端点(参见第十章)。

这种两阶段方法允许处理管道在不受输出传递过程干扰的情况下运行。但是,传递可能仍会出现问题。特别是,当传递通知涉及 I/O 操作时,这些操作可能会暂时阻塞调用者(例如,在网络减速时)或无限期阻塞(例如,在目标磁盘上没有剩余空间时)。两个阶段之间的队列非常擅长吸收临时减速,以至于您甚至都不会注意到(默认情况下,Falco 可以在队列中积累未决通知达到两秒)。但是,当通知的接收者长时间(或无限期)阻塞时,Falco 无法自动执行任何操作。作为最后的手段,它会尝试通过记录到标准错误流(stderr通知您发生了什么。当这种情况发生时,通常是配置错误的症状(例如,目标路径错误)或资源不足(目标中没有剩余空间),用户需要手动修复。

一旦通知传递过程完成,Falco 的用户空间程序已经完成了其目的。接下来由消费者决定如何处理警报。

输出框架适用于许多不同的用例,并且可以处理许多可能的问题。它还足够灵活,可以让您以各种方式和到不同目的地接收通知。本章的其余部分将详细介绍所有可用的可能性。我们还将快速查看一些其他工具,这些工具允许您在将通知传递到最终目的地之前进一步扩展输出处理(我们将在第十三章深入讨论这个问题)。

输出格式化

在通知传递的第一阶段,Falco 会在转发到输出通道之前对通知应用格式化。您可以自定义 Falco 向其消费者呈现通知的方式,以便轻松集成到您的具体用例中。

Falco 配置文件(/etc/falco/falco.yaml)中有两个选项控制此操作。第一个控制时间戳的格式化:

time_format_iso_8601: false

如果此选项为false(默认值),Falco 将根据/etc/localtime的设置显示日期和时间。如果为true(当 Falco 在容器中运行时的默认值),Falco 将使用 ISO 8601 标准表示日期和时间。请注意,此选项不仅控制输出通知,还包括 Falco 记录的任何其他消息。

第二个选项实际上是一组选项,用于启用通知的 JSON 格式化。默认情况下,禁用了 JSON 格式化:

json_output: false

使用此设置,Falco 将通知格式化为纯文本字符串(包括时间戳、严重性和消息)。如果设置为true,Falco 将通知封装在 JSON 格式的字符串中,包括多个字段。以下两个选项允许您包含或排除输出中的某些字段:

json_include_output_property: true

如果启用此选项(默认情况下是启用的),你仍然可以在 JSON 对象的output字段中找到通知的纯文本表示。如果不需要,可以禁用此选项以节省一些字节。

json_include_tags_property: true

如果启用此选项,您将在 JSON 对象中找到一个tags字段,其中包含匹配规则中指定的标签数组。未定义标签的规则在输出中将具有空数组(tags:[])。如果禁用此选项,您将无法在 JSON 对象中获取tags字段。

注意

尽管名字是json_output,但它并不是一个输出通道。json_output配置控制处理第一阶段的通知格式化,因此它会影响通道传递的通知内容。接下来的部分描述了可用的输出通道。

输出通道

Falco 提供了六种内置输出通道,列在 Table 8-1 中。我们将在接下来的子章节中更详细地描述每一种。默认情况下仅启用了两个通道——标准输出和 syslog 输出——但 Falco 允许您同时启用多个通道。

Table 8-1. Falco 的内置输出通道

通道 描述
标准输出 将通知发送到 Falco 的标准输出(stdout)
Syslog 输出 通过 syslog 发送通知到系统
文件输出 将通知写入文件
程序输出 将通知发送到程序的标准输入
HTTP 输出 将通知发布到 URL
gRPC 输出 允许客户端程序通过 gRPC API 消费通知

您可以在 Falco 配置文件(/etc/falco/falco.yaml)中配置这些输出。请注意,本节中的所有配置片段都是该文件的一部分。

每个输出通道至少有一个名为enabled的选项,可以是truefalse。其他特定输出可能有其他选项(你很快会发现)。此外,还有一些全局选项可以影响所有或部分输出通道的功能。其中一个选项(你在上一节看到的)是json_output;当启用时,警报消息将以 JSON 格式呈现,无论使用的输出通道是哪种。可以影响输出通道行为的其他全局选项列在 Table 8-2 中。

Table 8-2. 输出通道的全局选项

全局选项(带默认值) 描述
buffered_outputs: false 此选项启用或禁用输出通道的完全缓冲。当禁用时,Falco 在每次警报时立即刷新输出缓冲区,这可能会导致更高的 CPU 使用率,但在将输出导入另一个进程或脚本时非常有用。除非您遇到默认值的问题,通常不需要启用此选项。请注意,Falco 的--unbuffered命令行标志可以覆盖此选项。并非所有输出通道都遵循此全局选项。某些输出通道可能实现了您无法禁用的特定缓冲策略。
output_timeout: 2000 此选项的值指定在超过传递通知截止时间之前等待的持续时间(以毫秒为单位)。当通知消费者阻塞且输出通道无法在给定的截止时间内传递警报时,Falco 将报告一个错误,指示哪个输出正在阻止通知。这样的错误表明消费者中存在配置错误或 I/O 问题,Falco 无法恢复。

| outputs: rate: 1

max_burst: 1000 | 这些选项控制通知速率限制器,以防止输出通道淹没其目标。速率限制器实现了令牌桶算法。要发送通知,系统必须从桶中移除一个令牌。rate设置系统每秒获取的令牌数,max_burst设置桶中的最大令牌数。使用默认设置,Falco 可以连续发送高达 1,000 个通知;然后,必须等待将令牌添加到桶中,该过程以每秒 1 个令牌的速率进行。换句话说,一旦桶被清空,通知的发送速率限制为每秒一个。 |

提示

尽管与输出机制不严格相关,Falco 的其他设置可能会影响您在输出中收到的内容。例如,配置priority: *<severity>*控制要加载和运行的最小规则优先级级别,命令行选项-t *<tag>*允许您仅加载具有特定标签的规则。在这些情况下,显然,您将不会收到 Falco 未加载规则相关的任何输出。一般来说,任何与规则相关的选项或配置都可能间接影响输出。

现在您已经了解了输出通道及其行为可能发生变化的设置,让我们依次进行详细说明。

标准输出

标准输出(配置文件中的stdout_output,默认情况下启用)是 Falco 最直接的输出通道。启用时,Falco 将为每个警报在标准输出打印一行。这允许您在从控制台手动运行 Falco 或查看容器或 Kubernetes Pod 日志时查看警报通知。此输出通道唯一的特定可用选项是enabled(可以是truefalse)。但是,它也受全局缓冲选项buffered_outputs的影响。当输出被缓冲时,stdout 流将完全缓冲或在流是交互设备(如 TTY)时进行行缓冲。

Syslog 输出

syslog 输出通道(配置文件中的syslog_output,默认情况下也启用)允许 Falco 为每个警报发送一个 syslog 消息。与标准输出类似,该输出通道唯一的可用选项是enabled(可以是truefalse)。启用时,Falco 会使用LOG_USER^(2)设施和由规则定义的优先级值作为严重级别发送消息到 syslog。

根据您使用的 syslog 守护程序,您可以使用类似于tail -f /var/log/syslogjournalctl -xe的命令读取这些消息。实际的消息格式也取决于 syslog 守护程序。

文件输出

如果启用文件输出,Falco 将每个警报写入一个文件。此输出通道的默认配置为:

file_output:
  enabled: false
  keep_alive: false
  filename: ./events.txt

filename选项允许您指定 Falco 将写入的目标文件。如果文件尚不存在,则会创建该文件,如果文件已存在,则不会尝试截断或旋转该文件。

如果禁用keep_alive(默认情况),Falco 将为每个警报打开文件进行追加写入,然后关闭文件。如果将keep_alive设置为true,Falco 将仅在第一个警报之前打开文件一次,并将保持打开状态以处理所有后续警报。无论keep_alive是否启用,Falco 在接收到SIGUSR1信号时关闭并重新打开文件。如果您想要使用程序来旋转输出文件(例如logrotate),这个特性非常方便。

最后,除非禁用全局缓冲选项,否则写入文件通常会进行缓冲。关闭文件将刷新缓冲区。

程序输出

程序输出与文件输出非常相似,但在这种情况下,Falco 将每个警报的内容写入到配置文件中指定的程序的标准输入中。此输出通道的默认配置为:

program_output:
  enabled: false
  keep_alive: false
  program: > 
    jq '{text: .output}' | curl -d @- -X POST https://hooks.slack.com/services...

program字段允许您指定将警报发送到的程序。Falco 通过 shell 运行程序,因此您可以在将消息传递到程序之前指定任何处理步骤的命令管道。该字段的默认值显示了其用法的一个很好的例子:当执行时,这个单行程序将警报发布到 Slack webhook 端点。(然而,使用 Falcosidekick 会是一个更好的选择;请参阅第十二章。)

如果将keep_alive设置为false,Falco 在每次有通知需要传递时都会重新启动程序,并将警报内容写入其标准输入。如果将keep_alive设置为true,Falco 会在发送第一个警报之前启动程序一次,并保持程序管道开放以传递后续警报。

当接收到SIGUSR1信号时,Falco 关闭并重新打开程序。然而,该程序与 Falco 运行在同一个进程组中,因此它会接收到 Falco 接收到的所有信号。如果需要,您可以覆盖程序信号处理程序。

缓冲通过全局选项支持。当 Falco 关闭程序时,它也会刷新缓冲区。

HTTP 输出

当您需要通过 HTTP(S)连接发送警报时,最好的选择是使用 HTTP 输出。其默认配置非常简单:

http_output:
  enabled: false
  url: http://some.url

一旦启用,您需要指定的唯一其他配置是您端点的url。Falco 将为每个警报向指定的 URL 发起 HTTP POST 请求。支持未加密的 HTTP 和安全的 HTTPS 端点。该输出通道始终启用缓冲(即使您禁用了全局缓冲选项)。

当您使用 Falcosidekick 时,首选 HTTP 输出通道;它以扇出方式将 Falco 的警报转发到许多不同的目的地(目前支持 50 多个)。如果您希望 Falco 将警报转发到 Falcosidekick,请应用此 Falco 配置:

json_output: true
json_include_output_property: true
http_output:
  enabled: true
  url: "http://localhost:2801/"

请注意,此配置假定您已经运行并配置了 Falcosidekick 来监听localhost:2801;如果您的设置不同,请相应更改。您可以在第十二章以及其在线文档中找到有关配置 Falcosidekick 的详细信息。

gRPC 输出

gRPC输出是最复杂的输出通道。与其他通道相比,它允许更大的控制权,可以完全控制所接收的信息。如果您希望将警报发送到通过 Falco 的 gRPC API 连接的外部程序,则可以选择此输出通道。其默认配置为:

grpc_output:
  enabled: false

如您所见,默认情况下它是禁用的——在您启用它之前,有一些事情您应该考虑。Falco 自带一个暴露 API 的 gRPC 服务器。您需要同时启用 gRPC 服务器和 gRPC 输出(我们稍后会向您展示如何操作)。该 API 提供了多个 gRPC 服务,其中只有一些与 gRPC 输出相关。一个服务允许您获取所有待处理的警报,另一个服务允许您订阅警报流。客户端程序可以决定哪种实现最适合其需求。在两种情况下,当启用 gRPC 输出时,Falco 使用内部队列暂时存储警报,直到客户端程序消费它们。这意味着如果没有设置好客户端程序来消费警报,则不应启用 gRPC 输出;否则,内部队列可能会无限增长。全局缓冲选项不会影响此输出通道。

有了这些,为了使此输出通道正常工作,您首先必须启用 gRPC 服务器。它支持两种绑定类型:在 Unix socket 上和在具有强制性双向 TLS 认证的网络上。

下面是如何在 Unix socket 上启用 gRPC 服务器:

grpc:
  enabled: true
  bind_address: "unix:///var/run/falco.sock"
  threadiness: 0

以及如何在网络上启用具有强制性双向 TLS 认证的 gRPC 服务器:

grpc:
  enabled: true
  bind_address: "0.0.0.0:5060"
  threadiness: 0
  private_key: "/etc/falco/certs/server.key"
  cert_chain: "/etc/falco/certs/server.crt"
  root_certs: "/etc/falco/certs/ca.crt"

两种绑定类型都提供相同的 gRPC 功能,因此您可以选择满足您需求的那一种。一旦启用了 gRPC 服务器,下一步是启用 gRPC 输出:

grpc_output:
  enabled: true

最后,您将需要配置您的客户端程序以连接到 Falco gRPC API。如何完成这一步取决于您使用的程序。Falcosecurity 组织提供了两个可以连接到此输出的程序(参见 第二章):falco-exporter,它连接到 Falco gRPC API 以导出 Prometheus 可消耗的指标(更多信息请参阅 第十二章),以及 event-generator,它可以选择连接到 Falco gRPC API,以测试是否实际处理了虚假事件(在开发集成测试时很有帮助)。您也可以实现自己的程序。Falcosecurity 组织提供了 SDK,允许您轻松地使用多种编程语言为 Falco 创建 gRPC 客户端程序,例如 Golang 的 client-goRust 的 client-rsPython 的 client-py。您可以在 第十四章 中找到关于使用 Falco gRPC API 进行开发的更多信息。

最后,这是 Falco 通过 gRCP API 发送的消息的 proto 定义摘录:

// The `response` message is the representation of the output model.
// It contains all the elements that Falco emits in an output along 
// with the definitions for priorities and source.
message response {
  google.protobuf.Timestamp time = 1;
  falco.schema.priority priority = 2;
  falco.schema.source source_deprecated = 3 [deprecated=true];
  string rule = 4;
  string output = 5;
  map<string, string> output_fields = 6;
  string hostname = 7;
  repeated string tags = 8;
  string source = 9;
}

response 消息包括已经格式化的警报字符串(您将在 output 字段中找到)以及所有组件信息,这些信息分布在各种字段中。客户端程序可以根据需要组装和处理它们,这在您希望在 Falco 之上构建自己的应用程序时非常有用。

其他日志选项

到目前为止,我们已经描述了输出框架的核心部分。现在让我们看看一些帮助您进行故障排除的选项。与大多数应用程序一样,Falco 可以输出调试信息和错误。这些信息性的消息涉及到 Falco 本身的功能,而不是其主要输出。

Falco 在内部实现了各种日志消息。它们可能会随着不同的发布版本而有所变化。一个常见的例子是 Falco 启动时打印的初始信息。另一个不太常见的情况是当 Falco 通知您无法加载驱动时:

Mon Dec 20 14:00:23 2021: Unable to load the driver.

术语 logging 不涉及输出安全通知的过程。本节讨论的日志消息不是安全警报。日志选项不以任何方式影响通知处理。此外,由于这些日志消息不是通知,因此 Falco 不会通过输出渠道输出它们。尽管在终端中运行 Falco 时可能会看到通常的通知与日志消息交织在一起,但请记住它们是不同的。

Falco 通过 标准错误流 输出这些消息,并将它们发送到 syslog。您可以配置 Falco 根据其严重性级别丢弃某些消息。表 8-3 列出了您可以在 Falco 配置文件(/etc/falco/falco.yaml)中配置的日志选项。

表 8-3. Falco 内部日志选项

日志选项(带默认值) 描述
log_stderr: true 如果启用,Falco 将日志消息发送到 stderr。
log_syslog: true 如果启用,Falco 将日志消息发送到 syslog。请注意,此选项与 syslog 输出无关,也不会影响它。
log_level: info 此选项定义要包含在日志中的最低日志级别:emergencyalertcriticalerrorwarningnoticeinfodebug。请注意,尽管这些值相似,但它们不是规则优先级级别。

结论

本章结束了本书的 第二部分。在这一点上,您应该对 Falco 的架构及其内部工作原理有一个扎实的理解。您对处理管道数据流的熟悉,以输出框架结束,将允许您以多种方式使用 Falco。例如,您可以在喜爱的仪表板中查看安全通知,甚至在 Falco 之上创建响应引擎(在特定事件发生时采取行动的机制)。要发现更多用例,请发挥您的想象力,并继续阅读本书。

接下来是真实的使用案例,所以第 III 部分 是关于在生产环境中运行 Falco 的内容。像往常一样,我们会指导您完成每一步。

^(1) 并发队列 是实现多个运行线程可以安全并行访问的一种 队列数据结构poppush 操作是队列支持的典型操作(分别用于入队和出队)。大多数实现允许以阻塞或非阻塞方式执行这些操作。

^(2) 在 syslog 协议中,设施值确定创建消息的进程的功能。LOG_USER 用于用户级应用程序生成的消息。

第三部分:在生产环境中运行 Falco

第九章:安装 Falco

欢迎来到本书的第三部分,本部分将指导您如何在实际环境中使用 Falco。现在您已了解 Falco 及其架构的工作原理,下一步是开始使用它来保护您的应用程序和系统。在本章中,您将学习在生产环境中安装 Falco 所需的知识。我们将展示不同的场景和常见的最佳实践,以便您找到适合您用例的正确指导。

首先,我们将为您提供常见使用场景的概述,然后我们将为每种场景描述不同的安装方法。我们强烈建议您阅读所有安装方法的相关内容,即使您只需要其中的一部分,也能全面了解可能性并选择最适合您需求的方法。

选择您的设置

Falco 项目正式支持三种在生产环境中运行 Falco 的方式:

  • 直接在主机上运行 Falco

  • 在容器中运行 Falco

  • 将 Falco 部署到 Kubernetes 集群

每个选项都有不同的安装方法,第一个选项与其他选项之间有一些重要的区别。在您的环境中不包含容器运行时或 Kubernetes 时,直接在主机上安装 Falco 是唯一的选择。这也是运行 Falco 最安全的方式,因为它与容器系统隔离(因此在受损情况下难以侵入)。然而,直接在主机上安装 Falco 通常是最难维护的解决方案。它也不总是可能的(例如,当您的应用程序在托管的 Kubernetes 集群中运行且您无法完全访问主机时)。其他选项通常更直接且更易于管理。特别是如果您的应用程序在 Kubernetes 集群上运行,将 Falco 部署到 Kubernetes 是一个常见的选择。在做出选择之前,请考虑每种选择的利弊以及您的需求。

在使用任何这些方法安装 Falco 之前,您需要决定如何使用 Falco,这会对安装过程和配置产生重要影响。最常见的两种场景是监控系统调用和使用插件提供的数据源。

默认情况是在系统上部署 Falco 传感器以监控系统调用。在这种情况下,您将需要在每台机器或集群节点上部署一个 Falco 传感器,并在每个底层主机上安装驱动程序。

当您使用插件提供的数据源时,您可能只需要安装一个 Falco 传感器(或每个事件生成器一个),并且不需要驱动程序。虽然每个数据源的实际设置可能略有不同,但为简单起见,我们可以将其视为单一安装场景,因为整体过程非常相似。通常,后一种情况的要求较少,实施起来更简单。

如果您需要同时满足多个场景,则需要更多的 Falco 安装。然后,您可以通过使用其他工具(如 Falcosidekick,在 第十二章 中讨论)来汇总来自每个传感器的通知。

您的最终设置将取决于您的需求和选择。以下部分为上述两种情况(监视系统调用和使用插件提供的数据源)中的每种安装方法提供了说明。

直接在主机上安装

直接在主机上安装 Falco 是一项简单的任务——您在 第二章 中已了解了关键方面。此安装方法主要用于 Falco 使用系统调用来安全监控系统的默认情况,因此它还会安装驱动程序并配置 Falco 以使用它。(在 第十章 中,我们将讨论如何更改 Falco 配置并为其他数据源设置它。)

此方法将安装以下内容:

  • 用户空间程序 falco

  • 驱动程序(默认情况下的内核模块)

  • 默认配置文件和位于 /etc/falco 中的默认规则集文件

  • falco-driver-loader 实用程序(您可以使用此工具来管理驱动程序)

  • 一些捆绑插件(这些可能因版本而异)

要安装 Falco,您将使用 Falco 的“下载”页面提供的以下工件 之一:

  • .rpm

  • .deb

  • .tar.gz(二进制)包

如果您打算通过兼容的软件包管理器安装 Falco,则应使用前两个软件包之一;否则,请使用二进制软件包。继续阅读获取更多详细信息。

注意

下面的小节包括您需要在系统上运行的各种命令。确保您有足够的权限来执行它们(例如,使用 sudo)。

使用软件包管理器

此安装方法适用于支持 .deb.rpm 包的 Linux 发行版。.deb.rpm 包的设置过程还将安装一个 systemd 单元,以便在您的系统上将 Falco 作为服务使用,并通过动态内核模块支持(dkms)安装内核模块——默认驱动程序。

aptyum 是最流行的软件包管理器,分别允许安装 .deb.rpm 包。如果您使用支持 .deb.rpm 包的不同软件包管理器,安装过程将非常类似,尽管确切的说明可能会有所不同。请参阅其文档以获取更多详细信息。

使用 apt(.deb 包)

apt 是 Debian 及基于 Debian 的发行版(如 Ubuntu)的默认软件包管理器。它允许您安装分布为 .deb 包的软件应用程序。要使用 apt 安装 Falco,您首先需要信任 Falco 项目的 GPG 密钥 并配置包含 Falco 包的 apt 仓库:

$ curl -s https://falco.org/repo/falcosecurity-3672BA8F.asc | apt-key add -
$ echo "deb https://download.falco.org/packages/deb stable main" | tee \
 -a /etc/apt/sources.list.d/falcosecurity.list

然后更新 apt 软件包列表:

$ apt-get update -y

由于此安装方法还将安装 Falco 的内核模块,您必须首先安装 Linux 内核头文件:

$ apt-get -y install linux-headers-$(uname -r)

最后,安装 Falco:

$ apt-get install -y falco

使用 yum(.rpm 软件包)

yum 是用于使用 RPM 软件包管理器的 Linux 发行版的命令行实用程序,例如 CentOS、RHEL、Fedora 和 Amazon Linux。它允许您安装分布为 .rpm 软件包的软件应用程序。在使用 yum 安装 Falco 之前,您必须确保系统上存在 make 包和 dkms 包。您可以通过运行以下命令检查:

$ yum list make dkms

如果不存在,请安装它们:

$ yum install epel-release
$ yum install make dkms

接下来,信任 Falco 项目的 GPG 密钥 并配置保存 Falco 软件包的 RPM 仓库:

$ rpm --import https://falco.org/repo/falcosecurity-3672BA8F.asc
$ curl -s -o /etc/yum.repos.d/falcosecurity.repo \
 https://falco.org/repo/falcosecurity-rpm.repo

由于此安装方法还将安装 Falco 的内核模块,您必须首先安装 Linux 内核头文件:

$ yum -y install kernel-devel-$(uname -r)
提示

如果 yum -y install kernel-devel-$(uname -r) 没有找到内核头文件包,请运行 yum distro-sync 然后重启系统。重启后,再次尝试上述命令。

最后,安装 Falco:

$ yum -y install falco

完成安装

现在,您应该已经通过 dkms 安装了内核模块,并安装了一个 systemd 单元以将 Falco 作为服务运行。

在开始使用 Falco 之前,您需要启用 Falco systemd 服务:

$ systemctl enable falco

现在您的安装已完成。服务将在下次重启时自动启动运行。如果您希望立即启动它,只需运行:

$ systemctl start falco

从现在开始,您可以通过 systemd 提供的功能来管理 Falco 服务。

切换到 eBPF 探针

Falco 默认使用内核模块,这通常是直接在主机上安装 Falco 的最佳选择。但是,如果您有特定要求或其他原因不使用内核模块,则可以轻松切换到 eBPF 探针。

首先确保系统上已安装 eBPF 探针。您可以使用 falco-driver-loader 脚本安装它,如 “管理驱动程序” 中所述。

然后需要编辑 systemd 单元文件,位置位于 /usr/lib/systemd/user/falco­.ser⁠vice(路径可能因您的发行版而异)。您可以使用 systemctl edit falco 进行修改。您需要在该文件的 [Service] 部分添加一个选项来设置 FALCO_BPF_PROBE 环境变量。此外,在相同部分,注释(或删除)ExecStartPreExecStartPost 选项,以便 Falco 服务不再加载内核模块。以下摘录自 falco.service 文件中的更改已经突出显示:

[Unit]
Description=Falco: Container Native Runtime Security
Documentation=https://falco.org/docs/

[Service]
Type=simple
User=root
Environment='FALCO_BPF_PROBE=""'
#ExecStartPre=/sbin/modprobe falco
ExecStart=/usr/bin/falco --pidfile=/var/run/falco.pid
#ExecStopPost=/sbin/rmmod falco

完成后,请不要忘记重新启动 Falco 服务:

$ systemctl restart falco

现在 Falco 应该开始使用 eBPF 探针。

使用插件

Falco 软件包配置为支持系统调用检测场景,因此包含的 systemd 单元在 Falco 启动时加载内核模块。但是,如果您不使用系统调用,则无需加载驱动程序。如前一部分所述,为了防止 Falco 服务加载内核模块,请编辑 /usr/lib/systemd/user/falco.service 文件并删除(或注释掉)ExecStartPreExecStartPost 选项。选择性地,您还可以通过修改 User 选项的值来配置服务以更少特权的用户运行 Falco。

接下来,您需要配置 Falco 来使用您选择的插件(我们将在第十章中说明如何操作),并重新启动 Falco 服务。Falco 将使用新配置运行。

在不使用软件包管理器的情况下

安装 Falco 而不使用软件包管理器快捷简便。此安装方法适用于不支持兼容软件包管理器的发行版。我们在第二章详细介绍了这些步骤,但在这里我们将为您提供简要复习。

您只需获取最新可用版本的二进制软件包链接,从Falco“下载”页面下载到本地文件夹中:

$ curl -L -O \
    https://download.falco.org/packages/bin/x86_64/falco-0.32.0-x86_64.tar.gz

然后解压软件包并将其内容复制到文件系统的根目录:

$ tar -xvf falco-0.32.0-x86_64.tar.gz
$ cp -R falco-0.32.0-x86_64/* /

最后,如果您计划将系统调用作为数据源,请在使用 Falco 之前手动安装驱动程序(您将在接下来的部分找到说明)。如果要使用插件,则不需要安装驱动程序。还请注意,二进制软件包不提供 systemd 单元或任何其他机制以在系统启动时自动运行 Falco,因此是否执行 Falco 或将其作为服务运行完全取决于您。

管理驱动程序

如果您将系统调用作为数据源,可能需要管理驱动程序。如果您未使用软件包管理器安装 Falco,则需要在手动使用 Falco 之前安装驱动程序。所有可用的软件包都提供一个有用的脚本称为falco-driver-loader(在第二章介绍),您可以用此脚本来执行此操作。如果您在本章前面的说明中已经按照指示操作,您的系统上应该已经安装了它。

我们建议您通过使用 --help 来熟悉脚本的命令行用法。只需运行:

$ falco-driver-loader --help

此脚本允许您执行多种操作,包括通过编译或下载安装驱动程序(内核模块或 eBPF 探针)。它还允许您移除先前安装的驱动程序。

如果您运行脚本而不带任何选项:

$ falco-driver-loader

默认情况下,它会尝试通过 dkms 安装内核模块。准确地说,它首先尝试下载预构建的驱动程序,如果你的发行版和内核版本有可用的话。否则,它将尝试在本地编译驱动程序。脚本还会提示你是否缺少任何必需的依赖项(例如,如果系统上没有 dkms 或 make)。

如果你想安装 eBPF 探针,请运行:

$ falco-driver-loader bpf

在容器中运行 Falco

Falco 项目提供了几个容器镜像,可以用来在容器中运行 Falco。虽然本节描述的 Falco 容器镜像几乎适用于任何容器运行时,但我们在示例中简单地使用 Docker。如果你想使用其他工具,包括 Kubernetes,可以应用相同的概念。即使你只对在 Kubernetes 上部署 Falco 感兴趣,我们仍建议你阅读本节,因为它介绍了一些重要的概念。

表 9-1 列出了主要的可用镜像,你可以从 Falco “下载”页面 获取这些镜像。这些镜像包含了安装驱动程序和运行 Falco 所需的所有组件。本节稍后将讨论如何使用它们来支持一些常见的用例。

表 9-1. docker.io 注册表中托管的 Falco 容器镜像

镜像名称 描述
falcosecurity/falco 这是默认的 Falco 镜像。它包含了 Falco、falco-driver-loader 脚本和构建工具链(用于即时构建驱动程序)。镜像的入口点将调用 falco-driver-loader 脚本,在运行 Falco 之前自动安装驱动程序到主机。
falcosecurity/falco-driver-loader 这个镜像与默认镜像类似,但不会运行 Falco。镜像的入口点只会运行 falco-driver-loader 脚本。当你想在不同的时机安装驱动程序或者使用最少权限原则(参见“最少权限模式”)时,可以使用这个镜像。由于这个镜像本身无法运行 Falco,所以需要与其他镜像结合使用,比如 falcosecurity/falco-no-driver
falcosecurity/falco-no-driver 这是默认镜像的替代方案,只包含 Falco,因此无法安装驱动程序。当使用最少权限原则或者数据源不需要驱动程序时(例如使用插件时),可以使用它。

每个分发镜像都有不同的标签可供选择特定版本的 Falco。例如,falcosecurity/falco:0.32.0 包含了 Falco 的 0.32.0 发布版本。:latest 标签指向最新发布的 Falco 版本。

如果您想尝试一个尚未发布的 Falco 版本,:master标签会发布最新的可用开发版本。自动流程会在每次将新代码合并到 Falco GitHub 存储库的主分支时构建和发布带有此标签的镜像。这意味着它不是一个稳定版本——除非您想尝试实验性功能或调试特定问题,否则不要在生产环境中使用它。通常,我们建议始终使用:latest标签,因为它包含最新的 Falco 版本和规则集更新。

接下来,我们将描述如何在我们讨论过的两种常见场景中使用这些镜像:系统调用检测,需要驱动程序;使用插件作为数据源则不需要。

系统调用检测场景

系统调用检测需要在主机上直接安装 Falco 驱动程序(可以是内核模块或 eBPF 探针)。Falco 需要足够的权限来与驱动程序交互;当然,如果你想使用一个容器镜像来安装驱动程序,该镜像需要以完全权限运行。

Falco 项目提供两种模式来在运行时安装驱动程序,然后在容器中运行 Falco。第一种和最简单的模式只使用一个具有完全权限的容器镜像。第二种使用两个镜像:一个镜像临时以完全权限运行以安装驱动程序,另一个镜像然后以较低权限运行 Falco。第二种方法允许增强安全性,因为长时间运行的容器只获得受限的权限集,使潜在攻击者的生活更加困难。我们建议在容器中运行 Falco 时使用最低权限模式。

完全权限模式

在 Docker 中以完全权限运行 Falco 非常简单。您只需拉取默认镜像:

$ docker pull falcosecurity/falco:latest

然后使用以下命令运行 Falco:

$ docker run --rm -i -t \
 --privileged \
 -v /var/run/docker.sock:/host/var/run/docker.sock \
 -v /dev:/host/dev \
 -v /proc:/host/proc:ro \
 -v /boot:/host/boot:ro \
 -v /lib/modules:/host/lib/modules:ro \
 -v /usr:/host/usr:ro \
 -v /etc:/host/etc:ro \
 falcosecurity/falco:latest

此命令将在运行 Falco 之前动态安装驱动程序。默认情况下,容器镜像使用内核模块。如果您想使用 eBPF 探针,请添加-e FALCO_BPF_PROBE=""选项并删除-v /dev:/host/dev(只有内核模块需要/dev)。

如您所见,除了--privileged选项外,上述命令还将一组路径从主机挂载到容器中(每个-v选项都是一个绑定挂载)。

具体来说,-v /var/run/docker.sock:/host/var/run/docker.sock选项共享了 Docker 套接字,因此 Falco 可以使用 Docker 获取容器元数据(如第五章中讨论的 Falco 数据丰富技术)。您可以为系统上可用的每个容器运行时添加类似的选项。例如,如果还有 containerd,则包括-v /run/containerd/containerd.sock:/host/run/containerd/containerd.sock

Falco 需要共享/dev/proc以与驱动程序和系统接口,其他共享路径用于安装驱动程序。

最低权限模式

此运行模式遵循最小权限原则以增强安全性。虽然此模式是在容器中运行 Falco 的推荐方式,但并不一定适用于所有系统和配置。我们建议您尝试并根据实际情况回退到完全权限模式。

正如提到的,此方法使用两种不同的容器镜像。第一步,需要完全权限,是使用 falcosecurity/falco-driver-loader 镜像安装驱动程序。在首次运行 Falco 之前,以及在任何时候想要升级驱动程序时,都需要这样做。(或者,如前所述,您可以直接在主机上使用随二进制软件包提供的 falco-driver-loader 脚本安装驱动程序。如果已这样做,请跳过此步骤。)

要使用容器镜像安装驱动程序,请首先拉取该镜像:

$ docker pull falcosecurity/falco-driver-loader:latest

然后运行安装命令:

$ docker run --rm -i -t \
 --privileged \
 -v /root/.falco:/root/.falco \
 -v /proc:/host/proc:ro \
 -v /boot:/host/boot:ro \
 -v /lib/modules:/host/lib/modules:ro \
 -v /usr:/host/usr:ro \
 -v /etc:/host/etc:ro \
 falcosecurity/falco-driver-loader:latest

此命令默认安装内核模块。如果想要使用 eBPF 探针,请添加 -e FALCO_BPF_PROBE="" 选项。

最后一步是运行 Falco。由于驱动程序已安装,您只需使用 falcosecurity/falco-no-driver 镜像即可。因此,首先拉取它:

$ docker pull falcosecurity/falco-no-driver:latest

然后运行 Falco:

$ docker run --rm -i -t \
 -e HOST_ROOT=/ \
 --cap-add SYS_PTRACE --pid=host $(ls /dev/falco* | xargs -I {} 
$ echo --device {}) \
 -v /var/run/docker.sock:/var/run/docker.sock \
 falcosecurity/falco-no-driver:latest

如果使用其他容器运行时,请根据需要添加 -v 选项来自定义此命令。

最后,在使用 eBPF 探针时有一些注意事项。除非您的内核版本至少为 5.8,否则无法使用最低权限模式。这是因为在之前的内核版本中,加载 eBPF 探针需要 --privileged 标志。如果您的内核版本等于或大于 5.8,您可以使用 SYS_BPF 权限来解决此问题,方法是按照以下方式自定义命令:

$ docker run --rm -i -t \
 -e FALCO_BPF_PROBE=""
 -e HOST_ROOT=/ \
 --cap-add SYS_PTRACE --cap-add SYS_BPF -pid=host \
 -v /root/.falco:/root/.falco \
 -v /var/run/docker.sock:/var/run/docker.sock \
 falcosecurity/falco-no-driver:latest

注意,在启用了 AppArmor Linux 安全模块(LSM)的系统上,您还需要传递以下内容:

--security-opt apparmor:unconfined
提示

根据您使用的 Falco 版本和环境,可能需要自定义本节中描述的命令;请参考在线文档

插件场景

当您将插件用作数据源时,无需安装驱动程序,也不需要 Falco 具有完全权限运行,因此我们建议您在此场景中使用 falcosecur⁠ity/​falco-no-driver 镜像。无论选择哪种容器镜像,它所包含的默认 Falco 配置都无法直接使用;您必须为插件提供所需的配置。您可以通过使用外部配置文件并将其挂载到容器中来完成这一操作。

作为准备步骤,您需要创建一个本地副本的 falco.yaml 并根据您的插件配置进行修改。我们将在下一章节中说明如何操作。

一旦准备好自定义的 falco.yaml,要运行 Falco,请使用以下命令:

$ docker run --rm -i -t \
 -v falco.yaml:/etc/falco/falco.yaml \
 falcosecurity/falco-no-driver:latest

如果您想使用默认 Falco 发行版中未包含的插件,您将需要在容器中挂载插件文件和其规则文件。例如,要挂载 libmyplugin.somyplugin_rules.yaml,请将以下选项添加到前述命令中:

-v /path/to/libmyplugin.so:/usr/share/falco/plugins/libmyplugin.so 
-v /path/to/myplugin_rules.yaml:/etc/falco/myplugin_rules.yaml

部署到 Kubernetes 集群

Falco 最常见的用例之一是保护集群,因此将 Falco 部署到 Kubernetes 可能是您需要了解的最重要的安装方法。Falco 项目推荐了两种方法:

Helm

第一种安装方法使用 Helm,这是一个非常流行的工具,用于在 Kubernetes 上安装和管理软件。Falco 社区提供并维护了一个 Helm 图表,用于 Falco 及其与 Falco 集成的其他工具。使用提供的图表安装 Falco 很简单,而且大部分是自动化的。

Kubernetes 清单文件

另一种安装方法,更加灵活,基于一组 Kubernetes 清单文件。这些文件提供了默认的安装设置,用户可以根据需要进行自定义。虽然这种方法需要更多的工作量,但它允许在几乎任何 Kubernetes 集群上安装 Falco,而无需额外的工具。

这两种方法都很可靠,您应选择最适合您的环境和组织需求的方法。在接下来的子章节中,我们将为您详细介绍每种方法。唯一的要求是已安装并运行一个 Kubernetes 集群。

Note

本节描述的 Kubernetes 安装方法使用了讨论中的默认 Falco 容器镜像 “在容器中运行 Falco”。

使用 Helm

如果您更喜欢完全自动化的安装过程或者已经在您的环境中使用 Helm,那么这种安装方法适合您。安装 Helm 是先决条件;有关说明,请参阅 在线文档

Falco 的 Helm 图表将通过 DaemonSet 将 Falco 添加到集群中的所有节点。然后,每个部署的 Falco Pod 将尝试在其自己的节点上安装驱动程序。这是反映最常见情况的默认配置,即系统调用仪器化。

Tip

Falco Pod 内部使用 falco-driver-loader,它尝试下载预构建的驱动程序;如果失败,将动态构建驱动程序。通常情况下不需要任何操作。如果您注意到 Falco Pod 在部署后不断重启,那么可能是无法安装驱动程序。这种问题通常发生在您的发行版或内核中无法获取预构建的驱动程序,并且主机上没有内核头文件的情况下。要构建驱动程序,必须在主机上安装内核头文件。您可以通过手动安装内核头文件,然后再次部署 Falco 来解决此问题。

Helm 使用由kubectl提供的 Kubernetes 上下文来访问您的集群。在使用 Helm 安装 Falco 之前,请确保您的本地配置指向正确的上下文。您可以通过运行以下命令来检查:

$ kubectl config current-context

如果上下文未指向您的目标集群或 kubectl 无法访问您的集群,则必须解决此问题。否则,您可以继续进行下一步操作。

在安装图表之前,请添加 Falco 的 Helm 存储库,以便您的本地 Helm 安装程序可以找到 Falco 图表:

$ helm repo add falcosecurity https://falcosecurity.github.io/charts

运行此命令通常是一次性操作。要获取关于 Falco 图表的最新信息,请使用:

$ helm repo update

每当您想要使用 Helm 安装和更新 Falco 时,请执行此命令。

下一个也是最后一步是运行以下命令来安装图表:

$ helm install falco falcosecurity/falco

默认情况下,该图表安装内核模块。如果您想改用 eBPF 探针,则只需在该命令后附加--set ebpf.enabled=true

完成!过一段时间,Falco 的 Pod 将出现在您的集群中。您可以使用以下命令检查它们是否准备就绪:

$ kubectl get all

该图表按照默认设置为默认场景(系统调用仪器化)安装 Falco。其他场景的 Helm 安装过程非常类似;只需提供适当的配置即可。我们将在第十章讨论如何自定义您的 Falco 部署。您可以在其在线文档中找到有关 Falco 图表配置的更多信息。

使用清单

Kubernetes 清单是 JSON 或 YAML 文件(主要是 YAML),包含一个或多个 Kubernetes API 对象的规格,并描述您的应用程序及其配置。kubectl 命令行实用程序允许您使用这些文件在 Kubernetes 中部署工作负载。项目通常提供几乎准备好使用的示例清单,但通常需要根据您的需求进行调整。

由于 Falco 支持非常不同的场景和环境,Falco 项目并未为所有用例正式提供清单。但是,对于系统调用仪器化场景,您可以使用Falco 示例清单(列在表 9-2 中)作为起点,以制作您的定制清单。^(1)

表 9-2. Falco 示例清单文件

文件名 描述
daemonset.yaml 指定DaemonSet,以便每个节点上运行 Falco Pod 的副本(系统调用仪器化场景所需)。Pod 规范使用falcosecurity/falco容器映像。它还包括在此场景中运行映像所需的所有设置,类似于“在容器中运行 Falco”中描述的设置。
configmap.yaml 指定了包含默认falco.yaml文件和规则文件的ConfigMap。根据您的需求进行修改。
serviceaccount.yaml 指定了一个ServiceAccount,用于运行 Falco 的 Pod。Falco 需要此账户与 Kubernetes API 通信。通常情况下,您不需要修改它,除非您想更改服务账户名称。
clusterrole.yaml 指定了一个ClusterRole,包括 Falco 与 Kubernetes API 通信所需的基于角色的访问控制(RBAC)授权。不要更改所需权限列表,否则 Falco 将无法正确丰富 Kubernetes 元数据。
clusterrolebinding.yaml 指定了一个ClusterRoleBinding,将clusterrole.yaml中定义的权限授予serviceaccount.yaml中定义的服务账户。通常情况下,您不需要更改此项,除非您已更改了其他文件中的服务账户或集群角色名称。

一旦根据您的需求修改了清单文件,要将它们应用到 Kubernetes(即部署 Falco 到 Kubernetes),只需运行以下命令:

$ kubectl apply \
 -f ./templates/serviceaccount.yaml \
 -f ./templates/clusterrole.yaml \
 -f ./templates/clusterrolebinding.yaml \
 -f ./templates/configmap.yaml \
 -f ./templates/daemonset.yaml

一段时间后,Falco 的 Pod 应该会出现在您的集群中。要检查它们是否准备就绪,请使用:

$ kubectl get all

如果一切顺利,Falco 现在已经在您的生产集群中运行起来了,并且您已经学会如何自定义您的 Falco 部署。恭喜!

结论

本章介绍了 Falco 的不同安装方法,并解释了两种最常见安装场景之间的区别。然而,在某些情况下,您的安装将需要特定的配置或自定义。下一章将为您提供所有必要的补充信息,以便最终在生产环境中运行 Falco 并完全控制您的 Falco 安装。

^(1) Kubernetes 中 Falco 清单示例文件的实际 URL 可能会不时更改,但您始终可以在官方文档中找到它们的链接。Falco 的 Helm 图表也可以生成这些文件。令人惊讶的是,Falco 项目使用此 Helm 功能自动发布最新的清单示例文件,位于Falcosecurity GitHub 组织下。

第十章:配置和运行 Falco

在前一章中,您学习了如何在生产环境中安装 Falco。然而,您仍需了解其配置系统的工作方式。学习如何更改其设置对于随时间管理和适应您的需求至关重要。您可以在安装期间或安装后立即配置 Falco,在更新到新版本时或任何时候根据您的需求进行配置。

本章将帮助您理解和使用可用的设置。首先,我们将解释主要的干预领域:命令行选项、环境变量、配置文件和规则文件。然后,我们将深入探讨每一个。您还将找到关于生产用例的宝贵建议,以及一些微调 Falco 配置的提示。在章节末尾,您将找到一个专门介绍配置插件的部分,并展示如何更新运行中的 Falco 实例配置。

配置 Falco

您可以通过其设置来配置 Falco,我们将这些设置分为三类:

命令行选项和环境变量

命令行选项和环境变量是您运行 Falco 所需的首要设置。其中大多数设置允许 Falco 与您的系统进行通信,这对于系统仪表化和数据丰富化特别重要。这里的其他设置可以帮助您适应特定需求或帮助故障排除。

配置文件

您可以通过主配置文件几乎配置任何 Falco 行为,根据需要进行自定义。例如,您可以加载规则文件,激活所需的输出通道,并在需要时使用插件。默认情况下,Falco 在 /etc/falco/falco.yaml 中查找此文件,但您可以使用命令行选项指定不同的路径。

规则集

Falco 自带丰富的默认规则集,以便您可以立即开始使用它。然而,规则集可能是定制化最关键的方面。它代表了 Falco 引擎的配置,并设置了 Falco 将检测到的内容。按照惯例,规则文件位于 /etc/falco

在详细讨论每个类别之前,我们想向您展示 Falco 如何根据您的安装方式进行变化。

安装方法之间的差异

无论您选择哪种安装方法,Falco 的配置区域始终相同。但是,您可以更改设置的方式可能略有不同。

主机安装

如果您使用软件包管理器安装了 Falco,则可以直接在 systemd 单元文件中指定命令行选项和环境变量,该文件位于 /usr/lib/systemd/user/falco.service。使用 systemctl edit falco 是一种方便的方法。完成后,请记得使用 systemctl restart falco 重新启动服务。

如果您不使用包管理器,运行 Falco 完全由您决定,包括传递命令行选项和设置环境变量。在这种情况下,您可以手动创建一个 systemd 单元。您可以使用 falco-service 文件的源代码 作为示例。

无论您使用哪个包,您都会在 /etc/falco 下找到 Falco 的配置文件和规则文件。您可以直接编辑这些文件,然后重新启动 Falco。

容器

Falco 的容器镜像允许您指定要运行的命令,默认为 /usr/bin/falco。如果需要传递命令行选项,请通过容器运行时的 CLI 进行。例如,在 Docker 中,要传递 --version,可以使用:

$ docker run --rm -it falcosecurity/falco /usr/bin/falco --version

请注意,falcosecurity/falco 容器镜像的入口点是一个脚本,尝试自动安装驱动程序。如果要跳过安装,需要将 SKIP_DRIVER_LOADER 环境变量设置为任何非空值。在 Docker 中,可以使用 -e 选项设置环境变量。^(1) 例如,要获取版本并同时跳过驱动程序安装,可以运行:

$ docker run --rm -it -e SKIP_DRIVER_LOADER=y \
 falcosecurity/falco /usr/bin/falco --version

Falco 容器镜像还捆绑了默认的配置文件和规则文件。如果需要修改其中任何文件,通常的做法是制作文件的外部副本(例如,/etc/falco/falco.yaml),然后挂载到容器中。您可以从二进制包中获取配置和规则文件(确保与容器中运行的 Falco 版本匹配),根据需要进行修改。然后,在 Docker 中,使用 -v 选项将修改后的文件挂载到容器中。^(2)

Kubernetes 部署

在 Kubernetes 中部署 Falco 时,还需在 DaemonSet 或 Deployment 清单中指定命令行选项和环境变量。如果使用 Helm 或来自 第九章 的示例清单,部署将已经配置了连接到您的容器运行时和 Kubernetes API 服务器的所有选项。如果需要修改选项,请查找相应的 Falco 图表配置 或直接修改 清单

另一个重要的区别是配置和规则文件存储在 ConfigMap 内,其内容会覆盖容器镜像中包含的文件。对于 Helm 用户,维护者会将 Falco 的图表、配置和规则文件与 Falco 发行版同步更新。另一方面,如果使用清单文件,您完全可以确保 ConfigMap 嵌入了正确的文件。

命令行选项和环境变量

在运行 Falco 时,有时通过命令行选项或设置环境变量是更改某些设置的唯一方式。您通过命令行配置的设置始终优先于从配置文件加载的设置。

要获取 Falco 的命令行选项完整列表,请运行 falco --help。Falco 将按字母顺序打印每个选项(以及简要描述)。可用选项可能根据 Falco 版本的不同而变化。有疑问时,请始终参考 falco --help

在本节的其余部分,为了帮助您熟悉最重要的设置,我们按功能对其进行分组。我们还提供了关于使用环境变量的详细信息,这些信息在 falco --help 中找不到。

配置设置

在 表 10-1 中显示的两个命令行选项与 Falco 的配置文件相关(默认位于 /etc/falco/falco.yaml)。第一个选项允许您从不同位置加载配置文件;第二个选项允许您即时覆盖某些配置值。通常情况下不需要使用它们,但在故障排除时可能会很有用。此外,在生产环境中运行 Falco 时,请确保没有人错误地设置它们,以便 Falco 使用正确的配置文件和预期的设置。

表 10-1. 配置命令行选项

选项 描述
-c 设置 Falco 将加载的配置文件路径。如果未设置,则 Falco 将使用默认路径: /etc/falco/falco.yaml
-o, --option *<key>*=*<val>* 通过将值 *<val>* 设置为指定的配置选项 *<key>* 来覆盖配置文件中的值。您可以使用点表示法(.)指定嵌套选项,或使用方括号表示法([])访问列表:例如,-o key.subkey.list[0]=myValue

仪表设置(仅系统调用)

如您在第 4 和 9 章节中学到的,Falco 默认使用内核模块驱动程序。您可以通过设置 FALCO_BPF_PROBE 环境变量切换到 eBPF 探针。您可以将其设置为您想使用的探针路径,例如 FALCO_BPF_PROBE="/path/to/falco-bpf.o"。否则,您可以将其设置为空字符串(FALCO_BPF_PROBE=""),Falco 将默认使用 ~/.falco/falco-bpf.o

当您在容器或 Kubernetes 中运行 Falco 时,容器镜像支持 FALCO_BPF_PROBE 控制即时驱动程序安装,以及其他环境变量。 (falco-driver-loader 脚本暴露了大部分,因此您也可以使用 falco-driver-loader --help 获取更多信息。)现在让我们看看这些环境变量:

DRIVERS_REPO

如果您创建了预构建驱动程序的存储库(无论是内核模块还是 eBPF 探针),可以使用此选项指示脚本从您的存储库下载驱动程序。驱动程序存储库以以下 URL 结构托管文件:

<DRIVERS_REPO>/<DRIVER_VERSION>/falco_<OS_ID>​_<KER⁠NEL_RELEASE>_<KERNEL_VERSION>.[ko|o]

此变量允许您设置您存储库的基本 URL(末尾没有斜杠)。如果您在离线环境中运行 Falco 或者不希望从互联网下载预构建的驱动程序,可能希望使用此设置。如果未设置,此变量默认为 The Falco Project 的 public driver repository

DRIVER_INSECURE_DOWNLOAD

如果您的驱动程序存储库不支持 HTTPS,请将其设置为任何值(例如 yes),以允许脚本从不安全的 URL 下载文件。

SKIP_DRIVER_LOADER

如果您通过其他方式在主机上安装了驱动程序,很可能希望在容器启动时禁用 falco-driver-loader 脚本。在这种情况下,请将此环境变量设置为任何值(例如 yes)。此设置仅影响使用 falco-driver-loader 作为入口点的 Falco 容器镜像,例如 falcosecurity/falco 容器镜像。

HOST_ROOT

这个环境变量与这里列出的其他环境变量不同,它与驱动程序安装无关,直接影响 Falco。HOST_ROOT 期望一个基本路径,影响仪表盘的设置和增强系统。如果该值不为空,Falco 在访问主机文件系统时会将其用作路径前缀(例如 /dev 下的内核模块设备或从 /proc 和容器运行时 Unix 套接字路径中获取数据增强的信息)。falco-driver-loader 脚本也会为类似的目的使用这个变量(例如,访问 /boot/lib/usr/etc)。

在容器中运行 Falco 时,请使用 HOST_ROOT。通常的约定是将 HOST_ROOT=/host 并将所有相关路径挂载到容器中的 /host 目录下。Kubernetes 部署使用这种方法;详见第五章和第九章了解更多详情。

为了完整起见,与系统调用仪表盘相关的其他设置列在 Table 10-2 中。这些设置对性能有显著影响,因此除非必要,否则不要使用它们。

Table 10-2. 系统调用仪表盘命令行选项

Option Description
-A Falco 默认不监视所有系统调用,因此通常无法在规则条件中使用所有事件类型(驱动程序跳过大多数嘈杂或处理昂贵的系统调用,如 readwritesendrecv)。如果您启用此设置,驱动程序将发送所有支持的系统调用事件到 Falco,这在边缘用例中可能会有所帮助。但启用此设置会严重影响性能。Falco 可能无法跟上事件流。支持的系统调用完整列表可在 syscall_info_table.c 中找到。默认情况下,驱动程序跳过那些标记为 EF_DROP_SIMPLE_CONS 的系统调用。
-u, --userspace 仅在无法使用内核空间工具时使用此选项。此选项必须与像 pdig(在 第四章 中讨论的用户空间驱动程序)一样使用。

数据丰富设置(仅系统调用)

使用系统调用作为数据源时,Falco 需要连接到驱动程序。它还需要从主机、容器运行时和 Kubernetes 获取信息。在 第五章 中,我们简要讨论了本节描述的设置;表 10-3 提供了影响数据丰富机制的命令行选项和环境变量的详细使用说明。

表 10-3. 数据丰富命令行选项

选项 描述
--cri *<path>* 使用此选项指定 CRI 兼容容器运行时的 Unix 套接字路径。如果 Unix 套接字有效,Falco 将连接到运行时以获取容器元数据。在最新版本的 Falco 中,您可以多次指定此选项。Falco 将按顺序尝试每个给定的路径,并使用第一个连接成功的路径。如果未设置此选项,Falco 将仅尝试使用 /run/containerd/containerd.sock
--disable-cri-async 此选项禁用异步 CRI 元数据获取。通常不需要设置它。但如果 Falco 间歇性显示容器元数据,则此选项可以帮助您解决问题。
-k *<url>*, --k8s-api *<url>* 通过连接到由 *<url>* 指定的 Kubernetes API 服务器,启用 Kubernetes 元数据丰富。或者,您可以使用 FALCO_K8S_API 环境变量,它接受与此选项允许的相同值。
-K *<bt_file>* &#124; *<cert_file>*:*<key_file[#pwd]>* [:*<ca_cert_file>*], --k8s-api-cert *<bt_file>* &#124; *<cert_file>*:*<key_file[#pwd]>* [:*<ca_cert_file>*] 使用此选项与 Kubernetes API 服务器进行身份验证。你可以提供一个令牌文件^(a) (*<bt_file>*) 或证书和私钥 (*<cert_file>*:*<key_file>*)。如果使用后者,你可以选择使用密码 (*#pwd*) 访问私钥(如果加密),以及 CA 证书 (:*<ca_cert_file>*) 用于验证 API 服务器的身份。证书和私钥必须以 PEM 文件格式提供。作为替代方案,你可以使用 FALCO_K8S_API_CERT 环境变量,该变量接受此选项允许的相同值。
--k8s-node *<node_name>* 此选项启用了 Kubernetes 元数据增强的重要性能优化:Falco 在从 API 服务器请求 Pod 的元数据时将使用节点名称作为过滤器,丢弃来自其他节点的不必要的元数据。你应该始终设置此选项。如果不设置,Falco 将工作,但在大型集群上可能存在性能问题。
^(a) 一个持有者令牌文件包含了用于验证 API 请求的字符串,这是 Kubernetes 的可用 认证策略 之一。

规则集设置

表 10-4 显示了可能影响规则集的命令行选项。如果没有使用这些选项,Falco 将仅使用配置文件来加载规则。

表 10-4. 规则集命令行选项

选项 描述
-D *<substring>* 此选项允许你禁用其名称中匹配*<substring>*的一个或多个规则。你可以多次指定它,但与-t选项(见下文)不兼容。
-r *<rules_file>* 此选项允许你指定 Falco 用来加载规则的文件或目录。对于目录,Falco 将加载其中包含的所有文件。你可以多次指定 -r 来加载多个文件或目录。如果使用此选项,Falco 将忽略配置文件(/etc/falco/falco.yaml)中指定的规则文件和目录。因此,我们不建议在生产环境中使用它,除非用于调试或特殊情况。
-T *<tag>* 此选项禁用具有给定 *<tag>* 的所有规则。你可以多次指定它,但与 -t 选项不兼容(见下文)。
-t *<tag>* 此选项仅启用具有给定 *<tag>* 的规则,并禁用所有其他规则。你可以多次指定它,但与 -T-D 选项不兼容。

输出设置

我们在 第 8 章 中描述了大部分的输出格式选项(以及 Falco 输出通道配置)。然而,还有另外两个命令行选项(列在 表 10-5 中),允许您进一步自定义 Falco 的输出行为。

表 10-5. 输出命令行选项

选项 描述

| -p*<output_format>*, --print*<output_format>* | 启用时,此选项将额外信息附加到 Falco 通知的输出中。有几种不同的风格可用;例如:

  • -pc-pcontainer 将添加容器信息,例如名称和 ID。

  • -pk-pkubernetes 将添加 Kubernetes 信息,例如命名空间和 Pod 名称。

在 Kubernetes 环境中使用 Falco 时,我们建议使用 -pk。 |

-U, --unbuffered 此选项禁用输出通道中的完全输出缓冲(参见 第 8 章)。仅在将 Falco 输出导入另一个进程或脚本时遇到问题时使用。关闭输出缓冲可能会增加 CPU 使用率。

其他用于调试和故障排除的设置

迄今为止我们描述的命令行选项是您在操作 Falco 时可能经常使用的选项。但是,还有另一组选项(列在 表 10-6 中),用于更偶尔的使用,比如当您需要关于 Falco 安装的信息或尝试解决问题时。

表 10-6. 调试和故障排除的命令行选项

选项 描述
-e *<events_file>* 告诉 Falco 使用由 *<events_file>* 指定的跟踪文件(参见 第 3 章)作为数据源,而不是使用实时事件源。一旦 Falco 消耗完文件中的所有事件,它就会退出。适用于测试和规则编写。
-L 打印有关所有加载的规则的信息。
-l *<rule>* 打印具有名称 *<rule>* 的规则的名称和描述(如果已加载)。
--list[=*<source>*] 按类别列出所有可用的条件字段(参见 第 6 章)。如果还提供 *<source>*,Falco 将仅列出该数据源的字段。*<source>* 的值可以是 syscall 或由已配置的插件提供的任何其他数据源。
--list-plugins 打印已配置插件的信息。
-s *<stats_file>* 告诉 Falco 创建文件 *<stats_file>* 并在运行时填充统计信息。
--stats-interval *<msec>* 设置更新由 -s *<stats_file>* 创建的文件的刷新间隔(以毫秒为单位)。
--support 打印有关加载的 Falco 配置和规则集以及其他有用的故障排除信息的详细信息,您在请求帮助时可以提供这些信息(例如,在 Falco GitHub 存储库中 提交问题)。
-V, --validate *<rules_file>* 验证给定的 *<rules_file>* 内容。用于测试和规则编写。
-v 在 Falco 运行时启用详细日志记录。此选项不影响通常的 Falco 通知,但日志消息可能会交错。用于调试非常有用。
--version 打印您正在使用的 Falco 版本。

配置文件

在本书中,我们一直在讨论 Falco 的配置文件,并且已经涵盖了它的大部分重要方面。本节提供了一个概述和指向您可能需要的所有内容的指针。

默认情况下,配置文件是一个 YAML 文件,位于 /etc/falco/falco.yaml。在此文件中,您可以配置:

规则文件

rules_file 配置节点是配置文件中第一个找到的节点。它允许您选择 Falco 将加载的规则文件(更多细节将在下一节介绍)。

插件

您可以通过 load_pluginsplugins 配置节点启用插件并传递设置(参见“使用插件”)。

输出通道

各种配置节点允许您配置格式化、日志记录和输出通道选项。有关输出框架的更多信息,请参阅第 8 章。

嵌入式服务器

Falco 提供了一个嵌入式 Web 服务器,公开了一个健康的端点。[³] 容器编排器和其他应用程序可以使用此端点检查 Falco 是否正在运行。webserver 配置节点允许您启用和配置服务器。

Falco 还提供了一个 gRPC 服务器,您可以使用 grpc 配置节点启用和配置它(参见第 8 和 12 章)。

高级调优设置(仅限系统调用)

系统调用仪器是 Falco 支持的最复杂功能之一,因此配置文件还提供了其高级设置。这些设置因 Falco 版本而异,因此我们建议您始终参考在线文档和配置文件中的内联注释。

在这里需要注意的选项包括 syscall_event_drops,用于控制事件丢弃的检测;syscall_event_timeouts,帮助检测事件缺失(对于系统调用来说是不常见的情况);以及 metadata_download,提供了几个选项来微调从容器编排器 API 服务器下载信息。

规则集

Falco 自带一组预定义的规则,您可以直接使用。然而,有很多理由尽可能定制您的规则集。默认的规则集旨在覆盖主要的攻击向量,但这些规则无法涵盖所有可能的情况。攻击机制始终在发展,因此您的规则集需要跟上这些变化。如果您希望达到最高级别的安全性,您需要一个根据特定环境定制的规则集。

定制规则的额外好处包括避免噪声误报和优化 Falco 的性能。出于所有这些原因,你需要学会如何正确配置规则集。

加载规则文件

有两种方式告诉 Falco 要加载哪些规则文件:通过命令行或配置文件。在命令行上,你可以使用 -r 标志指定规则文件。在配置文件中,将规则文件放在 rules_file 部分下。请注意,通过命令行设置的内容会优先于配置文件。出于这个原因,在生产环境中,我们建议仅通过配置文件加载规则文件。

无论你选择哪种方法,你都可以指定多个规则文件或目录。因此,你可以这样做:

$ falco -r path/to/my/rulefile1.yaml -r path/to/my/rulefile2.yaml

或者:

rules_file:
  - path/to/my/rulefile1.yaml
  - path/to/my/rulefile2.yaml

需要注意的是,规则文件会按照指定的顺序加载和解析。(当条目是目录时,Falco 会按字母顺序加载该目录中的每个文件。)这使得可以在后续文件中自定义规则、宏和列表(参见 第七章),这些定义在一个文件中。默认的 Falco 配置经过精心设计以利用这种机制。

让我们来看看随 Falco 一起提供的默认配置文件中的 rules_file 部分:

rules_file:
  - /etc/falco/falco_rules.yaml
  - /etc/falco/falco_rules.local.yaml
  - /etc/falco/rules.d

主规则文件 falco_rules.yaml 包含系统调用的规则,随后是名为 falco_rules.local.yaml 的文件。你应该在 falco_rules.local.yaml 中进行对 falco_rules.yaml 的修改。默认情况下,这个文件是空的,你可以在其中工作而不必担心污染主规则文件。根据需要,你可以创建其他本地文件。

通常,Falco 每个数据源提供一个规则文件。你可以使用这种方法或根据需要使用多个文件。只需记住加载顺序很重要。还要注意,Falco 只会加载与配置的数据源匹配的规则;所有其他规则将被忽略。这意味着你无需担心手动删除或禁用用于其他数据源的规则文件。

调整规则集

调整规则集最重要的一点是理解你的用例需要检测什么。这将帮助你决定哪些规则对你有用,哪些不需要。避免不必要的规则不仅可以增加性能(Falco 将使用更少的 CPU 资源),还可以减少误报。

一旦完成初步浏览,禁用你不感兴趣的规则(如 第七章 中描述)。我们不建议从规则文件中删除它们,除非你从头开始创建自己的规则文件。我们还建议定期评估你的规则集,因为随时间推移,你需要的规则会发生变化。

接下来,查看规则的条件。我们将在 第十三章 中详细介绍编写 Falco 规则的细节,但现在我们将提供两个一般评估 Falco 规则的指南。

首先,避免在条件中使用过多的异常,例如,长链条的and not (...) and not (...)。Falco 必须顺序检查条件中的任何异常,这是一项昂贵的任务。在可能的情况下,更短的条件可以显著改善规则评估性能。

第二条准则仅适用于系统调用,并且认为规则条件应始终匹配一个事件类型或一小组事件类型。例如,evt.type=connectevt.type in (open,openat,openat2)都可以接受,但evt.type!=execve不可以,因为该过滤器将匹配除一个之外的所有事件类型,这太多了。Falco 通过事件类型对规则进行索引,以优化其内部评估过程;匹配太多事件类型的规则会使此索引变得低效。为了帮助规则作者发现此问题,Falco 对匹配所有事件类型的规则发出警告。

使用插件

默认情况下,Falco 配置为使用系统调用。如果你想使用插件作为数据源,请确保:

  • 插件文件已经存在于/usr/share/falco/plugins中(一些插件已随 Falco 一起提供);如果没有,你需要将其安装到该文件夹中。

  • 插件的规则文件已准备好(我们建议将其放在/etc/falco下)。

  • 你已经阅读了插件的文档,并理解了它需要哪些配置参数。

然后,准备 Falco 的配置文件以使用插件是一个三步骤的过程:选择正确的规则文件、配置插件并启用它。

为了说明这个过程,我们将使用CloudTrail 插件,它获取包含CloudTrail事件的日志文件(有关使用此插件的详细信息将在下一章提供)。CloudTrail 插件具有一个规则集,需要另一个具有字段提取能力的插件:JSON 插件。这两个插件和规则集都与 Falco 捆绑在一起,所以如果你已经安装了 Falco,你应该已经拥有它们。你会在/usr/share/falco/plugins下找到插件文件libcloudtrail.solibjson.so,规则文件在/etc/falco/aws_cloudtrail_rules.yaml

插件的规则文件通常不会在 Falco 配置中默认配置,因此你需要添加一个rules_file条目来加载正确的规则文件(如果需要,也可以删除不必要的规则文件):

rules_file:
  - /etc/falco/aws_cloudtrail_rules.yaml

接下来,在plugins下添加相关条目:

plugins:
  - name: cloudtrail
    library_path: libcloudtrail.so
    init_config:
      sqsDelete: true
    open_params: "sqs://my-sqs-queue"
  - name: json
    library_path: libjson.so
    init_config: ""

name字段必须与插件名称匹配,library_path必须与/usr/share/falco/plugins下的插件文件匹配。

init_config中,添加 Falco 将传递给插件的初始化参数(有关详细信息,请参阅插件的文档)。大多数插件接受纯文本或 JSON 格式的字符串。如果插件支持 JSON 字符串,你仍然可以使用 YAML 语法来编写init_config(如前面的示例);Falco 会自动为你进行转换。

open_params设置仅适用于具有事件源能力的插件(例如 CloudTrail 插件),并且仅接受纯文本字符串。它提供打开事件流的参数(再次参考插件的文档)。某些插件可能不需要此设置;在这种情况下,您可以将其设置为空字符串("")。

最后一步是启用您的插件:

load_plugins: [cloudtrail, json]

load_plugins设置接受一个插件名称数组。您可以同时启用多个插件。

就这样!您的插件现在已配置并准备在 Falco 中运行。

修改配置

安装和配置 Falco 后,您可能需要不时地更改其配置。有两种方式告知 Falco 加载更新的配置(即配置文件或规则文件的任何修改)。

最简单的方法就是修改配置然后重新启动 Falco。如果您是通过包管理器在主机上安装的 Falco,您可以使用systemctl restart falco命令来实现这一点。如果您在容器中运行 Falco,则重新启动容器。如果您在 Kubernetes 集群中运行 Falco,则需要重新部署 Falco。重新启动 Falco 是升级到新版本或更改其命令行设置的唯一方法。

第二种加载更新配置的方式是热重载,即告诉 Falco 在不停止其运行进程的情况下重新加载配置和规则文件。您可以通过发送SIGHUP 信号来告诉 Falco 自行重新加载:

$ kill -HUP *<falco process ID here>*

一旦 Falco 收到信号,它将重新加载配置文件和配置的规则文件。

自 0.32.0 版本以来,当修改配置文件或规则文件时,Falco 可以自动进行热重载。在配置文件中,watch_config_files设置控制此功能(默认启用)。因此,在最新版本的 Falco 中,您只需修改配置文件或规则文件,无需手动发送 SIGHUP 信号。

请注意,在 Falco 重新启动或热重载时,它不会检测事件。然而,与重新启动进程所需的时间相比,热重载 Falco 所需的时间显著缩短,通常可以忽略不计。

结论

本章和前一章深入讲解了在生产环境中安装、配置和运行 Falco 的各个方面,无论是系统调用仪器化场景还是使用插件作为数据源的场景。现在是深入探讨具体插件案例的时候了:如何利用 Falco 进行云安全。在下一章中,您将了解如何通过使用 CloudTrail 插件来保护您的云环境。

^(1) 在 Docker 运行容器时,还有几种设置环境变量的方法;更多信息请参阅 Docker 的在线文档

^(2) 有几种方法可以将文件挂载到容器中。详细信息请参阅 Docker 的文档

^(3) Falco 的开发人员最初引入了 Web 服务器来支持 Kubernetes 审计日志作为数据源。最近,他们将此功能因子分解为插件。因此,您可以在不同的 Falco 版本下的webserver配置节点中找到的实际设置可能会有很大差异。

^(4) 带有插件系统的 Falco 的首个版本不允许您同时启用具有事件源能力的多个插件。然而,您可以仅启用具有字段提取能力的多个插件(参见第四章)。

第十一章:使用 Falco 进行云安全

现在您已经学会了如何配置和运行 Falco 的所有必要知识,是时候关注一个对您的安全姿态有重大影响的重要主题了:云安全。

如果您正在阅读本书,那么您的一些软件(或者全部!)很可能在云中运行。由于 AWS 是云服务的领先提供者,您的软件很可能也在那里运行。

公共云环境非常适合运行软件。它们对弹性、灵活性和自动化的支持使得构建和运行应用程序更加容易和高效。然而,基于云的应用程序及其使用的数据面临来自全球的攻击。它们还容易受到来自内部团队的配置错误、失误和恶意行为的影响。

综合安全姿态需要考虑许多领域,包括应用程序、用户(内部和外部)以及数据。如果不能正确保护这些领域中的任何一个,将会产生漏洞,从而导致风险。例如,保护在容器和主机中运行的工作负载(你可以通过 Falco 有效实现)如果不涵盖这些工作负载所运行的云基础设施,就不会有实际益处。

幸运的是,Falco 可以填补这一差距,并帮助您实现所需的覆盖范围。让我们来学习如何做到!

为什么选择 Falco 用于 AWS 安全?

云安全是一个肥沃且不断发展的领域,具有许多实施选项。从架构上讲,大多数选项都可以归为两种基本类别:

  1. 查询云 API 或监视云数据存储以检测配置错误或漏洞的工具

  2. 将云日志流式传输到后端并对其进行索引以及让您进行查询的工具

如果您的目标是检测基于云的软件中的威胁,那么第一类工具并不是非常有用。轮询对于检测漏洞和验证合规性很有用,但缺乏检测威胁并快速响应所需的实时性质。第二类工具功能强大,但在像公共云这样产生大量日志的环境中非常昂贵,并且部署和使用起来也不友好。

Falco 的运行时安全方法为解决这一问题提供了非常有效的解决方案。Falco 的理念基于三个关键概念。首先,它以流式处理方式解析数据,实时检测威胁。其次,它在轻量级易于运行和部署的引擎上实现检测。第三,它提供一种紧凑的规则语言,快速学习但灵活表达。正如您在整本书中看到的那样,这种理念在系统调用和像 AWS CloudTrail 生成的日志中同样有效。

Falco 资源消耗极少,并且最重要的是,以流式方式分析数据——无需执行昂贵的复制或等待数据索引。Falco 实时查看您的数据,并在几秒钟内通知您存在的问题。正如您在本书的 Part I 中看到的那样,只需几分钟即可使其运行,并将其用于云日志和系统调用允许了统一的威胁检测方法。让我们看看它是如何工作的。

Falco 的架构与 AWS 安全

当部署在 AWS 基础设施安全环境中时,Falco 在 AWS CloudTrail 生成的日志之上实施检测。其工作原理如 Figure 11-1 所示。

Figure 11-1. Falco 用于 AWS 安全的高级架构

CloudTrail 是亚马逊提供的日志聚合服务。它从数百个 AWS 服务收集日志,并使用一致且有文档化的格式存储在 S3 中。CloudTrail 容易设置,并提供一致的层,使客户免受收集用户和服务活动日志的复杂性影响。

CloudTrail 事件是 CloudTrail 定期写入 S3 存储桶中的 JSON 文件条目。Falco 通过 CloudTrail 插件(Figure 11-2)理解如何读取和解析这些事件,该插件由 Falco 社区创建和维护。(如果您需要了解 Falco 插件及其工作原理,请参阅 Chapter 4。)

Figure 11-2. 使用 CloudTrail 插件进行事件收集

除了提供多种收集 CloudTrail 日志的方法(本章稍后详细介绍每种方法),CloudTrail 插件还使用 AWS 特定字段扩展了 Falco,您可以使用这些字段创建类似这样的规则:

- rule: Console Login Without MFA
  desc: Detect a console login without MFA.
  condition: >
    ct.name="ConsoleLogin" and ct.error=""
    and ct.user.identitytype!="AssumedRole" 
    and json.value[/responseElements/ConsoleLogin]="Success"
    and json.value[/additionalEventData/MFAUsed]="No"
  output: >
    Detected a console login without MFA 
    (requesting user=%ct.user, requesting IP=%ct.srcip, AWS region=%ct.region)
  priority: CRITICAL
  source: aws_cloudtrail

一旦将 Falco 的 CloudTrail 插件配置为以 CloudTrail 跟踪作为输入,Falco 将持续分析跟踪的即将到来的数据,提供实时异常和威胁检测。这就像为您的云活动安装了安全摄像头!

检测示例

这是配置为 AWS 安全时 Falco 可以检测到的一些事物:

  • 有人在 AWS 控制台上未使用多因素身份验证(MFA)登录。

  • 有人为根用户停用了多因素身份验证(MFA)。

  • 有人创建了新的 AWS 用户或组。

  • 有人在非批准的区域运行实例。

  • 有人更改了 S3 存储桶的权限。

  • 有人禁用了 CloudTrail 日志记录。

要获取完整列表,请参考 CloudTrail rules file

配置和运行 Falco 以保障 CloudTrail 安全

本章的这部分将概述使用 Falco 设置云安全的方法,描述各组件,并指导您正确配置所有内容。正如我们提到的,Falco 与 CloudTrail 的集成通过CloudTrail 插件完成。可以配置插件以三种不同方式接收日志文件:

  • 一个 Simple Queue Service(SQS)队列,传递有关新日志文件的 Simple Notification Service(SNS)通知

  • 一个 S3 存储桶

  • 本地文件系统路径

在这三种方法中,第一种是您在绝大多数生产情况下将使用的方法,因此我们将首先重点介绍它。

通过 SQS 队列接收日志文件

此部署方法利用 SQS 来通知 Falco 新的 CloudTrail 日志产生。当新日志到达时,Falco 会监视 SQS 队列并实时解析这些日志。该过程如图 11-3 所示。

图 11-3. SQS 队列集合图示

在此配置中设置 Falco 的过程包括三个步骤:

  1. 创建 CloudTrail 跟踪并配置与 SNS 主题关联。SNS 主题检测到跟踪将文件存储在其中的 S3 存储桶的更改并广播到全球。

  2. 创建 SQS 队列并将其附加到 SNS 主题。这将创建一个 Falco 可以使用来检测新数据到达的端点。

  3. 配置 Falco 以使用 SQS 队列接收日志。

我们将通过逐步说明来指导您设置所有这些内容,以便您全面了解各个组成部分。但在此之前,我们将向您展示一个简单的快捷方式:一个 Terraform 模块将为您完成这些工作。

基于 Terraform 的部署

您可以在GitHub上找到 Terraform 模块。将存储库克隆到本地计算机,然后执行以下命令:

$ cd examples/single-account
$ terraform init
$ terraform validate
$ terraform apply

如果一切顺利,您应该获得类似于以下输出:

Apply complete! Resources: 14 added, 0 changed, 0 destroyed.

Outputs:

cloudtrail_sns_subscribed_sqs_arn = "arn:aws:sqs:*ZZZZ*"
cloudtrail_sns_subscribed_sqs_url = "https://sqs.*<REGION>*.amazonaws.com/.../
*<QUEUE_NAME>*"

您现在可以在falco.yaml文件中使用*<QUEUE_NAME>*了:

plugins:
  - name: cloudtrail
    library_path: libcloudtrail.so
    init_config: ""
    open_params: "sqs://*`<QUEUE_NAME>`*"
  - name: json
    library_path: libjson.so
    init_config: ""
load_plugins: [cloudtrail, json]

接下来,配置falco.yamlrules_file部分以加载 CloudTrail 规则:

rules_file:
  - /etc/falco/aws_cloudtrail_rules.yaml

现在您可以启动 Falco 了!

手动部署

如果您不想使用 Terraform 脚本,以下是设置使用 SQS 队列的 Falco 的步骤。第一步是创建跟踪。您可以按以下步骤执行:

  1. 转到 AWS 控制台的 CloudTrail 部分。

  2. 单击“创建跟踪”。

  3. 将跟踪命名为Falco

  4. 作为存储位置,您可以选择一个现有的跟踪或告诉 AWS 创建一个新的跟踪。

  5. 不选中“日志文件 SSE-KMS 加密”。SSE 加密是您应该使用的良好实践,但配置它超出了本章的范围。

  6. 勾选“SNS 通知交付”。

  7. 在“创建新 SNS 主题”下,选择新建并命名主题falco-cloudtrail-logs

  8. 单击下一步。

  9. “选择日志事件”页面让您选择要捕获的日志。默认设置足以使 Falco 正常运行。勾选“数据事件”或“排除 Amazon RDS 数据 API 事件”将允许您如有需要创建更精细的数据事件规则,例如 S3 存储桶级别和对象级别的访问。

  10. 点击下一步。

  11. 点击“创建跟踪”。

接下来,创建 SQS 队列:

  1. 转到 AWS 控制台的 SQS 部分。

  2. 点击“创建队列”。

  3. 将队列命名为 falco-queue

  4. Falco 可以直接使用默认访问策略正常运行。但是,考虑实施一个更少权限的访问策略,例如使用 AWS 策略生成器

  5. 在页面底部点击“创建队列”。这将带您进入 falco-queue 详细信息页面。

  6. 点击“订阅 Amazon SNS 主题”。

  7. 选择名称以 falco-cloudtrail-logs 结尾的主题。

  8. 点击保存。

现在您需要配置 Falco。这涉及设置 AWS 身份验证和配置 Falco 本身。要从 S3 存储桶读取日志文件或从 SQS 队列接收 SNS 通知,Falco 需要身份验证凭据,并且需要配置 AWS 区域。Falco 依赖于与 AWS Go SDK 相同的身份验证机制:环境变量或共享配置文件。请按以下方式配置这些内容:

环境变量

使用 AWS_REGION=*xxx* 指定 AWS 区域,使用 AWS_ACCESS_KEY_ID=*xxx* 指定访问密钥 ID,使用 AWS_SECRET_ACCESS_KEY=*xxx* 指定秘密密钥。以下是一个示例命令行:

AWS_DEFAULT_REGION=us-west-1 \
AWS_ACCESS_KEY_ID=*xxx* \
AWS_SECRET_ACCESS_KEY=*xxx* \
falco -c *<path-to-falco.yaml>* -r *<path-to-falco-rules>*

共享配置文件

\(HOME/.aws/config* 文件中指定 AWS 区域,并在 *\)HOME/.aws/credentials 文件中指定凭证。以下是这些文件的示例内容:

$HOME/.aws/config
[default]
region = us-west-1

$HOME/.aws/credentials
[default]
aws_access_key_id=*<YOUR-AWS-ACCESS-KEY-ID-HERE>*
aws_secret_access_key=*<YOUR-AWS-SECRET-ACCESS-KEY-HERE>*

现在,设置 Falco 本身:

  1. falco.yaml 中添加以下片段以配置基于 SQS 的日志收集:

    plugins:
      - name: cloudtrail
        library_path: libcloudtrail.so
        init_config: ""
        open_params: "sqs://falco-queue"
      - name: json
        library_path: libjson.so
        init_config: ""
    load_plugins: [cloudtrail, json]
    
  2. 配置 falco.yaml 中的 rules_file 部分以加载 CloudTrail 规则:

    rules_file:
      - /etc/falco/aws_cloudtrail_rules.yaml
    
  3. 启动 Falco。

Et voilà: 您的 AWS 基础设施现在受到保护!

从 S3 存储桶或本地文件系统读取事件

虽然推荐使用基于 SQS 的设置进行实时检测,但 Falco 也可以直接从 S3 存储桶或本地文件系统中读取 CloudTrail 日志。基于 SQS 的设置在处理到达的“实时”日志时起作用,而 S3 和本地文件系统的设置则读取存储的数据。这意味着它们实际上是在过去运行,并且在到达当前存储数据的末尾时会导致 Falco 退出。这种方法在几个方面非常有价值。首先,它允许您在规则开发过程中快速迭代。其次,它允许您在 CloudTrail 日志上“回溯”运行 Falco(即使这些日志已存储了很长时间)。想知道在过去三周内是否有人更改了存储桶的权限?指向日志,您就可以轻松找出!

让我们看看如何以此模式运行 Falco。

S3 存储桶

首先,你需要设置 AWS 认证。我们刚刚描述了如何为 SQS 访问做这些设置,对于 S3 来说也是一样的,所以回到上一节的结尾,按照步骤操作即可。

配置好 AWS 认证后,在 falco.yaml 中添加以下片段:

plugins:
  - name: cloudtrail
    library_path: libcloudtrail.so
    init_config:
    s3DownloadConcurrency: 64
    open_params: >
        s3://my-s3-bucket/AWSLogs/411571310278/CloudTrail/us-west-1/2021/09/23/
  - name: json
    library_path: libjson.so
    init_config: ""
    load_plugins: [cloudtrail, json]

注意 open_params 键仅仅是 S3 上路径的 URI,你可以通过访问 S3 控制台中的数据并点击 “复制 S3 URI” 轻松获取。你不需要指定整个存储桶;你可以指向子目录甚至特定的日志文件。

现在你需要配置 falco.yaml 中的 rules_file 部分以加载 CloudTrail 规则:

rules_file:
  - /etc/falco/aws_cloudtrail_rules.yaml

之后,你只需运行 Falco。它会处理提供的 S3 URI 下的每个文件,并在完成后返回。

从 AWS 外部的机器(比如你的笔记本电脑)解析日志可能会非常慢,因为机器需要下载数据以便处理。你可以通过增加下载并发数(在 init_config 键中的 s3DownloadConcurrency)来加快速度,或者预先使用 AWS CLI 将数据下载到本地,然后指向 Falco 到本地日志(接下来我们将描述)。

本地文件系统路径

你可以通过将以下配置放入 falco.yaml 中来处理存储在本地文件系统中的 CloudTrail 日志:

plugins:
  - name: cloudtrail
    library_path: libcloudtrail.so
    init_config: ""
    open_params: >
        /home/user/cloudtrail-logs/059797578166_CloudTrail_us-east-1_2021...
  - name: json
    library_path: libjson.so
    init_config: ""
    load_plugins: [cloudtrail, json]

你可以指向单个文件或目录,Falco 将递归读取目录中的所有文件。

你还需要编辑 falco.yaml 中的 rules_file 部分以加载 CloudTrail 规则:

rules_file:
  - /etc/falco/aws_cloudtrail_rules.yaml

完成以上步骤后,只需运行 Falco。它会处理所有文件并在完成后退出。

扩展 Falco 的 AWS 规则集

Falco 提供了一个强大的基于 CloudTrail 的规则集。然而,如果需要定制,CloudTrail 插件导出了丰富的字段集,可以用于以高精度制定自己的规则。

Falco 规则的编写将在第十三章中进行详细讨论。然而,由于该章节主要集中在基于系统调用的规则上,这里有一些可以帮助你开始云规则开发的提示:

  • CloudTrail 规则需要包含以下关键字:source: aws_cloudtrail。这告诉 Falco 规则条件和输出中的字段必须来自 CloudTrail 插件。

  • 通过使用 --list=aws_cloudtrail Falco 命令行开关,你可以获取可以在 CloudTrail 规则中使用的字段列表。同时,请参阅第六章中的 Table 6-10。

其他云平台呢?

AWS 是云计算中非常重要的一部分,所以 Falco 首先增加了对它的支持。然而,在撰写本文时,Falco 社区正在努力增加对 Microsoft Azure 和 Google Cloud Platform 的支持。预计长远来看会添加更多的云平台!

如果您想了解 Falco 是否支持您的云平台,请查看GitHub 上的插件仓库

结论

在本章中,您了解到 Falco 不仅涉及系统调用和容器,还了解了如何利用它来保护您的云软件并大大提升安全姿态。在下一章中,我们将转向输出方面,并展示如何收集和处理 Falco 事件。

第十二章:消费 Falco 事件

到目前为止,您已经学会了如何运行和配置 Falco。您了解 Falco 如何用于运行时和云安全,以及它如何检测广泛的威胁。现在,是时候关注您可以如何处理 Falco 检测的内容了。消费 Falco 的输出是这个拼图的最后一块,也是本章的主题。

Falco 生成的警报对于观察和保护生产系统非常有帮助,我们将为您提供如何有效使用这些警报的建议。本章的第一部分是关于帮助您有效消费 Falco 输出的工具。我们将教您如何在 Falco 检测到安全威胁时立即获得通知,以便您的安全团队尽快采取适当的对策。最后,我们将展示一种自动响应威胁以加快响应时间的机制。

处理 Falco 输出

最小化的 Falco 安装会输出一个简单的文本日志,您可以稍后查阅,但这并不是很有用。幸运的是,更智能的工具允许您处理 Falco 的输出并扩展其功能,这些工具是将 Falco 整合到您生态系统中的重要部分。

本节将详细讨论本书中已提及的两个工具。第一个工具是 falco-exporter,它专为一个目标而设计:从 Falco 检测的事件生成指标。第二个工具 Falcosidekick 则是 Falco 输出的瑞士军刀,它允许您聚合来自多个 Falco 传感器的数据,过滤通知,并将它们转发到您环境中的任何其他应用程序或平台。

falco-exporter

falco-exporter 项目 提供了用于 Falco 输出事件的 Prometheus 指标导出器。它通过流式 gRPC API 消费 Falco 输出,并公开指标端点。这些指标包括触发规则的数量信息,以及与规则相关的优先级和标签的详细信息,还包括用于识别每个事件来源的标签,例如主机名、命名空间和 Pod 名称。它还提供了一个预配置的 Grafana 仪表板。^(1) falco-exporter 对于只需要关于安全事件的指标非常有用。(相比之下,Falcosidekick 也可以导出指标,但它具有许多其他功能和输出。)

在安装 falco-exporter 之前,请确保已安装并配置了 Falco,且启用了通过 Unix socket 的 gRPC 服务器和 gRPC 输出(参见“gRPC 输出”进行复习)。

主机安装

要直接在主机上安装 falco-exporter,您需要从 发布页面 下载最新版本,解压存档,并将可执行文件 falco-exporter 复制到您喜欢的位置(例如 /usr/bin)。无论您是手动执行还是作为服务运行都可以。默认选项与 gRPC Unix 套接字 /var/run/falco.sock(Falco 的默认选项)兼容。如果您需要自定义选项,请运行 falco-exporter --help 获取帮助。

在容器中运行

要在使用 Docker 容器中运行 falco-exporter,请使用以下命令:

$ docker pull falcosecurity/falco-exporter:latest
$ docker run -v /var/run/falco.sock:/var/run/falco.sock \
 falcosecurity/falco-exporter:latest

docker run 命令假设 Falco 已安装在主机上,并且 Falco 的 gRPC Unix 套接字位于 /var/run/falco.sock

部署到 Kubernetes

您可以使用 Helm 或清单文件将 falco-exporter 部署到 Kubernetes 集群(有关两种安装方法的详细信息,请参阅 第九章)。我们推荐使用 Helm。首先需要添加 Falcosecurity 图表存储库:

$ helm repo add falcosecurity https://falcosecurity.github.io/charts
$ helm repo update

然后,要安装图表,请运行:

$ helm install falco-exporter falcosecurity/falco-exporter

有关详细说明,请参阅 falco-exporter 图表文档。如果要使用清单文件,请按照 falco-exporter 文档 中的步骤操作。

Falcosidekick

Falcosidekick 项目 提供了连接 Falco 到您生态系统的完整解决方案。它在 Falco 输出的基础上工作,并允许您将其通知转发到许多其他目的地(参见 图 12-1)。Falcosidekick 可以向通知添加自定义字段或按优先级过滤事件(按目的地分)。特别支持的输出包括以下平台和应用程序:

  • 沟通与协作

  • 指标和可观察性

  • 警报

  • 日志和存储

  • 函数即服务(FaaS)和无服务器

  • 消息队列和流式传输

图 12-1. Falcosidekick 标志(左)及其支持的部分通知目标(右)

Falcosidekick 还允许您使用一个辅助项目,falcosidekick-ui,在愉快的 web UI 中可视化 Falco 事件(如 图 12-2 所示)。Web UI 显示有关检测事件的统计信息,并以聚合形式和时间线显示值。您还可以筛选您感兴趣的事件并快速获取所有事件详细信息。

图 12-2. Falcosidekick web UI

使用 Falcosidekick 需要对 Falco 的配置进行小修改:在使用之前,启用 JSON 格式化并配置 HTTP 输出以将事件发送到 Falcosidekick 端点(默认监听端口 2801)。有关 Falco 输出配置说明,请参阅 第八章,有关特定细节,请参阅 Falcosidekick 在线文档。

主机安装

要直接在主机上安装 Falcosidekick,请从发布页面下载最新版本,解压缩存档,并将可执行文件 falcosidekick 复制到您喜欢的位置(例如 /usr/bin)。是否手动执行或作为服务运行完全取决于您。您还需要创建一个 YAML 配置文件并将其路径作为参数传递。例如:

$ falcosidekick -c falcosidekick_config.yaml

Falcosidekick 仓库包含一个示例配置文件,您可以从中开始。Falcosidekick 还支持环境变量,您可以用其作为配置文件值的替代方案或进行覆盖。

在容器中运行

若要在使用 Docker 的容器中运行 Falcosidekick,请使用以下命令:

$ docker pull falcosecurity/falcosidekick:latest
$ docker run -d -p 2801:2801 falcosecurity/falcosidekick:latest

docker run 命令假定 Falco 已安装在主机上,并且 HTTP 输出已配置为将事件发送到端口 2801。使用 Docker 的 -e 选项,您可以使用环境变量传递配置。或者,使用 Docker 的 -v 选项给它一个 YAML 配置文件。

部署到 Kubernetes

与 falco-exporter 类似,您可以使用 Helm 或清单文件将 Falcosidekick 部署到 Kubernetes 集群中。我们建议使用 Helm 安装选项,它有两种变体。在我们探索它们之前,如果您还没有将 Falcosecurity 图表仓库添加到 Helm,请通过运行以下命令来添加:

$ helm repo add falcosecurity https://falcosecurity.github.io/charts
$ helm repo update

现在,您可以准备部署到您的 Kubernetes 集群了。这样做的第一种更普通的方式是,当您已经部署并配置了 Falco 来发送事件到 Falcosidekick,并且只需安装 Falcosidekick 图表:

$ helm install falcosidekick falcosecurity/falcosidekick

另一种变体允许您在单个 Helm 安装中部署 Falco 和 Falcosidekick,并自动配置这两个图表以共同工作。通常这是最方便的解决方案。要执行此操作,请运行:

$ helm install falco falcosecurity/falco --set falcosidekick.enabled=true

可选地,如果您还想要部署 Falcosidekick 的 Web UI,请在安装命令中添加 --set webui.enabled=true(无论您选择哪种变体)。

您可以在Falcosidekick 图表文档中找到关于其他选项的详细信息。如果您想使用清单文件,可以使用提供的在线示例。^(2)

可观察性和分析

Falco 允许您观察和分析您的云原生环境的安全性。如果您计划利用 Falco 的检测功能进行审计或取证,通常需要尽可能多地存储信息,并使 Falco 的结果易于访问和搜索。本章描述的工具将为您提供充分的支持。

存储 Falco 事件就像摄取任何其他应用程序日志一样。这意味着您可以重用现有的日志后端用于 Falco。此外,Falcosidekick 可以轻松地将 Falco 事件发送到允许您存储和分析大量日志数据的系统,如 Elasticsearch 和 Splunk。由于您可能会使用这种方法进行后续分析,我们建议保留 Falco 发出的所有事件,不进行任何过滤。

您可能还希望收集指标,因为这可以帮助您检测应用程序中的错误和异常。例如,一个指标报告 Falco 规则在特定机器上定期触发可能是安全问题、配置错误或正在运行的应用程序中的实现 bug 的症状。用于此目的的可靠工具是 falco-exporter:它公开指标,连接 Falco 到 Prometheus,并提供一个预配置的 Grafana 仪表板(图 12-3)。

图 12-3. falco-exporter 提供的预配置 Grafana 仪表板,用于展示 Falco 事件指标

接收通知

尽管存储和聚合 Falco 事件对于可观察性是可以的,但在需要立即对安全事件做出反应时并不有用。您可能希望立即且在正确的位置接收重要的通知,以便您或您的团队可以立即采取对策或开始调查。

Falco 的内置输出通道没有提供特定的即时通知机制,但是 Falcosidekick 允许您仅转发重要的通知。例如,假设您希望在事件触发 Sudo 潜在权限升级 规则(具有 priority: CRITICAL)时收到通知,而不是其他优先级较低的更嘈杂的规则。Falcosidekick 允许您配置一个最低优先级级别,以便将事件发送到特定目的地,并可以为每个目的地调整此配置。它支持大多数的呼叫系统,如 PagerDuty、Opsgenie 和 Prometheus Alertmanager,并且可以将通知发送到包括 Slack、Mattermost、Rocket.Chat、Microsoft Teams 和 Discord 在内的大多数常见通信平台。

您可以使用 Falcosidekick 的配置轻松地将 Falco 警报集成到现有环境中。而且因为 Falcosidekick 允许您同时将 Falco 通知发送到多个目的地,例如同时将警报发送到 PagerDuty 和 Slack 频道。

响应威胁

另一种有意义且更复杂的使用 Falco 事件的方式是创建系统,以自动响应威胁或安全事件。实施针对威胁的自定义操作比您想象的要容易。

虽然 Falco 项目本身不提供专门用于此目的的工具,但社区中的一些新兴项目正在实现这一概念。这类系统有时被称为 响应引擎,通常专门用于管理 Kubernetes 中的威胁。

响应引擎提供了一个简单的机制,用于在违反 Falco 规则条件时执行预定义任务。您可以使用 Falcosidekick 创建一个简单的实现,将 Falco 通知转发到一个 FaaS 平台或无服务器解决方案,该解决方案进而执行所需的操作。例如,您可以通过实现一个使用 Kubernetes API 删除受损 Pod 的云函数来自动终止 Kubernetes Pod,每当 Falco 规则确定该 Pod 受到威胁时。Figure 12-4 展示了这种方法,并展示了 Falcosidekick 支持的一些云函数提供者。

图 12-4. Kubernetes 响应引擎功能方案示例,使用 Falcosidekick 输出执行操作

您可能希望无论规则的优先级如何都收到通知,但您可能只希望针对特定规则执行操作。例如,您可能只希望具有 CRITICAL 优先级的规则终止 Pods。Falcosidekick 在此方面非常有帮助,因为它允许您基于其优先级值过滤通知,从而控制每个目标接收的信息。

我们建议您分析您的需求并设计响应引擎以满足这些需求。Falco 和类似 Falcosidekick 的工具将为支持您的解决方案提供所需的一切。

结论

本章结束了 第三部分。您已经了解了在生产环境中运行 Falco 的所有基本方面,并且现在可以为几乎任何需求和场景配置和定制它。您还了解了如何正确消费 Falco 事件并将其集成到您的生态系统中。

在 第四部分 中,您将超越普通用户的知识,学习如何扩展 Falco 以满足任何高级需求。

^(1) Grafana 仪表板 是一组组织良好的 UI 元素,用于可视化数据。仪表板配置可以存储在文件中并进行共享。您可以从 Grafana 的 在线画廊 获取大多数可用的仪表板。

^(2) Falcosidekick 示例清单文件的实际 URL 可能会不时更改,但您始终可以在 Falcosecurity GitHub 组织下找到它们。请注意,任何 Helm 图表都可以生成这样的文件。事实上,与 Falco 的清单文件一样,Falcosidekick 的文件也是从其图表开始渲染的。

第四部分:扩展 Falco

第十三章:编写 Falco 规则

欢迎来到本书的第 IV 部分!现在你已经了解了 Falco 的基本信息和功能(第 I 部分),理解了其架构的复杂性(第 II 部分),并且在部署和运行它方面已经很专业了(第 III 部分),现在是时候再次提升你的水平了。

本书的最后部分(第十三章至第十五章)讨论的是超越默认内容的内容。你将学习如何根据自己的特定需求定制 Falco,以及如何(如果你愿意的话)将你的改进贡献给项目,使整个社区受益。这是释放你创造力的地方。

我们在本书中已经广泛讨论了规则,特别是在第七章中。但是,当你能够创建自己的规则并将现有规则适应你的环境时,你才能解锁 Falco 的真正力量——这正是我们将在这里向你展示如何做到的。

本章假设你对字段和过滤器有很好的理解(在第六章中介绍),以及规则和规则文件的基础知识(在第七章中介绍)。如果你觉得需要恢复记忆,只需回到那些章节。我们会等你准备好的。

定制默认的 Falco 规则

虽然 Falco 的默认规则集是丰富且不断扩展的,但在遇到需要自定义这些规则的情况下并不少见。以下是一些例子:

  • 你希望扩展某个规则的范围或增加其覆盖范围。

  • 你希望缩减 Falco 加载的规则数量,以降低其 CPU 使用率。

  • 你希望通过控制规则的行为或向其添加异常来减少警报噪声。

Falco 提供了一个框架,可以在不必分叉默认规则文件并维护自己的副本的情况下完成这些事情。第七章 教会了你如何替换和追加宏、列表和规则,以及如何禁用规则。这尤为重要,因为正如你在第十章中学到的那样,规则文件加载的顺序很重要,而你可以控制这个顺序。这意味着你可以在初始化链中后加载的单独文件中更改现有规则。

默认的 Falco 配置被设计为利用这种机制,提供了两个可以在不触及默认规则集的情况下定制现有规则的地方。 第一个是falco_rules.local.yaml。 这个文件最初是空的,在加载falco_rules.yaml之后加载,因此是禁用或修改默认规则集中规则的好地方。 第二个是/etc/falco/rules.d。 默认情况下,Falco 会加载此目录中找到的所有规则文件,在加载falco_rules.yamlfalco_rules.local.yaml后加载。 这使其成为另一个进行自定义的好地方。

写作新的 Falco 规则

从本质上讲,编写新规则只是制作条件和输出的问题,因此在概念上它是一个非常简单的过程。 然而,在实践中,需要考虑几个因素。 即兴的规则开发通常导致不完善甚至无法使用的规则。 经验丰富的 Falco 用户倾向于开发自己的规则编写流程,我们建议您也这样做。 最佳流程取决于您的设置、目标环境和喜好,因此我们无法为您提供绝对的处方。 相反,我们将分享我们的做法,希望它能为您提供灵感和指导。

我们的规则开发方法

本书作者使用的规则开发方法由九个步骤组成:

  1. 复制您想要检测的事件。

  2. 捕获事件并将其保存在跟踪文件中。

  3. 使用 sysdig 的帮助制作和测试条件过滤器。

  4. 使用 sysdig 的帮助来制作和测试输出。

  5. 将 sysdig 命令行转换为规则。

  6. 在 Falco 中验证规则。

  7. 模块化和优化规则。

  8. 创建一个回归。

  9. 将规则与社区分享。

在接下来的几节中,我们将扩展列表中的每一项,并提供一个真实的例子,引导您完成制作一个新规则的过程,该规则检测试图在/proc/bin/etc目录内创建符号链接的尝试^(1)。 这至少是奇怪的行为,并且可能表明可疑活动。 下面是如何应用我们的方法来构建这样一个规则。

1. 复制您想要检测的事件

要创建可靠的规则,几乎不可能没有测试和验证,因此第一步是重新创建规则应该检测到的场景(或场景)。 在这种情况下,您想要检测三个特定目录中的符号链接的创建。 您可以使用终端中的ln命令重新创建该场景:

$ ln -s ~ /proc/evillink
$ ln -s ~ /bin/evillink
$ ln -s ~ /etc/evillink

2. 捕获事件并将其保存在跟踪文件中

现在,您可以使用 sysdig 捕获可疑活动。 (如果您需要 sysdig 和跟踪文件的复习,请返回到“观察系统调用”。) sysdig 允许您使用-w命令行标志轻松将活动存储在跟踪文件中。 要查看其工作原理,请在终端中发出以下命令:

$ sysdig -w evillinks.scap

在另一个终端中,再次运行三个ln命令,然后返回第一个终端并使用 Ctrl-C 停止 sysdig。现在,您可以随意检查您的活动跟踪文件多次:

$ sysdig -r evillinks.scap

您会注意到跟踪文件包含所有主机的活动,而不仅仅是您的ln命令。您还会注意到文件相当大。通过在运行捕获时使用过滤器,您可以使其更小并更易于检查:

$ sysdig -w evillinks.scap proc.name=ln

现在,您拥有一个少于 1 MB 大小的无噪声文件,其中只包含您需要制作规则的特定活动。将触发规则的活动保存在跟踪文件中有几个优点:

  • 它只需要复制复杂行为一次。(并非所有可疑行为都像运行ln三次那样简单!)

  • 它允许您专注于事件并留在单个终端中,而无需多次复制触发规则的命令。

  • 它允许您在不同的机器上开发规则。您甚至不需要在行为发生的机器上部署和配置 Falco!如果您希望捕获云容器或边缘设备等“不友好”环境中的行为,这真是太好了。

  • 它使您能够以普通用户权限开发规则。

  • 它提供了一致性,不仅对于创建规则很有用,而且在完成规则后实施回归测试时也很有用。

3. 使用 sysdig 制作并测试条件过滤器

现在您已经拥有所需的数据,是时候处理条件了。通常,在这个阶段,您会想要回答几个问题:

  1. 您需要针对什么类型的系统调用(或系统调用)?当然,并非所有的 Falco 规则都基于系统调用;例如,您可能在使用插件。但总体而言,识别将触发规则的事件类型是首要任务。

  2. 一旦确定要解析的事件,您需要检查其参数或参数中的哪些?

sysdig 可以帮助您回答这些问题。使用它来读取和解码捕获文件:

$ sysdig -r evillinks.scap

在输出文件的末尾就是魔法发生的地方:

2313 11:21:22.782601383 1 ln (23859) > symlinkat 
2314 11:21:22.782662611 1 ln (23859) < symlinkat res=0 target=/home/foo 
linkdirfd=-100(AT_FDCWD) linkpath=/etc/evillink

我们的系统调用是symlinkat。系统调用的manpage告诉您它是另一个系统调用symlink的变体。您还可以看到linkpath参数包含符号链接的文件系统路径。这正是您需要了解的内容,以便制作您的过滤器,应如下所示:

(evt.type=symlink or evt.type=symlinkat) and (
  evt.arg.linkpath startswith /proc/ or 
  evt.arg.linkpath startswith /bin/ or 
  evt.arg.linkpath startswith /etc/
)

您可以立即利用 sysdig 验证这是正确的过滤器:

$ sysdig -r evillinks.scap \
  "(evt.type=symlink or evt.type=symlinkat) and \
   (evt.arg.linkpath startswith /proc/ or \
   evt.arg.linkpath startswith /bin/ or \
   evt.arg.linkpath startswith /etc/)"
438 11:21:13.204948767 2 ln (23814) < symlinkat res=-2(ENOENT) target=/home/foo 
linkdirfd=-100(AT_FDCWD) linkpath=/proc/evillink 
1679 11:21:19.420360948 0 ln (23850) < symlinkat res=0 target=/home/foo 
linkdirfd=-100(AT_FDCWD) linkpath=/bin/evillink 
2314 11:21:22.782662611 1 ln (23859) < symlinkat res=0 target=/home/foo 
linkdirfd=-100(AT_FDCWD) linkpath=/etc/evillink

太好了!输出正确显示了三个应触发规则的系统调用。

4. 使用 sysdig 制作并测试输出

sysdig 非常方便,可以帮助你创建规则的输出。特别是 sysdig 的-p标志,接收一个与 Falco 兼容的字符串作为输入,并用它打印类似 Falco 的输出到终端,以便于测试规则的输出,知道当规则触发时 Falco 将显示相同的内容。例如,以下看起来是规则的一个很好的输出:

a symlink was created in a sensitive directory (link=%evt.arg.linkpath, 
target=%evt.arg.target, cmd=%proc.cmdline)

在 sysdig 中与过滤器一起进行测试:

$ sysdig -r evillinks.scap \
  -p"a symlink was created in a sensitive directory \
  (link=%evt.arg.linkpath, target=%evt.arg.target, cmd=%proc.cmdline)" \
  "(evt.type=symlink or evt.type=symlinkat) and \
  (evt.arg.linkpath startswith /proc/ or \
  evt.arg.linkpath startswith /bin/ or \
  evt.arg.linkpath startswith /etc/)"
a symlink was created in a sensitive directory (link=/proc/evillink, 
target=/home/foo, cmd=ln -s /home/foo /proc/evillink)
a symlink was created in a sensitive directory (link=/bin/evillink, 
target=/home/foo, cmd=ln -s /home/foo /bin/evillink)
a symlink was created in a sensitive directory (link=/etc/evillink, 
target=/home/foo, cmd=ln -s /home/foo /etc/evillink)

注意过滤器和输出条件周围的引号。这可以防止 Shell 因为它们包含的任何字符而混淆。

你的条件和输出看起来非常不错。是时候切换到 Falco 了!

5. 将 sysdig 命令行转换为规则

下一步是将你拥有的内容转换为 Falco 规则。这只是一个复制粘贴的练习,因为你已经知道条件和输出是如何工作的:

- rule: Symlink in a Sensitive Directory
  desc: >
    Detect the creation of a symbolic link 
    in a sensitive directory like /etc or /bin.
  condition: > 
    (evt.type=symlink or evt.type=symlinkat) and (
      evt.arg.linkpath startswith /proc/ or 
      evt.arg.linkpath startswith /bin/ or 
      evt.arg.linkpath startswith /etc/)
  output: >
    a symlink was created in a sensitive directory 
    (link=%evt.arg.linkpath, target=%evt.arg.target, cmd=%proc.cmdline)
  priority: WARNING

6. 在 Falco 中验证规则

将规则保存在名为symlink.yaml的 YAML 文件中。现在在 Falco 中测试它只需使用-r标志加载它,然后使用-e标志使用捕获文件作为输入:

$ falco -r symlink.yaml -e evillinks.scap
2022-02-05T01:09:23+0000: Falco version 0.31.0 (driver version 
319368f1ad778691164d33d59945e00c5752cd27)
2022-02-05T01:09:23+0000: Falco initialized with configuration file 
/etc/falco/falco.yaml
2022-02-05T01:09:23+0000: Loading rules from file symlink.yaml:
2022-02-05T01:09:23+0000: Reading system call events from file: evillinks.scap
2022-02-04T19:21:13.204948767+0000: Warning a symlink was created in a 
sensitive directory (link=/proc/evillink, target=/home/foo, cmd=ln -s /home/foo 
/proc/evillink)
2022-02-04T19:21:19.420360948+0000: Warning a symlink was created in a 
sensitive directory (link=/bin/evillink, target=/home/foo, cmd=ln -s /home/foo 
/bin/evillink)
2022-02-04T19:21:22.782662611+0000: Warning a symlink was created in a 
sensitive directory (link=/etc/evillink, target=/home/foo, cmd=ln -s /home/foo 
/etc/evillink)
Events detected: 3
Rule counts by severity:
   WARNING: 3
Triggered rules by rule name:
   Symlink in a Sensitive Directory: 3
Syscall event drop monitoring:
   - event drop detected: 0 occurrences
   - num times actions taken: 0

规则触发了预期次数,并显示了正确的输出。恭喜!

注意,在 Falco 中,你可以利用与 sysdig 创建的相同跟踪文件。-e命令行选项告诉 Falco:“从给定文件读取系统调用,而不是使用驱动程序。当你到达文件末尾时,打印摘要并返回。”对于快速迭代非常方便!

7. 模块化和优化规则

你已经有一个工作规则并进行了测试,但还有改进的空间。第 7 步是将规则模块化:

- macro: sensitive_sylink_dir
  condition: >
    (evt.arg.linkpath startswith /proc/ or 
     evt.arg.linkpath startswith /bin/ or 
     evt.arg.linkpath startswith /etc/)

- macro: create_symlink
  condition: (evt.type=symlink or evt.type=symlinkat)

- rule: Symlink in a Sensitive Directory
  desc: > 
    Detect the creation of a symbolic link
    in a sensitive directory like /etc or /bin.
  condition:  create_symlink and sensitive_sylink_dir
  output: >
    a symlink was created in a sensitive directory 
    (link=%evt.arg.linkpath, target=%evt.arg.target, cmd=%proc.cmdline)
  priority: WARNING

这将条件的检查移入宏中,使条件更短更易读。这很棒,但你还可以做得更好:

- list: symlink_syscalls
  items: [symlink, symlinkat]
- list: sensitive_dirs
  items: [/proc/, /bin/, /etc/]

- macro: create_symlink
  condition: (evt.type in (symlink_syscalls))
- macro: sensitive_sylink_dir
  condition: (evt.arg.linkpath pmatch (sensitive_dirs))

- rule: Symlink in a Sensitive Directory
  desc: >
    Detect the creation of a symbolic link 
    in a sensitive directory like /etc or /bin.
  condition:  create_symlink and sensitive_sylink_dir
  output: >
    a symlink was created in a sensitive directory
    (link=%evt.arg.linkpath, target=%evt.arg.target, cmd=%proc.cmdline)
  priority: WARNING

在这里所做的是将条件常量移入列表中。这有多个好处。首先,以一种非侵入性的方式使规则易于扩展。如果你想要添加另一个敏感目录,可以通过将相关项目添加到列表或者更好地说,通过创建第二个symlink_syscalls列表来添加它。这还让你有机会通过使用inpmatch等操作符来优化规则,以便进行高效的多重检查。

8. 创建一个回归测试

当你创建一个新规则时,特别是如果你的目标是将其包含在官方规则集中,你可能希望在将来能够进行测试。例如,你可能希望确保它仍然能在 Falco 的新版本或不同的 Linux 发行版上工作。你还可能希望在压力下测量其性能(如 CPU 利用率)。你在过程开始时创建的捕获文件是回归测试的良好基础。

作为替代方案,Falco 社区创建了一个名为事件生成器的工具(在第二章中提到),用于测试。该工具对于你添加规则操作非常有用,你或其他人可以在任意机器上实时触发规则。该工具可以以灵活的方式重播触发规则的场景,包括多次触发规则和特定频率触发规则。因此,你可以精确测量其 CPU 利用率。你还可以检查在重压下,规则是否会导致 Falco 性能下降到驱动程序开始丢弃系统调用的程度。

讨论事件生成器的全部内容超出了本书的范围,但你可以查看其GitHub 存储库以了解更多信息。

9. 与社区分享规则

恭喜,你已经完成了全新规则的开发!现在很重要的一点是,Falco 是社区为社区编写的工具。你编写的每一个新规则都可能对许多其他人有价值,因此你应考虑将其贡献给默认规则集。第十五章将教你一切关于向 Falco 贡献的知识。作为 Falco 的维护者和社区成员,我们预先感谢你决定与社区分享的所有规则。

写规则时需要记住的事项

现在我们已经掌握了基础知识,让我们讨论一些略微高级但在开发规则时非常重要的概念。

优先级

如第七章所述,每个 Falco 规则必须有一个优先级。规则优先级通常与输出一起报告,可以有以下值之一:

  • EMERGENCY

  • ALERT

  • CRITICAL

  • ERROR

  • WARNING

  • NOTICE

  • INFORMATIONAL

  • DEBUG

选择适当的规则优先级非常重要,因为通常会基于优先级筛选规则。将规则分配过高的优先级可能会导致警报洪水并降低其价值。

官方 Falco 文档关于如何在默认规则集中使用优先级的解释如下:

  • 如果规则涉及写入状态(文件系统等),其优先级为ERROR

  • 如果规则涉及未经授权的状态读取(读取敏感文件等),其优先级为WARNING

  • 如果规则涉及意外行为(在容器中生成意外 shell、打开意外网络连接等),其优先级为NOTICE

  • 如果规则涉及违反良好实践(意外的特权容器、挂载敏感文件系统、以 root 身份运行交互式命令),其优先级为INFORMATIONAL

噪音

噪音是制定规则时需要考虑的最关键因素之一,也是安全领域中一般复杂话题。检测工具如 Falco 中检测精度和误报生成之间的权衡是一个持续的紧张源。

通常说,唯一没有误报的规则集是没有规则的规则集。完全避免误报是极其困难并且常常是不切实际的目标,但您可以遵循一些指导原则来减少这个问题:

指南 1:测试和验证。

在生产环境中使用规则之前,请确保在尽可能多的环境中广泛测试它(不同的操作系统发行版、内核、容器引擎和编排器)。

指南 2:优先级及基于优先级的过滤是您的好帮手。

避免首次使用ERRORCRITICAL作为优先级部署规则。从DEBUGINFO开始,观察情况,如果噪音不大,再逐步提升优先级。低优先级规则可以在输出管道的不同阶段轻松过滤,因此不会在半夜唤醒安全运营中心团队。

指南 3:利用标签。

您分配给规则的标签包含在 Falco 的 gRPC 和 JSON 输出中。这意味着您可以使用它们来补充优先级并更加灵活地过滤 Falco 的输出。

指南 4:计划异常。

良好的规则设计考虑到已知和未知的异常情况,以一种可读且模块化的方式,并且可以轻松扩展。

例如,看看默认规则集中的写入 rpm 数据库以下规则:

- rule: Write below rpm database
  desc: an attempt to write to the rpm database by any non-rpm related program
  condition: >
    fd.name startswith /var/lib/rpm and open_write
    and not rpm_procs
    and not ansible_running_python
    and not python_running_chef
    and not exe_running_docker_save
    and not amazon_linux_running_python_yum
    and not user_known_write_rpm_database_activities
  output: >
    Rpm database opened for writing by a non-rpm program 
    (command=%proc.cmdline file=%fd.name 
    parent=%proc.pname pcmdline=%proc.pcmdline
    container_id=%container.id image=%container.image.repository)
  priority: ERROR
  tags: [filesystem, software_mgmt, mitre_persistence]

注意已知异常如何作为宏(rpm_procsansible_running_python等)包含在规则中,但规则还包括一个宏(user_known_write_rpm_database_activities),让用户通过覆盖机制添加自己的异常。

性能

性能是撰写和部署规则时需要考虑的另一个重要主题,因为 Falco 通常与高频数据源一起运行。当您将 Falco 与诸如内核模块或 eBPF 探针之类的系统调用源一起使用时,您的整个规则集可能需要每秒评估数百万次。在这样的频率下,规则的性能至关重要。

拥有紧凑的规则集绝对是保持 Falco CPU 利用率可控的良好实践,正如您在第十章中学到的那样。然而,同样重要的是确保您创建的每个新规则都针对性能进行了优化。您的规则的开销或多或少与规则条件需要为每个输入事件执行的字段比较数量成正比。因此,您应该期望像这样一个简单条件:

proc.name=p1

与像这个更复杂的规则相比,将会使用大约 20%的 CPU:

proc.name=p1 or proc.name=p2 or proc.name=p3 or proc.name=p4 or proc.name=p5

优化规则就是确保在大多数常见情况下,Falco 引擎需要执行尽可能少的比较。

以下是降低规则 CPU 利用率的一些指南:

  • 规则应始终从事件类型检查开始(例如evt.type=openevt.type in (mkdir, mkdirat))。Falco 在这方面很智能:它能理解当你的规则仅限于某些事件类型时,并且仅在接收到匹配事件时评估规则。换句话说,如果你的规则以evt.type=open开头,Falco 甚至不会为任何非open系统调用的事件开始评估它。这是如此有效(和重要!)以至于当规则不包含事件类型检查时,Falco 会发出警告。

  • 在你的规则中,包括那些有较高失败概率的激进比较,最好是在早期而不是晚期。Falco 条件类似于编程语言中的if语句:它从左到右评估,直到某些条件失败为止。你越早让条件失败,完成工作所需的工作量就越少。尝试找到限制规则范围的简单方法。你能将其限制在特定的进程、文件或容器吗?能够仅应用于特定用户的子集吗?在规则中编码这些限制,尽早实现。

  • 复杂的重型规则逻辑应包含在激进比较和限制之后(向右)。例如,长的异常列表应放在规则的末尾。

  • 尽可能使用多值运算符,如inpmatch,而不是编写多个比较。换句话说,evt.type in (mkdir, mkdirat)evt.type=mkdir or evt.type=mkdirat更好。多值运算符经过了大量优化,在值数量增多时效果更好。

  • 通常情况下,小而简单是好的。养成尽可能保持简单的习惯。这不仅会加快处理规则的速度,还会确保它们易读且易维护!

标记

标记是制定规则的强大工具。它有三个重要用途:灵活过滤 Falco 加载的规则、为其输出添加上下文、支持通知过滤和优先级设置,从而减少噪音。慷慨地使用标记将提高你的 Falco 体验,并确保你充分利用你的规则。

结论

这是一章的高强度内容!编写规则是一个要求严格但也可以很有趣和创造性的主题。而且,编写完美的规则以执行令人印象深刻的检测将使你在同事中获得很多赞誉。

^(1) 符号链接(symlink)一词是符号连接(symbolic link)的缩写;在 Unix 中,它表示对另一个文件或目录的引用。

第十四章:Falco 开发

扩展 Falco 是确保其完全符合您独特需求的最佳方式。本章将展示三种扩展 Falco 的方法。我们将首先概述 Falco 的代码库,并快速指导如何从源代码构建 Falco,这样您可以直接处理 Falco 的代码。这种第一种方法给予您更多自由,但可能比其他两种方法更困难,也许不太方便。第二种方法允许您通过与 gRPC API 交互构建处理 Falco 通知的应用程序。第三种方法是扩展 Falco 的标准且最简单的方式:编写您自己的插件。

对于最后两种方法,我们将通过示例来进行讲解。在这些代码片段中,我们使用 Go 编程语言,因此对其有一些了解将会有所帮助,但不是必需的。本章还假定您已经阅读了本书的第二部分。如果您担心这些材料可能过于困难,不要害怕:我们认为即使您不是开发人员,您也会发现它易于理解且有趣。

与代码库一起工作

Falco 是开源的,其所有源代码都存储在 GitHub 上的 Falcosecuriy 组织下。要开始浏览代码库,您只需要一个浏览器。如果您希望将源代码存储在本地并使用您喜欢的编辑器打开它,您需要使用 Git。

Falcosecurity 组织托管了 Falco 和许多其他相关项目。社区非常活跃,因此您还会找到许多实验性项目。Falco 项目的核心存放在两个主要仓库中:falcosecurity/falcofalcosecurity/libs

falcosecurity/falco 仓库

falcosecurity/falco 仓库 包含 falco 用户空间程序(通常与您进行交互的程序)的源代码。这是主要且最重要的仓库。该项目组织如下:

/cmake

在这里,您可以找到 Falco 构建系统用于拉取依赖项和实现特定功能的 cmake 模块,包括在构建过程中拉取 falcosecurity/libs 源代码的 cmake 文件。

/docker

此文件夹分为各种子目录,每个子目录包含 Falco 容器镜像的源代码。一些并未发布,因为它们仅供开发使用。详细信息请参阅README 文件

/proposals

此文件夹包含由社区提出并由维护者批准的设计提案。您可能会在这里找到有用的信息,帮助您理解 Falco 作者在某些架构决策及其背后的理由上做出的决定。

/rules

默认的规则文件存储在这里。

/scripts

此文件夹中包含各种脚本文件。例如,您将在此找到 falco-driver-loader 脚本的源代码。

/test/tests

这两个文件夹分别包含 Falco 的回归测试和单元测试。

/userspace

Falco 的实际 C++源代码位于这个文件夹中。它的内容被组织成两个子目录:engine,其中包含规则引擎实现,以及falco,其中包含高层次功能的实现,如输出通道、gRPC 服务器和 CLI 应用程序。

尽管这是 Falco 主仓库,但项目的大部分源代码并不在这里。大部分实际上在falcosecurity/libs仓库中,它包含 Falco 的核心低级逻辑的实现。

falcosecurity/libs 仓库

在本书的整个过程中,我们多次提到了libscaplibsinsp和驱动程序。falcosecurity/libs仓库托管这些组件的源代码。它的组织如下:

/cmake/modules

此文件夹包含用于拉取外部依赖项的 cmake 模块和libscaplibsinsp的模块定义,这些消费者应用程序(如 Falco)可以使用。

/driver

此文件夹包括内核模块和 eBPF 探针的源代码(主要用 C 编写)。

/proposals

与 Falco 仓库中的类似,此文件夹包含设计提案文档。

/userspace

组 这里的几个子目录中,你可以找到libsinsplibscap的源代码(C 和 C++)以及其他共享代码。

本仓库包含用于内核仪器和数据丰富的所有低级逻辑。过滤语法、插件框架实现和许多其他功能都托管在这里。libs代码库庞大,但不要让它吓倒你:理解它所需的只是良好的 C/C++知识。

从源构建 Falco

从其源代码编译 Falco 类似于编译其他使用 cmake 的 C++项目。构建系统需要一些依赖项:cmake、make、gcc、wget,当然,还有 git(获取 Falco 仓库的本地副本也需要 Git)。你可以在文档中找到如何安装这些依赖项的说明。

一旦确保系统上安装了所需的依赖项,可以使用以下命令获取本地副本:

$ git clone git@github.com:falcosecurity/falco.git

Git 会将仓库克隆到一个名为falco的新创建文件夹中。进入该目录:

$ cd falco

准备一个目录来包含构建文件,然后进入其中:

$ mkdir -p build
$ cd build

最后,在构建目录中运行:

$ cmake -DUSE_BUNDLED_DEPS=On ..
$ make falco

第一次运行此命令可能需要大量时间,因为 cmake 会下载并构建所有依赖项。这是因为我们使用了-DUSE_BUNDLED_DEPS=On进行配置;或者,你可以设置-DUSE_BUNDLED_DEPS=Off来使用系统依赖项,但如果这样做,你需要在构建 Falco 之前手动安装所有必需的依赖项到你的系统上。你可以在文档中找到更新的依赖项列表和其他有用的 cmake 选项。

make命令完成后,如果没有错误,你应该会在./userspace/falco/falco(相对于构建目录的路径)下找到新创建的 Falco 可执行文件。

现在,如果你也想从源代码构建驱动程序,并且你的系统已经安装了内核头文件,请运行:

$ make driver

此命令默认仅构建内核模块。如果你想构建 eBPF 探针,请使用:

$ cmake -DBUILD_BPF=True ..
$ make bpf

在两种情况下,你会在./driver(相对于构建目录的路径)下找到新构建的驱动程序。

使用 gRPC API 扩展 Falco

虽然你可能会考虑直接向代码库引入新功能,但还有更方便的方法。例如,如果你想扩展 Falco 的输出机制,你可以创建一个在 Falco 之上运行并实现你的业务逻辑的程序。特别是,gRPC API 允许你的程序轻松消费 Falco 通知并接收元数据。

本节将使用一个示例程序向你展示如何开始使用 Falco gRPC API 进行开发。为了跟随示例,请确保你的系统上有一个运行的 Falco 实例,并启用了 gRCP 服务器和 gRPC 输出通道(参见第八章)。你将通过 Unix 套接字使用 gRPC,因此请确保已安装并配置了 Falco。

我们在以下示例中使用client-go,它使得使用 gRPC API 变得简单直接:

package main

import (
   "context"
   "fmt"
   "time"

   "github.com/falcosecurity/client-go/pkg/api/outputs" ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/prac-cldntv-sec-falco/img/1.png)
   "github.com/falcosecurity/client-go/pkg/client"  ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/prac-cldntv-sec-falco/img/1.png)
)

func main() {

   // Set up a connection to Falco via a Unix socket
   c, err := client.NewForConfig(context.Background(), &client.Config{
      UnixSocketPath: "unix:///var/run/falco.sock", ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/prac-cldntv-sec-falco/img/2.png)
   })
   if err != nil {
      panic(err)
   }
   defer c.Close()

   // Subscribe to a stream of Falco notifications
   err = c.OutputsWatch(context.Background(), ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/prac-cldntv-sec-falco/img/3.png)
      func(res *outputs.Response) error {
         // Put your business logic here
         fmt.Println(res.Output, res.OutputFields) ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/prac-cldntv-sec-falco/img/4.png)
         return nil
      }, time.Second)
   if err != nil {
      panic(err)
   }
}

1

我们首先导入client-go

2

main函数建立一个连接(由变量c表示)到 Falco 的 gRPC 服务器,通过 Unix 套接字使用默认路径。

3

连接c允许调用OutputsWatch函数,订阅通知流并使用回调函数处理任何传入的通知。

4

这个示例使用一个匿名函数,它将通知打印到标准输出。在实际应用中,你应该实现自己的业务逻辑来消费 Falco 通知。

使用 gRPC API 来实现与 Falco 交互的程序非常方便直接。如果你需要使 Falco 与其他数据源一起工作,插件系统可能是你需要的。

使用插件扩展 Falco

插件是扩展 Falco 的主要方式,我们在整本书中多次提到过它们。简而言之,插件是符合特定 API 的 共享库。在 Falco 插件框架中,插件的主要责任是通过连接 Falco 到外部源并生成事件来添加新的数据源,并通过导出字段列表并解码事件数据以在 Falco 需要时生成字段值,从事件中提取数据。

插件包含生成和解释数据的逻辑。这很强大,因为这意味着 Falco 只关心从插件中收集字段值并将它们组合成规则条件。换句话说,Falco 只知道哪些字段可以使用以及如何获取它们的值;其他所有操作都委托给插件。由于这种系统,你可以将 Falco 连接到任何领域。

设计插件时有几个重要方面需要考虑。首先,具有事件源功能的插件隐式定义了事件负载格式(插件返回给框架的序列化原始事件数据)。具有与数据源兼容的字段提取功能的同一插件或其他插件稍后将能够访问负载,并在提取字段时使用。其次,具有字段提取功能的插件显式定义了绑定到数据源的字段。最后,规则依赖于数据源规范,以消耗其期望格式的事件。

由于描述插件开发的每个技术细节都需要一本专门的书,因此在本节中,我们只提供一个教育性示例,展示如何实现一个既可以生成事件又可以提取字段的插件。如需更全面的覆盖范围,请参阅文档

我们的示例将实现一个插件,该插件从 bash 历史文件中读取(默认位于 ~/.bash_history)。每当用户在 shell 中输入命令时,bash 都会将该命令行存储下来。当 shell 会话结束时,bash 会将输入的命令行追加到历史文件中。它基本上就是一个日志文件。虽然它没有令人信服的用例,但它是学习如何创建从日志文件生成事件的插件的简单方法。因此,让我们开始用一些 Go 代码来玩乐。

准备一个 Go 中的插件

首先,创建一个文件(我们称之为 myplugin.go),并导入一堆 Go 包以简化开发。你还将导入tail,一个模拟tail命令的库(我们的示例使用它从日志文件中读取),以及来自 Falcosecurity 的 Go 插件 SDK中的一组包,让你可以实现具有提取器功能的源插件。你必须使用 main 包,否则 Go 将不允许你将其编译为共享对象:

package main

import (
   "encoding/json"
   "fmt"
   "io"
   "os"
   "time"

   "github.com/hpcloud/tail"

   "github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
   "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
   "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/extractor"
   "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/source"
)

SDK 定义了一组接口,帮助您按照简化且明确定义的模式实现插件。正如您马上将看到的,您需要通过向表示您的插件的一对数据结构添加方法(也称为 带接收器的函数 在 Go 中)来满足这些接口。在幕后,SDK 导出这些方法作为插件框架所需的调用约定函数(或简单的 C 符号)。(如果您需要回顾,请参阅 “Falco 插件”。)

插件状态和初始化

SDK 需要一个表示插件及其状态的数据结构。它可以实现各种可组合的接口,但所有类型的插件至少必须实现 Info 接口以公开有关插件的一般信息,并实现 Init 接口以使用给定的配置字符串初始化插件。

例如,此示例称此数据结构为 bashPlugin。您还将定义另一个数据结构(称为 bashPluginCfg),表示插件的配置,以在其中存储选项。这不是强制性的,但通常很方便:

// bashPluginCfg represents the plugin configuration.
type bashPluginCfg struct {
   Path string
}

// bashPlugin holds the state of the plugin.
type bashPlugin struct {
   plugins.BasePlugin
   config bashPluginCfg
}

现在,您将实现第一个公开有关插件一般信息的必需方法:

func (b *bashPlugin) Info() *plugins.Info {
   return &plugins.Info{
      ID:          999,
      Name:        "bash",
      Description: "A Plugin that reads from ~/.bash_history",
      Version:     "0.1.0",
      EventSource: "bash",
   }
}
提示

所有源插件都需要 ID 字段,并且在它们之间必须是唯一的,以确保互操作性。特殊值 999 仅保留用于开发目的;如果您打算分发您的插件,您应该在 插件注册表 中注册它以获取唯一的 ID。

另一个重要的互操作性字段是 EventSource,您可以在其中声明数据源的名称。提取器插件可以使用该值来确定它们是否与数据源兼容。

另一个必需的方法是 Init。Falco 仅在加载插件时调用此方法,并传递配置字符串(在 Falco 配置中为插件定义的字符串)。通常,配置字符串是 JSON 格式的。我们的示例首先为我们之前声明的插件配置数据结构 b.config 的成员设置默认值。然后,如果给定的 config 字符串不为空,函数将 JSON 值解码到 b.config 中:

func (b *bashPlugin) Init(config string) error {

   // default value
   homeDir, _ := os.UserHomeDir()
   b.config.Path = homeDir + "/.bash_history"

   // skip empty config
   if config == "" {
       return nil
   }

   // else parse the provided config
   return json.Unmarshal([]byte(config), &b.config)
}

添加事件源功能

特别是对于具有事件源功能的插件,SDK 需要另一个表示 捕获会话(事件流)的数据结构。它还需要以下方法:

  • Open 用于启动和初始化捕获会话

  • NextBatch 用于生成事件

Falco 在初始化后立即调用Open。这表示捕获会话的开始。该方法的主要责任是实例化保存会话状态的数据结构(在我们的示例中为bashInstance)。具体来说,我们在这里创建一个*tail.Tail实例(模仿tail -f -n 0的行为)并将其存储在t中。然后我们创建一个bashInstance实例(可以将t分配给它)并返回它:

// bashInstance holds the state of the current session.
type bashInstance struct {
   source.BaseInstance
   t      *tail.Tail
   ticker *time.Ticker
}

func (b *bashPlugin) Open(params string) (source.Instance, error) {
   t, err := tail.TailFile(b.config.Path, tail.Config{
      Follow: true,
      Location: &tail.SeekInfo{
         Offset: 0,
         Whence: os.SEEK_END,
      },
   })
   if err != nil {
      return nil, err
   }

   return &bashInstance{
      t:      t,
      ticker: time.NewTicker(time.Millisecond * 30),
   }, nil
}

插件系统存储Open返回的值,并将其作为源插件的最重要方法的参数传递:NextBatch。与其他方法不同,此方法属于会话数据结构(bashInstance),而不属于插件数据结构(bashPlugin)。在捕获会话期间,Falco 重复调用NextBatch,后者又生成一批新事件。批处理的最大大小取决于其底层可重用内存缓冲区的大小。然而,批处理中的事件数可以少于其最大容量;它可以仅包含一个事件,甚至是空的。该方法通常实现源插件的核心业务逻辑,但本例中仅实现了一些简单的逻辑:尝试从b.t.Lines通道接收行并将它们添加到批处理中。如果没有,则在一段时间后会超时:

func (b *bashInstance) NextBatch(
	bp sdk.PluginState,
	evts sdk.EventWriters,
) (int, error) {
   i := 0
   b.ticker.Reset(time.Millisecond * 30)

   for i < evts.Len() {
      select {
      case line := <-b.t.Lines:
         if line.Err != nil {
            return i, line.Err
         }

         // Add an event to the batch
         evt := evts.Get(i)
         if _, err := evt.Writer().Write([]byte(line.Text)); err != nil {
            return i, err
         }
         i++
      case <-b.ticker.C:
         // Timeout occurred, return early
         return i, sdk.ErrTimeout
      }
   }

   // The batch is full
   return i, nil
}

如您所见,SDK 提供了一个sdk.EventWriters接口。这自动管理批处理的可重用内存缓冲区,并允许实现者将原始事件负载作为字节序列写入。函数evts.Len返回批处理中允许的最大事件数。

事件负载格式的选择由插件作者决定,因为插件 API 允许数据的编码(在我们的示例中,为了简单起见,我们将整行文本作为普通文本存储在负载中)和数据的解码(稍后我们将看到)。这允许您创建可用于规则的字段。选择正确的格式至关重要,因为它不仅对性能有影响,还对与其他插件的兼容性有影响(其他作者可能希望实现与您的事件兼容的提取器插件)。

到目前为止,您已经看到了实现源插件所需的最小方法集。但是,如果我们不添加一种方法来导出字段以用于规则条件和输出,此时插件实际上并不会真正有用。

添加字段提取能力

具有字段提取能力的插件可以从事件数据中提取值并导出 Falco 可以使用的字段。一个插件可以只具有事件源功能(在前一节中描述),也可以只具有字段提取功能,或者两者兼有(就像我们的示例插件一样)。具有字段提取能力的插件将在其他插件提供的数据源上工作,而具有两种能力的插件通常只在自己的数据源上工作。然而,机制是相同的,无论数据源如何。SDK 允许您定义以下方法,这些方法在两种情况下都适用:

  • Fields 用于声明插件能够提取哪些字段

  • Extract 从事件数据中提取给定字段的值

让我们在示例插件中实现这些方法。第一个方法 Fields 返回一个 sdk.FieldEntry 切片。每个条目包含单个字段的规范。下面的代码告诉 Falco 插件可以提取名为 shell.command 的字段(本例仅添加了一个字段):

func (b *bashPlugin) Fields() []sdk.FieldEntry {
   return []sdk.FieldEntry{
      {Type: "string", Name: "shell.command", Display: "Shell command line", 
       Desc: "The command line typed by user in the shell"},
   }
}

现在,为了使提取工作正常,我们需要实现 Extract 方法,该方法提供实际的业务逻辑来提取字段。该方法接收提取请求(包含所请求字段的标识符)和一个读取器(用于访问事件负载)作为参数。由于本例只有一个字段,实现起来非常简单,它将简单地返回事件负载的所有内容。在实际场景中,您通常会有更多字段和特定的提取逻辑:

func (m *bashPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
   bb, err := io.ReadAll(evt.Reader())
   if err != nil {
      return err
   }

   switch req.FieldID() {
   case 0: // shell.command
      req.SetValue(string(bb))
      return nil
   default:
      return fmt.Errorf("unsupported field: %s", req.Field())
   }
}

有了字段提取功能,我们的示例插件几乎准备好了。让我们看看如何完成并使用它。

完成插件

你已经快完成了。接下来,你将创建插件的一个实例,并在 SDK 中注册其能力。您可以在 Go 初始化阶段通过使用特殊的 init 函数 来完成这一过程。(不要与 Init 方法混淆!)由于我们的示例插件具有源和提取器功能,因此我们必须使用提供的函数通知 SDK 两者的能力:

func init() {
   plugins.SetFactory(func() plugins.Plugin {
      p := &bashPlugin{}
      extractor.Register(p)
      source.Register(p)
      return p
   })
}

func main() {}

注意空的 main 函数。正如您马上会看到的,Go 构建系统需要它来正确构建插件,但它永远不会调用 main,所以您可以始终将其保持为空。

使您的代码成为真正的 Go 项目的最后一步是初始化 Go 模块并下载依赖项:

$ go mod init *example.com/my/plugin*
$ go mod tidy

这些命令分别创建了 go.modgo.sum 文件。现在,您的插件代码已经准备就绪。是时候编译它,以便您可以在 Falco 中使用它了!

构建用 Go 编写的插件

插件是一个共享库(也称为 共享对象),具体来说是一个编译文件,它导出了插件框架所需的一组 C 符号。(我们在示例中使用的 SDK 通过使用高级接口隐藏了这些 C 符号,但它们仍然存在于底层。)

Go 编译器有一个特定的命令称为 cgo,用于创建与 C 代码接口的 Go 包。它允许您编译插件并获得一个共享库文件(一个 .so.dll 文件)。该命令非常简单。在存放源代码的同一文件夹中运行:

$ go build -buildmode=c-shared -o libmyplugin.so

该命令创建了 libmyplugin.so,您可以在 Falco 中使用它。 (按照惯例,类 Unix 系统中的共享对象文件以 lib 开头,并以 .so 作为扩展名。)您在 第十章 中了解了插件配置,但以下部分将为您提供一些关于开发时如何使用插件的提示。

在开发过程中使用插件

默认情况下,Falco 会在/usr/share/falco/plugins中查找已安装的插件。但是,你可以在配置中指定绝对路径,并将插件放在任何你想放置的位置。(在开发过程中这很方便,因为你不需要将插件安装在默认路径下。)我们建议在开发插件的同时构建插件(使用上一节中的命令)。然后,在同一文件夹中,复制falco.yaml,根据需要添加你的插件配置,并将library_path选项设置为插件的绝对路径。例如:

plugins:
  - name: bash
    library_path: /path/to/your/plugin/libmyplugin.so
    init_config: ""

load_plugins: [bash]

现在,在使用插件之前,你需要一个与插件提供的数据源匹配的规则文件。(Falco 即使没有规则文件也会加载插件,但你将不会收到任何通知。)你可以在同一文件夹中创建一个规则文件,例如myplugin_rules.yaml,并在其中添加如下规则:

- rule: Cat in the shell
  desc: Match command lines starting with "cat".
  condition: shell.command startswith "cat "
  output: Cat in shell detected (command=%shell.command)
  priority: DEBUG
  source: bash

一旦准备好你定制的falco.yamlmyplugin_rules.yaml,最后一步就是运行 Falco 并传递这些文件给相应的选项:

$ falco -c falco.yaml -r myplugin_rules.yaml

完成!在开发过程中以这种方式运行 Falco 插件非常方便,因为它不需要你安装任何文件或干扰本地的 Falco 安装。

小贴士

如果你按照我们示例中的方法构建了插件,并希望触发规则,可以运行:

$ bash
$ cat --version
$ exit

结论

有几种方法可以扩展 Falco。编写插件通常是最佳选择,特别是如果你希望 Falco 能够处理新的数据源以启用新的用例。如果需要与输出进行接口化,gRPC API 可能会对你有所帮助。在极少数情况下,你可能需要直接修改 Falco 核心及其组件。

无论如何,你都需要阅读文档。有时你可能需要学习和理解高级主题。由于 Falco 是开源的且是一个协作项目,你总是有机会与其充满活力的社区联系。与他人分享想法和知识将帮助你更快找到答案。

你可能还会发现其他人有与你完全相同的需求,并愿意帮助你改进或扩展 Falco。这将是为 Falco 项目做贡献的绝佳机会。每个人都可以为 Falco 做贡献。这不仅是一种有益的经验,而且为项目和所有用户(包括你)提供了极大的帮助。想知道如何做?请阅读下一章!

第十五章:如何贡献

到达本书的这一部分意味着你正在掌握 Falco 的所有方面。本章将为你提供一些建议,帮助你贡献给 Falco 项目。贡献意味着不仅仅是编写代码(这是一个常见的误解) —— 实际上,有许多有价值的贡献方式。我们将解释从何处开始以及如何满足 Falcosecurity 组织的特定贡献要求。

参与开源软件是一种有益的体验。你不仅可以改进 Falco,还会结识志同道合的人,与他人分享反馈和想法,提升自己的技能。如果你是开源新手或者想了解更多,我们建议你参阅开源指南

贡献 Falco 意味着什么?

Falco 是一个Cloud Native Computing Foundation项目。CNCF 是云原生软件的供应商中立场所。它支持其托管项目的自治模型,并帮助维持健康的开源社区。Falco 主要由社区驱动,包括用户、维护者和开发者,通过以下方式不断策划和改进:

  • 提供反馈以改进设计和现有功能

  • 测试 Falco 以发现问题

  • 报告漏洞

  • 撰写项目文档

  • 尝试新的创意

  • 测试新功能

  • 提议变更

  • 编写代码

列举不胜枚举。总之,贡献意味着分享知识,为了 Falco 项目的利益进行合作。

我应该从哪里开始?

你应该首先加入 Falco 社区。你可以通过加入Falco Slack 频道并进行自我介绍来做到这一点。社区非常欢迎你的加入。我们建议你订阅官方邮件列表。社区成员,包括维护者,还会定期举行周会议,每个人都可以参加。你可以在社区 GitHub 存储库中找到关于周会议和其他活动的详细信息。

友情提醒,社区是由人类组成的:善待他们,他们也会善待你。参与社区的每个人都必须遵守其行为准则,所以请确保你已阅读并理解它。

贡献给 Falcosecurity 项目

正如你现在所知,Falco 及其所有相关项目都托管在GitHub 上的 Falco 安全组织下。每个项目都有自己的公共存储库 —— 你甚至可以找到一个包含 Falco 网站源代码的存储库。如果你还没有 GitHub 账户,你需要创建一个。我们还建议你花些时间熟悉 GitHub 的工作方式。如果你打算贡献代码,你需要对 Git 有一定的了解。

Falcosecurity 组织有一个自动化的支持机制(或机器人)来帮助你并简化贡献过程。你可能需要一点时间来熟悉它。如果需要帮助,请随时询问!社区的真实人员将乐意帮助你。

在准备任何贡献之前,请务必查阅在线贡献指南,因为这些指南会不时更新。不过,请继续阅读,我们将解释最重要的方面。

问题

GitHub 问题是与项目互动的主要方式。打开一个问题报告错误或提出改进建议是主要的贡献形式之一。正确使用问题对于项目非常重要,因为大多数反馈都来自它们。

每个 Falcosecurity 仓库定义了问题的类型。最常见的类型包括错误报告文档请求测试失败功能请求。在打开问题时选择类型。根据选择的类型,你会看到一个问题描述以及一个要填写的表单。通常表单包括问题,例如可能会要求你描述一个错误,如何重现它,出现错误的 Falco 版本等等。这些信息帮助他人理解你的问题并进行处理,因此回答所有问题非常重要,这样可以节省时间并增加成功解决问题的机会。

一旦问题被打开,协作过程就开始了。任何对该主题感兴趣的社区成员都可以参与,不仅限于维护者。参与这个过程是一个很好的方式来参与。

这个过程的初始阶段称为分类。它涉及验证和分类问题报告中的信息。例如,在错误的情况下,社区成员会尝试重现它,并检查是否如描述的那样出现。在某些情况下,过程会以正确回答问题或简单指向解决问题的资源而结束。在其他情况下,有人自愿实施请求的功能或修复错误,并负责提交拉取请求(见下一节)。

你可以在任何阶段参与这个过程。只要是建设性的,每个人都可以做出贡献。

Pull Requests

拉取请求(PRs)是将更改提交到 Falcosecurity 项目的唯一途径。当你想要提交新功能或修复时,你需要分叉相关的仓库,在你的分支中创建分支,并添加你的提交。一旦你确信你的更改按预期工作,你就可以提交 PR。与问题类似,PRs 使用预定义的模板填写。务必仔细阅读说明。模板还包括一些命令,帮助你与自动化进行交互。

提交 PR 后,你需要等待维护者进行审查。维护者有许多正在处理的问题和 PR,所以如果他们没有迅速回复,请耐心等待!他们可能会直接批准 PR 或要求你修改代码。审查过程是协作的:维护者和 PR 作者(有时还有其他用户)分享反馈和评论,直到 PR 被批准和合并。任何时候如果你有疑问,请寻求支持:维护者会解释如何继续。

制作 PR 时需要遵循一些通用指南:

  • 每个仓库可能有自己的编码风格和指南;确保你已阅读并理解它们。

  • 避免在单个 PR 中提交过多的代码更改;通常提交几个较小且独立的 PR 效果更好。

  • 维护者强烈推荐在你的 Git 提交消息中使用约定式提交风格

  • 你必须在所有 Git 提交上签名,并且你的 PR 不得包含合并提交(稍后我们将讨论此问题)。

下面的小节解释了在使用 Git 准备代码时必须满足的主要要求。

Git 冲突解决和线性历史

有时候在处理 PR 时,你可能需要与上游(远程)分支进行同步。如果远程分支与本地分支有分歧,可能会出现冲突。Git 允许你通过合并变基来同步和解决冲突。这两种方法解决同样的问题,但会产生不同的结果。

当本地和远程分支历史不一致时会发生合并,你可以使用git merge命令或git pull命令来调和非线性历史。然而,合并的缺点是不能保持仓库历史的清洁,这会使得像git bisectgit log等命令更难使用。因此,Falcosecurity 组织不允许在其项目中进行合并。

相反,变基 将你的提交移动到其他分支历史的顶部(而不是引入合并提交)。这确保了 Git 历史始终保持线性。在开发你的 PR 时,你必须始终使用变基来与上游同步或解决与主分支的冲突。下面的命令适用于这两种情况(将 *<branch>* 替换为远程分支的名称):

$ git fetch origin
$ git rebase -i origin/*<branch>*

如果你不小心引入了合并提交,这个命令还会移除它们。当你只需从远程分支拉取变更时(例如与同一分支上的协作者合作时),可以使用其简短版本git pull --rebase

重申一下:Falcosecurity 组织执行线性历史,并且不允许任何项目使用合并提交。如果你的 PR 包含合并提交,自动化系统会阻止 PR,并且维护者将无法合并它,直到你修复问题。始终使用变基,否则你的更改将无法被接受。

开发者证书的起源

2004 年,Linux 基金会(CNCF 的母组织)推出了开发者签署证书(DCO),这是一种轻量级的方式,让贡献者声明他们已经编写(或有权提交)一段代码。要求遵守 DCO 的项目需要贡献者在提交时签署,表示他们同意 DCO 的条款。Git CLI 提供了嵌入的签署功能,您可以通过-s选项使用,或者手动在提交消息中添加以下行:

Signed-off-by: *Full Name <example@example.net>*

行必须遵循这种格式,并包含您的姓名和电子邮件地址。

作为 CNCF 的一部分,Falco 及其所有相关项目都需要 DCO。Falco­se⁠curity 组织实施了一个自动化机制来检查 PR 中的 DCO。如果提交中缺少 DCO,自动化将阻止该 PR。因此,请不要忘记在每个提交上签署;否则,维护者无法接受您的贡献。

如果您提交了一个 PR,并且 DCO 检查失败,因为您未在一个或多个提交上签署,不用担心。您可以进行调整。如果您只需修改最后一次提交,请使用:

$ git commit --amend --signoff
$ git push --force-with-lease

如果您需要修复 PR 中的所有提交,请使用:

$ git rebase --signoff origin
$ git push --force-with-lease

结论

恭喜!您已经完成了本书的阅读!这是一个涵盖架构、语法、实际应用、定制、代码开发以及许多其他有趣主题的漫长旅程。我们真诚希望您喜欢阅读本书,并且更重要的是,书中的内容对您有价值,无论您是初学者还是高级用户。

对我们来说,这是个苦乐参半的时刻。虽然我们很难过地告别,但我们很感激能有机会与您共同经历这段旅程,并且我们为能够帮助使您的软件更加安全感到自豪。

您现在已经准备好开始另一场不可思议的冒险了。作为 Falco 的维护者,我们欢迎您加入项目,并希望能在社区论坛的其中一个见到您。

posted @ 2025-11-14 20:39  绝不原创的飞龙  阅读(24)  评论(0)    收藏  举报