Kubernetes是一个强大的容器调度系统,通常我们会使用一些声明式的定义来在Kubernetes中部署业务。但是当我们开始部署比较复杂的多层架构时,事情往往就会没有那么简单,在这种情况下,我们需要编写和维护多个YAML文件,同时在编写时需要理清各种对象和层级关系。这是一个比较麻烦的事情,所以这个时候Helm出现了。

我们熟悉的Python通过pip来管理包,Node.js使用npm管理包。那么在Kubernetes,我们可以使用Helm来管理。它降低了使用Kubernetes的门槛,对于开发者可以很方便的使用Helm打包,管理依赖关系,使用者可以在自己的Kubernetes通过Helm来一键部署所需的应用。
对于Helm本身可以研究的安全风险可以从很多角度来看比如Charts,Image等,详细的内容可以来看CNCF webinars关于Helm Security的一个分享(https://www.cncf.io/webinars/helm-security-a-look-below-deck/
本篇文章主要讨论的是Helm2的安全风险,因为在Helm2开发的时候,Kubernetes的RBAC体系还没有建立完成,Kubernetes社区直到2017年10月份v1.8版本才默认采用了RBAC权限体系,所以Helm2的架构设计是存在一定安全风险的。Helm3是在Helm2之上的一次大更改,于2019年11月份正式推出,同时Helm2开始退出历史舞台,到2020年的11月开始停止安全更新。但是目前网络上主流依然为关于Helm2的安装配置文章,所以我们这里将对使用Helm2可能造成的安全风险进行讨论。

Helm2架构

Helm2架构如下,是常见的CS架构,主要由两部分组成分别为Client和Tiller。

Helm Client主要负责跟用户进行交互,通过命令行就可以完成Chart的安装、升级、删除等操作。在收到前端的命令后就可以传输给后端的Tiller使之与集群通信。
其中Tiller是Helm的服务端主要用来接收Helm Client的请求,它们的请求是通过gRPC来传输。实际上它的主要作用就是在Helm2和Kubernetes集群中起到了一个中间人的转发作用,Tiller可以完成部署Chart,管理Release以及在Kubernetes中创建应用。
官方在更新到Helm3中这样说过

从kubernetes 1.6开始默认开启RBAC。这是Kubernetes安全性/企业可用的一个重要特性。但是在RBAC开启的情况下管理及配置Tiller变的非常复杂。为了简化Helm的尝试成本我们给出了一个不需要关注安全规则的默认配置。但是,这会导致一些用户意外获得了他们并不需要的权限。并且,管理员/SRE需要学习很多额外的知识才能将Tiller部署的到关注安全的生产环境的多租户K8S集群中并使其正常工作。

所以通过我们了解在Helm2这种架构设计下Tiller组件通常会被配置为非常高的权限,也因此会造成安全风险。

  1. 对外暴露端口
  2. 拥有和Kubernetes通信时的高权限(可以进行创建,修改和删除等操作)

因此对于目前将使用Helm的人员请安装Helm3,对于Helm2的使用者请尽快升级到Helm3。针对Helm3,最大的变化就是移除掉Tiller,由此大大简化了Helm的安全模型实现方式。Helm3现在可以支持所有的kubernetes认证及鉴权等全部安全特性。Helm和本地的kubeconfig flie中的配置使用一致的权限。管理员可以按照自己认为合适的粒度来管理用户权限。

安全实践

配置Helm2

下载并安装Helm2,我们用网上公开的常见的方法为Tiller创建一个具有完全集群管理权限的service account。

将其创建并初始化Helm

可以看到Tiller已经被部署到kube-system的命名空间下面。

创建应用

我们通过Helm来部署一个Tomcat


查看部署情况

模拟入侵

接下来我们针对集群内的攻击,模拟该Tomcat服务被入侵,攻击者拿到了容器的控制权。
通过

的方式我们进入容器内。
通常在Kubernetes的环境下的渗透,我们首先会看其中的环境变量,来获取集群的相关信息,服务位置以及一些敏感的配置文件。

在了解了Kubernetes API等信息位置后我们就可以通过curl等方式去尝试请求来看起是否配置授权。

尝试无果,我们接下来回归正题开始使用Helm-Tiller完成攻击。

Helm-Tiller-Pwn

首先通过DNS来查看是否存在Tiller服务,为了交互Tiller的端口会被开放到集群内,根据命名规则我们可以尝试默认Tiller的名称。

可以看到端口是开放的,不过因为连接是通过gRPC的方式交互,所以这里我们使用HTTP无法连接。那么在这种情况下即意味着我们可以连接Tiller,同时通过它可以在没有身份验证的情况下执行Kubernetes内操作。

我们可以通过gRPC的方式用Protobuf格式来与其交互,但是过于麻烦,这里最简单的方式就是我们通过用Client直接与Tiller连接。
所以我们下载客户端

tmp目录下尝试请求tiller

连接成功。
这意味着我们接下来可以做很多事情。
比如窃取高权限用户的token,因为我们可以控制tiller也就意味着我们可以使用其权限的账户创建pod,所以我们可以获取创建pod的/var/run/secrets/kubernetes.io/serviceaccount/token。这个token会在对应pod创建时被挂载,因此我们能够获取到它,之后便可以用它来完成和Kubernetes的交互。
当然这比较麻烦,事实上我们可以用更加简单粗暴的方式来提升权限,从而直接获取到整个集群的权限。
首先我们先下载一个kubectl方便后续的交互。

可以先看一下目前的权限能否读取secrets

能够看到目前权限是不够的。
那么现在我们需要获取到整个集群的权限,换而言之现在我们希望拥有一个可以访问所有namespace的ServiceAccount,在这里我们可以把default这个service account(当前账户)赋予ClusterRole RBAC中的全部权限。这个时候我们就需要用到ClusterRole和ClusterRoleBinding这两种对象了。

  • ClusterRole
    ClusterRole对象可以授予整个集群范围内资源访问权限, 也可以对以下几种资源的授予访问权限:
    集群范围资源(例如节点,即node)
    非资源类型endpoint(例如”/healthz”)
    跨所有namespaces的范围资源(例如pod,需要运行命令kubectl get pods –all-namespaces来查询集群中所有的pod)
  • ClusterRoleBinding
    ClusterRoleBinding在整个集群级别和所有namespaces将特定的subject与ClusterRole绑定,授予权限。

ClusterRole

ClusterRoleBinding

我们将其生成为对应的Chart并下载到被攻击的容器中,之后使用Client进行安装,配置之后便可进行高权限操作。


可以看到我们提升权限成功,现在已经能够获取到kube-system下的secrets,同时可以进行其他操作。
提升权限的Chart详情可见 https://github.com/Ruil1n/helm-tiller-pwn

总结

Helm简化了Kubernetes应用的部署和管理,越来越多的人在生产环境中使用它来部署和管理应用。Helm3是在Helm2之上的一次大更改,主要就是移除了Tiller。由于Helm2基本是去年(2020)年末才完全停止支持,目前仍有大量开发者在使用,所以依旧存在大量安全风险。本文主要从集群内攻击的角度来展示了使用Tiller获取Kubernetes的高权限并且完成敏感操作。
最后我们来说一下如何防御,如果你坚持希望使用Tiller,那么请一定要注意不要对外开放端口,同时配置TLS认证以及严格的RBAC认证(https://github.com/michelleN/helm-tiller-rbac)。这里更建议大家尽快升级Helm2到Helm3以及直接使用Helm3。