长亭百川云 - 文章详情

自动化安全工具平台 - 架构笔记

b1ngz的笔记本

80

2024-07-13

0x01. 简介

这篇笔记是这几年我在写自动化安全工具平台过程中,在架构方面的一些想法、思考、尝试和总结,主要内容包括:

  • 为什么我要写自动化安全工具平台?

  • 平台 1.0 版本架构介绍、所遇到的问题

  • 平台 2.0 版本架构介绍、如何解决 1.0 版本所遇到的问题

  • 未来的一些想法和计划

  • 内容总结

0x02. Why

在安全测试和挖洞的过程中,我们会用到许多的安全工具,个人在使用时,遇到了如下一些问题:

  • 工具的命令行交互方式用户体验不佳,结果查看和筛选不方便

  • 随着工具使用数量增多,需要记住更多的命令和参数,如何有效的打通各工具也成为一个问题

  • 手工操作存在重复劳动,如对于每一个根域名,都要进行子域名收集、IP 解析、端口扫描等操作

  • 结果持久化存储和管理问题,比如我想查询某个域名有哪些子域名、是什么时候发现的、解析到哪些 IP、开放了哪些端口,如果没有资产管理平台,很难实现这些功能

  • 工具开发语言、风格多样化,优化和定制修改成本较高

  • 运行速度,对于大量目标,单机运行的速度远远无法满足需求

  • 工具间缺少公共功能和模块,如定时任务、结果通知

  • ...

因为以上列出和没列出的种种原因,我决定写一个自用的安全工具平台,它需要满足以下条件:

  • 提供人性化的 web 管理界面,即能选择的,尽可能不输入,鼠标点击一下能完成的,尽可能不点两下

  • 将常用工具封装成任务模块,通过参数配置来控制任务执行,实现底层细节屏蔽,简化操作

  • 实行任务模块间的打通,即某个任务的结果可以作为其他任务的输入

  • 支持资产管理、任务运行和结果查看等功能

  • 分布式,可通过机器扩容来提高扫描速率

  • 支持定时模块,实现任务的自动化周期运行

  • ...

为了能够实现预期目标,需要有一套良好的架构来支撑,来看一下平台的 1.0 架构

0x03. 1.0 版本架构

1.0 版本的架构图如下:

使用到的技术栈和组件信息:

  • 前端:Vue,使用基于 Element UI 的开源管理后台模版

  • 后端:Python 3.6,API 使用 Django REST framework

  • 反向代理:Nginx

  • 数据库:PostgreSQL

  • 缓存:Redis

  • Message Broker:RabbitMQ

  • 分布式任务框架:Celery

  • 定时任务:Celery beat

  • 任务监控:Flower

  • 服务部署:Docker + Docker Compose + SSH

对于写过分布式工具的师父来说,整体架构应该还是相对比较简单的。

接着来一起了解一下我是如何选择技术框架和组件的

  • 首先最基础的是开发语言,它决定了后续用到的框架和组件生态。当时用的比较多的开发语言是 Java,但对于写自用的工具而言,Java 在开发效率和成本上都比较 "重" ,因为有很多优秀的开源工具是用 Python 写的,所以最终就选择了它

  • 第二点是前后端框架选型。后端框架上,因为 Django 进行了很多高阶封装,相比轻量级的 Flask 而言,开发速度上会更快。前端框架上,个人认为前后端分离更易于维护,开发效率更高,因此我没有选择 Django 的 templates,也因偶然的机会看到了基于 Vue 的 Element-UI 组件库,试用后觉得这真是像我这种不擅长前端人的福音,之后通过慢慢的学习,也感受到了 Vue 的简洁和强大

  • 第三点是数据库,当时之所以选择了 PostgreSQL,原因有两个,一是想接触一下没有使用过的东西。二是被官网的介绍 - The world's most advanced open source database 所吸引。在之后的使用过程中,遇到了某些 model 字段需要使用到 JSON 类型,当时 Django 2.x 版本只有 Postgresql 支持。不过在 Django 3.1 版本,主流数据库也都支持了 JSONField,因为是 ORM,只要不是使用到了某个数据库特有的 feature,理论上是可以切换的

  • 第四点是分布式组件,任务调度框架选择了历史悠久、使用广泛、功能全面的 Celery,任务停止、定时任务、任务监控、任务优先级,该有的一应俱全,不过也正因为它悠久的历史,也导致它代码量过于庞大、配置较为复杂,很多  bug 一两年都没有解决,在使用过程中也遇到了一些稳定性方面的问题,导致最终放弃了它,这部分原因后面会具体解释。还有就是 message broker,之前看过一些 RabbitMQ 的文章,稳定、使用广泛,所以就决定是它了

  • 最后一点是服务部署,这几年容器化是一个趋势,因此我将平台上所有的服务都基于 docker / docker compose 搭建,容器化能够保证开发和线上环境的一致性,提升服务部署和扩容速度

了解完技术选型的过程,再来一起看看 1.0 版本在实现和使用过程中,我所遇到的一些问题

  • 首先第一点是稳定性,安全工具中很多任务都是网络 IO 型,为了提高任务的执行效率,在运行 Celery worker 时是以 Gevent 协程方式启动,在运行一段时间后,会时常出现 BrokenPipeError 错误后 worker 卡死,不再消费队列任务的情况,只能通过重启解决。根据 Github issues 的记录这个问题在 17 年有人报告过,但直到我写这篇笔记时,该问题仍然没有解决,我自己也曾尝试通过阅读源码定位原因,但因为 Celery 代码量较大,最后也放弃了

  • 第二点是可用性,说到这点,就得提一下 19 年 8 月我发过的一条微博,大致内容是,凌晨收到 VPS 厂商的一封邮件,提示我的一台服务器所在物理机故障,需要重启。早上起来一看,运行任务全部失败,查了下原因,发现那台半夜重启的服务器上运行着 RabbitMQ 服务。然后就在一个月后,我在尝试增加 worker 节点数量,因为没有数据库连接池,高并发导致 worker 频繁的与 db 建立连接,使机器 CPU 飙升到 100%。从这两件事情,我开始思考现在架构在可用性方面的问题,例如是否存在单点故障、是否能够支撑水平无限扩展

  • 第三点是灵活性和用户体验,实现自动化离不开定时模块,但 1.0 版本的定时任务,无法支持动态创建和修改,需要重启后生效,因此灵活性上需要优化。此外,部分功能前端界面设计的不够友好,管理后台模版功能不够强大,也无法满足极致人性化的要求

  • 第四点是服务部署和扩容,部分服务仍依赖手工操作。对于自动化部分,配置没有与代码分离开,配置方面也比较复杂,服务上线效率不高

  • 第五点是代码维护成本,因为 1.0 版本是在边学习边实现的过程中完成的,存在着模块间代码高度耦合、重复代码多等问题,导致代码修改和新功能实现上都较困难。

  • ...

为了解决上述 1.0 版本所面临的问题,实现稳定、高可用、高度自动化的目标,我走上了 2.0 版本的重构和改造之路

0x04. 2.0 版本架构

2.0 版本的架构图如下

使用到的技术栈和组件信息,这里仅列出与 1.0 版本不同的地方

  • 前端:使用更为强大的管理后台模板 https://github.com/PanJiaChen/vue-admin-template

  • 数据库:PostgreSQL Cluster

  • 数据库连接池:PgBouncer

  • Message Broker:RabbitMQ Cluster

  • 消息监控:RabbitMQ Management Plugin

  • 分布式任务框架:Dramatiq

  • 定时任务:APScheduler

相比 1.0,新版本改进和优化的地方主要有:

  • RabbitMQ 由单节点变为 Cluster 模式,通过 Queue Mirroring 来保证高可用,即某个队列里的消息会在多个节点进行镜像 (mirrored),即使某个节点异常宕机,消息也不会丢失

  • 数据库 Postgresql 由单节点变为 Cluster 模式,通过读写分离,增加 slave 节点来支撑 worker 的水平扩展,保证 DB 的稳定性和可用性

  • 使用 PgBouncer 作为数据库连接池,来避免频繁建立、关闭数据库连接,降低机器负载,提升稳定性

  • 任务调度框架由 Celery 替换为 Dramatiq,该框架的优点是运行非常稳定、代码结构清晰,但功能上要相比 Celery 少,不过可以通过实现自定义 middleware 来进行扩展

  • 基于 APScheduler 和 Dramatiq 实现定时任务功能,支持动态添加和修改定时任务

  • 创建新项目,用于服务的自动化部署,提供修改配置文件的方式,来进行上线和扩容,提高部署效率

  • 项目重构,将各个任务模块的代码分离,封装公共 utils 函数,提升可维护性

  • ...

除了以上的点外,还有一个关于多任务同时运行,资源分配和抢占的问题,想和大家聊一下

前面提到,任务会通过定时模块周期性的运行,即队列中时时刻刻都有任务在运行或等待运行。假如某天我们发现了一个新漏洞,POC 已写好,扫描任务也已创建,但此时资源都已经被其他已运行的任务占用,只能等待其他任务完成或手动停止释放资源。为了能够更好的解决这个问题,需要有机制能够让新任务具备抢占其他正在运行任务的资源的能力,那么如何实现呢?以下是我的思路和做法:

  • 队列里的消息/任务需要有优先级之分,即当有空闲资源时,高优先级的任务会优先执行,RabbitMQ 的 Priority Queue 能够很好的支持这一点

  • 尽量避免一个任务的执行时间过长,即将一个大任务拆分成多个小任务,如每个小任务能够在几分钟内执行完成,这样可以缩短新建高优先级任务等待资源释放的时间

  • 控制并发执行任务数,假设要扫描几十万 IP 的全端口,我会把它拆分成数量更多的子任务,因为我的机器资源有限,我不会将所有子任务都一次性发送到队列中去,而是会先启动一个端口扫描的主任务,在主任务中来控制子任务的发送。这样做不仅可以实现控制子任务的并发执行数、动态调整优先级、停止任务等功能,还可以动态分配资源,让多个不同优先级的任务能同时运行

说了这么多,那么实际效果怎么样呢?以下是 2.0 架构今年的一些使用情况

  • 未遇到因框架或组件问题导致的服务中断

  • 最长稳定运行时间 180 天 +,也即我有半年时间没有添加新功能,每天按照配置的定时任务稳定运行的最长记录

  • 顶峰集群规模:共 84 台服务器,其中 80 台 worker 机器,其余 4 台机器混部 DB、RabbitMQ、Redis 等服务,这个大概跑了两周时间

0x05. 想法和计划

虽然 2.0 版本优化和解决了 1.0 版本中的很多问题,但它仍然有很多不足和待改进的地方,例如数据库使用集群模式后,存在多个节点,目前 worker 端连接时,是随机选择其一,一旦某个节点宕机,就会导致部分请求失败,再加上数据库配置是通过 docker env-file 传入,无法实现动态摘除节点,需要重新部署。另外,随机选择也带了另一个问题,不同节点之间的负载存在不均衡的情况。因此,为了能够让平台更加稳定,实现高度自动化目标,还需要对架构进行进一步的优化。以下是目前的一些 ToDo,因为只是初步的想法,有可能不一定会实际去实现,大家可以简单参考一下:

  • 基于如 ZooKeeper 或 etcd 实现统一分布式配置中心,解决数据库、RabbitMQ 等配置的动态更新问题

  • 基于如 HAProxy 实现 TCP 代理,解决数据库、RabbitMQ 等服务高可用和负载均衡问题

  • 继续完善和提高服务自动化部署程度,例如自动化扩容数据库 Slaver 节点、RabbitMQ 节点等

  • 完善服务监控和报警功能,如接入 Sentry 等

  • ...

0x06. 总结

这篇笔记介绍了我在写自动化安全工具平台过程中,在构架方面的一些个人思考和总结,文字比较多,感谢大家耐心看完,希望能够给同样在写自动化工具的人提供一些帮助。另外,因个人能力和水平有限,文中可能会有描述错误或理解不到位的地方,欢迎各位指正和交流。

最后是下篇笔记的预告时间,我会介绍平台上的一些功能和自己的想法,感兴趣的老板可以关注一下

0x07. 参考

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2