設定 CI/CD 整合

Security Command Center 客戶可能難以從持續整合/持續推送軟體更新 (CI/CD) 環境,取得掃描結果的集中式完整檢視畫面。缺乏整合式資料檢視會使安全漏洞管理變得複雜。CI/CD 整合是構件防護 (搶先版) 的元件,可讓您連結多個 CI/CD 管道,協助您在整個軟體開發生命週期中偵測到安全漏洞。

持續整合/持續推送軟體更新整合功能支援 GitHub Actions、Cloud Build 和 Jenkins。連線後,您可以使用任何 CI/CD 平台設定構件防護政策,主動掌握及控管資安態勢。

總覽

CI/CD 整合提供下列功能:

  • 端對端政策強制執行:協助構件防護機制從程式碼到雲端,套用一致的安全政策。
  • 全面偵測威脅:掃描安全漏洞、外洩的密鑰、有問題的授權和惡意套件。
  • 廣泛的 CI/CD 相容性:可與 Jenkins 和 GitHub Actions 搭配使用。
  • 專為 CI/CD 最佳化:輕量型二進位檔可確保在本地環境中高效運作。
  • 彈性輸出:以業界標準的 JSON 和 SARIF 格式提供結果。
  • 集中管理安全洞察:直接在安全資訊主頁中,清楚查看政策遵循情況。

這個掃描器會自動填入 Security Command Center,提供以資產為中心的統一檢視畫面,顯示從構件建立程序中發現的安全問題。

透過 CI/CD 整合,您可以在整個軟體生命週期中強制執行構件防護政策。確保從建構到部署作業,構件都經過驗證是否符合規範。

目標對象

整合 CI/CD 可協助利害關係人執行下列工作:

  • 主要使用者:DevOps 和平台工程團隊
    • 管理及整合:負責管理掃描工具,並直接整合至 CI/CD 管道。
    • 設定政策:設定掃描步驟。
    • 監控合規情形:追蹤建構合規情形,並與安全管理員合作,修正政策。
  • 次要使用者:安全管理員
    • 政策作者:根據業務重要性定義及強制執行安全政策。
    • 自動強制執行:在 CI/CD 中自動執行安全機制,減少安全漏洞干擾並設定閘道條件。
    • 提供監督:與開發運作團隊合作,並提供資訊主頁供管理層追蹤受限的安全性漏洞。
  • 次要使用者:應用程式開發人員
    • 查看並修正:與政策評估結果互動、查看建構結果,並修正標記的安全性問題。
    • 評估:透過 CI 管道的建構程序,間接啟動政策評估。
    • 維持法規遵循狀態:確保符合安全規定,同時不中斷開發工作流程。

重要詞彙與概念

  • 常見安全漏洞與弱點 (CVE):公開揭露的電腦安全漏洞,並獲派專屬 ID。這些 ID 有助於追蹤安全漏洞,以便進行修正。
  • 軟體物料清單 (SBOM):機器可讀的軟體元件和依附元件清單。SBOM 包含每個元件的版本、來源和其他相關詳細資料。SBOM 可用於找出 CVE 和其他安全風險。
  • 構件:軟體開發的經過驗證輸出內容,例如在建構程序中建立的資料或項目。
  • 連接器:圖片的標記。從 CI 管道傳遞至構件防護服務時,連接器會判斷要對建構中的映像檔執行哪些政策。
  • CI 政策:定義規則或條件的安全漏洞政策,可控管環境中允許使用的安全漏洞和套件。

高階工作流程

  1. 建立 CI 連接器
  2. 使用上一個步驟中設定的連接器建立構件防護政策,定義政策範圍。
  3. 開始評估構件

評估期間,系統會建構映像檔,並根據預先定義的政策進行評估。如果政策失敗,建構就會失敗。DevOps 或應用程式工程師隨後必須檢查失敗詳細資料,找出特定安全漏洞、根據 CVE 詳細資料更新依附元件,然後重新執行管道。

事前準備

如要使用 CI/CD 整合功能,必須啟用構件防護機制。如需操作說明,請參閱構件防護說明文件中的「事前準備」一節。

接著,您可以在Google Cloud 控制台中建立連接器,或使用 Google Cloud CLI 建立連接器。

在 Google Cloud 控制台中建立連接器

如要建立連結器,請按照下列步驟操作:

  1. 在 Google Cloud 控制台中,依序點選「Security」>「Settings」

  2. 在「構件防護」資訊卡中,按一下「管理設定」

  3. 點選「建立連接器」,然後輸入連接器的下列詳細資料:

    • 連接器 ID:新增連接器的 ID。
    • 說明:輸入連接器的說明。
    • CI/CD 平台:從清單中選取對應的 CI/CD 平台。這個連接器只能用於使用提供的 CI/CD 平台建構的管道。
  4. 點選「建立」

系統會顯示通知,確認連接器已成功建立。可用的連接器會列於「連接器」表格中。

如要移除連接器,請按一下連接器旁邊的 ,然後選取「刪除連接器」,並按照提示操作。按一下「取消」即可中止。

如要將政策連結至連接器,請按一下連接器旁的 ,然後選取「新增政策」。請按照步驟建立構件防護政策。詳情請參閱「建立政策」。

使用 Google Cloud CLI 建立連接器

本節將說明 CI/CD 構件掃描可用的 gcloud CLI 指令和使用方式。

Google Cloud CLI 必要條件

  • 確認 gcloud CLI 版本為 559.0.0 以上。
  • 將專案設為設定專案。

如要執行這項操作,請執行下列 gcloud CLI 指令:

   gcloud components update --version=559.0.0
   gcloud config set project PROJECT_ID

Google Cloud CLI 指令

create

gcloud alpha scc artifact-guard connectors create CONNECTOR_ID \
    --location=LOCATION \
    (--organization=ORGANIZATION_ID | --project=PROJECT_NUMBER) \
    --pipeline-type=PIPELINE_TYPE \
    [--description=DESCRIPTION] \
    [--display-name=DISPLAY_NAME]
  • CONNECTOR_ID:要建立的連接器 ID。
  • PIPELINE_TYPE:CI/CD 管道的類型。必須是下列其中一項:
    • GOOGLE_CLOUD_BUILD
    • GITHUB_ACTIONS
    • JENKINS_PIPELINE
  • DESCRIPTION:連接器的文字說明。
  • DISPLAY_NAME:連接器的好記顯示名稱。

get

gcloud alpha scc artifact-guard connectors describe CONNECTOR_ID \
    --location=LOCATION \
    (--organization=ORGANIZATION_ID | --project=PROJECT_NUMBER)
  • CONNECTOR_ID:要說明的連接器 ID。

list

gcloud alpha scc artifact-guard connectors list PARENT
  • PARENT:機構或專案。父項資源可接受的格式包括:
    • {organizations/ORGANIZATION_ID/locations/LOCATION}
    • {projects/PROJECT_NUMBER/locations/LOCATION}

刪除

gcloud alpha scc artifact-guard connectors delete CONNECTOR_ID \
    --location=LOCATION \
    (--organization=ORGANIZATION_ID | --project=PROJECT_NUMBER)
  • CONNECTOR_ID:要刪除的連接器 ID。

執行評估作業

GitHub Actions 和 Jenkins 管道都支援安全漏洞掃描功能。 如要執行評估,請完成下列步驟:

  1. 建立用於驗證的密鑰
  2. 建立管道專屬的整合範本檔案
  3. 啟動評估

密鑰設定

在 Google Cloud 外部執行的 CI/CD 管道可以使用服務帳戶金鑰或 Workload Identity Federation 進行驗證。如需建立密鑰的詳細操作說明,請參閱下列文章:

服務帳戶金鑰

Workload Identity Federation (適用於 GitHub Actions)

您必須使用下列其中一種方法,將密鑰新增至 CI/CD 環境:

服務帳戶金鑰方法

一個密鑰:

  • GCP_CREDENTIALS:您下載的服務帳戶 JSON 金鑰檔案內容。

Workload Identity Federation 方法

兩個密鑰:

  • GCP_WORKLOAD_IDENTITY_PROVIDER:Workload Identity Provider 的完整資源名稱。例如:projects/12345/locations/global/workloadIdentityPools/my-pool/providers/my-provider

  • GCP_SERVICE_ACCOUNT:要模擬的服務帳戶電子郵件地址。

管道整合範本

如要觸發評估作業,您必須使用下列範本範例,為管道 (Cloud Build、GitHub Actions 或 Jenkins) 建立專屬檔案:

Cloud Build

  • 如要瞭解每個欄位,請參閱變數定義

    steps:
          # Step 1: Generate auth token
          - name: 'gcr.io/cloud-builders/gcloud'
          id: 'Generate Token'
          entrypoint: 'bash'
          args:
                - '-c'
                - |
                echo "Starting token generation..."
                gcloud auth print-access-token > /workspace/gcp_token.txt
                if [ $? -eq 0 ]; then
                      echo "Token generated successfully."
                else
                      echo "Failed to generate token." >&2
                      exit 1
                fi
    
          # Step 2: Build the image locally
          - name: 'gcr.io/cloud-builders/docker'
          id: 'Build Image'
          entrypoint: 'bash'
          args:
                - '-c'
                - |
                echo "🚧 Building Docker image from source code..."
                docker build -t ${_IMAGE_NAME_TO_SCAN}:${_IMAGE_TAG} .
                if [ $? -ne 0 ]; then
                      echo "❌ Docker build failed."
                      exit 1
                fi
                echo "✅ Docker image built successfully: ${_IMAGE_NAME_TO_SCAN}:${_IMAGE_TAG}"
    
          # Step 3: Image scan for vulnerabilities
          - id: 'Image-Analysis'
          name: '${_SCANNER_IMAGE}'
          entrypoint: 'bash'
          args:
                - '-c'
                - |
                echo "Starting image scan with scanner: ${_SCANNER_IMAGE}"
    
                exit_code=0
    
                docker run --rm \
                      -v /var/run/docker.sock:/var/run/docker.sock \
                      -v /workspace:/workspace \
                      -e GCP_PROJECT_ID="${_PROJECT_ID}" \
                      -e ORGANIZATION_ID="${_ORGANIZATION_ID}" \
                      -e IMAGE_NAME="${_IMAGE_NAME_TO_SCAN}" \
                      -e IMAGE_TAG="${_IMAGE_TAG}" \
                      -e CONNECTOR_ID="${_CONNECTOR_ID}" \
                      -e TRIGGER_ID="${_TRIGGER_ID}" \
                      -e IGNORE_ERRORS="${_IGNORE_ERRORS}" \
                      -e GCP_ACCESS_TOKEN="$(cat /workspace/gcp_token.txt)" \
                      "${_SCANNER_IMAGE}" || exit_code=$?
    
                echo "Docker run finished with exit code: $exit_code"
    
                if [ $exit_code -eq 0 ]; then
                      echo "✅ Evaluation succeeded: Conformant image."
                elif [ $exit_code -eq 1 ]; then
                      echo "❌ Scan failed: Non-conformant image."
                      exit 1
                else
                      if [ "${_IGNORE_ERRORS}" = "true" ]; then
                            echo "⚠️ Server/internal error ignored. Continuing."
                      else
                            echo "❌ Server/internal error. Exiting."
                            exit 1
                      fi
                fi
    
          # Step 4: Configure Docker authentication for Artifact Registry
          - name: 'gcr.io/cloud-builders/gcloud'
          id: 'Configure Docker Auth'
          entrypoint: 'bash'
          args:
                - '-c'
                - |
                echo "🔐 Configuring Docker authentication for Artifact Registry..."
                gcloud auth configure-docker us-east1-docker.pkg.dev -q
                echo "✅ Docker authentication configured."
    
          # Step 5: Push image to Artifact Registry
          - name: 'gcr.io/cloud-builders/docker'
          id: 'Push Image to Artifact Registry'
          entrypoint: 'bash'
          args:
                - '-c'
                - |
    
                docker tag "${_IMAGE_NAME_TO_SCAN}:${_IMAGE_TAG}" "us-east1-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPOSITORY}/${_IMAGE_NAME_TO_SCAN}:${_IMAGE_TAG}"
    
                echo "🚀 Pushing $_FULL_AR_TAG..."
                docker push "us-east1-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPOSITORY}/${_IMAGE_NAME_TO_SCAN}:${_IMAGE_TAG}"
    
                echo "✅ Image pushed successfully."
    
    substitutions:
          _IMAGE_NAME_TO_SCAN: 'checkout-image'
          _ORGANIZATION_ID: 'orgId'
          _CONNECTOR_ID: 'connectorId'
          _SCANNER_IMAGE: 'us-central1-docker.pkg.dev/ci-plugin/ci-images/scc-artifactguard-scan-image:latest'
          _IMAGE_TAG: 'latest'
          _TRIGGER_ID: 'cloud-build-job'
          _PROJECT_ID: 'projectId'
          _AR_REPOSITORY: 'images'
          _IGNORE_ERRORS: "false"
    
    serviceAccount: "projects/projectId/serviceAccounts/id-compute@developer.gserviceaccount.com"
    
    options:
          logging: CLOUD_LOGGING_ONLY
    

GitHub Actions (密碼)

這個範本適用於使用私密金鑰的 GitHub Actions。

  • 新增服務帳戶私密金鑰 (GCP_CREDENTIALS)。
  • 如要瞭解其他欄位,請參閱「變數定義」。

    # A workflow to BUILD the app image, RUN the scanner, and PUSH to AR if scan passes
    name: Build, Scan and Push
    
    on:
     workflow_dispatch:
       inputs:
          IMAGE_NAME_TO_SCAN:
                description: 'The tag for your application image to be built (e.g., my-app:latest)'
                required: true
                default: 'checkout-image'
          GCP_PROJECT_ID:
                description: 'GCP Project ID for authentication'
                required: true
                default: 'projectId'
          AR_REPOSITORY:
                description: 'Artifact Registry repository name (e.g., app-repo)'
                required: false
                default: 'images'
          ORGANIZATION_ID:
                description: 'Your GCP Organization ID'
                required: true
                default: 'orgId'
          CONNECTOR_ID:
                description: 'The ID for your pipeline connector'
                required: true
                default: 'connectorId'
          SCANNER_IMAGE:
                description: 'The full registry path for your PRE-BUILT scanner tool'
                required: true
                default: 'us-central1-docker.pkg.dev/ci-plugin/ci-images/scc-artifactguard-scan-image:latest'
          IMAGE_TAG:
                description: 'The Docker image version (of the app image)'
                required: true
                default: 'latest'
          IGNORE_SERVER_ERRORS:
                description: 'Ignore server errors'
                required: false
                type: boolean
                default: false
          VERBOSITY:
                description: 'Verbosity flag'
                required: false
                default: 'HIGH'
    
    jobs:
     build-and-scan:
       runs-on: ubuntu-latest
       steps:
          # 1. Check out repository (for your app's Dockerfile)
          - name: Check out repository
            uses: actions/checkout@v4
    
          # 2. Authenticate to Google Cloud
          - name: Authenticate to GCP
            id: auth
            uses: 'google-github-actions/auth@v2'
            with:
                credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
    
          # 3. Set up the gcloud CLI
          - name: Set up Cloud SDK
            uses: 'google-github-actions/setup-gcloud@v2'
            with:
                project_id: ${{ inputs.GCP_PROJECT_ID }}
    
           # 4. Configure Docker (needed to pull SCANNER_IMAGE and push app image)
          - name: Configure Docker
            run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
    
          # 5. Build Application Image Locally (IMAGE_NAME_TO_SCAN)
          - name: Build Application Image Locally
            uses: docker/build-push-action@v5
            with:
                context: .
                file: ./Dockerfile
                push: false  # <-- Do not push
                load: true   # <-- Load image into the runner's local daemon
                # Tag the image with the name the scanner will look for
                tags: |
                ${{ inputs.IMAGE_NAME_TO_SCAN }}:${{ inputs.IMAGE_TAG }}
    
          # 6. Run Image Scan (Using the SCANNER_IMAGE)
          - name: 'Run Image Analysis Scan'
            if: steps.auth.outcome == 'success'
            run: |
                echo "📦 Pulling scanner image and running scan..."
    
          SCANNER_IMAGE="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.SCANNER_IMAGE || env.SCANNER_IMAGE }}"
          GCP_PROJECT_ID="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.GCP_PROJECT_ID || env.GCP_PROJECT_ID }}"
          ORGANIZATION_ID="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ORGANIZATION_ID || env.ORGANIZATION_ID }}"
          IMAGE_NAME="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.IMAGE_NAME_TO_SCAN || env.IMAGE_NAME_TO_SCAN }}"
          IMAGE_TAG="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.IMAGE_TAG || env.IMAGE_TAG }}"
          CONNECTOR_ID="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.CONNECTOR_ID || env.CONNECTOR_ID }}"
          VERBOSITY="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.VERBOSITY || env.VERBOSITY }}"
          IGNORE_ERRORS="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.IGNORE_SERVER_ERRORS || (env.IGNORE_SERVER_ERRORS == 'true') }}"
    
          exit_code=0
    
          # This 'docker run' pulls the SCANNER_IMAGE from the registry
          # and passes the name of the locally-built app image (IMAGE_NAME)
          docker run --rm \
                -v /var/run/docker.sock:/var/run/docker.sock \
                -v ${{ steps.auth.outputs.credentials_file_path }}:/tmp/scc-key.json \
                -e GCLOUD_KEY_PATH=/tmp/scc-key.json \
                -e GCP_PROJECT_ID="${GCP_PROJECT_ID}" \
                -e ORGANIZATION_ID="${ORGANIZATION_ID}" \
                -e IMAGE_NAME="${IMAGE_NAME}" \
                -e IMAGE_TAG="${IMAGE_TAG}" \
                -e CONNECTOR_ID="${CONNECTOR_ID}" \
                -e BUILD_TAG="${{ github.workflow }}" \
                -e BUILD_ID="${{ github.run_number }}" \
                -e VERBOSITY="${VERBOSITY}" \
                "${SCANNER_IMAGE}" \
                || exit_code=$?
    
          echo "Docker run finished with exit code: $exit_code"
    
          # --- Replicate Jenkins Exit Code Logic ---
          if [ $exit_code -eq 0 ]; then
            echo "✅ Evaluation succeeded: Conformant image."
          elif [ $exit_code -eq 1 ]; then
            echo "❌ Scan failed: Non-conformant image (vulnerabilities found)."
            exit 1 # Fail the step
          else
           if [ "$IGNORE_ERRORS" = "true" ]; then
                echo "⚠️ Server/internal error occurred (Code: $exit_code), but IGNORE_SERVER_ERRORS=true. Proceeding."
          else
            echo "❌ Server/internal error occurred (Code: $exit_code) during evaluation. Set IGNORE_SERVER_ERRORS=true to override."
            exit 1 # Fail the step
          fi
          fi
    
          # 8. Push Application Image (ONLY if scan succeeded)
          # This step only runs if the 'Run Image Analysis Scan' step above exited with 0
          - name: Push Application Image to Artifact Registry
            run: |
            # Define the local and remote tags
            LOCAL_IMAGE_NAME="${{ inputs.IMAGE_NAME_TO_SCAN }}:${{ inputs.IMAGE_TAG }}"
    
            # This path is based on your 'Configure Docker' step (us-central1)
            # and the new AR_REPOSITORY input.
            FULL_AR_TAG="us-central1-docker.pkg.dev/${{ inputs.GCP_PROJECT_ID }}/${{ inputs.AR_REPOSITORY }}/${{ inputs.IMAGE_NAME_TO_SCAN }}:${{ inputs.IMAGE_TAG }}"
    
            echo "Tagging local image ${LOCAL_IMAGE_NAME} as ${FULL_AR_TAG}"
            docker tag "${LOCAL_IMAGE_NAME}" "${FULL_AR_TAG}"
    
            echo "Pushing ${FULL_AR_TAG} to Artifact Registry..."
            docker push "${FULL_AR_TAG}"
    

GitHub Actions (WIF)

這個範本適用於使用 Workload Identity Federation 的 GitHub Actions。

  • 在 GitHub 密鑰 (GCP_WORKLOAD_IDENTITY_PROVIDER) 中新增 Workload Identity Provider。
  • 在 GitHub 祕密 (GCP_SERVICE_ACCOUNT) 中新增服務帳戶。
  • 如要瞭解其他欄位,請參閱「變數定義」。

    # A workflow to BUILD the app image, RUN the scanner, and PUSH to AR if scan passes
    name: Build, Scan and Push
    
    on:
      push:
        branches:
          - main
      workflow_dispatch:
      inputs:
        IMAGE_NAME_TO_SCAN:
          description: 'The tag for your application image to be built (e.g., my-app:latest)'
          required: true
          default: 'checkout-image'
        GCP_PROJECT_ID:
          description: 'GCP Project ID for authentication and configuration'
          required: true
          default: 'projectId'
        ORGANIZATION_ID:
          description: 'Your GCP Organization ID'
          required: true
          default: 'orgId'
        CONNECTOR_ID:
          description: 'The ID for your pipeline connector'
          required: true
          default: 'connectorId'
        SCANNER_IMAGE:
          description: 'The Docker image that contains your scanner script'
          required: true
          default: 'us-central1-docker.pkg.dev/ci-plugin/ci-images/scc-artifactguard-scan-image:latest'
        IMAGE_TAG:
          description: 'The Docker image version'
          required: true
          default: 'latest'
        IGNORE_SERVER_ERRORS:
          description: 'If true, the pipeline continues on server/internal scanner errors.'
          required: false
          type: boolean
          default: false
    
    jobs:
      image-analysis-job:
      runs-on: ubuntu-latest
      permissions:
          contents: 'read'
          id-token: 'write'
    
      env:
          IMAGE_NAME_TO_SCAN: 'webgoat/webgoat'
          GCP_PROJECT_ID: 'projectId'
          ORGANIZATION_ID: 'orgId'
          CONNECTOR_ID: 'connectorId'
          SCANNER_IMAGE: 'us-central1-docker.pkg.dev/ci-plugin/ci-images/scc-artifactguard-scan-image:latest'
          IMAGE_TAG: 'imageTag'
          IGNORE_SERVER_ERRORS: 'false'
    
      steps:
          # Step 1: Authenticate and create credential file
          - name: 'Authenticate to Google Cloud'
          id: 'auth'
          uses: 'google-github-actions/auth@v2'
          with:
          workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
          service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
          create_credentials_file: true
    
          # Step 2: Set up gcloud SDK
          - name: 'Set up gcloud SDK'
          uses: 'google-github-actions/setup-gcloud@v2'
    
          # Step 3: Configure Docker for registries
          - name: 'Configure Docker for Artifact Registry'
          run: |
          gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
    
          # Step 4: Run Image Analysis Scan and Handle Exit Codes
          - name: 'Run Image Analysis Scan'
          run: |
          echo "📦 Running container from scanner image..."
    
          # Determine values: Use manual inputs if available (event_name=workflow_dispatch), otherwise use env defaults
          SCANNER_IMAGE="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.SCANNER_IMAGE || env.SCANNER_IMAGE }}"
          GCP_PROJECT_ID="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.GCP_PROJECT_ID || env.GCP_PROJECT_ID }}"
          ORGANIZATION_ID="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ORGANIZATION_ID || env.ORGANIZATION_ID }}"
          IMAGE_NAME="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.IMAGE_NAME_TO_SCAN || env.IMAGE_NAME_TO_SCAN }}"
          IMAGE_TAG="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.IMAGE_TAG || env.IMAGE_TAG }}"
          CONNECTOR_ID="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.CONNECTOR_ID || env.CONNECTOR_ID }}"
          IGNORE_ERRORS="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.IGNORE_SERVER_ERRORS || (env.IGNORE_SERVER_ERRORS == 'true') }}"
    
          # Variable to store exit code
          exit_code=0
    
          # Run docker and capture exit code using || trick
          docker run --rm \
                -v /var/run/docker.sock:/var/run/docker.sock \
                -v ${{ steps.auth.outputs.credentials_file_path }}:/gcp-creds.json \
                -e GOOGLE_APPLICATION_CREDENTIALS=/gcp-creds.json \
                -e GCP_PROJECT_ID="${GCP_PROJECT_ID}" \
                -e ORGANIZATION_ID="${ORGANIZATION_ID}" \
                -e IMAGE_NAME="${IMAGE_NAME}" \
                -e IMAGE_TAG="${IMAGE_TAG}" \
                -e CONNECTOR_ID="${CONNECTOR_ID}" \
                -e RUN_ID="${{ github.run_number }}" \
                "${SCANNER_IMAGE}" \
                || exit_code=$?
    
          echo "Docker run finished with exit code: $exit_code"
    
          if [ $exit_code -eq 0 ]; then
                echo "✅ Evaluation succeeded: Conformant image."
          elif [ $exit_code -eq 1 ]; then
                echo "❌ Scan failed: Non-conformant image (vulnerabilities found)."
                exit 1 # Fail the step
          else
                if [ "$IGNORE_ERRORS" = "true" ]; then
                echo "⚠️ Server/internal error occurred (Code: $exit_code), but IGNORE_SERVER_ERRORS=true. Proceeding."
                # Do nothing, step passes
                else
                echo "❌ Server/internal error occurred (Code: $exit_code) during evaluation. Set IGNORE_SERVER_ERRORS=true to override."
                exit 1 # Fail the step
                fi
          fi
    

Jenkins (密鑰)

  • 新增服務帳戶私密金鑰 (GCP_CREDENTIALS)。
  • 如要瞭解其他欄位,請參閱「變數定義」。

    pipeline {
          agent any
    
          parameters {
                string(
                      name: 'IMAGE_NAME_TO_SCAN',
                      defaultValue: 'checkout-image',
                      description: 'The tag for your application image to be built (e.g., my-app:latest)'
                )
                string(
                      name: 'GCP_PROJECT_ID',
                      defaultValue: 'projectId',
                      description: 'GCP Project ID for authentication'
                )
                string(
                      name: 'AR_REPOSITORY',
                      defaultValue: 'images',
                      description: 'Artifact Registry repository name (e.g., app-repo)'
                )
                string(
                      name: 'ORGANIZATION_ID',
                      defaultValue: 'orgId',
                      description: 'Your GCP Organization ID'
                )
                string(
                      name: 'CONNECTOR_ID',
                      defaultValue: 'connectorId',
                      description: 'The ID for your pipeline connector'
                )
                string(
                      name: 'SCANNER_IMAGE',
                      defaultValue: 'us-central1-docker.pkg.dev/ci-plugin/ci-images/scc-artifactguard-scan-image:latest',
                      description: 'The full registry path for your PRE-BUILT scanner tool'
                )
                string(
                      name: 'IMAGE_TAG',
                      defaultValue: 'latest',
                      description: 'The Docker image version (of the app image)'
                )
                booleanParam(
                      name: 'IGNORE_SERVER_ERRORS',
                      defaultValue: false,
                      description: 'Ignore server errors'
                )
                string(
                      name: 'VERBOSITY',
                      defaultValue: 'HIGH',
                      description: 'Verbosity flag'
                )
          }
    
    stages {
          // Stage 1: Check out the source code
          stage('Checkout') {
                steps {
                echo "Checking out source code..."
                checkout scm
                }
          }
    
          // Stage 2: Build application image
          stage('Build Application Image') {
                steps {
                echo "Building application image: ${params.IMAGE_NAME_TO_SCAN}:${params.IMAGE_TAG}"
                sh "docker build -t ${params.IMAGE_NAME_TO_SCAN}:${params.IMAGE_TAG} -f ./Dockerfile ."
                }
          }
    
          // Stage 3: Authenticate to Google Cloud and run scanner
          stage('Scan Image') {
                steps {
                script {
                      withCredentials([file(credentialsId: 'GCP_CREDENTIALS', variable: 'GCP_KEY_FILE')]) {
                            // Authenticate
                            sh "gcloud auth activate-service-account --key-file=\"$GCP_KEY_FILE\""
                            sh 'gcloud auth list'
                            sh 'gcloud auth configure-docker gcr.io --quiet'
                            sh 'gcloud auth configure-docker us-central1-docker.pkg.dev --quiet'
    
                            // Run scanner container
                            def exitCode = sh(
                            script: """
                                  echo "📦 Running scanner container from image: ${params.SCANNER_IMAGE}"
    
                                  docker run --rm \\
                                        -v /var/run/docker.sock:/var/run/docker.sock \\
                                        -v "$GCP_KEY_FILE":/tmp/scc-key.json \\
                                        -e GCLOUD_KEY_PATH=/tmp/scc-key.json \\
                                        -e GCP_PROJECT_ID="${params.GCP_PROJECT_ID}" \\
                                        -e ORGANIZATION_ID="${params.ORGANIZATION_ID}" \\
                                        -e IMAGE_NAME="${params.IMAGE_NAME_TO_SCAN}" \\
                                        -e IMAGE_TAG="${params.IMAGE_TAG}" \\
                                        -e CONNECTOR_ID="${params.CONNECTOR_ID}" \\
                                        -e BUILD_TAG="${env.JOB_NAME}" \\
                                        -e BUILD_ID="${env.BUILD_NUMBER}" \\
                                        "${params.SCANNER_IMAGE}"
                            """,
                            returnStatus: true
                            )
    
                            if (exitCode == 0) {
                            echo "✅ Evaluation succeeded: Conformant image."
                            } else if (exitCode == 1) {
                            error("❌ Scan failed: Non-conformant image (vulnerabilities found).")
                            } else {
                            if (params.IGNORE_SERVER_ERRORS) {
                                  echo "⚠️ Server/internal error occurred, but IGNORE_SERVER_ERRORS=true. Proceeding with pipeline."
                            } else {
                                  error("❌ Server/internal error occurred during evaluation. Set IGNORE_SERVER_ERRORS=true to override.")
                            }
                            }
                      }
                }
                }
          }
    
          // Stage 4: Push Application Image
          stage('Push Application Image') {
                steps {
                script {
                      def localImage = "${params.IMAGE_NAME_TO_SCAN}:${params.IMAGE_TAG}"
                      def remoteTag = "us-central1-docker.pkg.dev/${params.GCP_PROJECT_ID}/${params.AR_REPOSITORY}/${params.IMAGE_NAME_TO_SCAN}:${params.IMAGE_TAG}"
    
                      echo "Tagging local image ${localImage} as ${remoteTag}"
                      sh "docker tag ${localImage} ${remoteTag}"
    
                      echo "Pushing ${remoteTag} to Artifact Registry..."
                      sh "docker push ${remoteTag}"
                }
                }
          }
    }
    

變數定義

本節提供管道整合範本中使用的變數欄位相關資訊。

IMAGE_NAME_TO_SCAN (必要)

  • 指定要建構的應用程式映像檔標記。

GCP_PROJECT_ID (必要)

  • 指定用於驗證和設定的 Google Cloud 專案 ID。

AR_REPOSITORY (選填)

  • 指定 Artifact Registry 存放區的名稱。如果建構成功,映像檔會發布至該存放區。

ORGANIZATION_ID (必要)

  • 機構 ID。 Google Cloud

CONNECTOR_ID (必要)

  • 指定要使用的管道連接器 ID。

SCANNER_IMAGE (必要)

  • 預先建構的掃描器映像檔會分析程式碼、在建構期間根據政策評估映像檔,藉此找出安全漏洞,並產生一致性結果,判斷 CI/CD 管道是否通過。

    圖片詳細資料:us-central1-docker.pkg.dev/ci-plugin/ci-images/scc-artifactguard-scan-image:latest

VERBOSITY (選填)

  • 掃描器支援選用的 VERBOSITY 標記,可控制掃描輸出內容中顯示的詳細程度。掃描器輸出內容會因詳細程度和一致性結果 (通過或失敗) 而異。
  • 詳細程度旗標可以設為 LOWHIGH。如未提供,則預設值為 LOW

詳細程度低 (簡潔)

ArtifactGuard Conformance : False

  • 詳細說明失敗原因。
  • 只列出導致失敗的特定 CVE。
  • 提供按嚴重程度分類的 CVE 摘要計數。

ArtifactGuard 一致性:通過

  • 詳細列出政策名稱。
  • 僅提供依嚴重性分類的 CVE 摘要計數。

詳細程度高 (詳細)

提供發現的所有安全漏洞完整清單。

ArtifactGuard Conformance : False

  • 包括失敗原因。
  • 列出導致失敗的 CVE。
  • 提供政策偵測到的所有 CVE 清單。
  • 提供依嚴重性細分的 CVE 摘要計數。
  • 提供所有偵測到的安全漏洞完整清單。

ArtifactGuard 一致性:通過

  • 提供政策偵測到的所有 CVE 清單。
  • 提供依嚴重性細分的 CVE 摘要計數。
  • 提供所有偵測到的安全漏洞完整清單。

IGNORE_SERVER_ERRORS (選填)

  • 選用布林值旗標。如果設為 true,即使發生伺服器錯誤,管道仍會繼續運作。預設值為 false

啟動評估

在建構程序中,以 Docker 為基礎的策略會根據預先定義的政策評估映像檔。安全漏洞掃描邏輯位於us-central1-docker.pkg.dev/ci-plugin/ci-images/scc-artifactguard-scan-image:latest映像檔中。

如要在 CI/CD 管道中啟動安全漏洞掃描,請執行下列指令:

docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v ${{ steps.auth.outputs.credentials_file_path }}:/gcp-creds.json \
    -e GOOGLE_APPLICATION_CREDENTIALS=/gcp-creds.json \
    -e GCP_PROJECT_ID="${GCP_PROJECT_ID}" \
    -e ORGANIZATION_ID="${ORGANIZATION_ID}" \
    -e IMAGE_NAME="${IMAGE_NAME}" \
    -e IMAGE_TAG="${IMAGE_TAG}" \
    -e CONNECTOR_ID="${CONNECTOR_ID}" \
    -e RUN_ID="${{ github.run_number }}" \
    "${SCANNER_IMAGE}" \
    || exit_code=$?

持續整合/持續推送軟體更新整合功能會將 Docker 容器的結束代碼傳播至 Jenkins 或 GitHub Actions 執行階段,然後判斷管道的通過或失敗狀態。

效能和限制

  • 構件評估:圖片必須符合下列條件,才會接受評估:
    • 套件統一資源定位器 (pURL) 物件大小不得超過 100 MB。
    • 圖片不得包含超過 500 個 pURL。
  • 每個消費者專案每分鐘最多可發出 1,200 個 API 要求 (20 QPS),適用於構件防護服務中的所有方法。
  • SLO:構件評估作業約需兩分鐘。

疑難排解

本節將說明常見錯誤和解決方法。

CreateConnector 失敗

欄位 必要/選用 限制
name 必填 格式:必須符合規則運算式 [a-zA-Z0-9\\-\\s_]+$
符合一或多個下列項目:
  • 字母 (大寫或小寫)
  • 數字
  • 連字號 -
  • 空白字元 (\s)
  • 底線 _


長度上限:64 個半形字元。
pipeline_type 必填 必須是下列其中一個列舉值:
  • JENKINS_PIPELINE
  • GITHUB_ACTIONS
  • GOOGLE_CLOUD_BUILD
description 選用 不得超過 256 個字元。
display_name 選用 不得超過 256 個字元。

其他錯誤

下表列出一些常見錯誤和解決方法。

錯誤訊息 原因 具體行動/解決方式
資源「artifactscanguard.connectors.create」的權限遭拒 使用者或服務帳戶缺少資源 (專案、資料夾或機構) 的 artifactscanguard.connectors.create IAM 權限。 授予呼叫端包含 artifactscanguard.connectors.create 權限的 IAM 角色。
status.ErrFailedPrecondition 即使 Google Cloud 控制台 顯示服務已啟用,上線程序可能仍在進行中。 向支援團隊回報問題。
status.ErrInvalidArgument 欄位驗證失敗 確認 CreateConnector 要求符合指定限制。