DevSecOps实践Skill devsecops-practices

本技能提供DevSecOps实践的全面指南,用于在软件开发生命周期中集成安全,涵盖左移安全、静态和动态应用安全测试(SAST/DAST/IAST)、CI/CD管道安全门、漏洞管理工作流和安全冠军计划。关键词:DevSecOps, 安全集成, CI/CD, 漏洞管理, SAST, DAST, 安全冠军, 左移安全

DevOps 0 次安装 0 次浏览 更新于 3/11/2026

name: devsecops-practices description: DevSecOps方法论指南,涵盖左移安全、SAST/DAST/IAST集成、CI/CD管道中的安全门、漏洞管理工作流和安全冠军计划。 allowed-tools: Read, Glob, Grep, Task

DevSecOps实践

使用DevSecOps原则在软件开发生命周期中集成安全的全面指南。

何时使用此技能

  • 实施左移安全实践
  • 设置SAST工具(Semgrep、CodeQL、SonarQube)
  • 配置DAST扫描(OWASP ZAP、Burp Suite)
  • 在CI/CD管道中集成安全门
  • 构建漏洞管理工作流
  • 建立安全冠军计划
  • 创建安全SDLC过程

快速参考

DevSecOps成熟度级别

级别 特征 关键实践
级别1:初始 手动安全审查,临时测试 基本漏洞扫描,安全培训
级别2:管理 CI/CD中的自动化扫描,定义的过程 SAST集成,安全门
级别3:定义 安全嵌入所有阶段,指标跟踪 DAST/IAST,威胁建模,SLA
级别4:测量 持续监控,基于风险的决策 完全自动化,安全仪表板
级别5:优化 预测性安全,持续改进 AI辅助,混沌工程

安全测试类型

类型 何时 发现什么 工具
SAST 构建时间 代码漏洞,模式 Semgrep, CodeQL, SonarQube
SCA 构建时间 依赖漏洞 Snyk, Dependabot, npm audit
DAST 运行时 运行应用漏洞 OWASP ZAP, Burp Suite
IAST 运行时 结合SAST+DAST Contrast, Seeker
Secrets 提交时间 硬编码凭据 Gitleaks, truffleHog

按管道阶段的安全门

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  提交    │───►│  构建    │───►│  测试    │───►│  部署    │───►│  生产    │
└────┬─────┘    └────┬─────┘    └────┬─────┘    └────┬─────┘    └────┬─────┘
     │               │               │               │               │
     ▼               ▼               ▼               ▼               ▼
┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│秘密扫描  │    │SAST     │    │DAST     │    │容器扫描  │    │运行时安全│
│提交前扫描  │    │SCA      │    │渗透测试  │    │配置检查  │    │监控      │
│          │    │许可证检查│    │IAST     │    │          │    │          │
└─────────┘    └─────────┘    └─────────┘    └─────────┘    └─────────┘

SAST(静态应用安全测试)

Semgrep设置

# .github/workflows/semgrep.yml
name: Semgrep
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  semgrep:
    runs-on: ubuntu-latest
    container:
      image: semgrep/semgrep

    steps:
      - uses: actions/checkout@v5

      - name: 运行Semgrep
        run: semgrep scan --config auto --sarif --output semgrep.sarif

      - name: 上传SARIF
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif

Semgrep规则配置

# .semgrep.yml
rules:
  # SQL注入
  - id: sql-injection
    patterns:
      - pattern-either:
          - pattern: cursor.execute($QUERY % ...)
          - pattern: cursor.execute($QUERY.format(...))
          - pattern: cursor.execute(f"...")
    message: "潜在SQL注入。使用参数化查询。"
    severity: ERROR
    languages: [python]

  # 硬编码秘密
  - id: hardcoded-password
    pattern-regex: '(?i)(password|passwd|pwd)\s*=\s*["\'][^"\']{8,}["\']'
    message: "检测到硬编码密码"
    severity: ERROR
    languages: [python, javascript, typescript]

  # 不安全加密
  - id: insecure-hash
    patterns:
      - pattern-either:
          - pattern: hashlib.md5(...)
          - pattern: hashlib.sha1(...)
    message: "用于加密目的时使用SHA-256或更强的算法"
    severity: WARNING
    languages: [python]

CodeQL设置

# .github/workflows/codeql.yml
name: CodeQL分析
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # 每周

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write

    strategy:
      matrix:
        language: [javascript, python]

    steps:
      - uses: actions/checkout@v5

      - name: 初始化CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          queries: +security-extended

      - name: 构建(如果需要)
        uses: github/codeql-action/autobuild@v3

      - name: 执行分析
        uses: github/codeql-action/analyze@v3
        with:
          category: "/language:${{ matrix.language }}"

SonarQube集成

# .github/workflows/sonarqube.yml
name: SonarQube分析
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  sonarqube:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0  # 完整历史以准确归因

      - name: SonarQube扫描
        uses: sonarsource/sonarqube-scan-action@master
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

      - name: 质量门检查
        uses: sonarsource/sonarqube-quality-gate-action@master
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# sonar-project.properties
sonar.projectKey=my-project
sonar.organization=my-org

# 源路径
sonar.sources=src
sonar.tests=tests

# 排除
sonar.exclusions=**/node_modules/**,**/*.test.js,**/vendor/**

# 覆盖率
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.python.coverage.reportPaths=coverage.xml

# 安全热点审查
sonar.security.hotspots.review.priority=HIGH

DAST(动态应用安全测试)

OWASP ZAP集成

# .github/workflows/zap.yml
name: OWASP ZAP扫描
on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * 0'  # 每周日2点

jobs:
  zap-scan:
    runs-on: ubuntu-latest
    services:
      app:
        image: my-app:latest
        ports:
          - 8080:8080

    steps:
      - uses: actions/checkout@v5

      - name: ZAP基线扫描
        uses: zaproxy/action-baseline@v0.11.0
        with:
          target: 'http://localhost:8080'
          rules_file_name: '.zap/rules.tsv'

      - name: ZAP完整扫描
        uses: zaproxy/action-full-scan@v0.9.0
        with:
          target: 'http://localhost:8080'
          cmd_options: '-a -j'

      - name: 上传报告
        uses: actions/upload-artifact@v4
        with:
          name: zap-report
          path: report_html.html

ZAP规则配置

# .zap/rules.tsv
# 规则ID    操作    描述
10010       忽略    # Cookie无HttpOnly标志(由框架处理)
10011       警告    # Cookie无安全标志
10015       失败    # 不完整或无缓存控制和Pragma
10016       警告    # Web浏览器XSS保护未启用
10017       失败    # 跨域JavaScript源文件包含
10019       失败    # Content-Type头缺失
10020       失败    # X-Frame-Options头未设置
10021       失败    # X-Content-Type-Options头缺失
10038       失败    # 内容安全策略头未设置

DAST在Docker Compose中

# docker-compose.security.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 5s
      timeout: 10s
      retries: 5

  zap:
    image: ghcr.io/zaproxy/zaproxy:stable
    depends_on:
      app:
        condition: service_healthy
    volumes:
      - ./zap-reports:/zap/wrk
    command: >
      zap-full-scan.py
      -t http://app:8080
      -r zap-report.html
      -J zap-report.json
      -x zap-report.xml

安全门

门配置

using System.Text.Json;
using System.Text.Json.Serialization;

/// <summary>
/// CI/CD管道中的安全门强制执行。
/// </summary>
public enum Severity { Critical, High, Medium, Low, Info }

public enum GateDecision { Pass, Warn, Fail }

/// <summary>
/// 安全门阈值的配置。
/// </summary>
public sealed record SecurityGateConfig
{
    // SAST阈值
    public int SastCriticalMax { get; init; } = 0;
    public int SastHighMax { get; init; } = 0;
    public int SastMediumMax { get; init; } = 5;

    // SCA阈值
    public int ScaCriticalMax { get; init; } = 0;
    public int ScaHighMax { get; init; } = 2;
    public double ScaCvssThreshold { get; init; } = 7.0;

    // DAST阈值
    public int DastCriticalMax { get; init; } = 0;
    public int DastHighMax { get; init; } = 1;

    // 秘密
    public int SecretsAllowed { get; init; } = 0;

    // 许可证限制
    public IReadOnlyList<string> ForbiddenLicenses { get; init; } = ["GPL-3.0", "AGPL-3.0"];
}

/// <summary>
/// 安全扫描的聚合结果。
/// </summary>
public sealed record ScanResults(
    Dictionary<string, int> SastFindings,
    Dictionary<string, int> ScaFindings,
    Dictionary<string, int> DastFindings,
    int SecretsFound,
    IReadOnlyList<string> Licenses);

/// <summary>
/// 评估CI/CD管道的安全门。
/// </summary>
public static class SecurityGateEvaluator
{
    public static (GateDecision Decision, List<string> Reasons) Evaluate(
        SecurityGateConfig config,
        ScanResults results)
    {
        var reasons = new List<string>();
        var decision = GateDecision.Pass;

        // 检查SAST
        if (results.SastFindings.GetValueOrDefault("critical", 0) > config.SastCriticalMax)
        {
            decision = GateDecision.Fail;
            reasons.Add($"SAST: {results.SastFindings["critical"]} 个关键发现(最大: {config.SastCriticalMax})");
        }

        if (results.SastFindings.GetValueOrDefault("high", 0) > config.SastHighMax)
        {
            decision = GateDecision.Fail;
            reasons.Add($"SAST: {results.SastFindings["high"]} 个高发现(最大: {config.SastHighMax})");
        }

        // 检查SCA
        if (results.ScaFindings.GetValueOrDefault("critical", 0) > config.ScaCriticalMax)
        {
            decision = GateDecision.Fail;
            reasons.Add($"SCA: {results.ScaFindings["critical"]} 个关键漏洞(最大: {config.ScaCriticalMax})");
        }

        // 检查秘密
        if (results.SecretsFound > config.SecretsAllowed)
        {
            decision = GateDecision.Fail;
            reasons.Add($"秘密: 检测到 {results.SecretsFound} 个秘密");
        }

        // 检查许可证
        foreach (var license in results.Licenses)
        {
            if (config.ForbiddenLicenses.Contains(license))
            {
                decision = GateDecision.Fail;
                reasons.Add($"许可证: 检测到禁止许可证 {license}");
            }
        }

        // 警告(不失败但报告)
        if (results.SastFindings.GetValueOrDefault("medium", 0) > config.SastMediumMax)
        {
            if (decision == GateDecision.Pass)
                decision = GateDecision.Warn;
            reasons.Add($"SAST: {results.SastFindings["medium"]} 个中等发现(阈值: {config.SastMediumMax})");
        }

        return (decision, reasons);
    }
}

// 在CI中的使用(控制台应用入口点)
public static class SecurityGateCli
{
    public static async Task<int> Main(string[] args)
    {
        var jsonPath = args.FirstOrDefault() ?? "scan-results.json";
        var json = await File.ReadAllTextAsync(jsonPath);
        var rawResults = JsonSerializer.Deserialize<RawScanResults>(json)!;

        var results = new ScanResults(
            rawResults.Sast ?? new(),
            rawResults.Sca ?? new(),
            rawResults.Dast ?? new(),
            rawResults.Secrets,
            rawResults.Licenses ?? []);

        var config = new SecurityGateConfig();
        var (decision, reasons) = SecurityGateEvaluator.Evaluate(config, results);

        Console.WriteLine($"安全门: {decision.ToString().ToUpper()}");
        foreach (var reason in reasons)
            Console.WriteLine($"  - {reason}");

        return decision == GateDecision.Fail ? 1 : 0;
    }

    private sealed record RawScanResults(
        [property: JsonPropertyName("sast")] Dictionary<string, int>? Sast,
        [property: JsonPropertyName("sca")] Dictionary<string, int>? Sca,
        [property: JsonPropertyName("dast")] Dictionary<string, int>? Dast,
        [property: JsonPropertyName("secrets")] int Secrets,
        [property: JsonPropertyName("licenses")] List<string>? Licenses);
}

GitHub Actions安全门

# .github/workflows/security-gate.yml
name: 安全门
on:
  pull_request:
    branches: [main]

jobs:
  security-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      # 运行所有安全扫描
      - name: SAST - Semgrep
        uses: semgrep/semgrep-action@v1
        with:
          config: auto
          generateSarif: true

      - name: SCA - npm audit
        run: npm audit --json > npm-audit.json || true

      - name: 秘密 - Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # 聚合和评估
      - name: 评估安全门
        run: |
          python scripts/security_gate.py \
            --sast-results semgrep.sarif \
            --sca-results npm-audit.json \
            --secrets-results gitleaks.json

      - name: 在PR上评论
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('security-report.md', 'utf8');
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: report
            });

秘密扫描

使用Gitleaks的提交前钩子

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

Gitleaks配置

# .gitleaks.toml
title = "Gitleaks配置"

[extend]
useDefault = true

[[rules]]
id = "custom-api-key"
description = "自定义API密钥模式"
regex = '''(?i)api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9]{32,}['"]?'''
tags = ["key", "api"]

[[rules]]
id = "custom-password"
description = "硬编码密码"
regex = '''(?i)(password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]'''
tags = ["password"]

[allowlist]
description = "全局允许列表"
paths = [
  '''\.gitleaks\.toml$''',
  '''\.secrets\.baseline$''',
  '''test/.*\.py$''',
  '''.*_test\.go$''',
]

GitHub秘密扫描

# .github/workflows/secret-scanning.yml
name: 秘密扫描
on:
  push:
    branches: [main]
  pull_request:

jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0

      - name: Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  trufflehog:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0

      - name: TruffleHog
        uses: trufflesecurity/trufflehog@main
        with:
          extra_args: --only-verified

漏洞管理工作流

漏洞跟踪

/// <summary>
/// 漏洞管理工作流自动化。
/// </summary>
public enum VulnStatus
{
    New,
    Triaged,
    InProgress,
    Resolved,
    AcceptedRisk,
    FalsePositive
}

public enum VulnSeverity { Critical = 1, High = 2, Medium = 3, Low = 4 }

/// <summary>
/// 具有生命周期管理的跟踪漏洞。
/// </summary>
public sealed class Vulnerability
{
    public required string Id { get; init; }
    public string? CveId { get; init; }
    public required string Title { get; init; }
    public required string Description { get; init; }
    public required VulnSeverity Severity { get; init; }
    public required double CvssScore { get; init; }
    public required string AffectedComponent { get; init; }
    public required string AffectedVersion { get; init; }
    public string? FixedVersion { get; init; }

    // 跟踪
    public VulnStatus Status { get; set; } = VulnStatus.New;
    public string? Assignee { get; set; }
    public DateTime DiscoveredDate { get; init; } = DateTime.UtcNow;
    public DateTime? DueDate { get; set; }
    public DateTime? ResolvedDate { get; set; }
    public List<string> Notes { get; } = [];
}

/// <summary>
/// 管理漏洞生命周期与SLA跟踪。
/// </summary>
public sealed class VulnerabilityManager
{
    private static readonly IReadOnlyDictionary<VulnSeverity, int> SlaDays = new Dictionary<VulnSeverity, int>
    {
        [VulnSeverity.Critical] = 7,
        [VulnSeverity.High] = 30,
        [VulnSeverity.Medium] = 90,
        [VulnSeverity.Low] = 180,
    };

    private readonly Dictionary<string, Vulnerability> _vulnerabilities = new();

    public void AddVulnerability(Vulnerability vuln)
    {
        // 基于SLA自动设置截止日期
        vuln.DueDate ??= vuln.DiscoveredDate.AddDays(
            SlaDays.GetValueOrDefault(vuln.Severity, 90));

        _vulnerabilities[vuln.Id] = vuln;
    }

    public void Triage(string vulnId, string assignee, VulnStatus status = VulnStatus.Triaged)
    {
        if (_vulnerabilities.TryGetValue(vulnId, out var vuln))
        {
            vuln.Status = status;
            vuln.Assignee = assignee;
            vuln.Notes.Add($"{DateTime.UtcNow:O}: 分配给 {assignee}");
        }
    }

    public void Resolve(string vulnId, string resolution, VulnStatus status = VulnStatus.Resolved)
    {
        if (_vulnerabilities.TryGetValue(vulnId, out var vuln))
        {
            vuln.Status = status;
            vuln.ResolvedDate = DateTime.UtcNow;
            vuln.Notes.Add($"{DateTime.UtcNow:O}: 已解决 - {resolution}");
        }
    }

    public void AcceptRisk(string vulnId, string justification, string approver)
    {
        if (_vulnerabilities.TryGetValue(vulnId, out var vuln))
        {
            vuln.Status = VulnStatus.AcceptedRisk;
            vuln.Notes.Add($"{DateTime.UtcNow:O}: 风险已接受,批准人 {approver} - 理由: {justification}");
        }
    }

    public IEnumerable<Vulnerability> GetOverdue()
    {
        var now = DateTime.UtcNow;
        return _vulnerabilities.Values.Where(v =>
            v.Status is not (VulnStatus.Resolved or VulnStatus.AcceptedRisk or VulnStatus.FalsePositive) &&
            v.DueDate.HasValue &&
            v.DueDate.Value < now);
    }

    public VulnerabilityMetrics GetMetrics()
    {
        var vulns = _vulnerabilities.Values.ToList();

        return new VulnerabilityMetrics(
            Total: vulns.Count,
            Open: vulns.Count(v => v.Status is VulnStatus.New or VulnStatus.Triaged or VulnStatus.InProgress),
            Resolved: vulns.Count(v => v.Status == VulnStatus.Resolved),
            Overdue: GetOverdue().Count(),
            BySeverity: Enum.GetValues<VulnSeverity>().ToDictionary(
                sev => sev.ToString(),
                sev => vulns.Count(v => v.Severity == sev)),
            MttrDays: CalculateMttr(vulns));
    }

    private static double CalculateMttr(List<Vulnerability> vulns)
    {
        var resolved = vulns
            .Where(v => v.Status == VulnStatus.Resolved && v.ResolvedDate.HasValue)
            .ToList();

        if (resolved.Count == 0) return 0.0;

        var totalDays = resolved.Sum(v => (v.ResolvedDate!.Value - v.DiscoveredDate).TotalDays);
        return totalDays / resolved.Count;
    }
}

public sealed record VulnerabilityMetrics(
    int Total,
    int Open,
    int Resolved,
    int Overdue,
    Dictionary<string, int> BySeverity,
    double MttrDays);

安全冠军计划

计划结构

# 安全冠军计划

## 角色和职责

### 安全冠军
- 开发团队中的嵌入式安全倡导者
- 安全问题的第一联系人
- 参与安全培训并分享知识
- 审查安全关键代码更改
- 为团队分类安全发现

### 时间承诺
- 10-20%的工作时间用于安全活动
- 每周安全站会(30分钟)
- 每月安全培训(2小时)
- 每季度安全深度探讨(4小时)

## 选择标准
- 在团队中工作1年以上
- 对安全感兴趣
- 良好的沟通技巧
- 与同事的技术可信度

## 培训路径
1. **第1个月**:安全基础
   - OWASP Top 10
   - 安全编码基础
   - 公司安全政策

2. **第2个月**:工具和流程
   - SAST/DAST工具使用
   - 安全门流程
   - 漏洞管理

3. **第3个月**:高级主题
   - 威胁建模
   - 安全架构审查
   - 事件响应基础

## 指标
- 冠军审查发现的漏洞
- 安全培训完成率
- 修复发现的时间
- 安全文化调查分数

安全清单

开发前

  • [ ] 新功能的威胁模型已完成
  • [ ] 安全需求已文档化
  • [ ] 安全设计模式已识别
  • [ ] 安全冠军已分配

开发中

  • [ ] 启用提交前钩子(秘密、代码检查)
  • [ ] IDE中集成SAST
  • [ ] 遵循安全编码指南
  • [ ] 安全关键代码由冠军审查

部署前

  • [ ] 所有安全门已通过
  • [ ] SAST发现已处理
  • [ ] SCA漏洞已解决或接受
  • [ ] DAST扫描已完成
  • [ ] 安全审查已批准

部署后

  • [ ] 启用运行时安全监控
  • [ ] 计划漏洞扫描
  • [ ] 更新事件响应计划
  • [ ] 收集安全指标

参考

  • SAST工具:参见 references/sast-tools.md 获取详细工具配置
  • 安全门:参见 references/security-gates.md 获取门实现
  • 漏洞工作流:参见 references/vulnerability-workflow.md 获取完整工作流

相关技能

  • secure-coding - 安全开发实践
  • supply-chain-security - 依赖和SBOM管理
  • threat-modeling - 威胁识别和缓解

最后更新: 2025-12-26