DevSecOps 闭环:如何将镜像扫描结果强制引入 K8s 准入控制(Admission Control)
在 DevSecOps 的实践中,很多团队仅仅停留在“在 CI 流水线里跑一下扫描”的阶段。然而,如果扫描结果只是发一份邮件或者留在 Dashboard 里,而没有在集群入口处进行拦截,那么“左移安全”就只是一句空话。
要实现真正的安全闭环,必须通过 Kubernetes Admission Controller (准入控制器),根据镜像扫描的实时状态,决定是否允许该 Pod 运行。
一、 核心逻辑架构
实现镜像扫描与准入控制联动的标准流程通常分为三个环节:
- 扫描触发与元数据持久化:镜像推送到仓库(如 Harbor)后自动触发扫描,结果存入数据库或通过签名工具(如 Cosign)生成证明。
- 准入请求截获:当用户执行
kubectl apply时,K8s API Server 将请求转发给Validating Admission Webhook。 - 决策判定:Webhook 查询镜像的安全分值、漏洞等级(CVE Severity)或签名状态,返回
allow或deny。
二、 方案一:使用原生策略引擎(推荐方案)
与其从零开发一个 Webhook Server,目前工业界的最佳实践是使用 Gatekeeper (OPA) 或 Kyverno。
1. 基于 Kyverno 的实现
Kyverno 相比 OPA 的优势在于它直接使用声明式的 YAML,不需要学习 Rego 语言。
场景描述:只允许部署漏洞等级(Severity)低于 High 的镜像。
实现思路:
- Image Verification:利用 Kyverno 的
verifyImages特性,配合 Cosign 检查镜像是否经过了流水线的安全认证签名。 - 外部查询:Kyverno 可以配置查询 OCI 仓库中的符号链接(Attestations),如果镜像没有对应的“扫描通过证明”,则拒绝进入。
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image-vulnerabilities
spec:
validationFailureAction: Enforce
rules:
- name: scan-check
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "my-registry.com/*"
attestations:
- predicateType: cosign.sigstore.dev/attestation/vuln/v1
conditions:
- all:
- key: "{{ json_decode(content).metadata.scan_status }}"
operator: Equals
value: "passed"
2. 基于 OPA Gatekeeper 的实现
如果你需要更复杂的逻辑(例如:根据不同的 Namespace 设置不同的安全等级),OPA 的 Rego 语言更具表现力。
你需要编写一个 ConstraintTemplate,在其中通过 http.send 实时调用镜像仓库(如 Harbor API)获取漏洞报告,或者查询内存中缓存的扫描白名单。
三、 方案二:自建 Validating Webhook
如果企业内部有复杂的权限校验逻辑(例如需要对接内部自研的 SoC 安全平台),则需要自建 Webhook。
技术细节:
- 输入:
AdmissionReview对象,包含 Pod 的所有 Container 镜像 Tag。 - 逻辑:
- 解析镜像地址。
- 调用后端 API(Trivy API 或 Harbor API)查询该 Tag 的
vulnerability_summary。 - Fail-close vs Fail-open:如果扫描服务宕机,是允许还是禁止?(生产环境通常建议 Fail-open 并记录审计日志)。
- 输出:
AdmissionResponse,并在message中明确告知开发者由于哪些 CVE 导致部署失败。
四、 关键实战挑战与避坑指南
1. 扫描延迟问题 (Race Condition)
现象:镜像刚推送,CI 还没扫完,CD 流程就开始部署,导致准入控制器查不到结果。
对策:
- 阻塞 CI:确保 CI 流水线在扫描返回结果前不结束。
- 中间状态判定:如果状态为
Scanning,Webhook 返回禁止,并提示“镜像扫描中,请稍后重试”。
2. 避免 API 性能瓶颈
每启动一个 Pod 都要实时调接口查漏洞,会导致 API Server 延迟增加。
对策:
- 本地缓存:Webhook 内部维护一个短时间的缓存(如 Redis),存储镜像 Tag 与安全结论的映射。
- 签名化(Attestation):这是最优雅的方案。CI 扫完后,将结果作为签名附加到镜像仓库中。Webhook 只校验签名是否存在且内容合法,无需发起跨网络请求。
3. 排除特定命名空间
不要对 kube-system 或监控组件进行镜像扫描拦截,否则可能导致集群基础组件故障时无法自动恢复。
五、 总结:DevSecOps 的分级防御架构
- 第一道防线 (IDE/Git):开发者提交代码时,SAST 静态扫描发现基础镜像漏洞。
- 第二道防线 (Registry):镜像推送到私有仓库时触发自动扫描。
- 第三道防线 (Admission Control):部署时执行“最后一公里”的拦截,确保只有通过审核的镜像能跑在集群内。
通过这种方式,安全人员不再是去“救火”,而是定义好准入规则,让整个安全治理过程自动化、透明化。