Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

依赖管理与供应链安全 🟢

你将学到:

  • 使用 cargo-audit 扫描已知漏洞
  • 使用 cargo-deny 执行许可证、咨询和来源策略
  • 使用 Mozilla 的 cargo-vet 验证供应链信任
  • 跟踪过时的依赖并检测破坏性 API 变更
  • 可视化和去重你的依赖树

交叉引用:发布 Profile — cargo-udeps 修剪在此处发现的未使用依赖 · CI/CD 流水线 — 流水线中的审计和 deny 任务 · 构建脚本 — build-dependencies 也是你的供应链的一部分

Rust 二进制文件不仅仅包含你的代码——它还包含 Cargo.lock 中的每个传递依赖。依赖树中任何位置的漏洞、许可证违规或恶意 crate 都会成为你的问题。本章介绍使依赖管理可审计和自动化的工具。

cargo-audit — 已知漏洞扫描

cargo-audit 根据 RustSec 咨询数据库 检查你的 Cargo.lock,该数据库跟踪已发布 crate 中的已知漏洞。

# 安装
cargo install cargo-audit

# 扫描已知漏洞
cargo audit

# 输出:
# Crate:     chrono
# Version:   0.4.19
# Title:     localtime_r 调用中潜在的 segfault
# Date:      2020-11-10
# ID:        RUSTSEC-2020-0159
# URL:       https://rustsec.org/advisories/RUSTSEC-2020-0159
# Solution:  升级到 >= 0.4.20

# 检查并在存在漏洞时使 CI 失败
cargo audit --deny warnings

# 生成 JSON 输出用于自动处理
cargo audit --json

# 通过更新 Cargo.lock 修复漏洞
cargo audit fix

CI 集成:

# .github/workflows/audit.yml
name: Security Audit
on:
  schedule:
    - cron: '0 0 * * *'  # 每日检查 — 咨询持续出现
  push:
    paths: ['Cargo.lock']

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: rustsec/audit-check@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

cargo-deny — 综合策略执行

cargo-deny 远超漏洞扫描。它在四个维度执行策略:

  1. Advisories — 已知漏洞(类似 cargo-audit)
  2. Licenses — 允许/拒绝的许可证列表
  3. Bans — 禁止的 crate 或重复版本
  4. Sources — 允许的注册表和 git 来源
# 安装
cargo install cargo-deny

# 初始化配置
cargo deny init
# 创建带有文档化默认值的 deny.toml

# 运行所有检查
cargo deny check

# 运行特定检查
cargo deny check advisories
cargo deny check licenses
cargo deny check bans
cargo deny check sources

示例 deny.toml:

# deny.toml

[advisories]
vulnerability = "deny"        # 在已知漏洞时失败
unmaintained = "warn"         # 在未维护的 crate 时警告
yanked = "deny"               # 在被撤回的 crate 时失败
notice = "warn"               # 在信息性咨询时警告

[licenses]
unlicensed = "deny"           # 所有 crate 必须有许可证
allow = [
    "MIT",
    "Apache-2.0",
    "BSD-2-Clause",
    "BSD-3-Clause",
    "ISC",
    "Unicode-DFS-2016",
]
copyleft = "deny"             # 此项目中无 GPL/LGPL/AGPL
default = "deny"              # 拒绝任何未明确允许的内容

[bans]
multiple-versions = "warn"    # 如果同一 crate 出现在 2 个版本则警告
wildcards = "deny"            # 依赖中无 path = "*"

# 禁止特定有问题的 crate
deny = [
    # openssl-sys 引入 C OpenSSL —  предпочитать rustls
    { name = "openssl-sys", wrappers = ["native-tls"] },
]

# 允许特定重复版本(当不可避免时)
[[bans.skip]]
name = "syn"
version = "1.0"               # syn 1.x 和 2.x 经常共存

[sources]
unknown-registry = "deny"     # 只允许 crates.io
unknown-git = "deny"          # 无随机 git 依赖
allow-registry = ["https://github.com/rust-lang/crates.io-index"]

许可证执行对于商业项目特别有价值:

# 检查你的依赖树中有哪些许可证
cargo deny list

# 输出:
# MIT          — 127 crates
# Apache-2.0   — 89 crates
# BSD-3-Clause — 12 crates
# MPL-2.0      — 3 crates   ← 可能需要法律审查
# Unicode-DFS  — 1 crate

cargo-vet — 供应链信任验证

cargo-vet(来自 Mozilla)解决一个不同的问题:不是“这个 crate 有已知 bug 吗?“而是“有可信的人实际审查过这段代码吗?”

# 安装
cargo install cargo-vet

# 初始化(创建 supply-chain/ 目录)
cargo vet init

# 检查哪些 crate 需要审查
cargo vet

# 审查 crate 后,认证它:
cargo vet certify serde 1.0.203
# 记录你已为你的标准审计了 serde 1.0.203

# 从可信组织导入审计
cargo vet import mozilla
cargo vet import google
cargo vet import bytecode-alliance

工作原理:

supply-chain/
├── audits.toml       ← 你团队的审计认证
├── config.toml       ← 信任配置和标准
└── imports.lock      ← 从其他组织导入的固定

cargo-vet 对于具有严格供应链要求的组织(政府、金融、基础设施)最有价值。对于大多数团队,cargo-deny 提供足够的保护。

cargo-outdated 和 cargo-semver-checks

cargo-outdated — 查找有更新的依赖:

cargo install cargo-outdated

cargo outdated --workspace
# 输出:
# Name        Project  Compat  Latest   Kind
# serde       1.0.193  1.0.203 1.0.203  Normal
# regex       1.9.6    1.10.4  1.10.4   Normal
# thiserror   1.0.50   1.0.61  2.0.3    Normal  ← 主要版本可用

cargo-semver-checks — 在发布前检测破坏性 API 变更。对于库 crate 至关重要:

cargo install cargo-semver-checks

# 检查你的更改是否是 semver 兼容的
cargo semver-checks

# 输出:
# ✗ 函数 `parse_gpu_csv` 现在是私有的(之前是公共的)
#   → 这是 BREAKING 更改。提升 MAJOR 版本。
#
# ✗ 结构体 `GpuInfo` 有新的必需字段 `power_limit_w`
#   → 这是 BREAKING 更改。提升 MAJOR 版本。
#
# ✓ 函数 `parse_gpu_csv_v2` 已添加(非破坏性)

cargo-tree — 依赖可视化和去重

cargo tree 内置于 Cargo 中(无需安装),对于理解依赖图非常宝贵:

# 完整依赖树
cargo tree

# 查找为什么包含特定 crate
cargo tree --invert --package openssl-sys
# 显示从你的 crate 到 openssl-sys 的所有路径

# 查找重复版本
cargo tree --duplicates
# 输出:
# syn v1.0.109
# └── serde_derive v1.0.193
#
# syn v2.0.48
# ├── thiserror-impl v1.0.56
# └── tokio-macros v2.2.0

# 仅显示直接依赖
cargo tree --depth 1

# 显示依赖特性
cargo tree --format "{p} {f}"

# 计算依赖总数
cargo tree | wc -l

去重策略:当 cargo tree --duplicates 显示同一 crate 有两个主要版本时,检查是否可以更新依赖链以统一它们。每个重复都会增加编译时间和二进制大小。

应用:多 Crate 依赖卫生

workspace 使用 [workspace.dependencies] 进行集中版本管理——这是一个极好的实践。与用于大小分析的 cargo tree --duplicates 结合使用,可以防止版本漂移并减少二进制膨胀:

# 根 Cargo.toml — 所有版本集中在一个地方固定
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
regex = "1.10"
thiserror = "1.0"
anyhow = "1.0"
rayon = "1.8"

项目推荐的添加:

# 添加到 CI 流水线:
cargo deny init              # 一次性设置
cargo deny check             # 每个 PR — 许可证、咨询、禁止
cargo audit --deny warnings  # 每个推送 — 漏洞扫描
cargo outdated --workspace   # 每周 — 跟踪可用更新

项目推荐的 deny.toml:

[advisories]
vulnerability = "deny"
yanked = "deny"

[licenses]
allow = ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC", "Unicode-DFS-2016"]
copyleft = "deny"     # 硬件诊断工具 — 无 copyleft

[bans]
multiple-versions = "warn"   # 跟踪重复,暂时不阻塞
wildcards = "deny"

[sources]
unknown-registry = "deny"
unknown-git = "deny"

供应链审计流水线

flowchart LR
    PR["Pull Request"] --> AUDIT["cargo audit<br/>已知 CVE"]
    AUDIT --> DENY["cargo deny check<br/>许可证 + 禁止 + 来源"]
    DENY --> OUTDATED["cargo outdated<br/>每周计划"]
    OUTDATED --> SEMVER["cargo semver-checks<br/>仅库 crate"]
    
    AUDIT -->|"失败"| BLOCK["❌ 阻止合并"]
    DENY -->|"失败"| BLOCK
    SEMVER -->|"破坏性更改"| BUMP["提升主要版本"]
    
    style BLOCK fill:#ff6b6b,color:#000
    style BUMP fill:#ffd43b,color:#000
    style PR fill:#e3f2fd,color:#000

🏋️ 练习

🟢 练习 1:审计你的依赖

在任何 Rust 项目上运行 cargo audit 和 cargo deny init && cargo deny check。发现多少咨询?你的树中有多少许可证类别?

解决方案
cargo audit
# 注意任何咨询 — 通常是 chrono、time 或较旧的 crate

cargo deny init
cargo deny list
# 显示许可证细分:MIT (N)、Apache-2.0 (N) 等

cargo deny check
# 显示所有四个维度的完整审计

🟡 练习 2:查找并消除重复依赖

在 workspace 上运行 cargo tree --duplicates。找到出现在两个版本的 crate。你能更新 Cargo.toml 来统一它们吗?测量编译时间和二进制大小的影响。

解决方案
cargo tree --duplicates
# 典型:syn 1.x 和 syn 2.x

# 查找谁引入了旧版本:
cargo tree --invert --package syn@1.0.109
# 输出:serde_derive 1.0.xxx -> syn 1.0.109

# 检查是否有更新的 serde_derive 使用 syn 2.x:
cargo update -p serde_derive
cargo tree --duplicates
# 如果 syn 1.x 消失了,你已经消除了一个重复

# 测量影响:
time cargo build --release  # 之前和之后
cargo bloat --release --crates | head -20

关键要点

  • cargo audit 捕获已知 CVE — 在每个推送和每日计划上运行它
  • cargo deny 执行四个策略维度:咨询、许可证、禁止和来源
  • 使用 [workspace.dependencies] 在多 crate workspace 中集中版本管理
  • cargo tree --duplicates 揭示膨胀;每个重复都会增加编译时间和二进制大小
  • cargo-vet 适用于高安全环境;cargo-deny 对大多数团队足够