深色模式
供应链安全:依赖与镜像来源治理
摘要:你的系统安全性不超过你引入的最弱依赖。本文从依赖锁定、SBOM 生成、镜像来源校验、CI 流水线防护四个方向给出可复制做法。
适用环境
bash
cat /etc/os-release
command -v trivy && trivy --version
command -v syft && syft version
command -v cosign && cosign version
command -v npm && npm --version
command -v pip && pip --version1
2
3
4
5
6
2
3
4
5
6
操作步骤
1. 依赖来源:先做到"可复现、可追溯"
bash
# Python:锁定版本并带哈希
pip freeze > requirements.txt
pip install --require-hashes -r requirements.txt
# Node.js:提交 lock 文件,CI 用 ci 而非 install
npm ci --ignore-scripts # --ignore-scripts 可避免安装期执行任意脚本
npm audit --audit-level=high
# Go:校验依赖哈希
go mod tidy && go mod verify
grep -c "" go.sum1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
允许依赖包执行安装脚本等于给了上游任意代码执行权
npm install 默认会跑 postinstall。CI 环境建议 --ignore-scripts,确需脚本的单独加白名单。
2. 私有源与代理仓库
搭建内部代理源(Nexus/Artifactory/自建 PyPI),好处:
- 缓存并审查外部包,上游被下架或被投毒时仍有缓冲。
- 可设置准入策略(禁止引入某些包或版本)。
- 出问题时能快速定位受影响范围。
bash
pip config set global.index-url https://pypi.internal/simple
npm config set registry https://npm.internal/
cat ~/.npmrc1
2
3
2
3
3. 生成 SBOM:知道你到底用了什么
bash
# 镜像 SBOM
syft nginx:1.25 -o cyclonedx-json > sbom-nginx.json
# 目录 SBOM
syft dir:/opt/myapp -o spdx-json > sbom-app.json
jq -r '.artifacts[] | "\(.name)@\(.version)"' sbom-nginx.json | head -201
2
3
4
5
2
3
4
5
漏洞爆发时的价值:用 SBOM 直接查"我们有没有用到这个包",而不是全盘排查。
bash
# 检查某个组件是否在 SBOM 中
grep -i "log4j" sbom-app.json | head -31
2
2
4. 镜像来源校验
bash
# 只用可信仓库的基础镜像,且用摘要锁定
docker pull alpine@sha256:<digest>
docker inspect alpine:3.19 --format '{{index .RepoDigests 0}}'
# 签名与验签
cosign sign --key cosign.key registry.internal/base/alpine:3.19
cosign verify --key cosign.pub registry.internal/base/alpine:3.191
2
3
4
5
6
7
2
3
4
5
6
7
CI 中强制验签:
bash
cosign verify --key cosign.pub "$IMAGE" || { echo "镜像未签名,拒绝部署"; exit 1; }1
5. 镜像构建来源可信
bash
# 优先用官方/可信基础镜像,避免来历不明的 "xxx/latest"
docker history myapp:1.0 --no-trunc | head -20
docker inspect myapp:1.0 --format '{{.Config.Labels}}'1
2
3
2
3
Dockerfile 检查项:
- 基础镜像带 tag 或 digest,不用
latest。 - 不在 Dockerfile 里写密钥(
ARG也会留在历史里)。 - 不
curl xxx | sh安装(上游被劫持即中招)。
bash
grep -nE "curl .*\| *(sudo )?(ba)?sh|wget .*\| *(sudo )?(ba)?sh" Dockerfile1
curl | sh 是典型的供应链攻击入口
必须改为下载 → 校验哈希/签名 → 执行,并把安装包固化到内部源。
6. CI 流水线防护
- 流水线密钥用平台 secret 注入,不写进代码或日志。
- 禁止流水线打印敏感变量(注意
set -x)。 - 限制谁能修改流水线定义(改 CI 等于改发布权限)。
- 保护分支:主分支禁止直接 push,必须走评审。
bash
# 检查流水线日志是否泄漏密钥
grep -rInE "(AWS_SECRET|GITHUB_TOKEN|password)=[A-Za-z0-9/+]{16,}" ci-logs/ 2>/dev/null | head -5
# 排查代码中的硬编码凭据
detect-secrets scan --all-files 2>/dev/null | head -201
2
3
4
2
3
4
7. 准入控制:部署前的最后一道闸
bash
# 镜像扫描门禁
trivy image --exit-code 1 --severity CRITICAL "$IMAGE"
# 配置合规扫描
trivy config --exit-code 1 k8s/1
2
3
4
2
3
4
K8s 侧限制镜像来源(需配合准入控制器如 OPA/Gatekeeper/Kyverno):
yaml
# Kyverno 示例:只允许指定仓库且必须带 digest
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
validationFailureAction: Enforce
rules:
- name: only-internal-registry
match:
any:
- resources:
kinds: [Pod]
validate:
message: "只允许使用 registry.internal 的镜像"
pattern:
spec:
containers:
- image: "registry.internal/*"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
8. 建立上游风险响应机制
- 订阅所使用关键组件的安全公告(邮件列表/GitHub Security Advisory)。
- 漏洞爆发时:用 SBOM 定位影响面 → 升级或缓解 → 验证 → 复盘。
bash
trivy image --severity CRITICAL --format json -o /tmp/scan.json "$IMAGE"
jq -r '.Results[].Vulnerabilities[]? | "\(.PkgName) \(.InstalledVersion) → \(.FixedVersion)"' /tmp/scan.json | head1
2
2
验证
bash
syft myapp:1.0 -o cyclonedx-json | jq '.artifacts | length'
cosign verify --key cosign.pub registry.internal/myapp:1.0 && echo "签名校验通过"
trivy image --severity CRITICAL --exit-code 1 myapp:1.0; echo "exit=$?"
grep -nE "curl .*\| *(sudo )?(ba)?sh" Dockerfile | wc -l # 应为 0
npm ci --ignore-scripts && npm audit --audit-level=high1
2
3
4
5
2
3
4
5
判定标准:依赖有 lock 文件与哈希校验;镜像全部签名并可验签;有 SBOM 可查;CI 无密钥泄漏。
常见坑
使用 latest 标签导致无法追溯和回滚
必须打不可变 tag 或 digest,否则出事时不知道跑的是哪份代码。
允许依赖执行安装脚本
恶意包可在安装阶段执行命令窃取 CI 密钥。用 --ignore-scripts 并加白名单。
只扫自己的代码不扫依赖
现代应用 70% 以上代码来自第三方。依赖与镜像必须纳入扫描。
没有 SBOM,漏洞爆发时全盘抓瞎
Log4j 类事件的关键教训就是"不知道自己有没有用"。SBOM 应成为构建产物的一部分。
私有源无准入策略
私有源只是缓存,不等于审查。要配置准入规则并定期清理高危包。