TL;DR
- 镜像扫描解决已知漏洞,是安全基线;
- 镜像签名确保内容可信,防篡改保障;
- SBOM 提供供应链透明度,便于审计追溯;
- Kyverno 在准入阶段强制校验签名与漏洞策略;
- 建议从 CI 扫描起步,逐步完善签名与供应链治理。
摘要 :容器镜像安全是云原生交付的底线。本文系统讲解镜像扫描 如何发现已知漏洞、镜像签名 如何确保内容可信、SBOM 如何提供供应链透明度,并结合 Kyverno 在 Kubernetes 准入阶段强制落地安全策略,帮助你在构建、存储、分发、运行全链路建立可信防线,实现端到端的供应链安全治理。
1. 引言
2024 年,某知名开源镜像仓库被曝出投毒事件:攻击者将恶意代码植入多个热门镜像的构建脚本中,在镜像被拉取后静默执行挖矿程序并窃取环境变量中的密钥。由于这些镜像被大量企业直接引用,事件波及范围迅速扩大,多家公司被迫紧急排查并重建镜像。这并非孤例------从基础镜像的已知漏洞,到构建期植入的恶意代码,再到运行时被篡改的内容,每一次拉取与部署都可能成为攻击入口。镜像安全刻不容缓。本文聚焦三道关键防线:镜像扫描 发现已知漏洞、镜像签名 确保内容可信、SBOM 与供应链安全从源头建立信任,并给出从 CI 扫描到 Kubernetes 准入校验的完整落地路径。## 2. 为什么镜像安全如此重要
镜像一旦被发布到仓库并被拉取运行,其内容就等同于线上运行的代码。如果镜像中存在高危漏洞或恶意组件,攻击者无需直接攻破你的服务器,只需利用镜像内的缺陷即可完成入侵。更严重的是,镜像具有复用性 与传播性------一个被污染的公共基础镜像可能被成千上万个下游项目引用,形成供应链级别的连锁污染。
因此,镜像安全不仅是「上线前的一次检查」,而应贯穿镜像的构建、存储、分发、运行全生命周期。
3. 镜像扫描:发现已知漏洞
3.1 扫描的原理
镜像扫描的核心思路是:将镜像内的软件包清单(如操作系统包、语言依赖、二进制文件)与漏洞数据库(如 CVE、NVD、OSV)进行比对,找出已知漏洞并评估其风险等级。
扫描通常分为两类:
- 静态扫描:在镜像构建后、运行前,对镜像层文件系统进行分析,识别其中的软件版本并匹配漏洞库。
- 运行时扫描:对正在运行的容器进行实时检测,发现动态引入的风险(如运行时下载的恶意依赖)。
3.2 常用扫描工具
| 工具 | 特点 | 适用场景 |
|---|---|---|
| Trivy | 轻量、速度快、覆盖 OS 包与语言依赖 | CI 阶段快速扫描 |
| Clair | 由 CoreOS 开源,支持 API 集成 | 仓库侧集中扫描 |
| Anchore | 策略引擎强大,支持自定义规则 | 企业级合规管控 |
| Grype | Anchore 出品,与 Syft 配合 | 与 SBOM 联动 |
3.3 扫描的落地实践
bash
# 使用 Trivy 扫描本地镜像
trivy image --severity HIGH,CRITICAL --ignore-unfixed myapp:latest
# 在 CI 中阻断高危漏洞
trivy image --exit-code 1 --severity CRITICAL myapp:latest
建议将扫描嵌入 CI 流水线,高危漏洞直接阻断发布,中低危漏洞记录并跟踪修复。同时,扫描结果应生成报告归档,便于审计与追溯。
4. 镜像签名:确保内容可信
4.1 为什么需要签名
扫描只能发现「已知漏洞」,却无法防止「内容被篡改」。攻击者可能通过劫持镜像仓库、中间人攻击或内部人员操作,替换或篡改镜像内容。镜像签名通过密码学手段,为镜像内容生成不可伪造的数字签名,确保拉取到的镜像确实来自可信发布者且未被篡改。
4.2 签名的工作原理
签名流程通常包含以下步骤:
- 发布者使用私钥对镜像的 manifest(清单)进行签名;
- 签名结果与公钥信息一同发布到仓库或独立的签名存储;
- 拉取方使用公钥验证签名,确认镜像来源与完整性。
#mermaid-svg-GqtMAbNPY51A5NdE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GqtMAbNPY51A5NdE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GqtMAbNPY51A5NdE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GqtMAbNPY51A5NdE .error-icon{fill:#552222;}#mermaid-svg-GqtMAbNPY51A5NdE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GqtMAbNPY51A5NdE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GqtMAbNPY51A5NdE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GqtMAbNPY51A5NdE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GqtMAbNPY51A5NdE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GqtMAbNPY51A5NdE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GqtMAbNPY51A5NdE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GqtMAbNPY51A5NdE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GqtMAbNPY51A5NdE .marker.cross{stroke:#333333;}#mermaid-svg-GqtMAbNPY51A5NdE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GqtMAbNPY51A5NdE p{margin:0;}#mermaid-svg-GqtMAbNPY51A5NdE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GqtMAbNPY51A5NdE .cluster-label text{fill:#333;}#mermaid-svg-GqtMAbNPY51A5NdE .cluster-label span{color:#333;}#mermaid-svg-GqtMAbNPY51A5NdE .cluster-label span p{background-color:transparent;}#mermaid-svg-GqtMAbNPY51A5NdE .label text,#mermaid-svg-GqtMAbNPY51A5NdE span{fill:#333;color:#333;}#mermaid-svg-GqtMAbNPY51A5NdE .node rect,#mermaid-svg-GqtMAbNPY51A5NdE .node circle,#mermaid-svg-GqtMAbNPY51A5NdE .node ellipse,#mermaid-svg-GqtMAbNPY51A5NdE .node polygon,#mermaid-svg-GqtMAbNPY51A5NdE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GqtMAbNPY51A5NdE .rough-node .label text,#mermaid-svg-GqtMAbNPY51A5NdE .node .label text,#mermaid-svg-GqtMAbNPY51A5NdE .image-shape .label,#mermaid-svg-GqtMAbNPY51A5NdE .icon-shape .label{text-anchor:middle;}#mermaid-svg-GqtMAbNPY51A5NdE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GqtMAbNPY51A5NdE .rough-node .label,#mermaid-svg-GqtMAbNPY51A5NdE .node .label,#mermaid-svg-GqtMAbNPY51A5NdE .image-shape .label,#mermaid-svg-GqtMAbNPY51A5NdE .icon-shape .label{text-align:center;}#mermaid-svg-GqtMAbNPY51A5NdE .node.clickable{cursor:pointer;}#mermaid-svg-GqtMAbNPY51A5NdE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GqtMAbNPY51A5NdE .arrowheadPath{fill:#333333;}#mermaid-svg-GqtMAbNPY51A5NdE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GqtMAbNPY51A5NdE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GqtMAbNPY51A5NdE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GqtMAbNPY51A5NdE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GqtMAbNPY51A5NdE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GqtMAbNPY51A5NdE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GqtMAbNPY51A5NdE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GqtMAbNPY51A5NdE .cluster text{fill:#333;}#mermaid-svg-GqtMAbNPY51A5NdE .cluster span{color:#333;}#mermaid-svg-GqtMAbNPY51A5NdE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GqtMAbNPY51A5NdE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GqtMAbNPY51A5NdE rect.text{fill:none;stroke-width:0;}#mermaid-svg-GqtMAbNPY51A5NdE .icon-shape,#mermaid-svg-GqtMAbNPY51A5NdE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GqtMAbNPY51A5NdE .icon-shape p,#mermaid-svg-GqtMAbNPY51A5NdE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GqtMAbNPY51A5NdE .icon-shape .label rect,#mermaid-svg-GqtMAbNPY51A5NdE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GqtMAbNPY51A5NdE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GqtMAbNPY51A5NdE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GqtMAbNPY51A5NdE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
开发者构建镜像
使用私钥签名
推送镜像与签名到仓库
用户拉取镜像
使用公钥验证签名
验证通过?
允许运行
拒绝运行并告警
4.3 主流签名方案
- Cosign:Sigstore 项目的一部分,支持无密钥签名(Keyless),与 OCI 仓库天然集成,是目前最流行的方案。
- Notation:CNCF 孵化项目,基于 OCI 1.1 规范,适合与云厂商生态结合。
- Docker Content Trust(DCT):Docker 原生方案,基于 Notary,适合 Docker 生态内的简单场景。
4.4 Cosign 实践示例
bash
# 生成密钥对
cosign generate-key-pair
# 对镜像签名
cosign sign --key cosign.key myregistry/myapp:latest
# 验证镜像签名
cosign verify --key cosign.pub myregistry/myapp:latest
在 Kubernetes 中,可结合 Kyverno 或 Ratify 等策略引擎,在准入控制阶段强制校验镜像签名,未通过验证的镜像直接拒绝创建 Pod。
5. 软件供应链安全:从源头建立信任
5.1 供应链风险的来源
镜像的供应链风险不仅来自镜像本身,还来自其依赖的上游组件:
- 基础镜像(如
alpine、ubuntu)本身存在漏洞; - 第三方依赖库被投毒(如恶意 npm 包、PyPI 包);
- 构建工具链被篡改;
- 构建过程使用了不可信的缓存或代理。
5.2 SBOM:软件物料清单
SBOM(Software Bill of Materials) 是镜像内所有组件的清单,相当于「镜像的配料表」。通过 SBOM,你可以:
- 快速定位漏洞影响的组件范围;
- 满足合规审计要求(如美国 EO 14028、欧盟 Cyber Resilience Act);
- 在漏洞爆发时第一时间评估自身受影响程度。
bash
# 使用 Syft 生成 SBOM
syft myapp:latest -o spdx-json > sbom.json
# 使用 Grype 基于 SBOM 扫描漏洞
grype sbom:./sbom.json
5.3 供应链安全的整体实践
5.4 供应链安全实战:从 SBOM 到策略落地
下面用一个完整的端到端示例,把 SBOM 生成、漏洞扫描、镜像签名与 Kubernetes 准入校验串联起来,展示一条可落地的供应链安全闭环。
第一步:生成 SBOM 并扫描漏洞
bash
# 生成 SBOM(SPDX 格式)
syft myapp:latest -o spdx-json > sbom.json
# 基于 SBOM 扫描漏洞,输出 JSON 报告
grype sbom:./sbom.json -o json > vuln-report.json
# 查看高危漏洞摘要
grype sbom:./sbom.json --only-severity critical,high
第二步:对镜像签名并推送
bash
# 生成密钥对(生产环境建议使用 KMS 或 Sigstore Keyless)
cosign generate-key-pair
# 对镜像签名
cosign sign --key cosign.key myregistry/myapp:latest
# 推送镜像与签名到仓库
docker push myregistry/myapp:latest
cosign push --key cosign.key myregistry/myapp:latest
第三步:用 Kyverno 在准入控制阶段校验签名与漏洞策略
创建 Kyverno 策略,强制要求镜像必须通过签名验证,并拒绝携带高危漏洞的镜像:
yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry/myapp:*"
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
<cosign.pub 内容>
-----END PUBLIC KEY-----
再创建一条策略,基于镜像漏洞扫描结果阻断高危镜像部署(需配合 Ratify 或 Kyverno 的 imageVerify 扩展):
yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: block-high-vulnerability-images
spec:
validationFailureAction: Enforce
rules:
- name: reject-critical-vulns
match:
any:
- resources:
kinds:
- Pod
validate:
message: "镜像存在高危漏洞,禁止部署"
deny:
conditions:
any:
- key: "{{ image.ratify.vulnerabilities.critical }}"
operator: GreaterThan
value: 0
第四步:验证策略生效
bash
# 部署一个未签名或带高危漏洞的镜像,应被拒绝
kubectl apply -f pod-unsafe.yaml
# Error from server: Pod "pod-unsafe" is forbidden: require-image-signature
# 部署通过签名与漏洞校验的镜像,正常创建
kubectl apply -f pod-safe.yaml
# pod/pod-safe created
通过以上四步,SBOM 提供了「镜像里有什么」的透明度,Grype 负责「有没有已知漏洞」的检查,Cosign 保证「内容未被篡改」,Kyverno 则在部署入口强制落地这些策略,形成从构建到运行的完整供应链安全闭环。
- 使用可信基础镜像:优先选择官方或经过安全审核的基础镜像,并定期更新。
- 锁定依赖版本 :使用锁文件(如
package-lock.json、go.sum)固定依赖版本,避免意外引入恶意版本。 - 构建过程可复现:使用多阶段构建、固定构建工具版本,减少构建环境的不确定性。
- 最小化镜像内容:只保留运行所需的最小文件集,减少攻击面。
- 建立镜像策略:通过 OPA、Kyverno 等策略引擎,强制要求镜像必须通过扫描与签名校验才能部署。
6. 端到端落地:一条可信的镜像流水线
将上述能力串联起来,可以构建一条端到端的可信镜像流水线:
#mermaid-svg-2iRvH4eckCgoj7hx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2iRvH4eckCgoj7hx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2iRvH4eckCgoj7hx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2iRvH4eckCgoj7hx .error-icon{fill:#552222;}#mermaid-svg-2iRvH4eckCgoj7hx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2iRvH4eckCgoj7hx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2iRvH4eckCgoj7hx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2iRvH4eckCgoj7hx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2iRvH4eckCgoj7hx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2iRvH4eckCgoj7hx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2iRvH4eckCgoj7hx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2iRvH4eckCgoj7hx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2iRvH4eckCgoj7hx .marker.cross{stroke:#333333;}#mermaid-svg-2iRvH4eckCgoj7hx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2iRvH4eckCgoj7hx p{margin:0;}#mermaid-svg-2iRvH4eckCgoj7hx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2iRvH4eckCgoj7hx .cluster-label text{fill:#333;}#mermaid-svg-2iRvH4eckCgoj7hx .cluster-label span{color:#333;}#mermaid-svg-2iRvH4eckCgoj7hx .cluster-label span p{background-color:transparent;}#mermaid-svg-2iRvH4eckCgoj7hx .label text,#mermaid-svg-2iRvH4eckCgoj7hx span{fill:#333;color:#333;}#mermaid-svg-2iRvH4eckCgoj7hx .node rect,#mermaid-svg-2iRvH4eckCgoj7hx .node circle,#mermaid-svg-2iRvH4eckCgoj7hx .node ellipse,#mermaid-svg-2iRvH4eckCgoj7hx .node polygon,#mermaid-svg-2iRvH4eckCgoj7hx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2iRvH4eckCgoj7hx .rough-node .label text,#mermaid-svg-2iRvH4eckCgoj7hx .node .label text,#mermaid-svg-2iRvH4eckCgoj7hx .image-shape .label,#mermaid-svg-2iRvH4eckCgoj7hx .icon-shape .label{text-anchor:middle;}#mermaid-svg-2iRvH4eckCgoj7hx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2iRvH4eckCgoj7hx .rough-node .label,#mermaid-svg-2iRvH4eckCgoj7hx .node .label,#mermaid-svg-2iRvH4eckCgoj7hx .image-shape .label,#mermaid-svg-2iRvH4eckCgoj7hx .icon-shape .label{text-align:center;}#mermaid-svg-2iRvH4eckCgoj7hx .node.clickable{cursor:pointer;}#mermaid-svg-2iRvH4eckCgoj7hx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2iRvH4eckCgoj7hx .arrowheadPath{fill:#333333;}#mermaid-svg-2iRvH4eckCgoj7hx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2iRvH4eckCgoj7hx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2iRvH4eckCgoj7hx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2iRvH4eckCgoj7hx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2iRvH4eckCgoj7hx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2iRvH4eckCgoj7hx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2iRvH4eckCgoj7hx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2iRvH4eckCgoj7hx .cluster text{fill:#333;}#mermaid-svg-2iRvH4eckCgoj7hx .cluster span{color:#333;}#mermaid-svg-2iRvH4eckCgoj7hx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2iRvH4eckCgoj7hx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2iRvH4eckCgoj7hx rect.text{fill:none;stroke-width:0;}#mermaid-svg-2iRvH4eckCgoj7hx .icon-shape,#mermaid-svg-2iRvH4eckCgoj7hx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2iRvH4eckCgoj7hx .icon-shape p,#mermaid-svg-2iRvH4eckCgoj7hx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2iRvH4eckCgoj7hx .icon-shape .label rect,#mermaid-svg-2iRvH4eckCgoj7hx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2iRvH4eckCgoj7hx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2iRvH4eckCgoj7hx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2iRvH4eckCgoj7hx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
代码提交
CI 构建镜像
SBOM 生成
镜像扫描
存在高危漏洞?
阻断并修复
镜像签名
推送镜像仓库
部署前策略校验
签名验证 + 漏洞复核
准入运行
7. 总结
镜像安全不是单一工具能解决的问题,而是扫描、签名、供应链治理三者协同的系统工程:
- 扫描解决「已知漏洞」问题,是安全基线;
- 签名解决「内容可信」问题,是防篡改保障;
- 供应链治理解决「源头可信」问题,是长期防线。
建议从「在 CI 中接入 Trivy 扫描」起步,逐步引入 Cosign 签名与 Kyverno 准入校验,再以 SBOM 完善供应链透明度。安全建设是持续演进的过程,越早建立可信基线,后续的运维与合规成本就越低。