1. 课程导入

在持续集成与持续交付(CI/CD)体系中,GitLab 早已不只是代码托管平台。它同时承担依赖拉取、流水线构建、镜像打包、制品存储与部署触发等职责,因此也成为软件供应链安全的“咽喉节点”。本课程讨论的“依赖与容器镜像安全治理”,核心目标可以概括为一句话:让进入流水线的每一个第三方依赖和每一个容器镜像,都可识别、可判断、可阻断、可追溯。

这句话包含四个动作。可识别,是指依赖清单(SBOM)与镜像层内容能够被自动生成和采集;可判断,是指漏洞、许可证、密钥、恶意包等风险有明确的判定标准;可阻断,是指高风险对象在合并请求或镜像推送阶段被策略拦截;可追溯,是指任一镜像能反查到它由哪次提交、哪条流水线、哪些依赖构建而来。缺少任何一环,治理都会退化为“事后扫描报告”。

风险全景:依赖与镜像的六类主要风险

依赖与镜像的风险并非只有“CVE 数量”一个维度。实际治理中,以下六类风险最值得优先覆盖:

  • 已知漏洞:如 Log4Shell(CVE-2021-44228)这类传递依赖漏洞,往往不在项目直接声明的依赖中,却随构建进入镜像。
  • 许可证合规:GPL、AGPL 等强传染性许可证若被商业闭源产品引入,可能带来法律风险。
  • 密钥与凭据泄露:构建时误将 .env、私钥、Token 写入镜像层,即使后续删除,历史层仍可提取。
  • 基础镜像过期:长期未更新的 ubuntu:18.04、node:14 等基础镜像,会持续累积未修复漏洞。
  • 依赖混淆与投毒:攻击者上传与内部包同名的公共包,或篡改依赖源,诱导流水线拉取恶意版本。
  • 镜像配置缺陷:以 root 运行、暴露不必要端口、使用 latest 标签导致不可复现构建。

这六类风险在流水线中的位置不同,因此治理手段也不同。下图给出从代码提交到镜像入库的完整风险链路与对应控制点,帮助建立整体认知。

软件供应链风险链路与 GitLab 治理控制点 代码提交 MR / Push 依赖解析 npm / pip / maven 镜像构建 Dockerfile / BuildKit 镜像推送 Registry 部署运行 K8s / 主机 密钥硬编码 .env / Token 入库 依赖混淆 同名恶意公共包 基础镜像过期 累积未修复漏洞 latest 标签 构建不可复现 root 运行 权限过大 Secret Detection 提交前/后扫描 阻断含密钥 MR Dependency Scanning 生成 SBOM 比对漏洞库 Container Scanning 扫描镜像层 识别 OS 包漏洞 镜像签名 Cosign / Notation 校验来源可信 准入策略 Admission Controller 拒绝未签名镜像 治理框架四层结构 ① 策略层 漏洞等级阈值 许可证白名单 ② 检测层 SAST / 依赖扫描 镜像扫描 / SBOM ③ 阻断层 MR 门禁 镜像推送拦截 ④ 追溯层 镜像→提交→依赖 审计与复盘

治理框架:策略、检测、阻断、追溯四层联动

上图下半部分给出了治理框架的四层结构。这四层不是并列的功能清单,而是有依赖关系的闭环:策略层定义“什么算风险”,检测层负责“发现风险”,阻断层决定“是否放行”,追溯层保证“事后能查清”。如果只有检测层而没有阻断层,扫描结果就只是报告;如果只有阻断层而没有策略层,规则会因误报过多而被团队绕过。

以漏洞等级为例,策略层通常会设定:Critical 与 High 必须阻断,Medium 允许在记录豁免原因后放行,Low 仅记录。这个阈值不是拍脑袋决定的,而应结合镜像的实际暴露面。一个仅在内网运行、不对外提供服务的镜像,与一个直接暴露在公网的网关镜像,对同一个 CVE 的风险承受度完全不同。因此策略层还应支持按项目、按环境、按镜像用途设置差异化阈值。

一个可落地的判断示例

假设某项目在 GitLab CI 中构建镜像 registry.example.com/app/api:1.4.0,流水线中集成了依赖扫描与容器扫描。扫描结果如下(以下为教学示例数据,非实测结果):

风险项对象等级策略判定
CVE-2021-44228log4j-core 2.14.1(传递依赖)Critical阻断
CVE-2023-1234openssl 1.1.1(基础镜像 OS 包)High阻断
GPL-3.0某工具库许可证需法务确认
root 运行镜像配置配置缺陷告警

面对这个结果,正确的处理顺序不是“先修最容易的”,而是先判断阻断项是否真实可达。log4j-core 若确实被应用加载并处理外部输入,则必须升级到 2.17.1 以上;若只是构建期工具依赖、不进入运行时镜像,则可通过调整依赖范围或使用多阶段构建将其排除。openssl 属于基础镜像层,应通过更新基础镜像标签或重建镜像解决,而不是在应用层打补丁。许可证问题需要法务介入,不能由工程单方面决定。root 运行属于配置问题,可在 Dockerfile 中增加非 root 用户并设置 USER 指令。

判断依据:阻断项优先看“是否可达、是否在运行时镜像中、是否暴露于外部输入”。不可达的漏洞可以记录豁免,但豁免必须有期限和责任人,不能永久静默。

本课程的学习路径

后续章节将围绕这条链路逐步展开:先建立依赖清单与 SBOM 的生成能力,再配置依赖扫描与许可证检查;随后进入容器镜像扫描、基础镜像治理与镜像签名;最后落到 GitLab CI 中的门禁策略、豁免流程与追溯审计。每一部分都会给出可执行的配置片段、示例参数和结果判读方法,而不是停留在概念层面。

需要特别强调的是,治理的目标不是“零漏洞”——这在真实项目中几乎不可达,也不经济。合理目标是:高风险对象在进入生产前被识别并阻断,中低风险对象有记录、有判断、有期限,整体风险处于可接受且可解释的状态。当团队能够对每一个放行的镜像说清“它包含什么、为什么可以放行、谁批准的”,治理才算真正落地。