多年以前美国有一档非常流行的电视真人秀,名叫 ”i've got a secret“,节目的内容从我们所处的时代看起来很俗套,节目的参与者面对一组名人组成的评委团队,试图通过各种手段来影藏自己人生中非常重要的秘密,不让评委团发现。对于软件系统,特别是大规模分布式的复杂系统,由于组成系统的组件众多,就如同”我有一个秘密“节目的参与者,要使劲各种办法,才可能降低敏感信息不被未授权的用户的访问。
计算机已经从”堆满一屋子“的科研工具已经走入寻常百姓家,但是对于软件来说,总需要各种各样的配置数据才能正常工作,比如数据库连接,用户名和密码等等。配置的形式多种多样,比如我们开发SpringBoot应用程序的时候,需要使用properties或者yml文件来给应用提供配置信息,配置信息中的有些字段对所有人都可见,比如应用程序的名称等;但是有些敏感字段不应该暴露给所有人,比如密码,私钥等。
Kuberntes平台已经逐步坐稳了企业级容器化部署,管理和编排的头把交易,提供高度的灵活性,同时意味着需要相对复杂的配置驱动才能按预期工作。和应用程序类似,有些配置信息purely提供给应用程序正常运行,或者让基础设施正常工作,而有些是高度敏感的,如果泄露,可能会造成业务数据被窃取,严重的恶化可能会让整个集群挂掉,比如笔者在前边文章中提到的hostPath数据卷类型,使用恰当,可以提升应用部署的灵活性,但是使用不恰当,恶意的攻击者会看到宿主机(工作节点的整个文件系统),并且是以root的权限ssh上去,因此让这台机器挂掉就是一句命令的事情。
随着Kubernetes版本的升级,功能逐渐丰富起来,特别是配置管理部分,我们可以通过容器镜像,YAML文件,configMap,Secret,downward API以及projected volume等机制来给应用程序提供需要的配置信息。虽然说手段很多,如果从核心功能的角度看,可以分为两类:Secret和configMap,两个对象都用来给应用提供键值对格式的配置信息,Secret主要用来管理对安全隐私要求比较高的”敏感数据“。
由于安全的重要性,Secret只是整个Kubernetes平台以及周边生态提供的安全体系的冰山一角,这里边的原因也不难理解,Kubernetes原生的Secret机制并不足以支持应用程序复杂多变的安全需求,如我们前边的文章所属,在Secret中,数据并不是加密存储,而是base64编码的形式,不幸的的是,base64并不是加密格式,没有任何安全性可言,只是一种掩耳盗铃的方式,任何会上网的同学,3-5分钟就能把base64编码的数据还原成明文。
因此围绕Kubernetes构建的安全工具越来越多,这也符合Kubernetes把自己定位成整个容器化部署,管理和编排框架这个大目标,具体的实现留给上下游的厂商,蛋糕大了,才有的分嘛,像Docker那种搞法,迟早会被历史审判。那么这些围绕Kubernetes构建的安全工具到底凭什么能够站稳脚跟?基于笔者的经验,主要是因为全面,稳定,易用以及更加容器和目标系统集成。其实这几个维度也是我们在设计技术方案是需要考虑的要点,特别是安全相关的部分,因为没有100%完美的解决方案。这也印证了Don Box多年前在一次技术大会上说的一句话,its all about perspective,某个特定的技术是确定的,只是每个人理解的角度不一致。
而对于安全来说,不同的架构师和开发人员,对安全的方案和评估是不一样的,对一个项目适用的安全规范,不见得符合另外一个项目。笔者在实际的大型项目上经常会被问到,请把你的安全标准给我看看?面对这样的问题,我们必须深入了解客户的动机具体是什么,因为大部分国企都有自己严格的安全规范标准,有些甚至有国家强制执行的合规标准,因此在回答这个问题之前,我们必须充分了解这个问题的上下文,不要随便信口开河。
在架构师这个圈子里,很流行一句话,没有什么一招鲜的技术方案,这句话同样适用于安全。关心安全的同学一定听过过OWASP这个网站(https://owasp.org/),你会发现每天都会有新的安全问题被爆出,因此我们的安全方案或者规范也必须是迭代式的,逐步更新演进的。随着容器化部署成为主流,企业选择容器化部署又多了一项理由:容器化平台提供的安全红利。容器化部署彻底改变了应用程序打包,部署和运维的机制,让企业可以将自己的安全规范和策略完整的落地实现,并能持续的迭代和更新。比如在容器化部署的平台上,我们可以很容易实现:
- Least privilege原则,也叫最小权限原则,访问者只会被赋予访问所需资源的最小权限集合,多一点都不给,减少攻击面,让受保护资源做到可控访问。
- Role base control access(RBAC)允许受保护资源只允许任何和授权的用户访问
- 等等
在系统中落地这些安全规范其实很容易,但是由于变化无处不在,因此我们需要定期对这些安全规范进行review,确保新的安全风险被合理的处理,已经离开公司的用户的权限被收回等等,这种持续优化的思路,是整个安全体系中最核心的保障手段。
回到Kubernetes平台,基于Secret对象机制构建一套适当的安全保护体系远比通过kubectl apply部署POD和Service复杂的多,随着服务网格等扩展能力的发展,我们越来越发现Kubernetes的原生安全机制的局限性。笔者认为理解Kubenretes提供的扩展性的核心,就是理解CDR机制。通过扩展API对象来提供功能更加强大的自定义安全对象。
当然活跃的Kubernetes社区和整个编排系统的上下游厂商也没有闲着,越来越多的安全方案被设计和开发出来,逐步的形成了一套完整,可靠和易用的容器化编排系统安全体系,这是我们稍后介绍的重点。
软件架构设计是一项平衡的艺术,对于安全方案的设计来说,也是如此。我们必须时刻关注安全风险,但是也需要考虑和成本之间的平衡。比如我们要开发一套监控设备当前温度的应用程序,应用的基本逻辑是,当温度高于5摄氏度就发送告警给相关的团队,那么问题是这个5摄氏度阀值的配置,是否属于敏感数据?
这个问题没有标准答案,我们可以对比一下如果把5摄氏度当做敏感数据来保护需要花费的时间,系统资源,管理费用,和产生的价值作比较,来决定是否应该保护5摄氏度阀值数据。笔者想要强调的时候,在很多项目上,我们的设计就是不管三七二十一,只要是配置项,都当做敏感数据来处理,资源消耗巨大,管理流程复杂,开发人员叫苦连连,最后回退到都不保护的状态,印证了中国那句古话:过犹不及。
对于架构师来说,正确的做法应该是和整个团队一起(包括业务,运营,管理等),充分征求大家的意见,决定哪些数据是敏感数据,需要加密存储保护;哪些可以作为明文存储,并且逐步建立起来确定敏感数据的流程或者工作机制,来降低漏判的风险,以及提供明确的工作机制和职责分工,减轻安全带给团队的额外负担。
坦白讲,并不是说有了Kubernetes之后,数据安全才突然变得重要和具有挑战起来,其实这个问题一直伴随着软件开发领域。在Kubernetes社区和上下游厂商的共同努力下,逐步形成了如下所示的一套标准安全操作流程:
当然上图是从几千米高空来看Kubernetes的安全体系,很多细节都被忽略了,但是对于架构师来说,魔鬼都在细节里啊。咱就先来个庖丁解牛,把这个大图拆开看看。首先我们在执行任何实施工作之前,需要确定具体有哪些类型的配置项,以及识别哪些属于需要特殊的安全保护措施,如下图所示:
如上图所示,很多时候我们判断敏感数据非常简单直接,比如数据库访问密码,有些场景下就没有那么容易了。比如说我们微服务架构的应用程序需要访问数据库来保存订单数据,那么数据库实例的hostname是否属于敏感数据?这取决于具体的场景,在我们需要混淆数据库的具体物理位置来抵御可用性攻击的场景下,hostname属于敏感数据;而如果是开发环境,被多个团队共享使用,那么hostname就不属于敏感数据。
确定了敏感数据项之后,接下来我们需要确定这些敏感数据项以什么机制来持久化存储,在作出决定之前,有几个维度需要读者注意:
- 企业当前可用的安全数据管理方案有哪些?
- 安全数据在哪些业务场景中会被使用?
- 应用程序开发的技术栈是什么,有哪些外部配置集成的机制可用?
上边三个问题以及答案如下图所示:
如笔者在前边多篇文章中的介绍,Secret是所有类型Kubernests集群(如阿里云的ACK, minikube等)中都提供的安全信息保存和管理的方案,但是我们还有哪些选项?如果你使用的是阿里云的ACK,阿里云提供了KMS组件,我们可以使用KMS来管理敏感数据,这可以说是最佳的解决方案,考虑到KMS和ACK的集成度和稳定性因素。
另外你也可以自建Hashicorp的Vault,提供了和阿里云KMS基本类似的功能组件,在开发和测试阶段,或者企业对此类安全组件的验证阶段可以使用。
确定了敏感数据持久化的工具之后,接下来就是具体的”labor work“了,将敏感数据保存到如KMS这样的持久化工具中。大部分的此类KMS工具都提供了Restful风格的API,有些也提供了客户端命令行工具,这些选项可以支持我们在开发和生产部署阶段的所有需求,比如通过CICD来和API交互,把敏感的配置信息保存到KMS中。如下图所示:
最后,我们的应用程序需要在启动或者运行的过程中,能够访问到这些敏感数据,幸运的是,Kubernetes已经帮我们完成了大部分工作,从KMS访问敏感数据可以分为两步:
- 第一步:将读取到的值翻译成明文
- 第二步:将明文信息暴露给运行在容器中的进程使用(如果笔者还记得如何将配置信息注入到容器的环境变量中,或者以文件挂载到容器的文件系统中)
具体来说,第一步把密文转换成明文的过程一般由KMS这样的工具提供的客户端代码库来实现,如果你还记得我们安全概述中关于对称加密部分的内容,其实加密和解密的过程是相同的(使用相同的私钥),只是顺序不同而已。
有了明文的数据之后,接下里的步骤就很有意思了,我们在前边的文章中花费了大量的篇章进行过详细的介绍,要不你把这些信息作为环境变量注入到容器中,要么作为文件挂载到容器的文件系统对应目录上。当然除了两种方案之外,随着安全工具的不断发展,我们还可以使用像边车容器这样的方案,来把安全处理和业务逻辑分开,进一步提升安全的可运维性。
无论采用什么方案,架构师的部署方案中,需要评估方案,并且和需求进行匹配,以及评估落地实施的可行性,运维和监控的手段等,总之一句,安全是个体系的概念,不存在一个方案可以解决所有问题的场景。
今天这篇文章是安全系列的第一篇文章,因此笔者不打算深入展开前边提到的所有技术方案和工具,但是我希望大家能关注安全,希望大家能够在Kubernetes平台上,通过笔者的系列文章,对Kubernetes自身的安全机制,周边的工具有深入的理解,这样我们才能在帮客户做方案的时候,提前规避风险,做到云原生架构师的这种”能把事情讲清楚”的能力。下篇文章我们从安全的角度,来”重新“介绍一下Kubernetes提供的机制,敬请期待!