强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Rekor 透明日志完整教程 / 07 - CI/CD 集成

第 7 章:CI/CD 集成

本章介绍如何将 Rekor 和 cosign 集成到主流 CI/CD 平台中,实现自动化签名、验证和发布流程。


7.1 集成概览

7.1.1 为什么 CI/CD 中需要 Rekor?

问题CI/CD 集成的解决方案
手动签名容易遗漏自动化签名,每次构建都签名
签名记录分散所有签名统一记录在 Rekor 中
验证流程复杂部署前自动验证签名和 Rekor 记录
审计困难Rekor 提供完整的构建签名历史

7.1.2 典型集成流程

代码提交 ──► CI 触发 ──► 构建 ──► 测试 ──► 签名 ──► 上传到 Rekor ──► 发布
   │          │          │        │        │              │           │
   │          │          │        │        │              │           │
   ▼          ▼          ▼        ▼        ▼              ▼           ▼
  Git      GitHub     Docker   pytest   cosign        rekor      Registry
          Actions    Build              sign         entry

7.2 GitHub Actions 集成

7.2.1 基本配置

GitHub Actions 是最常用的 CI/CD 平台,原生支持 OIDC,与 Sigstore 无缝集成。

# .github/workflows/build-and-sign.yml
name: Build and Sign

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

permissions:
  contents: read
  packages: write
  id-token: write  # 关键:允许 OIDC 令牌请求

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  sign:
    needs: build
    if: github.event_name != 'pull_request'
    runs-on: ubuntu-latest
    
    steps:
      - name: Install cosign
        uses: sigstore/cosign-installer@v3

      - name: Sign container image
        env:
          DIGEST: ${{ needs.build.outputs.digest }}
        run: |
          cosign sign --yes \
            --oidc-issuer=https://token.actions.githubusercontent.com \
            ${REGISTRY}/${IMAGE_NAME}@${DIGEST}

7.2.2 完整工作流(构建 + 签名 + 验证 + SBOM)

# .github/workflows/secure-build.yml
name: Secure Build Pipeline

on:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write
  id-token: write
  attestations: write
  security-events: write

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
      
      - uses: docker/setup-buildx-action@v3
      
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  sign:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: sigstore/cosign-installer@v3
      
      - name: Sign image (keyless)
        run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}
      
      - name: Verify signature
        run: |
          cosign verify \
            --certificate-identity-regexp="https://github.com/${{ github.repository }}/" \
            --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
            ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}

  sbom:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}
          format: spdx-json
          output-file: sbom.spdx.json
      
      - name: Attest SBOM
        uses: actions/attest-build-provenance@v1
        with:
          subject-name: ghcr.io/${{ github.repository }}
          subject-digest: ${{ needs.build.outputs.digest }}
          push-to-registry: true

  verify:
    needs: [sign, sbom]
    runs-on: ubuntu-latest
    steps:
      - uses: sigstore/cosign-installer@v3
      
      - name: Verify signature
        run: |
          cosign verify \
            --certificate-identity-regexp=".*" \
            --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
            ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}
      
      - name: Verify attestation
        run: |
          cosign verify-attestation \
            --certificate-identity-regexp=".*" \
            --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
            --type spdxjson \
            ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}

7.2.3 GitHub Actions 权限说明

权限用途
id-token: write请求 OIDC 令牌,用于无密钥签名
packages: write推送容器镜像到 GHCR
contents: read读取仓库代码
attestations: write创建构建证明(attestation)

7.3 GitLab CI 集成

7.3.1 基本配置

# .gitlab-ci.yml
stages:
  - build
  - sign
  - verify

variables:
  IMAGE_NAME: $CI_REGISTRY_IMAGE
  IMAGE_TAG: $CI_COMMIT_SHORT_SHA

build:
  stage: build
  image: docker:24-dind
  services:
    - docker:24-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .
    - docker push $IMAGE_NAME:$IMAGE_TAG
    - echo "IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $IMAGE_NAME:$IMAGE_TAG | cut -d@ -f2)" >> build.env
  artifacts:
    reports:
      dotenv: build.env

sign:
  stage: sign
  image: alpine:3.18
  needs:
    - build
  before_script:
    - apk add --no-cache cosign
  script:
    - cosign sign --yes
        --oidc-issuer=$CI_SERVER_URL
        $IMAGE_NAME:$IMAGE_TAG@$IMAGE_DIGEST
  id_tokens:
    OIDC_TOKEN:
      aud: sigstore

verify:
  stage: verify
  image: alpine:3.18
  needs:
    - sign
  before_script:
    - apk add --no-cache cosign
  script:
    - cosign verify
        --certificate-identity=$CI_PIPELINE_URL
        --certificate-oidc-issuer=$CI_SERVER_URL
        $IMAGE_NAME:$IMAGE_TAG@$IMAGE_DIGEST

7.3.2 GitLab CI 变量

变量说明
CI_SERVER_URLGitLab 实例 URL
CI_REGISTRY容器注册表地址
CI_PIPELINE_URL流水线 URL(用作证书身份)
CI_COMMIT_SHA提交 SHA

7.4 Jenkins 集成

7.4.1 Jenkinsfile 示例

// Jenkinsfile
pipeline {
    agent any
    
    environment {
        REGISTRY = 'ghcr.io'
        IMAGE_NAME = 'myorg/myapp'
        IMAGE_TAG = "${env.BUILD_NUMBER}-${env.GIT_COMMIT[0..7]}"
    }
    
    stages {
        stage('Build') {
            steps {
                script {
                    docker.withRegistry("https://${REGISTRY}", 'ghcr-credentials') {
                        def image = docker.build("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}")
                        image.push()
                        env.IMAGE_DIGEST = sh(
                            script: "docker inspect --format='{{index .RepoDigests 0}}' ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} | cut -d@ -f2",
                            returnStdout: true
                        ).trim()
                    }
                }
            }
        }
        
        stage('Sign') {
            steps {
                withCredentials([file(credentialsId: 'cosign-private-key', variable: 'COSIGN_KEY')]) {
                    sh """
                        cosign sign --key ${COSIGN_KEY} --yes \
                            ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}@${IMAGE_DIGEST}
                    """
                }
            }
        }
        
        stage('Verify') {
            steps {
                withCredentials([file(credentialsId: 'cosign-public-key', variable: 'COSIGN_PUB')]) {
                    sh """
                        cosign verify --key ${COSIGN_PUB} \
                            ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}@${IMAGE_DIGEST}
                    """
                }
            }
        }
    }
}

7.5 多平台 CI 集成对比

平台OIDC 支持配置复杂度Rekor 集成推荐指数
GitHub Actions✅ 原生⭐⭐自动⭐⭐⭐⭐⭐
GitLab CI✅ 原生⭐⭐⭐自动⭐⭐⭐⭐
Jenkins⚠️ 需插件⭐⭐⭐⭐手动⭐⭐⭐
Azure DevOps✅ 原生⭐⭐⭐自动⭐⭐⭐⭐
CircleCI✅ 原生⭐⭐⭐自动⭐⭐⭐⭐

7.6 自动化签名脚本

7.6.1 通用签名脚本

#!/bin/bash
# scripts/sign-artifact.sh
# 通用构件签名脚本

set -euo pipefail

# 配置
REKOR_SERVER="${REKOR_SERVER:-https://rekor.sigstore.dev}"
SIGN_MODE="${SIGN_MODE:-keyless}"  # keyless 或 key

# 参数
ARTIFACT="$1"
SIGNATURE_OUTPUT="${2:-${ARTIFACT}.sig}"
CERTIFICATE_OUTPUT="${3:-${ARTIFACT}.cert}"

echo "=== 构件签名 ==="
echo "构件: $ARTIFACT"
echo "模式: $SIGN_MODE"

case "$SIGN_MODE" in
  keyless)
    echo "使用无密钥签名..."
    cosign sign-blob --yes \
      --output-signature "$SIGNATURE_OUTPUT" \
      --output-certificate "$CERTIFICATE_OUTPUT" \
      "$ARTIFACT"
    ;;
  key)
    echo "使用密钥签名..."
    cosign sign-blob \
      --key "${COSIGN_KEY:-cosign.key}" \
      --yes \
      --output-signature "$SIGNATURE_OUTPUT" \
      --output-certificate "$CERTIFICATE_OUTPUT" \
      "$ARTIFACT"
    ;;
  *)
    echo "未知签名模式: $SIGN_MODE"
    exit 1
    ;;
esac

echo "签名文件: $SIGNATURE_OUTPUT"
echo "证书文件: $CERTIFICATE_OUTPUT"

# 输出 Rekor 条目信息
echo "=== Rekor 条目已创建 ==="

7.6.2 通用验证脚本

#!/bin/bash
# scripts/verify-artifact.sh
# 通用构件验证脚本

set -euo pipefail

# 参数
ARTIFACT="$1"
SIGNATURE_FILE="${2:-${ARTIFACT}.sig}"
CERTIFICATE_FILE="${3:-${ARTIFACT}.cert}"

# 验证配置
CERTIFICATE_IDENTITY="${CERTIFICATE_IDENTITY:-}"
CERTIFICATE_OIDC_ISSUER="${CERTIFICATE_OIDC_ISSUER:-}"
PUBLIC_KEY="${PUBLIC_KEY:-}"

echo "=== 构件验证 ==="
echo "构件: $ARTIFACT"

if [ -n "$PUBLIC_KEY" ]; then
  echo "模式: 密钥验证"
  cosign verify-blob \
    --key "$PUBLIC_KEY" \
    --signature "$SIGNATURE_FILE" \
    --certificate "$CERTIFICATE_FILE" \
    "$ARTIFACT"
elif [ -n "$CERTIFICATE_IDENTITY" ]; then
  echo "模式: 无密钥验证"
  cosign verify-blob \
    --certificate-identity "$CERTIFICATE_IDENTITY" \
    --certificate-oidc-issuer "$CERTIFICATE_OIDC_ISSUER" \
    --signature "$SIGNATURE_FILE" \
    --certificate "$CERTIFICATE_FILE" \
    "$ARTIFACT"
else
  echo "错误: 必须提供 PUBLIC_KEY 或 CERTIFICATE_IDENTITY"
  exit 1
fi

echo "✅ 验证通过"

7.7 自动化发布流水线

7.7.1 完整发布流程

┌──────────────────────────────────────────────────────────────────────┐
│                    自动化发布流水线                                    │
│                                                                      │
│  开发者                                                               │
│   │                                                                  │
│   │  1. git tag v1.0.0                                              │
│   │  2. git push origin v1.0.0                                      │
│   │                                                                  │
│   ▼                                                                  │
│  CI/CD 触发                                                          │
│   │                                                                  │
│   ├─► 3. 构建容器镜像                                                │
│   │                                                                  │
│   ├─► 4. 运行测试                                                    │
│   │                                                                  │
│   ├─► 5. cosign sign --yes <image>                                  │
│   │       └─ 自动上传签名到 Rekor                                    │
│   │                                                                  │
│   ├─► 6. 生成 SBOM                                                  │
│   │       └─ cosign attest --type spdxjson <image>                  │
│   │                                                                  │
│   ├─► 7. 漏洞扫描                                                   │
│   │       └─ grype <image> --fail-on critical                       │
│   │                                                                  │
│   ├─► 8. 验证签名                                                    │
│   │       └─ cosign verify <image>                                  │
│   │                                                                  │
│   └─► 9. 推送到生产注册表                                            │
│                                                                      │
│  部署                                                                │
│   │                                                                  │
│   ├─► 10. 验证镜像签名                                               │
│   │        └─ cosign verify --certificate-identity=... <image>      │
│   │                                                                  │
│   └─► 11. 部署到集群                                                 │
└──────────────────────────────────────────────────────────────────────┘

7.7.2 GitHub Actions 发布工作流

# .github/workflows/release.yml
name: Release

on:
  push:
    tags: ['v*']

permissions:
  contents: write
  packages: write
  id-token: write

jobs:
  release:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.meta.outputs.version }}
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=raw,value=latest
      
      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
      
      - name: Install cosign
        uses: sigstore/cosign-installer@v3
      
      - name: Sign image
        run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
      
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
          format: spdx-json
          output-file: sbom.spdx.json
      
      - name: Upload SBOM
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.spdx.json
      
      - name: Verify signature
        run: |
          cosign verify \
            --certificate-identity-regexp="https://github.com/${{ github.repository }}/" \
            --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

  deploy:
    needs: release
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Verify image before deploy
        run: |
          cosign verify \
            --certificate-identity-regexp="https://github.com/${{ github.repository }}/" \
            --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
            ghcr.io/${{ github.repository }}@${{ needs.release.outputs.digest }}
      
      - name: Deploy
        run: |
          echo "Deploying version ${{ needs.release.outputs.version }}"
          # kubectl apply, helm upgrade, etc.

7.8 签名验证集成到部署

7.8.1 Kubernetes 准入控制器

使用 Sigstore Policy Controller 在 Kubernetes 中强制验证签名:

# install-policy-controller.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: sigstore-policy-controller
---
# 安装 Policy Controller
# helm repo add sigstore https://sigstore.github.io/helm-charts
# helm install policy-controller sigstore/policy-controller -n sigstore-policy-controller
# policy.yaml - 要求所有镜像必须签名
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: require-signatures
spec:
  images:
    - glob: "ghcr.io/myorg/**"
  authorities:
    - keyless:
        url: https://fulcio.sigstore.dev
        identities:
          - issuer: https://token.actions.githubusercontent.com
            subjectRegExp: ".*myorg/myrepo.*"
      ctlog:
        url: https://rekor.sigstore.dev

7.8.2 Argo CD 集成

# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/myrepo.git
    targetRevision: main
    path: k8s
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

7.9 多仓库/多服务签名管理

7.9.1 统一签名策略

# .github/workflows/reusable-sign.yml
name: Reusable Sign Workflow

on:
  workflow_call:
    inputs:
      image:
        required: true
        type: string
      digest:
        required: true
        type: string

jobs:
  sign:
    runs-on: ubuntu-latest
    permissions:
      packages: write
      id-token: write
    steps:
      - uses: sigstore/cosign-installer@v3
      - name: Sign image
        run: |
          cosign sign --yes \
            ${{ inputs.image }}@${{ inputs.digest }}
# 在其他 workflow 中调用
jobs:
  build:
    # ... 构建逻辑
    outputs:
      digest: ${{ steps.build.outputs.digest }}
  
  sign:
    needs: build
    uses: ./.github/workflows/reusable-sign.yml
    with:
      image: ghcr.io/myorg/myapp
      digest: ${{ needs.build.outputs.digest }}

7.10 注意事项

OIDC 令牌过期:GitHub Actions 的 OIDC 令牌有效期较短,签名步骤应在令牌获取后尽快执行。

并发构建:同一镜像的多次并发签名可能导致 Rekor 写入冲突,建议使用 digest 而非 tag 作为签名目标。

速率限制:公共 Rekor 有速率限制,大规模构建应考虑私有实例。

密钥安全:如果使用传统密钥签名,确保密钥在 CI/CD 中的安全存储(GitHub Secrets、GitLab Variables 等)。


7.11 本章小结

平台签名方式配置复杂度关键配置
GitHub Actions无密钥id-token: write
GitLab CI无密钥id_tokens
Jenkins密钥/KMS凭据管理
Azure DevOps无密钥OIDC 服务连接

扩展阅读


下一章08 - 私有实例部署 — 部署和运维私有 Rekor 实例的完整指南。