云计算时代操作系统Kubernetes之安全(三)

在Kubernetes平台上,通过Secret来管理敏感数据只是让我们系统安全的必要条件而已,接下来我们先来分析一下,为啥Secret就不安全了?

笔者在安全(二)这篇文章中给大家介绍了两种方法把Secret中的敏感数据传递给容器中运行的应用程序,读者可能会问,到底哪种方式更加安全?你可能会很主观的认为通过文件关在的方式会比环境变量安全,因为如果攻击者进入到POD中,直接通过运行env环境变量命令就可以获取敏感隐私数据,并且你可能会主观的认为,这要比在浩如烟海的容器文件系统中找敏感数据更加容易。

我们先不下结论,接下来我们分析一下,看看这两种方式提供的安全保障等级是否有差异。假设恶意攻击者通过某种方式已经远程登陆到POD中,并且他可以运行export命令能够列出所有的环境变量,通过这种方式,攻击者就可以很冗余获取到我们的隐私信息yunpan.name。

因此环境变量这种方式非常不安全,接着我们来分析一下挂载文件系统这种方式,由于用户可以将包含敏感数据的文件系统挂载到整个文件树的任意位置,因此比起环境变量这种方式,挂载文件会更加安全一些,因为攻击者需要首先知道敏感信息在哪里,才能窃取。不幸的是,对Linux的shell稍微有了解的攻击者,可以通过命令mount | grep tmpfs来罗列出来所有挂载点,然后很容器就收敛我们的隐私信息上。

基于上边的讨论,这两个给应用程序传递敏感配置信息的方法其实不分伯仲,都有缺陷。使用文件挂载略强的点在于,有些应用在启动的时候,会把所有的环境变量打印到启动日志中,这些信息如果被进一步统一收集到日志系统,那么这些信息就会被所有有权限访问日志的同学看到,造成信息泄露。这么说难道我们就一点办法没有了?其实安全原则中的最小权限和核心就是减小攻击面,因此我们可以禁止用户访问我们的POD,Node以及基础设置,只开放给有权限的角色和用户,以及彻底从把执行kubectl exec的权限给disable掉,来减小可能的攻击面。

我们在安全(二)中介绍Kubernetes体系架构的时候,介绍过Kubernetes中所有的对象和对象实例都存储在键值对数据库etcd中,这当然也包括Secrets对象和ConfigMap对象。我们创建一个secret对象的具体处理流程如下图所示:

《图1.1 secret对象的创建流程》

如上图所示,用户首先创建secret对象的yaml文件,然后通过kubectl apply发送对象创建请求到API Server,接下来API Server会负责处理客户端发送过来的数据,然后将secret对象信息保存到etcd中,具体来说保存使用的key是/registry/secrets//

由于etcd只是一个键值数据库,因此我们必须限制只有admin权限的用户才能访问,要不然任何人就可以直接从etcd查询secret的数据了;其次etcd的数据会持久化到磁盘上,并且数据一般情况下并未加密存储,任何有磁盘访问权限的用户理论上也可以访问到etcd中保存的secret敏感数据;再次,为了数据可靠新,磁盘会进行定期的备份,由于数据没有加密,因此备份数据中的敏感数据也是明文存储,有备份数据访问权限的用户,同样可以很容易访问到敏感数据;最后,etcd和API Server之间需要通过网络进行数据传输,因此这条链路上必须使用SSL/TLS来进行加密,确保data in transit安全。

注:笔者必须再次强调一次,base64不是加密方法,只是一种数据编码方法,因此在Kubernetes集群上,如果我们使用Secret来管理敏感数据,那么数据在etcd和内存中基本可以认为是明文存储的,因此在这种场景下,提升安全性的第一件事情就是加密数据,然后存储到etcd中,接下来笔者会在后续内容详细介绍。

由于恶意攻击者获取到了POD的访问权限后,就可以很容易窃取不管是以环境变量注入还是文件挂载方式配置的敏感数据,因此我们需要考虑还有哪些方式可以让攻击者获取到远程登陆权限。笔者在介绍容器环境应用调试的时候,介绍过通过运行一个和目标POD共享相同命名空间的方法,来远程登陆到POD中,因此我们也需要考虑如何在生产环境禁止所有用户创建POD的权限,只开放给运维团队。

另外,secret一般都是通过YAML文件来创建,YAML文件中的敏感数据顶多是base64编码的格式,因此有YAML文件访问的用户都可以获取到敏感数据,比如将YAML文件share给其他人,将YAML文件提交到所有人都有读权限的代码仓库,YAML文件备份到其他不安全的介质上等,都可能造成敏感数据泄露。

最后,Kubernetes的最新版本中,任何有工作节点上root权限的用户,是可以模拟kubelet访问API Server中的所有对象信息(原因是kublet其实可以看成是Master node运行在工作节点上的代理,必须有所有的权限才能完成管理工作),因此secret信息也可以被有任意工作节点(node)root权限的用户访问到。

到这里为止,我们其实已经分析出来了很多安全风险,笔者的系列文章的目的就是把如何处理这些安全问题的方案说清楚。中国有句古话,千里之行,始于足下,我们先从如何如何在Kubernetes上管理敏感数据开始,以及有哪些最佳实践。

我们还是回到本质的问题,当敏感数据被保存到Kubernetes的secret对象后,数据会默认以base64编码的形式保存到etcd,那么我们如何确保数据的secure at rest特性?敏感数据怎么管理?如何敏感数据不小心被删除了,怎么办?这些问题就是我们接下来要讨论的核心,如何确保敏感数据的存储安全。

企业引入Kubernetes这样的企业级容器编排和管理平台带来的最大好处是,用户可以按需来构建和部署自己的应用程序,通过简单的几次鼠标点击,或者执行几个简单的命令,整个系统就运行起来了。特别是刚刚将系统从传统的数据中心迁移上来的企业,这种革命性的运维让叫苦连天的运维团队如沐春风,特别是那些组件之间有较复杂关系的系统,Kubernetes带来的震感,是溢于言表的。

开发和运维人员尝到甜头后,会迫不及待的讲Kubernetes提供的能力展示给老板,同事以及朋友,以期得到大家的肯定。但是不幸的是,老板可能会问,你如何来横向复制(多环境部署)?这个时候我们意识到这些在自己机器上的YAML文件,连同包含敏感数据的secret对象定义文件,必须持久化保存到某个地方,以期在测试环境,生产环境来部署应用程序。

关注笔者Kubernets系列文章的读者应该还记得我们曾经介绍过两种在Kubernetes上创建对象的方法:1,直接使用命令行来创建资源,比如通过kubectl命令和特定的参数;2,通过YAML文件。其中第一种方法我们一般称作是命令式对象创建,比如我们通过kubectl create secret来创建secret对象,背后的逻辑是kubectl帮我们做了转换,将参数转换成具体的API Server调用来创建secret对象。

虽然说通过kubectl create这种方式简单直接,但是这种方式不具备长期的运维性,并且这个命令不是幂等的,当我们在集群上第二次运行这个命令的时候,Kubernetes集群会报错。我们来验证一下,在自己的集群上执行命令:kubectl create secret generic demo-secret -n default --from-literal=sec.message=qigaopan,这句命令会在集群的默认命名空间创建一个名叫demo-secret的对象,当第一次创建成功,运行第二次的时候,集群就会保存,如下是笔者的环境错误输出信息:

➜  Kubernetes安全 kubectl create secret generic demo-secret -n default --from-literal=sec.message=qigaopan

secret/demo-secret created

➜  Kubernetes安全 kubectl create secret generic demo-secret -n default --from-literal=sec.message=qigaopan

error: failed to create secret secrets "demo-secret" already exists

为了解决幂等性和运维性问题,Kubernetes提供了基于YAML文件的这种声明式的方式,我们可以通过熟悉的kubectl apply命令来将对象的定义apply到集群上。由于具备系统的长期的运维性,这种声明式的的资源管理方式是Kubernetes推荐的方法。如下图所示:

《图1.2 IOC基础设施代码化概念》

另外这种声明式的系统部署配置方式也是Iac(Infrastructure as code)概念的基石。和命令式的对象创建方式相比,声明式的对象管理方式具有如下的好处:

- 减少错误,即便是每天都用的命令,也有输错的时候

- 可重复性

- 审计和变更追踪

由于YAML文件和代码文件没有区别,因此我们很容易通过源代码管理工具来对YAML文件进行版本追踪和变更审计,比如使用GIT仓库。当YAML文件被加入到配置仓库之后,我们就很容易能够对谁,什么时候做了什么变更有全面的的掌握。考虑集群出现故障的时候,我们根本就不需要绞尽脑汁的分析集群奔溃之前的状态到底是什么,因为最后一次的状态变更都已经被版本管理工具记录了,我们只需要将仓库中的版本重新在集群上appy一遍,系统就恢复了,极大的缩短了系统的回复时间,并且也降低了复杂度,工作量也减少了。

当我们决定从命令式的对象管理模式转向声明式的对象管理模式,遇到的第一个问题是,声明对象的YAML文件从哪里来?通常情况下我们有两种选择:

1,基于kubectl命令的输出

2,从运行中的Kubernetes集群中请求获取

Kubernetes的客户端工具kubectl提供了特定的选项来输出YAML文件,包括:--dry-run选项来输出资源apply到Kubernetes之后的YAML文件;-o选项来输出比如说YAML格式的对象文本表达。

有了这两个选项的帮助,我们在不实际将对象发送到生产环境的情况下,可以输出对应的YAML文件,比如运行:kubectl create secret generic demo-secret -n default --from-literal=greeting.me --dry-run来输出secret对象的YAML文件。

我们也可以从运行的集群中获取对象的YAML文件,这个读者应该应很熟悉了,我们使用kubectl get secrets demo-secret -o yaml就可以获取上文创建的demo-secret的YAML格式的数据。笔者的机器上的输入出下图所示:

《图1.3 从运行集群上获取secret对象的YAML文件》

如上图所示,我们可以很方便的从集群中通过get命令来获取对象的YAML文件信息。最后,我们也可以通过命令:kubectl get secrets demo-secret -o yaml > demo-secret.yaml来讲从Kubernetes集群获取的数据定向到一个文件中。如下图是笔者机器上输出文件打开后的样子,和自己编写没有任何区别:

《图1.4 将get命令返回的YAML数据写入文件》

通过上边介绍的这些方式,我们的系统部署对象成功转换成声明式资源管理方式,并且这些YAML文件可以像源代码一样被管理。因此如何保护这些YAML文件和文件中的数据就变成直观重要了。由于YAML文件中的数据只是base64编码而已,因此我们需要其他工具的协助,来对敏感数据进行加密,这是我们下篇文章的主题。

好了,今天这篇文章就到这里,下一篇我们来看看在Kubernetes集群中,如何对敏感数据进行加密,敬请期待!

你可能感兴趣的:(云计算时代操作系统Kubernetes之安全(三))