随着Kuberntes逐渐坐稳了容器编排平台的头把交易,容器化部署已经从Gartner技术风向报告上,变成实际运行在宿主机上的一个个进程。而支撑Kubernetes的容器化技术,或者说支持Docker的容器化概念,早就在多年前已经诞生,笔者相信大部分人都认可:由于Docker提供了对用户更加友好操作界面和客户端命令行工具,containers这个技术逐渐从墙角走到了聚光灯下,特别是工具的主要用户群体:开发人员。
容器技术有很多好处,相信读者已经能够耳熟能详。build once,run everywhere让开发人员终于不再为”在我机器上跑的好好的,为啥在测试环境不工作“这样的问题绞尽脑汁,可以把大把的时间用在在价值更高的新功能开发的任务上。而这一切华丽的背后,是Docker创新的应用打包机制,通过Docker build,我们可以把应用程序以及所有的依赖进行打包,并且通过Linux操作系统提供的内核功能,给应用程序提供一个独立于其他应用的运行环境,即便是同一台机器上有很多其他的应用在同时运行。
容器打包后镜像有除了操作系统内核之外的完整依赖,因此无论是在开发人员的机器上,测试环境,或者生产环境,运行起来的行为都会完全一样。笔者需要特别强调的是,基于容器的隔离,可以在同一台机器上运行多个容器实例,并且这些容器实例时间可以做到相对有效的隔离,应用之间不会受到相互影响。
如果大家有过Windows上的开发经验,一定听说过dll hell这个属于,你给机器上装了一个新版本的软件。然后,然后你会发现某些之前正常工作的应用突然就不工作了,这就是dll地狱,因为windows机器上,软件在安装的时候,需要给windows下的几个共享目录写数据,两个不同的应用如果使用了相同的动态链接库,那么A部署在前,用的是版本V1.3,而B部署在后,用过的是版本V1.1,当B部署后,A就会挂掉,因为A无法使用V1.1来正常工作。
解决这个问题的办法其实很容易,就是把应用程序运行在不同的机器上,无论是物理机还是虚拟机,但是很快成本就变成另外一个问题。有了容器技术,这种多版本依赖的问题,可以通过将应用运行在同一台机器上的不同的容器实例中来解决,兼顾了功能和成本。随着容器化部署被大部分企业接受,接下来我们要考虑的问题就变成了如何在多台物理机器上运行容器实例,而Kubernetes的出现就是为了应对企业对于容器编排的迫切需要。有了Kubernetes这样的容器化部署编排平台,企业不需要手动来在每台物理机上运维容器实例级别的应用,只要告诉Kubernetes我们的部署需求:请帮我把应用程序k8ssamples运行起来,并创建3个实例,Kubernetes会基于具体的任务来找到”合适“的机器,并把应用运行起来。另外Kuberntes也会实时的监控应用的健康状态,当有某个实例故障退出后,Kubernetes会负责重启应用,确保总是有3个应用的实例对外提供服务。
虽然说部署技术随着容器化占据主流,发生了天翻复地的变化,而影藏在计算机虚拟世界的恶意攻击者们,缺从未消失。他们躲在角落里,在自己的机器上快速敲着命令,试图窃取用户数据;修改系统的行为来中饱私囊,或者把互联网上用户的机器变成自己用来进行数字货币挖矿的矿机。这一切并不会因为我们使用容器化部署而消失,并且由于容器化部署,企业花费多年构建的安全纵深防御可能被撕出新的口子,我们要面对的安全风险本质上来说是增加了。
容器安全是一个很容器被忽略的话题,笔者在华南的多个项目上的实践经历显示,大部分客户还是以传统数据中心部署的方式来考虑容器化部署的安全体系;其次容器化安全还处于逐步发展的阶段,因为Kubenretes本身从社区的某些极客的”玩意“成长为企业级的部署平台才不过几年,因此安全措施,以及安全漏洞还处于收敛阶段;最后Kubernetes并不太适合直接作为企业级的PAAS平台。PASS平台提供的核心功能是让应用程序更加便利的部署并运行起来,并且可以做到全面的监控。而Kubernetes更多的是提供了一套框架,或者脚手架,如果企业直接操作原生了Kubernetes功能来进行应用的发布和运行管理,那么很容器就会从入门到放弃,原因是Kubernetes太复杂了,不适合业务性企业直接操作原生的功能。那么这和安全有啥关系呢?有,关系大着呢!
由于我们需要在Kubernetes构建另外一套抽象,因此我们应用的安全就和这套构建的抽象平台有关,并且也和底层的Kubernetes有关,当我们引入一个组件(层)的时候,由于整个问题域的变量增加,因此整个安全的函数f(安全)相应的也变复杂了。就如同大数据计算一样,这个概念对于IT从业者来说,几乎是每天必用的科技术语。大数据计算用大白话说就是通大量的数据来求解f(x,y.....)过程,而和数学计算互通的是,这个函数的变量我们只能逼近,并且变量之间可能相互有影响,计算的本质就是通过多组不同的数据,来找到最优解的过程(注意,理论上没有准确的一组解)。
从安全的角度,由于变量增多了,我们的安全防御措施就变得复杂了,而笔者一直强调的一个思维模式就是结构化思考,解决容器化带来的安全挑战,我们也可以使用结构化的安全模型,这就需要我们首先理解在容器化部署的场景下,具体的安全风险模型具体是什么?
有了方向之后,我们先从容器部署场景下应用具体需要面对那些安全风险开始,在了解了这些安全风险后,我们的安全模型自然而然就出来了。安全领域有几个标准的术语来描述应用具体面对的安全风险和应对措施:Risk,Threat以及Mitigation。
Risk指风险,风险是尚未发生的问题,风险一般都有优先级和发生的可能性。做管理的同学对问题和风险应该有非常准确的认知,要不然你写的周报一定会被老板diss,如果你不理解这句话,可以停下来仔细思考一下。
Threat指威胁,威胁发展的结果就是安全风险最终变成了安全问题。
Mitigation也叫风险应对计划。做技术管理的同学应该对自己项目上的主要技术风险有一个充分的认知,但是最最关键的是:要管理起来。怎么管理呢,mitigation计划就派上用场了,我们可以对每个技术风险评估发生的可能性,以及应对计划,应对计划的目的是降低风险的发生概率,以及在风险发生的时候,按照计划来应对,而不是手忙脚乱。笔者在华南的某头部直销项目上做大促总负责的时候,每次大促都会制定详细的应急发难,而应急方案的目的,就是未了解决在这些风险发生的时候,我们具体要干啥,从而避免在压力的情况下做决定。坦白讲,在压力下基本上做不出来正确的决定,即便你是天才也不行,这是概率决定的。因此笔者对力挽狂澜这种浮夸词汇总是嗤之以鼻,这种成语都是后人用来谄媚用的,严重和实际不符。因此作为技术管理人员,笔者建议还是多花时间在技术风险评估和管理上,尽早的做好mitigaton计划,而不要迷行什么力挽狂澜,或者对自己有迷之自信。
这三个概念在安全领域该如何理解呢?举个例子,我们日常生活中,的确存在有人从家里偷走我们的汽车钥匙,然后要汽车开走的风险。小偷可以有几种偷走钥匙的途径:a,打破家里的玻璃,偷走钥匙;b,敲家里的门,然后乘我们不注意潜入家里偷走钥匙;c,捕获汽车的红外信号,然后复制汽车钥匙开走汽车。这三种偷走汽车的方式就叫Threat,而我们migitation就是如何来应对这几种威胁,确保我在广州出差两周后回到家里汽车还停在小区没有被偷走。
对于安全风险来说,每家企业需要的面对的可以说小同大异。银行需要面对的是如何确保账户上的资金不被非法支取;商业组织需要面对的是交易伪造,比如有天猫这样的平台,我们不能说在双十一买了一台苹果笔记本电脑,然后被快递给黑客了。对于政企类的企业来说,比如说航司,所有发布在外的内容,文件,必须放篡改,不能有人上去讲文件的内容进行了修改,然后以航司的名义对外公开,或者发布有损社会稳定的内容。个人敏感数据保护是国家为了保护个人信息作出的重要安排,大数据杀熟,个人数据倒卖已经到了令人发指的程度,因此保护个人信息是互联网企业必须遵守的基本原则。
基于上边的讨论,安全风险对于不同的企业来说有很大的不同,而即便是相同的安全风险,每个企业的优先级也不同,因此对于技术管理人员来说,特别是TM这个角色来说,必须有一套安全风险框架(这也是结构化思考的延伸)。特别是安全这个话题从来都是体系性的,单点安全并没有啥安全性可言,因此我们需要构建一套体系化的安全框架,框架中枚举出不同层级的具体安全风向项目,以及对每个安全风险项目进行优先级管理,为优先级高的安全风险定义具体的,可落地的应对计划和方案。
有了上边的知识储备,读者可能会问,具体如何做呢?对于安全性要求比较高的项目,作为项目的技术管理人员,我们需要有一个安全建模的过程(Threat modeling),这过程的核心就是识别和罗列出来应用程序为了满足安全规范,满足业务需求,可能存在的所有安全威胁。通过这种体系化的安全风险识别和罗列,我们就可以深入的分析系统中的每个组件,以及每个组件可能遭受的安全攻击,最终识别出来我们负责应用的安全威胁模型,这个模型中会准确的体现出系统的安全隐患具体在哪里,这也是我们在项目上后续安全工作的核心。
软件行业没有一招鲜,没有银弹,因此也不存在放之四海皆准的一套安全风险模型,这取决于企业的性质,企业的软硬件环境,我们的应用程序,以及所处的行业。但是在容器化部署的领域,由于这个层面的差异性已经被极大的消除,因此我们的确可以定义出初步的安全模型,来帮助我们尽最大可能的应对安全风险的威胁。
那么如何给容器化部署的应用进行安全建模呢?我们可以站在容器化部署应用参与者的角度来分析,比如:
1,外部的攻击者视图从外部访问部署在集群中容器应序实例
2,内部用户试图非法访问授权外的资源
3,内部的恶意攻击者试图从企业内部来攻击系统,窃取敏感数据
4,内部的用户不小心将敏感数据泄露出去
5,运行在集群中的进程,可能会被攻破作为跳板来窃取敏感数据
而对于系统中的这些参与者(内部用户,外部用户,开发人员,运维人员等),我们应该仔细考虑他们的权限范围:
1,用户持有账号的权限是否合理?比如有应用访问权限的用户是否同时也有访问应用运行宿主机的登陆权限?
2,用户对系统的访问权限是否合理,笔者见过有些项目所有人都有root权限,并且有Kuberntes上的管理员权限,我们需要仔细的通过RBAC来控制不同类型用户的可访问范围。
3,网络和基础设施的访问权限。
基于上边的信息,我们可以看到有很多中方式可以用来攻击部署容器化部署的引用程序。因此我们需要入下图所示的一套容器化部署应用的安全威胁模型,来帮助我们做为技术管理人员应对项目上的大部分安全风险。
接下来我们分别就上边模型中的威胁和风险进行详细的介绍。
- 代码安全。整个安全体系的源头可以说来自于开发人员编写的代码,以及代码外部的三方包依赖。特别是三方包依赖,这是很多安全扫描工具重点关注的部分。因为这些三方包依赖中,包含了上千个已知的安全风险。因此从代码级别,我们需要扫描镜像,以及镜像中运行的代码和代码所有的三方依赖包。并且这种静态的安全扫描不能是一次性,而应该是定期的,被固化到安全流程中的长期安全手段,因为安全风险是逐步被发现的。
- 镜像安全。解决可代码安全之后,代码会被打包到容器进项中,比如使用docker build。而我们在配置容器镜像该如何打包的时候,也会随即引入安全风险,比如容器以root账户在宿主机上运行,容器的权限过高,可以修改操作系统内核的配置等,这些安全配置最终会造成运行的容器实例被用来窃取敏感数据,或者直接让运行在同一台机器上的应用宕机。
- 镜像构建机器安全威胁。如果恶意攻击者攻破了镜像构建机器,那么就可以给镜像中诸如任意的代码,这些恶意的特洛伊代码最终会在生产环境运行起来,用来窃取敏感数据,或者给恶意攻击者打开后门。因此容器安全非常重要的一步就是加固镜像构建机器。
- 镜像仓库安全。镜像被构建好之后,会被推到镜像仓库中,生产环境会使用docker pull这样的客户端命令行工具来在运行镜像之前将数据下载到工作节点的缓存中。这里会有个问题,我们如何保证docker pull是从安全的站点下载镜像数据?镜像数据可能会被更换,可能被置入了木马,因此我们也需要考虑镜像仓库的安全以及镜像数据防篡改。
- 镜像在运行起来后,可能持有自己不需要的额外权限。如果你的YAML文件是从网络上下载下来的, 在执行kubectl -f apply或者docker run之前,请仔细的评估里边包含的内容,因为网络下载的应用可能包含容器实例权限提升的配置,当这样的应用在你的环境运行起来之后,会基于额外获取的权限干非法的事情。
- 容器运行在宿主机上(在Kuberntes上也叫工作节点),我们需要定期对宿主机上运行的组件和依赖进行扫描来确保其不包含安全漏洞。笔者建议大家不要在宿主机上安装太多的无关应用,来缩小宿主机的攻击面。最后,宿主机也需要被正确的配置,需要符合Linux操作系统配置安全最佳实践。
- 应用程序运行的过程中,需要从数据库读取数据或者给数据需写入数据,访问数据需需要密码,而我们在容器化应用程序中,也需要解决如何将密码这样的敏感数据安全的传递个应用程序进程。Kubernetes提供了Secret这样的对象来帮助开发和运维人员管理敏感数据,但是Secret并不安全,详细的信息可以参考笔者的Kubernetes安全系列文章。如何传递数据库密码这个事情在很多项目上都做的不正确,比如笔者最近在项目上,听同事说把密码加密放到配置管理中,然后应用在读取密文,对密文进行解密拿到明文的密码来访问数据库。光看描述貌似很严谨啊,但是你如何回答解密用的秘钥如何保证安全?你会发现这个依然是明文存储,或者说硬编码在代码里。这种掩耳盗铃的安全方式具有很强的迷惑性,是很多安全设计能力不足的架构师很容易犯的一个错误,大家一定要引以为戒,不要到时候线上出故障了,再后悔莫及啊。大家可以参考笔者关于kubernetes多篇安全文章了解什么才是正确的做法。
- 由于分布式系统大多都是分布式部署,这句话怎么看都是废话:),而分布式系统需要通过网络来进行交互,而网络的不可靠和缺乏安全是整个应用安全木桶的短板。由于TCPIP协议路由的不确定性,因此你发给另外一个用户的数据,很有可能会途径不毛之地(不安全的中间节点),然后被窃取。
- 容器逃逸风险说新也不新,但是绝对是分分钟钟会让我们下跪的主。当前被广泛应用的容器运行时技术主要包含了containerd和CRI-O框架,虽说这两个运行时已经被大量的生产部署案例验证过,但是它们两个并不是绝对安全,存在运行在其中的恶意代码逃逸出来,访问宿主机或者其他容器中的数据。2019年就揭露过一个叫Runcescape的漏洞,基于runc的容器运行时都可能被恶意代码用来进行逃逸攻击。
除了上边这些和容器相关的风险之外,由于安全是个非常宽泛的话题,读者还需要考虑如下列举的安全风险,来整体上确保容器化部署的纵深防范:
- 源代码保存在代码仓库中,代码仓库需要确保不受攻击,并且要进行适当的权限管理。比如笔者在华南的某头部直销项目上,配置和代码分开存储,并且生产环境的配置信息严格管理,只有少数几个具有涉密权限的人员才能访问。特别是代码中如果有解密秘钥这样的信息,你肯定不希望这样的信息被所有人访问到,要不然这个就没有任何意义了。
- 容器运行之上的宿主机通常通过网络连接形成集群,这些机器运行在相同的VPC中,因此我们也需要保护这些宿主机的安全,不受到各种潜在攻击的破坏。传统的手段比如安全配置,防火墙,以及认证和授权在容器化部署场景下依然有效。
- 容器化部署通常需要容器编排平台,特别是企业级部署,而Kubernetes已经是这个领域的事实标准了,但是我们依然可以选择Docker Swarm和Hashicorp Nomad。如果容器编排平台有安全漏洞,或者配置的不正确,那么也会造成应用的数据被窃取。
注:笔者这里讨论的主要还是从容器container角度的安全,关于kubernetes的安全,请参考笔者的系列文章。另外CNCF也发布了Kubenrertes风险模型,大家可以从CNCF网站上参考。
好了,我们关于容器安全模型(上)这篇文章就这么多内容了,希望大家对容器化应用安全这个话题有全面的认知,并且能够将笔者梳理的安全风险项运用到自己实际的项目中。我们下篇文章来讨论容器化部署中,涉及到的多个组件的安全边界,以及通用的安全原则,这样我们有模型,有边界,也有原则,整个结构化的安全体系就建立起来了,敬请期待!