CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
DevSecOps 入门/课时/供应链安全
RawBlame
课时目录12 节

DevSecOps 入门

  1. 01DevSecOps 概览
  2. 02网络安全基础
  3. 03密码学
  4. 04身份与访问
  5. 05应用安全
  6. 06威胁建模
  7. 07云安全
  8. 08容器安全
  9. 09供应链安全
  10. 10检测与 SIEM
  11. 11Incident 响应
  12. 12合规与扫描

供应链安全

依赖风险与管道加固。

30 分钟

供应链安全

这一课你将能做到

  1. 说清依赖风险的本质:为什么你只看了一页 pom.xml,却信任了 500 个陌生 jar
  2. 在 CI 里配置 Dependabot + lockfile + 签名验证,阻止已知漏洞和恶意包进入构建
  3. 画出本站的依赖树,并识别出 3 类高风险依赖(过旧版本、未使用、来自个人维护者)

为什么要学

你的应用不是完全由你写的。pom.xml 里 30 个直接依赖,实际拉下来可能 300 个间接依赖。Log4j2(log4j-core)只是其中之一,但一次远程代码执行漏洞就席卷全球。2023 年 xz 后门事件则证明:依赖不仅可以有漏洞,还可以被主动植入后门。

人话:供应链攻击是「借刀杀人」——我不打你的代码,我打你信任的第三方包。你 import 的那个库如果被攻陷,等于你自己的服务器被攻陷。防御三原则:知道你在依赖什么、只信你验证过的、能回退能审计。

先建立图像

一个 Spring Boot 项目的真实依赖图:

你的代码: myapp.jar
    │
    ├─ spring-boot-starter-web (官方)
    │       ├─ spring-web  ← 官方可信
    │       ├─ tomcat-embed-core  ← 注意 CVE 历史
    │       └─ jackson-databind  ← 多次 RCE 漏洞
    │
    ├─ mybatis-plus (第三方)
    │       ├─ mybatis  ← 个人维护者
    │       └─ jsqlparser  ← 更小项目
    │
    └─ commons-compress (第三方)
            └─ xz-java  ← xz 后门同款

直接依赖: 30 个
传递依赖: 300+ 个
攻击面: 每个 jar 都是

构建管道的信任链:

源代码 ──→ 依赖解析 ──→ 编译构建 ──→ 镜像构建 ──→ 部署上线
   │          │             │             │             │
   │          ├ lockfile    ├ 签名工具    ├ SBOM       ├ 准入校验
   │          │ 防漂移      │ 防篡改      │ 可追溯     │ 拒绝未签名的
   │          │             │             │             │
   └─ git ────┴─ 仓库 ─────┴─ 制品库 ────┴─ 镜像仓库 ──┴─ K8s

跟着做

步骤 1:看依赖树,知道你在信任什么

mvn dependency:tree > deps.txt
# 重点看:
# - 过旧版本(是否有 CVE)
# - 未使用的依赖(代码里没 import)
# - 来自个人或极小项目的传递依赖

也可以用插件可视化:

mvn com.github.ferstl:depgraph-maven-plugin:graph
# 生成 .dot 文件,用 Graphviz 转成 PNG 查看

清理未使用依赖(用 mvn dependency:analyze):

mvn dependency:analyze
# Unused declared dependencies:   可以删掉
# Used undeclared dependencies:   显式声明(避免传递版本变更导致意外)

步骤 2:锁定依赖版本(lockfile)

Maven 不像 npm 有原生 lockfile,用 dependency:resolve 生成固定版本快照:

# 把当前所有解析出的版本锁定到一个文件
mvn dependency:resolve dependency:copy-dependencies \
  -DoutputDirectory=lib -DincludeScope=runtime

# 更推荐:在 pom 里显式管理传递依赖版本
<dependencyManagement>
  <dependencies>
    <!-- 显式声明你关心的传递依赖版本,避免被覆盖 -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.17.0</version>
    </dependency>
  </dependencies>
</dependencyManagement>

步骤 3:Dependabot / Renovate 自动提升级 PR

GitHub 仓库开启 Dependabot:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "maven"
    directory: "/"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 10
    # 高危 CVE 自动创建 PR
    allow:
      - dependency-type: "direct"
    commit-message:
      prefix: "deps"

规则:Dependabot 提的 PR 不是直接合并,而是:

  1. 看 CHANGELOG 确认没有破坏性变更
  2. 跑全量测试
  3. 扫描升级后的新依赖树是否引入新的 CVE
  4. 合并并打 tag

步骤 4:CI 中强制依赖扫描(已在本课之前实现,这里补充策略)

# 在 CI 中,顺序很重要:
1. 编译前: gitleaks 扫描(防密钥泄漏)
2. 编译中: mvn dependency-check:check(OWASP 漏洞扫描)
   - 配置 failBuildOnCVSS=7
3. 编译后: Trivy 扫描 jar 包(容器镜像扫描)
4. 归档:   生成 SBOM(Trivy/ Syft)

步骤 5:验证下载的依赖(签名校验)

Maven Central 上的包有 PGP 签名。开启严格校验:

<!-- settings.xml -->
<profiles>
  <profile>
    <id>strict-signatures</id>
    <properties>
      <maven.artifact.threads>1</maven.artifact.threads>
    </properties>
    <build>
      <plugins>
        <plugin>
          <groupId>org.simplify4u.plugins</groupId>
          <artifactId>pgpverify-maven-plugin</artifactId>
          <version>1.18.0</version>
          <executions>
            <execution>
              <goals><goal>check</goal></goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

这能防止「依赖包在传输中被替换」或「仓库被投毒」。但注意:签名只能证明「这个包是谁发布的」,不能证明「这个包没有漏洞」。漏洞扫描仍然是必要的。

步骤 6:构建管道加固

CI 加固清单:
□ 编译和部署用不同身份(编译只读源码,部署只推送制品)
□ CI 密钥(AK、Docker Hub token)走 Vault / KMS,不写在环境变量明文
□ CI 里禁止 `--no-verify` 跳过 git hooks
□ 每次构建产物附带 SBOM + 签名(cosign sign)
□ 生产只部署有签名的制品,拒绝任何手工上传的 jar/镜像
# 用 cosign 给镜像签名(基于 Sigstore 生态)
cosign sign --key env://COSIGN_PRIVATE_KEY coderoadmap/app:1.2.3

# K8s 准入控制器验证签名,未签名的镜像拒绝启动

讲清楚要点

  1. 依赖数量爆炸是常态:30 个直接依赖可能带 300 个传递依赖,你不是在审核 30 个,是在信任一个巨大的陌生人网络。必须自动化——人工不可能看 300 个库的代码。

  2. lockfile 防的是「构建不可重现」:今天 mvn package 拉的是版本 A,明天开发者 B 构建时因为某个传递依赖发了新版本 B,行为变了,排查爆炸。lockfile 或 dependencyManagement 显式锁定每个版本。

  3. 已知漏洞修得快,未知后门防不了:Dependabot 解决的是前者(公开 CVE)。xz 后门这类未知威胁,要靠「基础镜像最小化」「应用沙箱」「行为监控」兜底——供应链安全不是万能药。

  4. 签名解决「谁发布的」不解决「有没有 bug」:PGP/cosign 签名防止中间人替换包,但原作者的代码可能有漏洞。签名+扫描+最小化三层都要。

  5. 构建管道本身也是攻击面:如果 CI 服务器被攻破,攻击者可以在编译时往 jar 里注入恶意代码。CI 加固(最小权限、密钥管理、审计日志、签名验证)是供应链安全的最后一环。

你可能会懵的地方

  • 所有依赖升级都要手动审吗? 不需要。低风险(patch 版本、测试覆盖好的)可以自动化合并;中风险(minor 版本)跑测试后合;高风险(major 版本、核心框架如 Spring Boot)人工审。Dependabot 可以按 severity 自动分组。

  • Maven 没有 lockfile 怎么办? 用 dependencyManagement 显式声明所有关键传递依赖版本,或用 versions-maven-plugin 锁定。或者用 Gradle(原生支持 lockfile)。核心原则一样:构建结果可重现。

  • 私有仓库(Nexus/Artifactory)怎么防投毒? 配置代理仓库时只缓存,不自动信任;对私有发布包强制签名;定期和上游中央仓库同步校验哈希;内部包和外部包用不同 groupId 前缀区分。

  • SBOM 和 lockfile 什么区别? lockfile 是构建输入(我要用什么版本),SBOM 是构建输出(实际打进去的是什么)。两者都重要——lockfile 保证可重现,SBOM 用于事后溯源。

  • 如果依赖的作者删库跑路(left-pad 事件)怎么办? 私有仓库缓存解决(Nexus/Artifactory 缓存了就不会消失)。生产构建不从公网直接拉,从私有仓库拉。同时评估依赖的「巴士系数」——一个人维护的项目风险极高。

错法对照

错法问题正解
pom.xml 里版本写 LATEST 或范围不可重现、随时引入新 CVE显式固定版本
看到 Dependabot PR 直接合可能引入破坏性变更看 CHANGELOG + 跑全量测试
构建从公网 Maven Central 直接拉网络故障/删库即构建失败私有 Nexus 缓存
不扫传递依赖只关注 30 个直接依赖,漏掉 300 个OWASP Dependency-Check 扫全树
CI 用 root / 主账号 AKCI 被攻破=全账号沦陷最小权限角色+KMS/Vault
没有 SBOM出漏洞时不知道哪个版本受影响每次发版生成并归档

卡点急救

  1. Dependency-Check 报告误报太多:对确认的误报加 suppression XML(写明 CVE、原因、复查日期),不要简单设成 failBuildOnCVSS=10 回避问题。维护好 suppression 列表是工程纪律。

  2. 升级 Spring Boot 大版本后一堆兼容问题:不要一次升级多个 major 版本。Spring Boot 有迁移指南和 spring-boot-properties-migrator。先在测试环境跑全量集成测试,确认 actuator、security 配置都正常再推生产。

  3. 私有仓库缓存的包和上游不一致:可能是缓存时上游还没被篡改,之后被篡改了——但你的缓存是干净的;也可能是缓存本身被污染。定期用 mvn dependency:purge-local-repository 清本地,再和上游哈希比对。对关键包启用 PGP 签名验证。

面试常问

  • 什么是供应链攻击?举个例子? 攻击者不直接攻击目标系统,而是攻击其依赖的第三方组件(开源库、构建工具、CI/CD 管道)。例子:Log4Shell(log4j2 的 JNDI 解析漏洞)、xz 后门(压缩库维护者身份被窃取后植入后门)、SolarWinds(构建管道被入侵,在编译时注入恶意代码)。防御需要覆盖依赖选择、构建加固、签名验证、SBOM 溯源全链路。

  • 如何防止恶意依赖包进入生产? ① 来源控制:只从可信仓库(Maven Central)拉取,私有 Nexus 缓存;② 版本锁定:lockfile/dependencyManagement 固定版本;③ 签名验证:PGP/cosign 验证包未被篡改;④ 漏洞扫描:CI 中 OWASP Dependency-Check + Trivy 卡高危;⑤ 准入控制:K8s 只运行签过名的镜像;⑥ SBOM 归档:出事后快速定位受影响版本。

  • Log4Shell 的教训是什么? ① 日志框架做 lookup(JNDI/LDAP)是过度设计,一个日志库不应能触发网络请求;② 间接依赖同样是攻击面;③ 修复窗口极短但定位窗口极长——没有 SBOM 的团队手动排查数周;④ 安全修复要快:先升级再验证,不要等完美方案;⑤ 纵深防御:即使应用有漏洞,WAF 规则、网络隔离、最小权限也能降低损害。

自测三问

  1. 你的 Spring Boot 项目实际有多少个传递依赖?怎么查看?
  2. lockfile 和 SBOM 分别解决什么问题?
  3. 如果 CI 服务器被攻破,攻击者能在供应链的哪个环节作恶?怎么防?

小结一句话

供应链安全的铁律:知道你在依赖什么(SBOM)、只信验证过的(签名+扫描)、构建可重现(lockfile)——Log4Shell 不会是你遇到的最后一次依赖危机。

延伸

  • SLSA 框架 — Google 发起的供应链安全框架,四级成熟度模型
  • 下一课:检测与 SIEM——从「防住攻击」到「发现攻击」,日志、告警、关联规则与自动化响应。
加载本节练习…
← 上一课容器安全下一课 →检测与 SIEM