Skip to content

Latest commit

 

History

History
297 lines (200 loc) · 13.9 KB

doc_security.md

File metadata and controls

297 lines (200 loc) · 13.9 KB

K8s安全分析

声明,本篇文章的内容主要来自Kubernetes修炼手册 @Nigel Poulton

1. 安全模型

本文采用STRIDE模型来对K8s进行安全分析,该模型定义了6种潜在威胁:

  • 伪装
  • 篡改
  • 抵赖
  • 信息泄露
  • 拒绝服务
  • 提升权限

下文将对这些威胁进行详细分析。

2. 伪装

在信安领域,伪装指攻击者为了获得更多的系统权限而冒充另一个人或主体。

2.1 与API Server的安全通信

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进行通信。

2.2 Pod间的安全通信

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的受众

这些属性的具体示例,你可以在官方找到。

3. 篡改

篡改通常基于以下目的:

  • 拒绝服务:篡改资源使其不可用
  • 提高权限:篡改资源获取额外权限

3.1 对K8s组件的篡改

K8s系统内可以篡改的资源有以下几种:

  • etcd
  • 配置文件:API Server、ControllerManager、Scheduler、Kube-proxy和Kubelet
  • 容器运行时的二进制
  • 容器镜像
  • K8s的二进制

篡改行为通常发生在(网络)传输和保存过程中。TLS可以确保数据的完整性,下面的建议有助于防范对保存在集群中数据的篡改攻击:

  • 严格限制对运行由K8s组件的服务器的访问,尤其是部署了控制层组件的节点
  • 严格限制对保存有K8s配置文件的库的访问
  • 仅在最初部署K8s时进行ssh访问节点
  • 对下载的二进制文件进行哈希验证
  • 严格限制镜像仓库及相关库的访问

此外,建议在生产环境中对关键组件的二进制文件的审计和监控,相关的工具有auditctl等。

3.2 对于运行在K8s中的应用的篡改

推荐将Pod中容器的文件系统设置为只读是一种推荐的办法。我们可以在Pod的清单中设置securityContext.readOnlyRootFilesystem 属性为true, 也可以通过部署PodSecurityPolicy对象来全局性的限制所有Pod的文件系统的读写权限。

PodSecurityPolicy特性从v1.25开始被删除,转而使用Pod 安全性标准进行代替。

4. 抵赖

抵赖就是制造对某件事的不确定性。不可抵赖就是提供证据(以证实某事),具体来说应该能够证明以下信息:

  • 发生了什么
  • 什么时间发生的
  • 谁操作的
  • 在哪里发生的
  • 为什么发生的
  • 如何发生的

对于后两个信息,通常需要一段时间内的多个相关事件的信息。

K8s提供针对API Server的审计(Audit)功能来完成回答以上问题,该功能需要手动开启,请参考官方文档审计部分。

然而,除了API Server,K8s还提供了对其他组件的审计,比如容器运行时、Kubelet等各应用的审计日志。如果要收集多个组件的日志, 那么就需要一个中心化的日志后端来实现对事件的保存和分析。一种常见的做法是在每个节点上部署DaemonSet类型的日志代理来收集日志, 然后发送至中心日志数据库,同时要确保这个中心化的日志库是安全的。

5. 信息泄露

主要是指敏感数据的泄露。

5.1 保护集群数据

K8s中的所有集群配置都是保存在集群存储中的(目前是etcd),包含网络和存储配置以及Secret形式保存的密码登敏感数据。 这就使得集群存储会成为被攻击的首要目标。

我们必须对运行由集群存储的节点进行访问限制和审计。

5.2 保护Pod中的数据

前面提到,K8s提供了Secret对象来保存密码等敏感数据。但请注意,Secret是以未加密的形式存储在集群存储中的, 我们可以为其使用静态加密,请参考官方文档Secrets良好实践部分。

6. 拒绝服务

这种方式的攻击目的在于使服务不可用。在K8s中,拒绝服务的最大可能攻击对象就是API Server。

6.1 保护集群资源免于DoS攻击

首先,我们应该对主节点进行高可用(HA)部署。

进一步,我们还可以考虑将主节点部署在多个可用域中, 这样可以避免某个可用域的网络遭受故障导致整个集群不可用。这个防范原则也适用于工作节点。

此外,我们还应当为以下资源配置限额(参考ResourceQuota对象):

  • 内存
  • CPU
  • 存储
  • K8s对象(ReplicaSet、Pod、Service、Configmap、Secret等)

添加配额限制有助于避免重要系统资源被消耗殆尽,从而提高抵御DoS的能力。

6.2 保护API Server防范DoS攻击

参考以下方法:

  • 高可用部署主节点
  • 对到达API Server的请求进行合理监控和预警
  • 不将API Server暴露在互联网上(借助防火墙/安全组规则)

即使K8s拥有良好的鉴权机制,也需要注意妥善保管具有较高权限的账号。

6.3 保护集群存储防范DoS攻击

集群存储(etcd)的重要性已经不需要过多强调,参考一下方法加固etcd:

  • 将etcd部署为3个或5个节点的集群
  • 对etcd收到的请求进行监控和预警
  • 在网络层面对etcd进行隔离,只允许控制平面组件与其交互

默认情况下,K8s会将etcd安装到与控制平面组件相同的节点上,对于非生产环境这是OK的。但是生产环境就不太合适了, 应该考虑为K8s单独部署一个etcd集群,有助于提高系统的性能和弹性。

在性能方面,etcd可能是大型K8s集群的瓶颈所在。所以在集群部署阶段应当进行适当的性能测试,以确保整体架构能够在较大规模下维持较高性能。 性能不足的etcd集群的表现基本等同于正在遭受DoS攻击的etcd集群。

6.4 保护应用组件防范DoS攻击

多数Pod会将其服务暴露于互联网之上,如果没有采取合理的控制,任何人都可以访问Pod并对其实施DoS攻击。 好在,K8s支持对Pod进行资源请求限制。推荐下面的安全措施:

  • 对Pod之间和Pod与外部的通信配置网络安全策略以控制流量的出站/入站(NetworkPolicy)
  • 利用mTLS和基于Token的API认证来提供(Pod层面)应用级的认证

7. 提升权限

7.1 保护API Server

K8s提供以下鉴权方式来保证API Server的安全性:

  • RBAC(基于角色的访问控制)
  • webhook
  • 节点认证

建议同时开启多个鉴权机制。例如,一种常见的方式是同时启用RBAC和节点两种鉴权机制。

RBAC模式能够指限制哪个用户(组)能够对哪些资源执行哪些操作。Webhook模式则是将鉴权工作交给外部的基于REST接口的策略引擎来完成。 不过需要额外搭建和维护一套外部引擎,且这个引擎也可能会带来API Server的单点故障隐患,因为API Server会持续调用外部引擎来完成每一次鉴权。 所以一旦外部引擎宕机,则API Server无法继续完成请求。鉴于此,应该严格谨慎地对待webhook鉴权服务的设计与实现。

节点认证模式是指对来自kubelet的请求进行鉴权。对节点请求的鉴权工作由节点鉴权器(node authorizer)完成。

7.2 保护Pod

下面提供一些手段来显著降低以Pod和容器为目标的"提升权限"攻击的风险。

  • 避免进程以root身份运行(通过PodSpec中的spec.securityContext.runAsUser,此属性还可以在容器层面进行配置)
    • 若Pod由不同容器组成,建议为不同容器配置不同用户,以避免容器之间互相干扰
  • 限制capabilities
    • 在linux内部,root用户的权限是由不同的capabilities的权限组合而成的,比如,名为SYS_TIME的capability允许用户设置系统时钟。
    • NET_ADMIN允许用户执行网关相关操作(如修改本地路由表、配置本地接口)
    • root用户拥有所有的capability
    • 我们可以在配置非root用户的同时,为其添加capability来满足容器需求
  • 过滤系统调用
    • Seccomp是linux自 2.6.12 以来一直是Linux 内核的一个特性。它可以用来沙箱化进程的权限,限制进程从用户态到内核态的调用。
  • 避免权限提升
    • linux中允许子进程申请比父进程更多的权限。我们可以通过spec.securityContext.allowPrivilegeEscalation属性来禁止权限提升
  • 配置selinux

这些功能配置基本都是通过spec.securityContext 属性来完成的。具体配置参考官方文档的为 Pod 或容器配置安全上下文

Capabilities
如今linux-capabilities总共有30个,我们可以在spec.securityContext.capabilities 中配置容器所拥有的capabilities。

8. K8s安全展望

2019年CNCF委托了一个第三方机构对K8s进行了安全审计。通过安全威胁建模、人工代码审查、动态渗透测试和加密审查等方面的审计方法发现了一些安全隐患。 所有的隐患都给出了难度和严重程度级别。这次审查非常细致,本着负责人的态度,所有严重级别的隐患都在对外发布前被修复了。

不过,仍然有许多问题等待社区来解决。

9. 一些实践建议汇总

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日志
  • 记录每个节点上的命令执行,发送到日志中心(可以采用跳板机)

参考