本文将介绍牧云为什么需要插件系统。我们将探究使用插件系统的优势和挑战,并介绍我们过去使用基于 Lua 的插件系统时所遇到的一些问题和限制。此外,我们还将简要介绍未来我们打算引入基于 WASM 的插件系统的计划和期望。
这是系列文章“主机Agent插件引擎开发故事”的第一篇,后续将会持续更新。该系列文章将带领您深入探究长亭牧云团队主机Agent插件引擎的开发历程,内容涵盖技术选型、插件接口设计、组件通信框架等多个方面,并详细讲解背后的原理和实现方式,无论您是网络安全专业人员还是对技术开发感兴趣的读者,都可以从中得到收获。我们希望通过分享在开发过程中面临的挑战、解决方案以及实践经验,提供深入见解和有价值的技术参考,帮助读者了解如何构建高效可靠的安全产品,共同推动安全技术社区的发展。
牧云是长亭的主机安全产品,主要产品定位是基于Agent的深度服务器工作负载安全平台,大致归类于主机安全(HIDS
)和云工作负载保护平台(CWPP
)。与防火墙、扫描器等安全产品定位不同的是,牧云以服务器主机为单位,将一个 Agent
程序部署在服务器内部,通过深入服务器内部监测和分析服务器的行为,可以做到以最准确、最及时的手段和方法获知服务器的安全状态。
牧云的主要目标是为甲方安全负责人提供一种统一管控大规模服务器安全状态的解决方案,同时需要适应本地部署、公有云部署、混合云部署等复杂场景下的主机安全需求。
作为牧云最核心的系统,智能、轻量、安全、兼容、稳定是 Agent
设计的核心目标。在这个目标下进行 Agent
程序设计需要满足很多相互约束、看似不可能同时满足的条件。例如:
其实从一开始我们就都知道,上面这些东西过于理想化了,很难真正实现,更何况眼前明晃晃就摆着一种极其朴素的逃课方式。
这种朴素的设计方式就是,通过扫描和命令执行来提供基础的安全能力。市面上也确实存在一部分通过这种方式实现的主机安全系统。这种方式存在以下几个限制:
也许有些人认为只要能用就行了,但显然这种粗暴的形式是无法满足这些苛刻到近乎变态的设计目标的,所以我们决定不妥协。虽然这看起来像是一个“无法解决的问题”,但成年人都知道既要也要。
为了使 Agent
程序达到稳定、灵活、可拓展的设计目标,我们在早期做过非常多探索,最终确定了一条准则。简单来说,就是在 Agent
程序设计中需要分离出安全业务能力和安全基础能力,从而做到在保证安全能力灵活可拓展的同时保持核心系统持久稳定。由 Agent
提供基础支撑,并通过插件系统实现具体安全检测算法,就是这条准则的具体体现。
长亭的安全理念认为,任何单一安全产品都不能彻底解决安全问题,多产品、多角度联合的“塔防体系”是必然发展方向。而安全的主动权应该交还给甲方,因为只有甲方最了解实际的安全需求。即乙方提供基础设施、框架和工具,甲方根据需求使用乙方提供的设施和工具进行灵活扩展,解决问题。插件系统就为这种安全理念的落地准备好了充分的土壤。
分离安全业务能力和安全基础能力是为了提高系统的灵活性和可扩展性。
在安全产品中,安全业务能力指的是具体的安全检测算法和策略,包括资产清点、病毒扫描、漏洞检测、入侵检测等。而安全基础能力则是指系统提供的基础设施和工具,如数据采集、插件管理、日志管理、网络通信等。
将安全业务能力和安全基础能力分离后,可以实现不同安全业务能力的模块化开发和部署,从而实现安全业务能力的灵活扩展和定制,同时提高系统的稳定性和可靠性。此外,通过插件系统,还可以实现安全业务能力的动态更新和升级,从而适应不断变化的安全威胁和攻击手段。
插件系统的优势主要包括以下几个方面:
Agent
程序的代码过于臃肿和复杂,从而提高代码的可维护性,同时也可以减少因 Agent
程序代码变动变动而带来的稳定性风险。Agent
的稳定性和可靠性。虽然说了这么多好处,但要真正落地给出一个高性能的实现,仍然是一条充满挑战的漫漫长路。要解决这条路上的困难,首先就要考虑清除插件系统的设计和实现中的以下问题:
Agent
需要为插件提供操作系统和安全事件的统一接口,并考虑 API 的稳定性和兼容性。Agent
需要提供跨平台抽象,使开发聚焦安全业务的插件时不必考虑不同平台的差异。为了便于实现 Agent
程序的跨平台设计目标,当前使用了 Go
语言进行开发,试图借助 Go
语言跨平台编译的基础设施降低 Agent
程序的开发和维护成本,并且在项目早期非常有帮助。使用 Go
语言开发的另外两个重要原因是:
Go
开发的成功产品,而牧云的早期团队很大一部分成员来自于这个产品。Go
是一个新兴的时髦技术,当时长亭全公司从上到下全都是激进的技术爱好者。为了达到插件程序的灵活、高效的设计目标,插件的开发语言做过许多考虑,包括 Python
的嵌入式版本和 JavaScript
都是我们仔细考虑过的。这两个都是我们熟悉而且生态极其丰富的编程语言,但遗憾的是距离理想中的设计目标还有不小的距离。通过进一步对整个插件生态进行仔细研究,最终我们认为结合我们整体的设计目标,选择插件开发语言的标准对我们来说最重要的应该是“稳定”“简单”和“快”。因为“稳定”是安全产品的核心目标,“简单”可以让代码逻辑直面业务避免无谓的干扰;“快”帮助我们更好地达到“轻量”且“高性能”的目标。
所以我们在最初使用了在这三点上做到极致了的 Lua
作为插件开发语言。为了更快,底层 Lua
虚拟机使用 LuaJIT
。后来,这种过分的简单性带来了一些坑,为了解决可维护性问题转型到了带类型的 Lua
方言,即 CtLua
。关于 CtLua
的故事,在【主机安全|开发避坑奇旅】中我们曾经详细分享过。简单来说是脚本语言的动态性给了我们在软件工程上的一记重击。
为了使 Agent
程序与插件程序进行有效交互,我们开发了 lng
项目(Lua Engine for Go)支持 LuaVM
和 Go
的通信,在这个项目上做了许多技术创新。例如:
LuaVM
做了兼容性的增强使原本不支持的环境下也可正常运行。Agent
的审查和管控。Agent
稳定性的插件。Lua
插件提供了基于 MessagePack
序列化的通信接口,为 Lua
极大提高了跨插件边界传递复杂数据结构的能力。VM
中分配执行时间片以控制插件资源滥用。JavaScript
中 Promise
语法的异步能力。在 lng
项目的支撑下,在相当长一段时间里我们得到了当时效果感到十分满意的插件开发体验,并在很大程度上达到了灵活、稳定、轻量的设计目标。
但经过长期的维护和使用,安全业务越来越复杂庞大,对插件的工程质量和可维护性提出了更高的目标;对探针的轻量级、高性能和兼容性提出了更加苛刻的要求。原本的选型不再令人满意。
从 Agent 的角度来看,作为一个系统应用,难以精确控制行为和资源占用是一个巨大的缺点。对于 Agent
中 IO
密集型的部分,长期以来一直通过使用混合语言开发缓解相关的问题。从插件的角度来看,基于渐进式类型系统和类型标注来在动态语言中进行类型推理是一件困难而且效果不理想的事情,难以对大型软件仓库进行有效组织。从 lng 的角度来看,由于与 LuaVM
的交互需要借助 CGO
导致要传递数据必须拷贝数据,在大规模安全事件触发时因为通信开销过高很难做到兼具轻量和高性能。
在我们经历了自行设计插件通信标准并提供开发调试基础设施、自行修复 LuaJIT VM 实现缺陷、自行设计和维护 CtLua 编译器、自行兼容 Go 语言和低版本操作系统、自行修复 Linux 内核 Bug等传奇事件后,终于做出了一个惊人的决定。
经过长期的挣扎和斗争,团队最终决定使用全新技术栈打造下一代主机安全探针体系。
一句话技术选型:使用 Rust 开发 Agent 程序,基于 WASM 打造新的插件体系。
这样做的期望是通过 Rust
获得较佳的开发体验,同时克服在 Go
的技术体系下难以逾越的兼容性和底层控制的困难。同时,WASM
的跨语言特性将为静态语言的插件开发提供支持,从而提高工程质量,降低插件通信开销,增强插件隔离性。
新的技术栈中可以更好地满足牧云的需求,从而实现更加灵活和可扩展的安全检测功能,以适应日益复杂的安全威胁环境。同时,也将可以更加精细地控制 Agent
程序的行为和资源占用,以提高系统性能和稳定性。
关于新的选择中新的考虑和挣扎,将会在后续的系列文章中逐渐揭秘。系列文章将会与全新的次时代主机 Agent
的开发和打磨过程同步发布,分享最为真实鲜活的故事和经验。
对于主机安全产品的未来发展,我们可以预见以下几个趋势:
未来的主机安全产品需要适应不断变化的安全威胁环境和用户需求,采用更加灵活、自动化、智能化、合规化的安全解决方案。长亭主机安全产品团队将会不断创新和突破,不断推进产品的发展,为社区和用户提供更加优秀的安全解决方案和服务。
系列文章目录:【预告】主机Agent插件引擎开发故事汇总