浅谈分布式渗透测试框架的落地实践

作者:v1ll4n

安全研发工程师,现就职于长亭科技,喜欢喵喵

“本文读起来依旧还是非常枯燥”

0x00 Intro

本文基于上一篇文章《浅谈分布式渗透框架的架构与设计》的内容,并且实践了在上一篇文章中提到的各种想法和设计,勉勉强强算是落地实现。当然意料之中地会遇到各种各样的问题,不管最后解决方案是优雅还是丑陋,对今后的工作和兴趣开发都是很有益的经验积累。本文就简单谈一些关于项目研发落地实践出现的各种矛盾和启示。

Quick Look:

业务需求模型特异性 vs 原始数据多样性

系统设计伪需求 vs Over-designed

Service-Oriented Architecture vs 耦合痛点

以上几个话题在下面的文章中都会涉及到,并没有先后顺序。

0x01 业务需求模型特异性 vs 原始数据多样性

在项目中,业务创造直接的价值,各种用户接口和相关交互都是建立在业务的基础上的;然而我们都知道,在我们文章中的这类系统有一个很大的特点:原始数据多且复杂,而且随着功能单元的增加,如果想要每一个功能单元产生的结果都能得到妥善处理,我认为有两种解决方案:

Formatter 或 API 协定:为每一个功能单元的结果(为每一类结果)都设定一个 Formatter 去直接转换为业务需求的结果;或者与原始数据约定 API。

数据转换协议或数据获取协议:设定数据转换协议或数据获取协议,Producer 和 Consumer 同时遵守一定规范,Producer 不需要关心 Consumer 到底想要什么样的模型,他只提供符合协议的中间模型,同样, Consumer 不关心接口怎么样,他想要的在中间模型中都可以拿得到。

这两种方案都可以一定程度上缓解业务需求模型特异性与原始数据多样性之间的矛盾

Formatter 或 API 协定

Q:为什么这两种方式会被并列来讲?有什么关联吗?

A:从本质上来说,这两种方式都是多样性向特异性妥协而产生的解决方案。何谓“妥协”?与需求方沟通或者协商其实就已经算是妥协了。当然这并不是说妥协不好,毕竟沟通成本也是一大成本。

值得另外提的是,业务的需求方并不一定是后端的用户接口层,当然原始数据的生产者也并不一定是整个系统的底层:这样的矛盾也同样存在于前后端。传统的前后端开发,需要一定的 API 规范(可能使用 Swagger 去规范 Rest API)去处理前端业务与后端原始数据的适配,前端需要的模型的特异性同样会和后端提供原始数据产生矛盾。

我相信虽然说 Rest API 或者其他什么的方案会解决一部分这类前后端协作开发上的问题,但是有一个隐藏矛盾是很难处理的,也会上这一个矛盾更加严重:

日益增长的客户业务方需求与现阶段旧的模型之间矛盾

其实约定固定的模型 API 或者编写固定 Formatter 这种方式,是很难解决需求增长导致的特异性加重的问题的,也就意味着:

新的需求 >> 新的 API >> 新的沟通与协商

我相信,每个 Coder 都比较想砍死需求改来改去和新需求分分钟冒出来的产品经理,对嘛?

设定数据转换协议或者数据获取协议

这种方法其实是可以极大程度上缓解在上一种方法中提到的

日益增长的客户业务方需求与现阶段旧的模型之间矛盾

Q:这种方法和上一种有什么本质区别?

A:表面上看其实最大的不同是增加了一个中间模型,但是恰恰是这一个中间模型,会让需求方洞悉原始数据所有可以提供的数据,并且不需要通过每一个需求都约定 API,而是直接在中间模型上构建业务模型;除非新的需求并不是在现有的原始数据上可以满足的,这个时候才需要进行沟通,扩充中间模型。本质上来说,原始数据的多样性不需要频繁向业务数据进行妥协。

这种解决方案其实也并不只是一种设想,在某些领域和工程应用中已经实现,并且取得了非常良好的开发体验;笔者认为这种科学的方法其实本来就已经被很好实践和理论化(Proxy-Pattern)了:

AMQP 中 Exchange 的设计

GraphQL 设计

0x02 系统设计伪需求 vs Over-designed

Over-designed 就是过度设计,是在进行实现的时候没有正确把握复杂度导致了多余的设计。

其实在这个话题中,“伪需求” 与 Over-designed 的矛盾并不只发生在调度系统中,反而是在很多地方,都会发生 Over-desighed 的问题。

底层想提供更多的 Useless 的功能导致底层 Over-designed

应用服务处理不好 Infrastructures 与业务逻辑之间的关系导致 Over-designed

被高估的可靠性需求导致 Over-designed

hype-driven development

...

在这个话题中,我们其实很难像第一个话题一样,提出明确的方法去缓解“伪需求”带来的 Over-designed。从一开始接触 Code 直到现在,我一直难以抛弃掉一个信念,就是对自己代码过分的高估和多业务的过分高估。我觉得这其实更多的是一种诱惑,比如 HDD(Hype-Driven Development)对我来说一直是很大的诱惑。

当然我在这个话题中说到的“伪需求”并不是产品经理说的“业务伪需求”,而是对系统的某一个 Feature 没有做到正确估计其紧急程度或者可靠程度,凭空给自己增加了一些“负担”。这样的问题很容易导致代码冗余,过分追求设计;或者因为自己觉得这个 Feature 相关联的别的 Feature 可能需要在不久的未来实现,而自己花了更多的精力和时间在并不是这一阶段的工作上。我管这种“伪需求”叫作“负担”其实是不太妥当的,他其实并不是真的负担,反而有时候,对于一个热爱编程的 Coder 来说,它会成为一种有毒的诱惑。

学会抵制诱惑不去 Over-designed,学会”大道至简“我相信对于每一个 Coder 来说都会是一个漫长的过程。

说到这个,可能需要再讲一个例子:微服务有一万种美好的特性,但是真的所有的系统都使用微服务就一定好吗?恐怕这里是有很大问题的。服务簇的维护需要成本,服务的研发也需要成本,科学的协议设计,配置中心,协调中心,DevOps 都需要成本。一个 Passion Coder 自然是非常热爱这种 Cloud Native 和诸多特性的新技术,但是没有团队或者团队资源不够,人不够,都没有办法支撑微服务这种高复杂度的分布式系统;另外,更需要值得思考的是,你正在开发的系统值不值得微服务?当然,可能你的需求方突然砍掉了很多很多功能,你的系统突然不需要微服务了,因此你需不需要推倒重来?还是简单转为 SOA?这些其实都是一个合格的 Coder 需要思考的问题,并不只是学习新技术,采用新特性。

https://aadrake.com/posts/2017-05-20-enough-with-the-microservices.html

所以我管这个叫作“诱惑”,反而,越是对技术追求越多越是容易受到 Over-designed 的影响。

0x03 Service-Oriented Architecture vs 耦合痛点

耦合这个词伴随我第一次程序设计课程一直到现在,从微观代码的类之间的耦合一直到服务之间耦合,设计上的耦合,甚至配置之间的耦合,一步一步走来;从思考中,也有了很多解决方法,不管有没有付诸实现,我觉得这些都是很有价值的值得讨论的问题。

本文描述的场景是结合上一篇文章使用了消息队列作为通信基础服务,服务之间通信需要通过消息队列。因此,基本上所有的服务之间的耦合应该是必须有通信协议耦合的,这是必须的,我们在这个 Topic 中讨论其他的问题:

不能在数据模型上耦合

A:“我负责的这个服务的数据怎么传给你呢?”

B:“要不我把 Postgres 的端口暴露出来,你直接写到数据库吧”

A:“Models 就看 Git 上我的代码,COPY 到你那里就可以”

完成之后,过了几天,因为需求变动,数据模型需要修改……

B:“我需要改 Models,你那里可能也需要改改代码”

A:“…猫猫碰.jpg”

B:“我的 Migration 怎么老有问题?是不是 A 动了 Models?”

A:“..不,我没有,别瞎说啊.jpg”

这样的对话,确确实实是现实中发生的,这个数据模型牵扯到了两个不同的服务在开发过程中,定义的修改,Feature 和需求的增加,都会导致直接的与这个模型相关联的服务的代码的改动;造成这样的问题只是起初为了方便两个服务之间传输数据。

那么既然已经有了通信系统,为什么不能用来定义通信协议来传输数据呢,维护一套耦合方式总要比同时兼顾通信协议和数据库更轻松吧。另外在数据库耦合会造成更多奇奇怪怪的问题,比如:一方使用 ORM 一方没有使用 ORM,或者双方 ORM 对数据库不同的操作导致冲突,甚至说任意一个服务在数据库操作上的小问题都会影响到其他服务。

配置中心的必要性

A:“需要测试环境改点配置,我们的 MQ 服务器迁移了”

B:“那么几个节点的配置可能都需要改动之后重新启动”

A:“Orz……求一发批量操作脚本”

我想不只是我们,可能所有的项目都会遇到这种问题,内网一个服务的迁移直接导致了大批配置文件需要改动;某一项配置的改动,需要牵连一大批配置文件内容的修改。

当然,IP 迁移的问题相对来说还是比较好解决的,只需要内网 DNS 可以解析到新迁移的机器上就可以了;但是某一项配置的改动导致的关联问题,可并不是那么容易能解决的。

当然也并不是没有办法解决:配置中心和正确的配置中心客户端就可以解决这种问题。(如果你的配置中心也要迁移,那就爱莫能助了吧)

配置中心其实也并不只是可以用来关系配置,服务的注册,自动发现,甚至一些服务基础信息的同步,集群管理都可以通过配置中心来做。在实践中,Etcd 和 Zookeeper 应用的相对比较多,以 Zookeeper 为例,在我个人的使用中其实无所谓你本身应用服务究竟是什么语言编写,Zookeeper 客户端一般都有相应的语言绑定,在 acl 和 ZK 配置得当的情况下,完全可以做到保持配置文件或者关键数据在分布式系统中的一致性,热更新,热迁移,并且保证一定的安全性。

公共代码的耦合与 Infrastructure

除了上面提到的更多的上层的耦合的问题,在大型项目中代码的耦合问题虽然更不引人注意,但是可能存在的隐患依然是充满威胁。最具有代表性的例子其实就是一些基础设施公用库的问题,这些封装成了公共库的代码其实也是在迭代更新的,因此,如果说因为某一个服务的特殊需求导致公用代码的接口变动,可能会导致其他服务使用公用库的代码也要发生变动。

但是也并不意味着这样的问题是难以避免的,就公用代码而言,要避免这种尴尬的问题,比较好的方式其实是锁版本。没错,内部公用的代码库的发布也应该是有版本的,在一个服务 Stable 的时候,他的使用的公用代码库和基础设施的库一样都是锁定版本,避免因为公用代码的更新导致服务出现异常。

0x04 Outro

当然在实际的项目中,遇到的问题并不只这么多。在本文我只是特意选择了三个很具有代表性的方面来发表一些自己拙劣的见解,希望可以抛砖引玉,引来大佬一起交流 ;-)

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180420B1FKGB00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券