声明,本篇文章的内容主要来自Kubernetes修炼手册 @Nigel Poulton。
本文采用STRIDE模型来对K8s进行安全分析,该模型定义了6种潜在威胁:
- 伪装
- 篡改
- 抵赖
- 信息泄露
- 拒绝服务
- 提升权限
下文将对这些威胁进行详细分析。
在信安领域,伪装指攻击者为了获得更多的系统权限而冒充另一个人或主体。
K8s是一个由多个组件构成的系统,它们包括:
- API Server(Pod)
- Controller manager(Pod)
- Scheduler(Pod)
- Store(etcd)
- kube-proxy(Pod)
- kubelet(journald)
以上这些组件基本上都是与API Server进行通信的,K8s在组件通信之间采用了Mutual TLS(mTLS)认证。 这要求通信双方都要提供自己的证书给对方进行身份验证,这种方式区别于传统的单向证书认证(仅限于客户端验证服务端)。
K8s内部通过自旋证书来简化了mTLS的实现。简单来说,K8s部署时自动生成了一个自签名的CA,这个CA将用来为集群内的所有组件颁发证书。 所以mTLS的安全性依赖于CA的可靠性,CA私钥的泄露将导致整个集群组件之间的通信彻底陷入危险。 除此之外,还需要注意:
- CA证书仅在集群内使用
- 使用CA批准证书签名请求(CSR)时保持严谨
- 确保CA不会被系统外的任何组件设置为可信CA
此外,API Server还可能与集群外的组件进行通信(比如Webhook)。这时候推荐使用两套不同的可信秘钥——分别用于认证内部组件和外部组件。 要实现这一点,K8s需要将一个集群外的CA添加为可信CA,然后对于集群外的组件,应当使用外部CA颁发的证书来与API Server进行通信。
Pod间通信的认证方式可以通过Service Account(SA)来实现。每个Pod启动时默认都会自动分配一个ServiceAccount作为Secret挂载到Pod中。 并且该SA是被允许访问API Server的,只是权限受限,但我们要知道,大多数Pod并不需要访问API Server。
所以,对于不需要与API Server通信的Pod,我们建议将Pod清单中的automountServiceAccountToken
属性设置为false
。
如果需要挂载SA,那么有些非默认的配置需要了解一下:
- expireSeconds:设置token有效期
- audience:限制token的受众
这些属性的具体示例,你可以在官方找到。
篡改通常基于以下目的:
- 拒绝服务:篡改资源使其不可用
- 提高权限:篡改资源获取额外权限
K8s系统内可以篡改的资源有以下几种:
- etcd
- 配置文件:API Server、ControllerManager、Scheduler、Kube-proxy和Kubelet
- 容器运行时的二进制
- 容器镜像
- K8s的二进制
篡改行为通常发生在(网络)传输和保存过程中。TLS可以确保数据的完整性,下面的建议有助于防范对保存在集群中数据的篡改攻击:
- 严格限制对运行由K8s组件的服务器的访问,尤其是部署了控制层组件的节点
- 严格限制对保存有K8s配置文件的库的访问
- 仅在最初部署K8s时进行ssh访问节点
- 对下载的二进制文件进行哈希验证
- 严格限制镜像仓库及相关库的访问
此外,建议在生产环境中对关键组件的二进制文件的审计和监控,相关的工具有auditctl
等。
推荐将Pod中容器的文件系统设置为只读是一种推荐的办法。我们可以在Pod的清单中设置securityContext.readOnlyRootFilesystem
属性为true
,
也可以通过部署PodSecurityPolicy对象来全局性的限制所有Pod的文件系统的读写权限。
PodSecurityPolicy特性从v1.25开始被删除,转而使用Pod 安全性标准进行代替。
抵赖就是制造对某件事的不确定性。不可抵赖就是提供证据(以证实某事),具体来说应该能够证明以下信息:
- 发生了什么
- 什么时间发生的
- 谁操作的
- 在哪里发生的
- 为什么发生的
- 如何发生的
对于后两个信息,通常需要一段时间内的多个相关事件的信息。
K8s提供针对API Server的审计(Audit)功能来完成回答以上问题,该功能需要手动开启,请参考官方文档审计部分。
然而,除了API Server,K8s还提供了对其他组件的审计,比如容器运行时、Kubelet等各应用的审计日志。如果要收集多个组件的日志, 那么就需要一个中心化的日志后端来实现对事件的保存和分析。一种常见的做法是在每个节点上部署DaemonSet类型的日志代理来收集日志, 然后发送至中心日志数据库,同时要确保这个中心化的日志库是安全的。
主要是指敏感数据的泄露。
K8s中的所有集群配置都是保存在集群存储中的(目前是etcd),包含网络和存储配置以及Secret形式保存的密码登敏感数据。 这就使得集群存储会成为被攻击的首要目标。
我们必须对运行由集群存储的节点进行访问限制和审计。
前面提到,K8s提供了Secret对象来保存密码等敏感数据。但请注意,Secret是以未加密的形式存储在集群存储中的, 我们可以为其使用静态加密,请参考官方文档Secrets良好实践部分。
这种方式的攻击目的在于使服务不可用。在K8s中,拒绝服务的最大可能攻击对象就是API Server。
首先,我们应该对主节点进行高可用(HA)部署。
进一步,我们还可以考虑将主节点部署在多个可用域中, 这样可以避免某个可用域的网络遭受故障导致整个集群不可用。这个防范原则也适用于工作节点。
此外,我们还应当为以下资源配置限额(参考ResourceQuota
对象):
- 内存
- CPU
- 存储
- K8s对象(ReplicaSet、Pod、Service、Configmap、Secret等)
添加配额限制有助于避免重要系统资源被消耗殆尽,从而提高抵御DoS的能力。
参考以下方法:
- 高可用部署主节点
- 对到达API Server的请求进行合理监控和预警
- 不将API Server暴露在互联网上(借助防火墙/安全组规则)
即使K8s拥有良好的鉴权机制,也需要注意妥善保管具有较高权限的账号。
集群存储(etcd)的重要性已经不需要过多强调,参考一下方法加固etcd:
- 将etcd部署为3个或5个节点的集群
- 对etcd收到的请求进行监控和预警
- 在网络层面对etcd进行隔离,只允许控制平面组件与其交互
默认情况下,K8s会将etcd安装到与控制平面组件相同的节点上,对于非生产环境这是OK的。但是生产环境就不太合适了, 应该考虑为K8s单独部署一个etcd集群,有助于提高系统的性能和弹性。
在性能方面,etcd可能是大型K8s集群的瓶颈所在。所以在集群部署阶段应当进行适当的性能测试,以确保整体架构能够在较大规模下维持较高性能。 性能不足的etcd集群的表现基本等同于正在遭受DoS攻击的etcd集群。
多数Pod会将其服务暴露于互联网之上,如果没有采取合理的控制,任何人都可以访问Pod并对其实施DoS攻击。 好在,K8s支持对Pod进行资源请求限制。推荐下面的安全措施:
- 对Pod之间和Pod与外部的通信配置网络安全策略以控制流量的出站/入站(NetworkPolicy)
- 利用mTLS和基于Token的API认证来提供(Pod层面)应用级的认证
K8s提供以下鉴权方式来保证API Server的安全性:
- RBAC(基于角色的访问控制)
- webhook
- 节点认证
建议同时开启多个鉴权机制。例如,一种常见的方式是同时启用RBAC和节点两种鉴权机制。
RBAC模式能够指限制哪个用户(组)能够对哪些资源执行哪些操作。Webhook模式则是将鉴权工作交给外部的基于REST接口的策略引擎来完成。 不过需要额外搭建和维护一套外部引擎,且这个引擎也可能会带来API Server的单点故障隐患,因为API Server会持续调用外部引擎来完成每一次鉴权。 所以一旦外部引擎宕机,则API Server无法继续完成请求。鉴于此,应该严格谨慎地对待webhook鉴权服务的设计与实现。
节点认证模式是指对来自kubelet的请求进行鉴权。对节点请求的鉴权工作由节点鉴权器(node authorizer)完成。
下面提供一些手段来显著降低以Pod和容器为目标的"提升权限"攻击的风险。
- 避免进程以root身份运行(通过PodSpec中的
spec.securityContext.runAsUser
,此属性还可以在容器层面进行配置)- 若Pod由不同容器组成,建议为不同容器配置不同用户,以避免容器之间互相干扰
- 限制capabilities
- 在linux内部,root用户的权限是由不同的capabilities的权限组合而成的,比如,名为
SYS_TIME
的capability允许用户设置系统时钟。 NET_ADMIN
允许用户执行网关相关操作(如修改本地路由表、配置本地接口)- root用户拥有所有的capability
- 我们可以在配置非root用户的同时,为其添加capability来满足容器需求
- 在linux内部,root用户的权限是由不同的capabilities的权限组合而成的,比如,名为
- 过滤系统调用
- Seccomp是linux自 2.6.12 以来一直是Linux 内核的一个特性。它可以用来沙箱化进程的权限,限制进程从用户态到内核态的调用。
- 避免权限提升
- linux中允许子进程申请比父进程更多的权限。我们可以通过
spec.securityContext.allowPrivilegeEscalation
属性来禁止权限提升
- linux中允许子进程申请比父进程更多的权限。我们可以通过
- 配置selinux
这些功能配置基本都是通过spec.securityContext
属性来完成的。具体配置参考官方文档的为 Pod 或容器配置安全上下文。
Capabilities
如今linux-capabilities总共有30个,我们可以在spec.securityContext.capabilities
中配置容器所拥有的capabilities。
2019年CNCF委托了一个第三方机构对K8s进行了安全审计。通过安全威胁建模、人工代码审查、动态渗透测试和加密审查等方面的审计方法发现了一些安全隐患。 所有的隐患都给出了难度和严重程度级别。这次审查非常细致,本着负责人的态度,所有严重级别的隐患都在对外发布前被修复了。
不过,仍然有许多问题等待社区来解决。
CI/CD流水线:
- 使用私有镜像仓库(或公共仓库如DockerHub中的私有repository)
- 并且使用分离的repository(如开发、测试、生产)
- 使用已验证的基础镜像
- 控制镜像库的访问权限(push/pull)
- 生产repo限关键用户拥有push权限
- 限制用户进行push/pull镜像
- 限制某些客户端节点能访问镜像库
- 整合漏洞扫描
- 在扫描到镜像漏洞后自动令构建流水线失败
- 审查配置文件(Dockerfile、K8s YAML文件)
- 使用镜像签名,确保镜像完整性
- push时添加签名,pull时验证签名
基础设施隔离:
- 使用K8s命名空间隔离工作负载
- 对单一命名空间进行资源限额
- 创建单独的用户管理特点的命名空间
- 节点隔离
- 对于需要特殊权限(如root)运行的应用Pod,应将其部署到特定的节点(通过亲合度/污点/标签)
- 运行时隔离
- 容器和虚拟机的隔离性是不同的
- 多个容器会共享一个CPU内核,隔离性由内核指令提供。是一种软性隔离,不如虚拟机。
- 虚拟机的隔离性由硬件虚拟化技术提供,每台虚拟机独享一个硬件CPU内核
- 可以使用不同容器运行时(如gVisor和Kata)来提供更高级别的Pod隔离性
- 首先为特定节点安装配置特定的容器运行时
- 然后使用Runtime Class(K8s v1.20 stable)和nodeSelector将Pod调度到这类节点
网络隔离:
- K8s中的Pod网络通过CNI(Container Network Interface)实现,主流实现分为Overlay和BGP两类
- 使用K8s的网络策略(NetworkPolicy)来限制Pod之间的通信,支持跨命名空间
身份认证和访问控制管理:
- 充分使用RBAC系统
- 严格限制哪些人员能够ssh到控制平面节点的权限
- 部署审计和监控系统,应对未来某个时候被攻破的情况
- 安全配置
- 在PodSpec中限制容器以非root身份运行
- 信息安全中心(CIS)发布了一套针对K8s安全性的行业标准要求
- Aqua Security公司编写了一个易于使用的K8s安全评估工具kube-bench来执行对节点的CIS要求的测试
审计和安全监控:
- 保留容器和Pod的生命周期事件
- 通过采集每个节点上的容器运行时日志发送到外部存储(如ES),以便在未来调查问题时使用
- 采集每个节点上的Pod日志
- 记录每个节点上的命令执行,发送到日志中心(可以采用跳板机)