通过我们的限流器每天发送数十亿请求而不破坏系统
在Detectify,我们帮助客户保护其攻击面。为了有效且全面地测试其资产,我们必须向其系统发送大量请求,这带来了过载其服务器的潜在风险。自然地,我们解决了这一挑战,以确保我们的测试为客户提供最大价值,同时通过我们的限流器安全进行。
在介绍了我们的引擎框架并深入探讨了监控客户攻击面的技术之后,这篇技术博客揭示了一个重要部分:我们的限流器服务如何工作以使所有Detectify扫描安全。
限流器的需求
在我们之前的文章中,我们解释了我们在客户系统上执行安全测试时使用的技术,以避免可能过载其服务器的峰值。随着我们不断向研究库存添加更多安全测试,并且多个引擎同时工作,可以想象所有测试的组合负载可能达到显著水平,并可能为客户带来问题。
为了解决这个问题,我们引入了一个全局限流器,旨在限制针对任何给定目标的每秒最大请求数。设置了合理的默认值,客户可以根据需要灵活配置此限制。
没有限流器,引擎测试对客户的组合负载将不受控制地增加
产品需求
在深入限流器解决方案之前,了解我们的限流器需求非常重要。
作为一个概念,速率限制在软件工程中并不新鲜,有许多工具可用于解决速率限制挑战。然而,我们需要确定我们的情况是否有任何独特之处,需要定制解决方案,或者现成产品是否足够。
我们探索了几种流行的开源工具和基于云的解决方案。不幸的是,在我们的分析过程中,我们找不到符合我们标准的任何选项。Detectify基于来源(方案、主机名和端口号的组合)应用限制,并且需要允许个别目标和高度动态的配置。大多数现有工具在这方面不足,导致我们决定实现自己的限流器。
个别目标速率限制配置,可以随时更改
文献研究
速率限制是软件工程中一个众所周知的概念。尽管我们构建了自己的,但我们不必从零开始。我们研究了各种类型的速率限制,包括阻塞、节流和整形。我们还探索了最常用的算法,如令牌桶、漏桶、固定窗口计数器和滑动窗口计数器等。此外,我们还研究了我们可以使用的不同拓扑结构。
我们的目标是尽可能保持实现简单,同时满足我们的要求。最终,我们决定实现一个阻塞令牌桶速率限制器。有趣的是,考虑到我们的安全测试需求以及一些安全测试在“堆栈深处”的情况,我们选择了服务准入方法,而不是更常见的代理方法。
简而言之,阻塞速率限制器在超过限制时拒绝对目标的请求。相比之下,节流和整形速率限制器通过减慢、延迟或降低其优先级来管理请求。令牌桶算法的工作原理是为每个目标维护一个“桶”,该桶在时钟上填充一定数量的令牌,以达到限制。每个请求从桶中消耗一个令牌以被允许。当桶中的令牌用完时,请求被拒绝,直到桶被重新填充。服务准入方法意味着想要对目标执行安全测试的引擎首先需要从全局速率限制器获得准入,而代理方法将作为引擎和目标之间请求的更透明“中间件”。
令牌桶算法
作为准入服务的全局速率限制器
技术选择
定义了算法和拓扑后,是时候探索最能满足我们需求的技术了。全局速率限制器需要处理高吞吐量的请求,具有显著的并发水平,同时以非常低的延迟运行并且可扩展。
我们进一步扩展了这些需求,并确定解决方案应在内存中运行,并尽可能减少内部操作。例如,利用原子操作和具有过期策略的简单锁。在热并发点,我们选择了单线程方法,顺序运行,避免并发控制的开销。
在讨论了我们的选项后,我们得出结论,最佳解决方案是在由Redis分片集群支持的长寿命ECS任务上运行全局速率限制器服务。由于我们使用AWS,我们发现使用ElastiCache创建我们的Redis分片集群很方便。
我们全局速率限制器的高级设计
展示代码!
全局速率限制器服务相当简单,提供了一个简单的API来请求对目标的准入。更有趣的方面在于服务和Redis之间令牌桶算法的实现。
我们旨在利用原子操作和具有过期策略的简单锁,同时在高并发区域顺序运行任务。这种顺序执行在Redis中很简单,因为它以单线程方式运行。我们的重点是将并发挑战放在它身上。具有过期策略的简单锁在Redis中很方便,这是它擅长的领域之一。此时,我们只需专注于设计算法,尽可能减少服务-Redis交互并尽可能原子化。经过一些迭代,我们确定了一个在Redis服务器上运行Lua脚本的解决方案。Redis保证脚本的原子执行,这完全符合要求。
让我们看一下代码,随后有详细解释:
|
|
脚本接受几个参数:桶名称及其令牌限制值。至于时间窗口,我们只使用1秒时间窗口。然后,进入检查准入,我们首先尝试从桶中减少一个令牌。如果有可用令牌,我们允许请求。否则,我们检查是否是时候重新填充桶。为此,我们使用Redis的能力,如果锁键不存在则设置它,并提供1秒时间窗口作为过期时间。如果我们成功设置了锁键,这意味着我们进入了一个新的时间窗口,可以重新填充桶,我们在返回批准继续的同时这样做。如果我们没有足够的令牌,并且我们还不能重新填充桶,我们拒绝请求。令牌键也有过期时间,这样在一段时间内没有对目标的请求后,我们就不必进行额外的清理。
性能如何?
自其诞生以来,我们对性能感到满意。平均而言,它每秒处理20K请求,偶尔峰值高达40K请求每秒。p99延迟通常低于4毫秒,错误率接近0%。
与可观察性相关的一个有趣挑战是确定对单个目标执行了多少请求。对于那些熟悉时间序列数据库的人来说,使用目标作为标签是行不通的,会导致基数爆炸。另一种选择可能是依赖日志并构建基于日志的指标,但如果考虑到我们处理的量,可以想象在财务方面会极其昂贵。
为了解决这一挑战,我们必须创造性思考。经过一些构思,我们决定在桶被重新填充时记录。虽然这种方法不提供对目标的确切请求数,但它指示了在不同时间点可以发送到目标的最大请求数。这对我们来说是基本信息,因为它允许我们监控并确保不超过指定的限制。
更多引擎和测试,更安全的客户
保护客户的安全是关于实现完美的技术平衡,以确保在其攻击面上运行安全测试的安全方式,而不会对其服务器造成意外负载问题并可能影响其业务。我们全局速率限制器的实现使我们能够安全地增加库存中的引擎和安全测试数量,运行更多安全测试而不破坏其系统。
从工程角度来看,实现全局速率限制器提出了一个有趣的技术挑战,我们找到了一个对我们很有效的解决方案。如果出现任何变化,由于整个过程中的广泛构思,我们准备好适应,以确保为客户提供安全可靠的体验。也就是说,去黑客自己吧!
有兴趣了解更多关于Detectify的信息吗?开始2周免费试用或与我们的专家交谈。
如果您已经是Detectify客户,请不要错过“最新动态”页面,了解最新的产品更新、改进和新安全测试。