跳过正文
  1. 文章列表/

GitOps 安全实践:从仓库配置到 CI/CD 流水线的纵深防御

Elone Yue
作者
Elone Yue

引言
#

GitOps 将基础设施即代码 (IaC) 的理念扩展到整个部署流程,但随之而来的是新的安全挑战:GitHub Token 泄露、Pipeline 注入、供应链攻击。本文将演示如何构建安全的 GitOps 体系。

GitOps 安全模型
#

┌────────────────────────────────────────────────────┐
│                GitOps 信任边界                       │
├────────────────────────────────────────────────────┤
│                                                     │
│  [可信] Git Repository (签名提交)                    │
│    └─ [可信] CI/CD Pipeline (受控 Runner)            │
│       └─ [可信] Artifact Registry (签名镜像)          │
│          └─ [可信] Kubernetes Cluster (准入控制)      │
│                                                     │
│  [不可信] 外部输入                                    │
│    └─ Pull Request (未审计代码)                      │
│    └─ Pipeline Logs (可能泄露 Secrets)               │
│    └─ Dependencies (可能包含恶意包)                   │
│                                                     │
└────────────────────────────────────────────────────┘

Secret 管理
#

SOPS + Age 加密
#

# 安装 SOPS
go install go.mozilla.org/sops/v3@latest

# 生成 Age 密钥
age-keygen -o .age.key

# 加密 Kubernetes Secret
cat secret.yaml | sops --encrypt --age $(cat .age.key | grep -o "age1.*") > secret.enc.yaml

.sops.yaml 配置:

creation_rules:
  - path_regex: k8s/.*\.yaml$
    age: >-
      age1qljz4djpo64nnn82k3aoz2e0hyl6mjwt0xp0m740jzn2tn32mvesj4mzp0

External Secrets Operator
#

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: app-secrets
    creationPolicy: Owner
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: secret/data/production/db
        property: connection_string

Pipeline 安全防护
#

GitHub Actions 安全配置
#

# .github/workflows/deploy.yml
name: Secure Deploy Pipeline

on:
  pull_request:
    branches: [main]

permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      # 依赖安全扫描
      - name: Audit Dependencies
        run: npm audit --production
      
      # 代码安全扫描 (SAST)
      - name: Semgrep Scan
        uses: returntocorp/semgrep-action@v1
        with:
          config: >-
            p/security-audit
            p/owasp-top-ten
      
      # 容器镜像扫描
      - name: Scan Image
        uses: anchore/scan-action@v3
        with:
          image: myapp:latest
          fail-build: true
      
      # 使用 OIDC 代替长期 Token
      - name: Authenticate to GCP
        uses: google-github-actions/auth@v1
        with:
          workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
          service_account: deploy@project.iam.gserviceaccount.com
      
      # 禁止 Secrets 泄露到日志
      - name: Deploy
        run: ./deploy.sh
        env:
          # 使用 GITHUB_TOKEN (短期) 而非 PAT (长期)
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

供应链安全
#

SBOM 生成
#

# 在 Pipeline 中生成 SBOM
- name: Generate SBOM
  uses: anchore/sbom-action@v0
  with:
    image: myapp:latest
    format: cyclonedx-json
    output-file: sbom.json

- name: Upload SBOM
  uses: actions/upload-artifact@v3
  with:
    name: sbom
    path: sbom.json

Sigstore 镜像签名
#

# 使用 cosign 签名镜像
cosign sign --key env://COSIGN_KEY myapp:latest

# 验证签名
cosign verify --key cosign.pub myapp:latest

OPA/Gatekeeper 准入控制
#

# 禁止使用 latest 标签
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: blocklatesttag
spec:
  crd:
    spec:
      names:
        kind: BlockLatestTag
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package blocklatesttag
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          endswith(container.image, ":latest")
          msg := sprintf("Container %v uses :latest tag", [container.name])
        }

真实攻击场景
#

GitHub Token 泄露
#

场景: 开发者在 PR 评论中粘贴了完整的 CI 日志
日志包含:
  - AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
  - GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx

修复:
1. 立即轮换泄露的凭据
2. 配置 git-secrets / gitleaks 在 commit 前扫描
3. 使用短效 Token (OIDC)

gitleaks 配置
#

# 安装 gitleaks
go install github.com/gitleaks/gitleaks@latest

# Pre-commit hook
gitleaks detect --source . --report-path leak_report.json

# 添加到 .git/hooks/pre-commit
#!/bin/bash
gitleaks detect --source . --fail

总结
#

  1. 密钥永不落盘——使用 SOPS/ESO 管理 Secret
  2. 短效凭据优先——OIDC 替代长期 Token
  3. 流水线零信任——每个 Step 最小权限
  4. 供应链全链路——SBOM + 签名 + 准入控制

参考资源

  1. SOPS: https://github.com/mozilla/sops
  2. External Secrets Operator: https://external-secrets.io
  3. Sigstore: https://sigstore.dev
  4. OPA/Gatekeeper: https://openpolicyagent.org