在基于微服务的云原生架构中,客户端的一次服务调用,会产生包括服务和中间件在内的众多调用关系。对这些大量复杂的调用过程进行追踪,对于微服务的安全性分析、故障定位、以及性能提升等,有着重要的作用。
当前的互联网服务,大多数都是通过复杂的、大规模的分布式集群来实现,而随着云原生、微服务等架构的逐步成熟,传统的单体架构设计,向着更加松耦合的微服务架构进行演进。同时,考虑到微服务架构下的负载均衡以及高可用等设计,服务间通信以及调用关系的复杂度,将变得异常庞大。
我们看这样一个搜索查询的例子[[1]],比如前端发起的一个Web查询请求,其目标将可能是后端的上百个查询服务,每一个查询都有自己的index。这个查询可能会被发送到多个后端的子系统,这些子系统分别用来进行广告的处理、拼写检查或是查找一些图片、视频或新闻这样的特殊结果。根据每个子系统的查询结果进行筛选,进而得到最终结果,汇总到返回页面上。
总的来说,这一次全局搜索有可能调用上千台服务器,涉及多种服务。而且,用户对搜索的耗时是很敏感的,而任何一个子系统的低效都将可能影响最终的搜索耗时,如何在微服务架构下有效的发现并且解决系统的性能瓶颈问题。
同时,一旦前端暴露的服务被攻击者攻陷,或者是内部某个服务已被攻击者攻陷,那么,面对系统内部服务之间庞大复杂的调用关系,这些调用是否全部是完成这次搜索所必须发生的业务联系?是否存在API探测?API滥用?是否存在针对某个服务的拒绝服务攻击(DoS)?客户端服务发送的请求是否包含注入攻击?如何在海量的正常业务调用逻辑中,发现非法的异常调用请求?这些问题将严重关系到服务甚至整个系统的安全。
另外,一旦这次请求,最终以失败的结果返回,那么如何在这样错综复杂的服务之中,准确的进行故障定位,并且快速的实现故障的修复。
为了应对这些问题,可观察性(Observability)的概念被引入软件领域。提到可观察性,通常会引用如下这张经典的图,日志(Logging)、监控指标(Metrics)和追踪(Tracing)构成了可观察性的三大组成部分。关于日志和监控,作为传统的技术方法,本文将不再过多进行介绍,感兴趣的读者可以自行搜索[[2]]。
图1 可观察性组成
对于可观察性,它和传统的监控、日志有什么区别呢?从可观察性的三大组成可以看出,一方面,传统的日志系统和监控系统,将作为可观察性的重要组成部分,另一方面,可观察性还引入了一个很重要的因素,就是对整个系统进行追踪(Tracing)。一个经典的解释就是,“监控告诉我们系统的哪些部分不工作了,可观察性则告诉我们那里为什么不工作了”。
在CNCF给出的云原生参考体系(Cloud Native Landscape)[[3]]中,将可观察性(Observability)和分析(Analysis)放在了同一个维度。一方面通过实现可观察性的工具,获取系统中各个维度的运行数据,从而对整个云原生架构下的应用运行情况有全面深入的了解。另一方面,在拥有了这些数据之后,可以进行安全性分析、运维故障分析、性能分析等。
图2 Cloud Native Landscape(Observability)
分布式追踪是实现应用链路追踪的一种重要技术手段,同时也是实现云原生可观察性的重要组成部分,其主要应用于应用的性能分析(APM,Application Performance Management)和故障定位等。Google在2010年公开其生产环境下的分布式跟踪系统Dapper[[4]],奠定了整个分布式追踪以及APM模型的基础。
随着微服务、云原生等应用新架构的发展,分布式追踪系统的需求以及优势越来越明显。为了推进Tracing协议和工具的标准化,统一Trace数据结构和格式,云原生基金会(CNCF)推出了OpenTracing标准[[5]]。
OpenTracing是一个轻量级的标准化层,位于应用程序/类库或者追踪/日志分析程序之间[[6]],通过提供平台无关、厂商无关的API,使得开发人员能够方便的添加或更换追踪系统的实现,比如从Zipkin替换成Jaeger或Skywalking等后端。同时,OpenTracing还提供了用于运营支撑系统和针对特定平台的辅助程序库。当前OpenTracing得到了来自Uber、Apple等商业公司,以及Zipkin、Jaeger等开源社区的积极响应和推动。
图3 OpenTracing分层架构图
从上述架构图可以看出,基于OpenTracing的分布式追踪系统,上层的应用程序、类库、运营支撑服务、远程调用框架等,都可以直接使用OpenTracing定义的标准接口来描述和传递分布式追踪数据,而不必关心具体的OpenTracing以及下层多种Tracers的具体实现。
其后端的追踪器(Tracers),则可以是任何支持OpenTracing标准的追踪系统,包括应用链路的追踪、日志系统、监控系统等。当前支持的典型实现包括CNCF Jaeger、Apache SkyWalking、Instana、LightStep、Datadog等[[7]]。
基于OpenTracing的分布式追踪系统,有两个重要的概念:Trace和Span[[8]]。一个Span代表了系统中具有开始时间和执行时长的逻辑运行单元,表示追踪中的一次操作,Span之间通过嵌套或者顺序排列建立逻辑上的因果关系。Span中包含操作名(operationName)、Span ID、当前Span的前一个Span(root span除外)、操作起始与终止时间戳、属性键值对等信息。在广义上,一个Trace代表了一个事务或者流程在(分布式)系统中的一次执行过程,每个Trace是由多个Span组成的一个有向无环图(DAG)。
下面的示意图简单直观的展现了一个典型的Trace过程,同时清晰的描述了Trace和Span的关系。它很好的显示了组件的调用时间,是串行调用还是并行调用,调用间的时间间隔等。
图4 Trace示例
这种展现方式增加显示了执行时间的上下文,相关服务间的层次关系,进程或者任务的串行或并行调用关系。这样的视图有助于发现系统调用的关键路径。通过关注关键路径的执行过程,项目团队可能专注于优化路径中的关键位置,最大幅度的提升系统性能。例如:可以通过追踪一个资源定位的调用情况,明确底层的调用情况,发现哪些操作有阻塞的情况。
自Google Dapper首先提出分布式链路追踪的设计理念以来,许多分布式追踪工具不断涌现。当前,常见的分布式追踪工具包括Dapper,Zipkin,Jaeger,SkyWalking,Canopy,鹰眼,Hydra,Pinpoint等,其中常用的开源分布式追踪工具为Zipkin,Jaeger,SkyWalking和Pinpoint。这些分布式追踪工具大致可分为:基于SDK的和基于探针的。
基于SDK的分布式追踪工具。以Jaeger为例,Jaeger提供了大量可供追踪使用的API,通过侵入微服务业务的软件系统,在系统源代码中添加追踪模块实现分布式追踪。此类工具可以最大限度地抓取业务系统中的有效数据,提供了足够的可参考指标;但其通用性较差,需要针对每个服务进行重新实现,部署成本较高,工作量较大。
基于探针的分布式追踪工具。以SkyWalking Java探针为例,在使用SkyWalking Java探针时,需将探针文件打包到容器镜像中,并在镜像启动程序中添加-javaagent agent.jar命令实现探针的启动,以完成SkyWalking在微服务业务上的部署。SkyWalking的Java探针实现原理为字节码注入,将需要注入的类文件转换成byte数组,通过设置好的拦截器注入到正在运行的程序中。这种探针通过控制JVM中类加载器的行为,侵入运行时环境实现分布式追踪。此类工具无需修改业务系统的源代码,相对SDK有更好的通用性,但其可获取的有效数据相对SDK类工具较少。
下面我们以CNCF Jaeger[[9]]为例,简单看一下追踪器(Tracer)的一般实现原理。Jaeger是Uber开源的分布式追踪系统,现在是CNCF的开源项目,并成为CNCF继Kubernetes、Prometheus、Envoy等之后的第七个毕业项目。
Jaeger项目由Uber公司于2015年创建,目前被整合至众多的微服务架构当中,每秒负责记录成千上万条业务记录。Uber、Weaveworks、Symantec等20多家组织机构将其引入生产环境。此外,Jaeger还被Red Hat集成至OpenShift Service Mesh等产品当中。
Jaeger于2017年9月被CNCF接纳为孵化项目,当前最新版本为1.21。背后有大厂和强大的组织支持,项目目前开发活跃;Jaeger原生支持OpenTracing标准,可以认为是OpenTracing协议的参考实现,支持多种主流语言,可以复用大量的OpenTracing组件。支持云原生的部署方式,非常容易部署在 Kubernetes 集群中。
下图展示的是Jaeger的简单架构,其主要由Client、Agent、Collector、Data Store、Query等几个部分组成。其中Jaeger Client为不同语言实现了符合OpenTracing标准的SDK,应用程序通过API写入数据,Client Library把trace信息按照应用程序指定的采样策略传递给Jaeger Agent;Jaeger Agent是一个监听在UDP端口上接收Span数据的守护进程,它会将数据批量发送给Collector;Collector接收Jaeger Agent发送来的数据,然后将数据写入后端存储。Collector 被设计成无状态的组件,因此可以同时运行任意数量的Collector。后端的存储被设计成可插拔的组件,支持Cassandra、Elastic Search。
图5 Jaeger架构
考虑到分布式追踪系统本身的性能损耗,一般需要进行采样设置。Jaeger当前支持四种采样频率的设置:包括固定采样(全采样或不采样)、百分比采样(随机采百分比数量的样本)、限制速度采样(设置每秒采样几个traces)、动态设置采样(可以通过配置从Agent中获取采样率的动态设置)。
前文介绍了在云原生架构下,可以通过分布式追踪系统,实现对云原生应用的可观察性追踪。根据追踪数据,进而实现对应用的性能分析、故障定位等。接下来本章将介绍,基于分布式追踪系统获取的可观察性数据,如何实现云原生应用的安全检测与防护。
应用安全是指应用级别的安全措施,旨在保护应用内的数据或代码免遭窃取和劫持。它涵盖了在应用设计和开发期间发生的安全注意事项,还涉及在应用部署后对其加以保护的系统和方法。
云原生应用在安全防护上,同样遵循上述应用安全的建设思路。区别在于,云原生架构下,应用在自身架构以及开发、运维等管理模式上,发生了一定的变化。因此,安全防护手段也较传统的应用安全有着一定的区别。
敏捷开发、DevOps等软件开发方法,加速了应用程序生命周期的迭代,应用的安全开发管理流程也从传统的SDL迈向了DevSecOps,尤其强调要在更早的软件开发生命周期,投入更多的安全资源,嵌入安全动作,这样能更容易的收敛安全漏洞问题,尽可能更早的确定其安全性,这也就是近几年提及较多的“安全前置”或“安全左移”。
“安全左移”可以有效的解决应用设计和开发期间发生的安全注意事项,接下来重要的一点,就是在应用部署后,对其加以安全防护。
基于微服务架构的云原生应用,通常采用Docker、Kubernetes、Service Mesh、Serverless等云原生计算支撑技术,实现对应用程序的运行支撑。针对这些运行时的安全防护问题,我们可以将其归类于对云工作负载的防护范畴中,也就是Gartner所提到的CWPP[[10]](Cloud Workload Protection Platforms),感兴趣的读者可以针对CWPP做更深入的了解,本文将不再过多的展开。
回到云原生应用的本身,基于微服务的架构设计,使得应用之间的交互模式从Web请求/响应为主转向各类API的请求/响应,API通信在云原生应用交互中占据了重要的地。因此,API安全也成为了云原生应用安全中尤为重要的一个部分。
对于云原生架构下应用的API安全,我们可以采用对API进行全生命周期监测防护的思路,这就包括API的识别、API的脆弱性检测、API的调用追踪以及API异常调用的检测等。
4.4.1 API的识别和发现
在复杂庞大的微服务之间的API调用中,要实现应用的API安全,首先要解决的是API的识别和发现,要能够知道整个系统中所有的服务都暴露了什么样的API,API本身的设计是什么样,比如API的URL、参数是什么,API的认证情况如何。然后把API作为安全防护的一种“资产”,根据发现识别到的API,实现其对应的Pod、Service、应用以及租户等属性的关联,完成API“资产”的梳理。
这个步骤中,分布式追踪所获取的链路追踪信息,则可以天然的解决这个问题,比如在Span的消息中,可以获取到相应API调用的URL、HTTP Header、HTTP Body等信息。
图6 Jaeger追踪数据示例
4.4.2 API的分析和评估
获取到了所有的API信息之后,接下来我们需要对其进行安全性的分析评估。这里的安全分析既包括API在设计实现上的脆弱性评估,也包括在业务逻辑上安全性、合规性的分析评估。
在脆弱性评估方面,可以参考传统的API安全方案或者一些API安全检测工具,例如42Crunch[[11]],或者一些商业性的API安全扫描产品[[12]],实现加密连接的检测、认证授权的检测、参数校验的检测、响应内容检测等。
图7 API脆弱性分析示例
在业务逻辑合规性检测方面,主要包括该服务所暴露的API范围是否合理,是否暴露了服务必要的API以外的API服务,是否增加了这个层面的攻击面范围;在网络侧是否实现了相应的访问控制逻辑,比如原则上某个服务只能访问另一个服务的某个GET API,可是实际上能够访问诸如POST、PUT等其他API,这一点通常会在网络层通过L7防火墙来实现,在应用安全侧,我们需要能够对这种合规性进行检测分析。
图8 API访问控制示例
4.4.3 API的调用监控
前面介绍的对API“资产”的识别,API脆弱性、合规性的检测,我们可以认为是针对单个API的静态安全性分析,有了这些基础之后,接下来需要对整个系统中所有服务间的API调用行为进行收集。
参考API的识别发现,这里可以持续的对API调用行为进行监控收集。区别在于,在API识别发现的环节,我们通常会以服务为单位,确定某个服务有哪些API,API会有哪些脆弱性等。在这一步的监控,我们关注的重点基本和分布式追踪是一致的,我们也将以一次完整的请求执行过程,也就是一个Trace为单位,监控这一次Trace请求的API调用链,以及某一个调用会属于哪个请求Trace等。
4.4.4 API的行为模型学习
通过分布式追踪系统收集到API调用数据后,首先对其进行预处理,筛选出相关有用的数据字段后,根据历史数据利用机器学习或统计学的方法,训练出业务系统中API的调用行为,比如基于API调用链、调用时间、响应时间、调用参数等属性,形成正常情况下的API调用行为画像,并生成与业务系统正常行为匹配的特征数据。
这里进行训练的先验知识为,我们认为业务系统中大量存在的行为是正常行为,而数量很少的行为是异常行为。在训练过程中,需要根据专家知识对训练结果的检验不断调整训练模型的参数。
4.4.5 API的异常检测
至此,我们就已经有了业务系统中正常的API调用行为数据了,将业务系统当前数据与特征数据库中数据进行检索匹配,并利用序列相似性计算等方法找出特征数据库中与当前行为最为匹配的特征数据。
检测引擎需要将特征数据与当前数据的相似性与基线进行比较,若比较结果显示当前行为与正常行为的差异在基线限制范围内,则为正常行为,若超出基线限制范围,则判定为异常行为。
对于基线,首先需要根据专家知识设置合理的初始基线,并根据不同场景,或利用无监督模型自行调整基线,或由运维人员手动维护基线。
下面我们以Jaeger为例,简单看一下如何基于分布式追踪系统,实现云原生应用的API安全检测。
如下图所示,通过Jaeger对应用程序的追踪,我们可以获取相关的API调用数据(图中绿色部分),结合这些数据,就可以生成对应的特征数据以及进行诸如序列逻辑异常、API参数异常、API调用频率异常等异常检测。
比如,对于调用逻辑异常,当攻击者采用某些方法使API调用的逻辑顺序出现异常,包括关键调用步骤缺失,颠倒等。这样在API调用序列中就会出现缺失关键步骤的情况,具体细节介绍可参考[[13]]。
图9 基于Jaeger的API异常检测示例
基于微服务的云原生架构,使得服务间的通信变的异常复杂,给云原生应用的安全检测和防护带来了巨大的挑战。
分布式追踪是实现应用链路追踪的一种重要技术手段,同时也是实现云原生可观察性的重要组成部分,实现了微服务间链路的可见可观测。
基于追踪数据,结合经典的安全检测方法以及机器学习等大数据分析技术,可以有效的解决云原生应用安全问题。
点此链接下载《云原生安全技术报告》
http://blog.nsfocus.net/wp-content/uploads/2021/01/Technical-Report-of-Cloud-Native-Security.pdf
关注绿盟科技公众号后台回复“云原生报告”可下载报告完整版!