LabHub
学习 学习路径 课程

CKS — Kubernetes 安全专家

先搞清楚进来的是什么

在 LabHub 中继续学习

一句话总结

供应链安全的核心问题不是“这个镜像安全吗”,而是“当前在集群中运行的那些字节究竟是什么、 来自哪里,以及是否遭到过调包”。

流程图: 第一,容器运行时。 · 每个节点上配置 · 第二,准入控制(admission)。 · 无法连接 Webhook 时会拒绝所有请求

为什么需要它

myapp:v1.2 这样的标签是可变指针。你可以向同一个标签推送另一个镜像, latest 更是如此。如果 Pod 规约中只写了标签,就没人能证明节点当前运行的究竟是哪些字节。 即使执行回滚,同一个标签也可能已经指向了别的内容。 摘要(@sha256:...)是内容的哈希值,因此能从根源上消除这个问题。

同样的逻辑也适用于语言包。某个项目直接声明的依赖只有 37 个, 实际安装的包却有 1,123 个,其中 486 个会随生产运行时一起部署。 经过审查和选择的是 37 个,决定信任的却是 1,123 个。锁文件的完整性哈希并不保证“这个 软件包是安全的”,只保证“这个软件包与我上次看到的相同”。 这两句话之间的差别,几乎就是供应链安全的全部。

工作原理

控制措施分为四层。

层级 回答什么问题 工具与字段
固定 当前运行的字节是什么 镜像摘要、imagePullPolicy
来源 它来自哪里 允许的镜像仓库列表、imagePullSecrets
证明 它是否由我们的流水线构建 签名(cosign)、SBOM、来源证明
强制 如何阻止不符合要求的镜像 准入策略(VAP、Kyverno、Gatekeeper)

imagePullPolicy 经常被误解。Always 会每次都向镜像仓库检查清单, IfNotPresent 则只在节点上不存在镜像时才拉取。使用标签时,Always 看起来更安全, 但这也意味着每次都要重新面对标签可能发生变化的问题。用摘要固定后, 内容按定义就不会变化,因此设置为 IfNotPresent 更合理,而且镜像仓库故障也不会 蔓延成部署故障。

还需要澄清对扫描器的误解。镜像扫描器只做两步:解开镜像层, 生成已安装软件包清单,再将清单与漏洞数据库比对。因此,通过 curl 下载后复制进去的二进制文件,或直接从源码构建的库,根本不会被识别。 这是扫描结果干净并不等于安全的第一个原因。

最后一层是准入控制。扫描结果和签名本身没有强制力。要阻止绕过流水线、 手工推送的镜像进入集群,只能依靠准入策略。在此之前,扫描门禁更接近一种 可以绕过的建议。

现场会遇到的情况

刚接入扫描报告时,通常会看到这样的数字:仅一个 payments:1.4.2 镜像就有 1,247 个问题(LOW 812、MEDIUM 289、HIGH 137、CRITICAL 9)。如果直接把它扔进团队频道, 什么也不会发生。加上一个 --ignore-unfixed 后,数量会降到 4 个(HIGH 3、CRITICAL 1)。 安全性没有任何改善,但剩下的都是当前可以处理的项目。再与 CISA KEV 清单 对照,确认已被实际利用的只有 1 个。今天要处理的就是这一个。

而消除其余 1,200 多个问题的办法并不是逐项打补丁,而是更换基础镜像。 把同一个应用从 node:22(432 个软件包)迁移到 distroless 基础镜像(19 个软件包)后, 可修复的 CRITICAL 从 9 个降到了 0。Shell 和包管理器消失并不是附带效果, 而是关键所在,因为这意味着镜像中没有入侵者可利用的工具。 代价是无法再用 kubectl exec 进入容器,因此调试方式要改为附加临时容器。

在隔离网络中,镜像仓库控制同时也是可用性问题。containerd 1.x 从 sandbox_image 键读取 pause 镜像地址,到了 2.x,该配置移到了 pinned_images 下的 sandbox 键。原本已切换到内部镜像仓库的集群升级到 2.x 后,旧键会被静默忽略, 并回退到默认的公共镜像仓库。结果是该节点上一个 Pod 都无法启动。 由于出问题的不是某个工作负载,而是整个节点,因此很容易被误判为网络故障。

控制镜像的三个层面

供应链领域的问题常以“禁止使用这个镜像”的形式出现。可以实施阻断的位置有三个, 不同问题考查的层面也不同。

第一,容器运行时。 通过节点配置,只允许特定镜像仓库或验证签名。 它会作用于整个集群,但必须在每个节点上配置

第二,准入控制(admission)。 在集群内通过策略实施阻断。这是考试中 最常出现的位置。ImagePolicyWebhook 是专门向外部服务询问镜像是否允许的 机制,而策略引擎(Kyverno、Gatekeeper)可以设置更通用的条件。

# ImagePolicyWebhook 을 켤 때 함께 필요한 것
# --admission-control-config-file 로 설정 파일을 주고,
# 그 파일이 kubeconfig 를 가리키고, 둘 다 정적 파드에 마운트되어야 한다.

设置 defaultAllow: false 后,无法连接 Webhook 时会拒绝所有请求。这样更安全, 但 Webhook 一旦宕机,集群就无法部署任何资源。考试题通常会考查这个值。

第三,镜像本身。 即签名和 SBOM。签名验证回答“由谁构建”, 扫描回答“里面有什么”。二者不能相互替代—— 既可能存在已签名但有漏洞的镜像,也可能存在干净却来源不明的镜像。

使用摘要而不是标签来固定镜像。 题目经常要求通过策略禁止 :latest。 标签会移动,因此经过验证的内容和实际运行的内容可能不同。

kubectl get pods -A -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"
"}{end}'   | sort -u | grep -vE '@sha256:'

静态分析也属于供应链。 题目还会涉及在部署前检查清单的工具 (kubesec、kube-linter、trivy config)。要点是:在资源进入集群之前阻止问题, 是成本最低的控制层。

下一实验要做什么

首先用摘要固定镜像,建立允许的镜像仓库列表,并把用于私有镜像仓库认证的 Secret 绑定到 ServiceAccount。签名和 SBOM 验证策略将以清单文件编写。接下来的实验中, 你将亲手创建一个可实际应用于集群的 ValidatingAdmissionPolicy,使用 CEL 表达式 拒绝“只使用标签的镜像”。