29-DevSecOps
传统研发流程里,安全测试往往排在最后——等功能开发完毕才交给安全团队做渗透测试,结果要么上线前发现一堆高危漏洞,要么"先上线再说",安全变成摆设。DevSecOps 的核心思想:安全左移,把安全检查前置到每一个研发阶段。本章覆盖安全左移理念、SAST/SCA/DAST 工具链、镜像签名、K8s 安全加固。
安全左移#
"左移"来自研发生命周期的时间轴:代码编写在左,生产部署在右。安全左移意味着在时间轴上尽量靠左发现问题——在 IDE 里、在 commit 钩子里、在 CI 流水线里,而不是等到生产环境。
成本与阶段相关:生产环境修复一个漏洞的成本是开发阶段的 30 倍以上。越晚发现,需要回滚的代码越多,影响面越广,协调成本越高。
安全即代码#
安全规则本身也应该版本化管理——OPA 策略、Semgrep 规则、Vault 的 Secret 路径定义都应该存在 Git 里,跟代码一起 Review、测试、部署。这样才能避免"安全配置漂移"。
每个阶段的安全门禁#
代码提交 → SAST 扫描 → 依赖漏洞扫描(SCA) → 构建镜像 → 镜像漏洞扫描
→ 镜像签名 → 部署 Staging → 动态扫描(DAST) → 合规检查 → 生产部署每个阶段都有"门禁"——发现高危问题则阻断流水线。门禁严格程度分级:HIGH 阻断,MEDIUM 警告,LOW 仅记录。
代码阶段:SAST#
静态应用安全测试(SAST)分析源代码,不需要运行程序。能发现 SQL 注入、XSS、路径遍历、硬编码密钥、不安全加密等。
Semgrep:轻量级可定制#
# 自定义规则——检测硬编码 AWS 密钥
rules:
- id: hardcoded-aws-key
patterns:
- pattern: |
$KEY = "AKIA..."
message: "检测到硬编码的 AWS Access Key,请使用环境变量或 Vault"
languages: [python, go, javascript]
severity: ERROR# 使用官方规则集扫描
semgrep --config=p/security-audit --config=p/owasp-top-ten .Pre-commit 钩子#
最早的安全左移是在开发者本地——提交前自动运行扫描:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
hooks:
- id: gitleaks
name: 检测敏感信息泄露
- repo: https://github.com/returntocorp/semgrep
hooks:
- id: semgrep
args: ['--config=p/secrets', '--error']依赖阶段:SCA#
现代应用 70-90% 的代码来自开源依赖。Log4Shell(CVE-2021-44228)就是最典型的例子——Java 应用广泛使用的 log4j 存在 RCE 漏洞,几乎影响了所有 Java 生态。SCA(软件成分分析)解决这类问题。
Trivy:最主流的扫描工具#
# 扫描镜像
trivy image nginx:1.25
# 只报告 HIGH 和 CRITICAL
trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:1.25
# 扫描文件系统(依赖文件)
trivy fs --scanners vuln,secret . # 旧参数 --security-checks 自 v0.37 起弃用CI 中集成——两次扫描设计:LOW/MEDIUM 只打印不阻断,HIGH/CRITICAL 直接失败:
trivy-scan:
script:
- trivy image --exit-code 0 --severity LOW,MEDIUM ${IMAGE}
- trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed ${IMAGE}--ignore-unfixed 过滤掉暂无修复版本的漏洞,减少误报噪音。
最小化基础镜像#
选择合适的基础镜像从源头减少攻击面:
# 避免:完整的 Ubuntu/Debian
FROM ubuntu:22.04
# 推荐:distroless(只包含运行时)
FROM gcr.io/distroless/static-debian12
# Go 应用最佳实践:多阶段构建 + distroless
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]使用 distroless 镜像后,Trivy 扫描结果通常从几十个 CVE 降到个位数甚至零。
镜像签名:Cosign#
供应链攻击(SolarWinds 事件)让行业重视软件供应链安全。Cosign 是 Sigstore 项目的核心工具,对容器镜像签名和验证。
# 生成密钥对
cosign generate-key-pair
# cosign.key(私钥)存 CI 密钥管理系统,cosign.pub(公钥)公开
# 在 CI 中签名
cosign sign --key env://COSIGN_PRIVATE_KEY ${IMAGE}
# 验证镜像签名
cosign verify --key cosign.pub myregistry.com/myapp:v1.0.0Kyverno 强制验证#
Kyverno 是 K8s 原生策略引擎,在 Admission 阶段拦截未签名镜像:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: check-image-signature
match:
any:
- resources:
kinds: [Pod]
namespaces: [production, staging]
verifyImages:
- imageReferences: ["myregistry.com/myapp/*"]
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----强制要求所有部署到 production/staging 的 Pod,镜像必须有 Cosign 签名,否则拒绝创建。
K8s 安全加固#
Pod Security Admission#
K8s 1.25 用 Pod Security Admission(PSA)替代了 PodSecurityPolicy。三个策略级别:
privileged:无限制(只给基础设施命名空间)baseline:禁止已知高危配置(hostPID、hostIPC、privileged 容器)restricted:最严格(只读文件系统、非 root 运行、禁止特权提升)
通过命名空间标签启用:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedSecurityContext 最佳实践#
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
volumeMounts:
- name: tmp # 需要写临时文件时挂载 emptyDir
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Network Policy:微分段#
默认 K8s 所有 Pod 互通。Network Policy 实现微分段,最小化爆炸半径:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
spec:
podSelector:
matchLabels: { app: backend }
policyTypes: [Ingress, Egress]
ingress:
- from:
- podSelector:
matchLabels: { app: frontend } # 只允许 frontend 访问
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels: { app: postgres } # 只允许访问数据库
ports:
- protocol: TCP
port: 5432完整 DevSecOps 流水线#
stages:
- security-sast # Semgrep + SonarQube
- security-sca # Snyk 依赖扫描
- build
- security-image # Trivy 镜像扫描
- sign # Cosign 签名
- deploy-staging
- security-dast # ZAP 动态扫描
- deploy-production # 人工审批门禁策略#
| 阶段 | 工具 | 阻断条件 |
|---|---|---|
| 代码扫描 | Semgrep | ERROR 级别规则 |
| 代码质量 | SonarQube | Quality Gate 不通过 |
| 依赖扫描 | Snyk/Trivy | CVSS ≥ 7.0 |
| 镜像扫描 | Trivy | HIGH/CRITICAL 且有修复版本 |
| 运行时 | Kyverno | 未签名镜像 |
| 合规 | kube-bench | CIS 检查项 FAIL 超阈值 |
落地注意事项#
渐进式引入,不要一刀切。第一周先跑扫描但不阻断,收集现有代码库的漏洞基线。第二周对新增代码启用阻断。第三周再逐步要求修复存量漏洞。
维护误报白名单。每个工具都有误报。建立白名单流程:安全团队 Review 后,将特定问题加入白名单并记录理由和到期时间。
安全指标可视化。把扫描结果推到 Grafana——每周新增漏洞数、修复平均时间(MTTR)、各严重级别趋势。数据可见,才能驱动改进。
小结#
DevSecOps 的核心是安全左移——把安全检查从末尾挪到每个研发阶段。SAST 扫代码(Semgrep)、SCA 扫依赖(Trivy/Snyk)、镜像扫描(Trivy)、镜像签名(Cosign)+ Kyverno 强制验证,构成从代码到生产的全链路安全门禁。K8s 层面用 Pod Security Admission(restricted 级别)+ SecurityContext + Network Policy 加固。落地要渐进——先跑不阻断收集基线,再逐步启用门禁。安全不是一次性项目,而是持续运营的能力建设。