文章首发于:火线Zone社区
作者:李祥乾
我今天主要分享云原生时代下的DevSecOps,分享一下我们客户在云原生时代下的持续安全实践。
大概摘要:
- 安全可能对于很多业务开发与运维来说,是“麻烦”与“被动”。
- 相比传统,云原生架构具备了一系列特性,使安全能够更低摩擦地内生于企业流程之中,内生于DevOps之中。
- 希望与大家讨论的是,在云原生架构下,在持续加速的业务迭代和CI/CD中,如何实践持续安全(Continous Security)。
我目前在探真科技,我们是一家做云原生安全的公司,是个To B的企业,我之前工作经历主要是互联网工作经历,涉及的领域是IT安全、业务安全,也做过一段时间的基础架构。
我的大概内容分为四个部分:
01:新的云原生,新的风险
02:安全落地的难处
03:云原生下的零摩擦实践
04:持续安全,做一个小结
首先新的云原生,新的风险。
首先云原生架构本身的特点,这里面列的几点,有些是来自于社区里面的一些分享,有些是我们认为比较重要的。
第一点标准化,标准化是一个非常重要的特点,对甲方乙方都很重要,传统的架构下各个公司的DevSecOps流程或者说开发和运维流程,概念上都差不多,但是具体到实施和实现上就千差万别。
但是新的云原生时代,其实Kubernetes或docker等围绕着Kubernetes这样一个社区,这部分的流程已经形成了比较好的标准化,所有的企业都需要去做构建镜像,需要镜像存储,需要CI/CD,需要Kubernetes或者是Kubernetes衍生的一些商业化版本,相关的运维操作其实也在API层面和概念层面都完成了标准化,这样有非常好的好处,就是社区里面的一些其他企业非常好的实践,都能够比较的摩擦的、比较快速的,在我们自己的流程和环境里面去做实验、落地,不像之前需要一些特别多的适配成本、金融成本。
自云原生社区成熟以来,Kubernetes这样的容器编排软件成熟以来,像AiOps这样的概念才真正的兴旺起来,对于我们这种做企业服务的公司来说,我们的一些安全能力和安全服务能够相对来说比较低成本的在不同的企业做可复制性的方案。
不可变基础架构,我们认为对安全来说也是一个非常重要的特点,对安全能力真正落地是一个比较好的基础。
微服务化和持续交付,非常容易理解。
Operator化是社区里面比较有名的张磊老师在他的分享里面提到的,Operator化的模式是K8S的一个重要的模式,是一种自定义资源和扩展K8S的一种非常重要的方式,也是实现很多扩展性能的一个重要的基础。
云原生下其实存在的安全风险是涵盖了传统架构下的安全风险,诸如WebShell、病毒、木马、反弹Shell,这些问题在新的架构下仍然存在,相反云原生的目标核心目标是降本提效,因为降本提效就是更快的集成和交付带来的风险、供应链的风险,漏洞风险的控制成本其实是加倍提升的,因为单位时间内交付的镜像,交付的新服务的速率都变大了,控制这些风险会与交付速度之间存在的冲突也会增强,因为原来大家都慢,多花点时间解决问题,这些问题也不是什么问题。
云原生架构下主要基于容器的更灵活的隔离技术、容器网络这种软件定义网络,在物理层面上面增加了一层更灵活的虚拟化技术。其实这种虚拟化技术本身,比传统的虚拟机的隔离,它存在逃逸或者提权的风险会变得更大,像docker,runc包括k8s都暴露出了很多容器逃逸的漏洞。
最近有一个比较新的漏洞,是用Linux的一些机制去逃逸Linux NameSpace来完成逃逸,也是最近比较新的漏洞,在这种隔离机制下,很多传统安全方案和能力是无法生效的,比如说传统的网络防火墙,在容器网络下其实pod的迁移是很频繁的,同时他的IP是动态分配的,传统基于IP的一些策略通通都会失效。更复杂的代码肯定会带来更多的bug,漏洞就是这些核心组件的缺漏洞和缺陷的风险也大幅提升了。
最后一个问题就是微服务化后,服务的数量,还有这些服务之间的调用关系的复杂度是呈几何比例去提升的,对于安全策略的管理,安全运维成本,如何感知这些流量和变化,存在着巨大的挑战。
从典型的DevOps流程看风险 ,比如说代码仓库层面,代码本身可能就会包含很多敏感的数据,比如说数据库密码,密钥,业务自己写的代码会存在漏洞和bug,可能会被攻击者利用。
当开发人员开始测试环境测试的时候,从Jenkins或者gitlab ci这样的CI/CD工具里面会去拉取代码,去做构建镜像。一些包含着病毒和木马的镜像可能会在这个阶段就被推送到仓库。
仓库里面存储的所有活跃的镜像,这些活跃的镜像可能有几万个,甚至十几万个镜像,然后镜像可能还包含着几十万个漏洞,这种漏洞的管理成本对于安全和业务来说还是很高的,这么多漏洞不大可能全修复了,成本太高,但是优先修复哪个就是问题?
接下来开发可能会经过多轮的代码提交、镜像测试,然后在测试环境里面做部署,做联调;然后交给QA验收之后,这个镜像会被mirror到网络隔离的另一个环境,比如生产环境的仓库,然后再去做部署交付。
在这个镜像的分发流程中,还存在一个风险,就是镜像可能会被篡改,可能会被替换掉。相当于是我可能在CI/CD阶段或者仓库阶段扫描,扫描认为他是个安全镜像,但是这个安全镜像在接下来的分发流程里面被篡改了,篡改之后的镜像可能会引入风险,但是相当于之前的措施就失效了。
在部署交付阶段,这个镜像也可能会被别有用心的人去替换掉,比如说节点镜像的缓存就是一个可能被遗漏的点,就存在着一定的风险镜像流入线上的一种可能性。在镜像的运行过程中,这些镜像漏洞可能会被攻击者利用,这些漏洞的攻击能否被检测到和管理,这方面也是安全需要解决的问题。
其实这种灵活性,加速的CI/CD,加速的迭代为安全带来的并不是只有烦恼。
如果从辩证的角度看这种灵活性,加速的业务迭代,是加速了持续集成和持续交付。
但是安全的运营和安全策略也需要去加速迭代。
安全需要实现更加灵活和深入对风险和对集群资产的感知能力。
安全需要利用数据和机器学习来降低安全运营成本,提高运营效率。其实就是为安全人员减少重复性的配置,提高他们的安全运营效率,让安全人员持续的关注在最需要关注的安全问题上。
安全需要更加灵活的策略执行和迭代 ,把固化的代码逻辑里面的策略变成数据、变成规则,这样的话数据是可持续迭代更新的,然后就可能蹭个热度,就是“低代码”。
我们希望的云原生安全,更希望他的解释是云上的原生安全,而不是原生环境下的一个安全,就是Security in Cloud Native 。
第二部分是在安全的具体落地中和我们在落地实践中遇到的一些相对来说比较困难的地方,安全落地难处在哪?
从业务视角来看,首先的观念可能是安全会让DevOps变慢。比如说零信任需要去频繁的做健全、去做审批,比如说在Google的BeyondCorp这样的方案,整个落地耗时了六到七年。其他的企业对零信任的认知可能还参差不齐,这个落地难度其实还是很高,或者说零信任到底要不要落地?
对微隔离来说,微服务这么多,网络策略配置是很复杂的,一旦少配一个,漏配一个,就可能会造成业务上的损失,然后他带来的报警也是很多,这个在我们跟客户的落地实践中也是我们遇到的问题。我们在和Tigera做技术交流的时候,他们也提到,企业版的微隔离在企业从开始落地到基本完成一个闭环平均时间也差不对需要半年多,这其实是一个比较大的成本。
镜像安全,我认为可能是更普遍,更容易理解的例子。镜像肯定是很多,漏洞肯定比镜像多的更多,修复也修复不过来,扫描可能也比较慢。
你可能还会发现很多漏洞是没法修复的,虽然他可能是高危漏洞,但是官方还没有发布修复的补丁。
安全团队可能发现的问题,但是他们不一定能帮助业务开发和运维解决问题。
这个是我们在一个金融客户里面的例子,在初期的扫描里面,他们活跃的镜像规模在14000多左右,但是包含高危和严重漏洞的镜像至少在一半以上,甚至到了百分之七十、八十。
也有用开源扫描器扫描过漏洞,但是除了高危漏洞有能力推进过几次,推动整体安全基线提升是比较困难的。服务太多,镜像太多,收益不显性,边际成本越来越高。
相比于业务迭代的需求中,安全的需求肯定不能override业务迭代需求,企业的核心动力还是业务的迭代,安全其实是控制业务的风险。
我觉得安全有两种逻辑:
一种是基于事中与事后的对抗与博弈逻辑。发生安全事件,需要紧急响应或者突发漏洞的时候, 比如说去年的Log4j这种时候,安全人需要跟攻击者,或者说潜在的风险去做持续的对抗和博弈。
这种一定程度上说是“擦屁股”,出现问题了要去遏制风险,事后我要通过复盘去避免的风险。他的核心逻辑其实是风险控制,安全永远没法消灭风险,相对风险可能成本会很高。对于攻击者来说它也存在成本,这个最终我认为是成本的对抗,就是安全团队需要通过合理的成本去将风险控制到一定程度下,在这个程度下攻击者需要付出,可能他不愿意承受成本去获得一些利益,然后达到这个平衡之后,是这种对抗和博弈逻辑所追求的一个目标。
另一种是基于事前和以ROI为中心的预防逻辑,就是Return On Investment,看中的其实是投入产出比,当企业发生过几次安全风险之后,企业肯定会重视这种安全防护体系,包括这种纵深防御的建设,去做流程安全,提升整个企业的安全水位线。
安全的目标和业务还有运维(DevOps)的目标是一致的,其实都是业务持续性管理,目标其实都是让业务持续性的高可用的运行在线上,保证业务的持续性运行,避免风险,比如说数据泄露的风险,PR、GR的风险。不会有任何公司会为了提高防御能力去穷尽所有,去修复所有的漏洞,这样边际成本会越来越高,最终的逻辑都是基于ROL逻辑,让业务迭代更安全,寻求一个平衡。
我们认为云原生安全的一个核心理念应该是零摩擦 Zero Friction ,然后这个Zero Friction主要诞生于零信任网络的落地实践中。
这个里面的零等同于零信任的零,是无限趋近的意思,对于零信任来说不可能什么都不信任,信任要有root根信任。对于零摩擦来说,也不可能没有任何安全方案是完全没有摩擦的,重点是平衡安全的收益和业务收益。
具体来说就是:
在降低安全风险的目标的前提下,做到安全运营成本的降低,运营效率的提升,提高用户体验。
能够更好的适应不同企业的流程和文化,降低实际用户在落地安全实践时候与其它部门和其它需求的摩擦。
安全不能只发现问题,还需要解决问题和管理问题。
接下来我们结合跟客户的一些落地的实践,来分享一些云原生在DevOps里的落地实践。
可信镜像的含义,首先可信镜像是经过比较全面和深入的扫描,我们能确认它的风险是在企业用户和安全团队所认可的安全标准之内的镜像。
可以信任的镜像,就是在整个分发过程中,从镜像的生命周期的诞生,到它最终落地到运行在线上成为一个容器,直到这个容器被销毁,整个过程中这个镜像是没有被篡改和替换的,来保证分发流程的安全,这样的现象我们认为是一个可信镜像。
所有的镜像基本上都是在一个CI/CD工具,比如说给了GitLab CI,或者Jenkins里面去做镜像构建,在这个时候就需要去对这个镜像去做镜像的扫描,同时这个扫描的效率还足够高,如果每次镜像构建都需要去运行一个小时的话,业务无法接受的,镜像扫描需要高效。
对于满足安全标准,扫描出的风险结果是满足安全标准的镜像,这个镜像去做签名,然后去阻断或者说预警这种不满足安全标准镜像,这样能保证推送到仓库里的镜像,它是一个满足安全标准的镜像。
接下来镜像的旅程里面,可能会从测试环境M irror到生产环境,在它真正被部署到生产集群的时候,或者部署到测试集群的时候,我们这个阶段需要去校验,因为只有安全的镜像,同时在这个分发流程里面没有被篡改过的镜像,才能成功的完成校验的签名,这个阶段就可以阻断那些不安全和不可信的镜像,这些镜像是无法部署到生产集群上的,同时这个阻断能力要覆盖没有经过安全的pipeline构建的镜像。
同时还有一个需要注意,K8S的每个节点上他都会有一个镜像缓存,来提高容器的一个拉起的效率.如果具有一定权限的话,也是存在一些镜像被篡改或替换的可能,所以需要去实时监控,我们节点镜像的情况去做扫描,同时监控线上运行容器的一些异常,可能会被替换的异常,来保证整个的镜像的全生命周期里面,他的安全是能得到保障和关注的。
第二个点镜像风险闭环管理,这里面罗列了一些镜像的风险,对于漏洞来说其实包含三种类型的漏洞。
第一种类型:这个漏洞是已经公开发布的,同时他是个可修复的漏洞,就是官方已经发布了修复补丁的漏洞,这样的漏洞大部分安全团队都知道怎么去处理,怎么去做。
第二种类型:虽然它是公开发布的漏洞,但是官方并没有发布修复的补丁,这种漏洞其实也很常见,大家在日常的扫描实践里面也经常会遇到,有很多漏洞虽然扫描出来,但是没有官方补丁,拿他也没什么办法。
第三种类型:0day漏洞,不是所有漏洞被发现了都会被公开,有很多安全厂商或者很多国家安全部门都会有一些私藏的漏洞,这些未发现的漏洞或者未公开漏洞,他可能不存在补丁,或者存在补丁也不知道。
第四种风险:病毒,木马,Webshell等恶意文件,这种风险其实理论上也应该按照前三个分类去做分类,那这里就简单一点。
第五种风险:敏感文件,在代码层面的敏感数据其实在镜像扫描里面无法去扫描的,但是比如说在配置文件里面包含了数据库的密码,包含了一些关键的密钥,其实是可以在镜像扫描这个过程中被检测出来的,这种保存在配置文件里面的数据库密码和密钥,其实是有很大的风险被泄露出来,因为这个镜像里面是可以直接读这个配置文件的,尤其是在互联网公司,其实已经出现了非常多的开发人员把自己在公司里面贡献的一些仓库代码上传到GitHub,然后就无意中把公司的数据库密码也上传上去了。
Root权限运行的镜像,这个在安全的建议里也是尽可能的不要用root权限去运行。
针对镜像风险的一个检测技术,其实目前其实最常见的就是静态扫描,目前开源的一些静态的扫描技术,比如说Clair这些技术,它的检测原理,第一步其实是要去检测和发现镜像里包含的公开的软件包,程序依赖的第三方库和依赖,比如说去扫描jar包,二进制等尽可能全面的制品,去扫描出程序依赖一些第三方的lib,比如Fastjson等。
首先一个能力就是需要了解你这个镜像里面的软件制品有哪些,依赖第三方库有哪些?他们的版本是什么?然后你还需要一个尽可能全面的公开漏洞数据库,去用镜像的软件制品这笔数据,去跟私有漏洞数据库比对,然后这个其实是非常依赖漏洞数据库的更新。
第二个就是无法扫描自己企业里面构建一些私有制品,一些自己的业务代码是没法去做漏洞扫描和检测的,你需要去持续去更新你的漏洞库,可能你的扫描技术也需要去做一些迭代,去覆盖到更多语言,尤其是企业所运用的一些编程语言的构建出来的制品。
比如说你用了一个比较小众的语言,扫描软件如果没法去解析这个语言,无法去解析出你这个制品里面依赖了哪些第三方仓库的话,就没法对他去做扫描。
对这种未知漏洞,未知漏洞的含义就是漏洞数据库里面没有的漏洞,还没有收录到漏洞数据库的漏洞是没有能力去做检测的。
基于这种情况,我们在部分客户里面实现了动态扫描的机制,类比于PC上面的病毒扫描,如果只依赖文件的hash比对和特征匹配,则检测能力有限。同时你需要在运行时需要时刻监控注册表、关键目录的异常行为,与常见病毒的行为特征比对,才能实现比较全面的扫描。
静态扫描的优点其实就是扫描的很快,对于这些不足来说,其实我们会把这个镜像按照模拟生产环境的配置,去在一个沙箱容器里面去做运行,通过我们运行检测的能力去检测,当这个镜像在容器里面运行起来的时候,存在一些风险行为。
第二点就是会通过实施一些尝试性的POC漏洞利用攻击去检测一些常见的攻击威胁,能不能有效达到一些效果,通过这些方式来做一个运营时的动态扫描。
镜像风险管理的第三点就是对可修复漏洞的风险管理,这个其实是刚才提到的这种ROI的逻辑,我现在有15000个镜像,我可能有几十万个漏洞,我是不可能随便排个序,每个镜像的所有漏洞都去修复。
漏洞本身的原信息里面其实是包含它的风险级别的,就是有些漏洞是高危的,有些漏洞是低风险的,有些漏洞是可忽略的。
同时这个镜像用来运行的服务的重要性也有所区别,比如说我这个服务只是一个在内网使用的,面向员工的一个,比如说内容管理平台,其实相对来说,他的优先级可能会需要低一点,但如果服务是面向外网API服务,同时是非常核心的,比如说账户服务,有可能是企业里面每个对外的APP或者网页都需要去访问的API,这个服务的它的重要性和优先级就会很高,它包含漏洞可能就需要优先去解决。
同时一个漏洞的覆盖率各有差别;修复覆盖面广的漏洞,修复基础镜像的漏洞事半功倍。
镜像风险对可修复的风险管理应该是一个修复计划,通过我们对集群和镜像的感知能力去分析漏洞的一个轻重缓急,来制定一个排序和修复计划,来让安全人员能够更高的关注到最重要、最需要紧急修复和修复它产生的收益最大的漏洞,然后持续去优化,提升整个镜像安全层面的一个水平线。
针对那些高危漏洞、0day漏洞,我是不是就没有办法?
举个例子,去年底的log4j的漏洞,基本上影响了企业的全部Java服务,都会被这个漏洞所影响。当时很多企业都火烧火燎熬夜的方式去升级服务,去修复漏洞,影响范围非常大。
即使官方已经发布了修复补丁的版本,它的实施成本和它的实施周期是非常大的,在这个实施周期里面,对于攻击者来说它是一个已知漏洞,但是企业并不能瞬间地修复或者防止漏洞,利用的机制能够利用起来的话,其实是被攻击的一个巨大的风险。
热补丁其实是一种我们在一些企业落地这种机制,就是无需更新软件,通过更新代码或者部署新的服务的方式,即可即时地下发补丁到需要补丁的“节点”,避免漏洞被利用。
一些常见的的修复方式,比如说通过WAF上线规则,通过所有流量的入口去检测有没有请求,有没有流量再利用这个漏洞。
WAF是比较普遍的安全解决方案,他的上线规则成本相对来说比较低,同时也很快,也可以认为是一种热补丁方案,但是WAF本身也存在着很多被绕过的可能性,同时WAF是没法防御一些集群内部的风险。
还有一种比较常见的解决方案,就是通过RASP的方式,RASP会对企业自身的进程注入代码去监控风险,去实施一些拦截,它是一种紧贴着业务的一种模式,能尽可能的避免被绕过,但是他对业务的侵入性也相对来说比较大,同时他是针对特定能解决的语言才能实施的,比如说常见的Java、Go可能存在的方案,但并不是一个通用方案,同时它的性能开销也存在着一些问题。
我们的安全标准是可以持续去迭代的,来保证一个持续的安全,当安全刚开始发挥作用的时候,你面临的可能是一个有几万镜像,有几十万个漏洞的非常难搞的局面,但是如果能通过合理的镜像风险分析,能够对现状有比较全面的感知,我们基于这个现状制定的修复计划里面,来制定一个对现阶段来说是可完成、可落地的安全标准。
安全标准的含义就是比如一个镜像需要检测出来的风险中需要满足什么样的条件,他才能被推送镜像仓库,才能被部署上线。比如说漏洞的评分必须高于90分,或者说不包含哪些特定的漏洞,或者说必须没有病毒和木马,一系列的policy去完成这个安全标准,同时安全标准可以从一个比较低的水位线开始,基于这个安全标准去利用全流程的可信镜像体系,去保证在这个安全标准实施之后,陆续的企业在线的容器,都能运行在满足安全标准的镜像之上。
基于这种持续性的迭代去更新安全标准,逐步把水平线提高,直到提高到一个相对合理的业务迭代平衡的水位线。如果说边际成本再往下做,我的边际成本就变得比较高,或者收益就不明显了,就可以稳定一下时间。尤其是一些新的漏洞发布或者漏洞更新之后,这个标准都可以持续性的去做更新,去提升,来保证企业整个镜像安全层面上的一个水平线的稳定。
云原生架构下另一个可以利用的非常重要的特性,就是它的不可变性。
在传统架构下,我的一个服务可能有10个workload,这是个Workload运行在十个虚拟机或者是十个物理机上,这十个虚拟机和物理机它所依赖的软件包可能大致相同,但是具体的版本和情况都有可能出现一些差异性,不过在内核版本和OS版本上也可能存在一些不影响业务运行的差异性,业务本身的进程可能还存在一些特殊情况。
我可能要在其中地挑出一两个workload,我去更新到一个debug版本来做一些测试。这个应用本身的代码版本无法保证这十个workload是完全一致的。
同时在传统架构下,在运行中的工作负载的主题上去下载一些新的软件,去更新这个软件版本,这种运维操作还是比较常见的。
但是在云原生架构下,它的不可变性,就能保证这个服务10个pod都是用同样镜像构建的,他能保证我环境软件的版本与预期是一致的。
在云原生的最佳实践里面,非常不推荐进入到容器里面去更新软件、下载软件、修改软件,最佳实践肯定是去更新镜像的定义,去完成软件更新,软件更新能保证应用到服务的所有workload上。
这种不可变性为安全能力带来一些新的可能性,比如我们在镜像构建阶段,就可以对这个镜像可能包含的所有可执行的二进制文件,都能为他制定一个二进制画像,在运行阶段去检测一些未知二进制的执行,去检测一些被篡改二进制,这样就有效避免攻击者去下载可执行文件,通过漏洞去运行一些未知的二进制,或者说被篡改的容器里的二进制,这样来实现一个偏移的检测和阻断。
基于DevOps流程,可以去对容器的各种类型的行为,比如说文件读写,网络去做一个行为画像的学习,基于这些行为画像去做检测和阻断。
然后根据这个服务的镜像版本的更新和环境变量的更新,像状态机一样持续去做新的增量学习,去更新这个行为画像,来保证实时性和有效性。
最后就是云原生安全的目标,希望在更加快速的CI/CD中,建设持续安全能力,并且能够在企业中低摩擦地实践持续安全;不仅仅能发现问题,而且能够解决问题,实现安全的闭环。