TT Lab
开始
学习 学习路径 课程

KCSA — Kubernetes 安全助理

CNI、存储、客户端 — 边界组件的风险

在 TT Lab 中继续学习

一句话总结

NetworkPolicy 由 CNI 插件来执行,所以如果没有执行它的插件,策略 不会有任何效果。hostPath 是通向节点凭据和运行时套接字的通道,CSI 驱动则握着存储后端的机密。在客户端一侧,一个 kubeconfig 文件既是整个集群的钥匙,同时也可能成为 执行任意命令的文件。

为什么需要它

如果说上一篇阅读里的三个组件在控制平面和节点之内,那么这三个就在集群的 边界 上。CNI 在 Pod 与网络之间,存储在 Pod 与磁盘之间,客户端在人与 API 服务器之间。边界是两侧信任等级不同的地方,所以是威胁模型里最先画的线。可是这三处,都是很容易误以为“Kubernetes 会替我守着的”地方。创建了 NetworkPolicy,应该就被拦住了吧;用了 PVC,应该就隔离了吧;kubeconfig 不过是个配置文件吧——三个都错了。

工作原理

CNI——策略由插件来执行

网络策略 文档的前提条件一节说得很清楚。网络策略由网络插件实现,必须使用支持 NetworkPolicy 的网络方案,并且 如果没有实现它的控制器,就算创建了 NetworkPolicy 资源,也不会有任何效果。API 服务器只是存储对象,并不会拒绝,所以会悄悄地出现“有策略,流量却照样流动”的局面。

还得知道策略的默认状态。如果命名空间里一个策略都没有,那么进出该命名空间 Pod 的所有流量都被允许。隔离是 ingress 和 egress 各自 声明的,按文档的说法,“隔离”不是绝对的,而是“会适用某些限制”的意思。所以安全检查清单 建议在每个命名空间里设置选中所有 Pod 的默认拒绝策略,采用白名单方式,并把所用的 CNI 是否支持策略放在第一项。CNI 插件本身,如上一篇阅读所看到的,由容器运行时加载,所以能往插件二进制文件和配置目录里写东西的权限,就是能让节点上的网络策略执行失效的权限。传输加密并不是所有 CNI 都提供,没有的话,服务网格是替代方案,这一点清单里也有。

存储——hostPath 的通道与 CSI 的机密

卷 文档的 hostPath 一节,以警告开头。hostPath 带有许多安全风险,能避免就避免,应该改用 local PersistentVolume。理由很具体——一旦触及主机文件系统,kubelet 的凭据这类特权系统凭据,或者容器运行时套接字这类特权 API,就会暴露出来,可能被用于容器逃逸或对集群其他部分的攻击。即使在准入时把允许的目录限制到特定目录,也必须把该挂载 强制设为只读,限制才有效。因为如果给不可信的 Pod 以读写方式开放任何主机路径,那个 Pod 的容器就可以把挂载翻转过来。Pod Security Standards 的 baseline 配置档禁止 hostPath,就是对这种风险的基本防御。

CSI 一侧的机密是另一种。据 CSI 驱动文档的 Secrets and Credentials 一节,驱动访问后端所需的凭据分为两层。驱动级别 的机密(比如后端的服务账号)在部署时,用标准的 Secret 部署方式直接注入驱动 Pod;操作级别、卷级别 的机密,CSI 规范允许它们附带在 CreateVolume 这样的每个操作请求里,管理员创建 Secret,并把它的键写在 StorageClass 或 VolumeSnapshotClass 里传递过去。sidecar 容器中与 Secret 相关的 RBAC 规则为了减少权限,默认是关闭的,只在需要时才打开,规范则在敏感字段上设置 csi_secret 标记,避免写进日志。换成威胁模型就是这样——能读取 CSI 控制器 Pod 的 ServiceAccount 或 StorageClass 所引用的 Secret 的主体,就拿到了针对整个存储后端的凭据。

客户端——kubeconfig 既是钥匙,也是可执行文件

用 kubeconfig 组织集群访问 文档在说明 kubectl 默认读取 $HOME/.kube/config,并可以用 KUBECONFIG 环境变量或 --kubeconfig 标志指定其他文件之后,附上了警告。只使用来自可信来源的 kubeconfig,特意构造的 kubeconfig 可能导致执行恶意代码或泄露文件,所以不可信的文件要像看 shell 脚本一样先检查。

配置文件为什么会变成代码执行,认证 文档的凭据插件一节做了说明。client-go 以及使用它的 kubectl、kubelet,为了获取用户凭据,可以 执行外部命令(1.22 中稳定)。这是为 LDAP、Kerberos、OAuth2、SAML 这类 client-go 不直接支持的协议而设的功能,client-go 把插件返回的令牌作为 bearer 令牌使用,服务器一侧则由 webhook 令牌认证器处理 TokenReview。所以,kubeconfig 的 exec 项里写的命令,每次输入 kubectl 时都会以用户的权限执行。因此,必须打开收到的 kubeconfig,看 exec 块指向哪个二进制文件。

kubeconfig 里所含凭据的性质,也决定了风险。认证机制加固指南 列出了 X.509 客户端证书的限制——无法单独撤销,所以一旦泄露,到过期为止都可以使用;要使它失效,就得重新签发 CA;签发记录不会留在集群里;私钥无法用密码保护,所以任何能读到这个文件的人都能使用;组写在证书的 O 值里,在有效期内无法更改。ServiceAccount 令牌也一样,认证 文档写道“在集群之外使用也完全有效”,所以放进流水线工具里的令牌,要与 kubeconfig 同等看待。应对措施正如集群安全文档所说——短有效期、自动轮换,以及能控制所签发令牌有效期的认证提供方。

在现场相遇的样子

部署了 NetworkPolicy,却全被穿透了。在用 kwok,或者用不支持策略的插件搭建的集群里很常见。kubectl get networkpolicy 里能看到对象,与流量被拦住,是两回事,执行与否,必须靠实际尝试连接来确认。

在团队频道里共享的 kubeconfig。文件里的 client-certificate-data 无法撤销,所以一旦确认泄露,到过期为止都是有效的。如果用的是基于证书的用户凭据,就把有效期设短,把人类用户迁移到 OIDC 这样的外部认证提供方,这就是加固指南所指的方向。

下一项测验要确认什么

测验会问:把调度器的认证标志留空时的行为,kube-proxy 指标端口的默认绑定,访问运行时套接字意味着什么,没有执行控制器的 NetworkPolicy 有什么效果,hostPath 限制在什么条件下才有效,以及 kubeconfig 为什么会变成代码执行。