CI/CD 통합 구성

Security Command Center 고객은 지속적 통합/지속적 배포 (CI/CD) 환경에서 중앙 집중식의 포괄적인 스캔 결과를 얻기가 어려울 수 있습니다. 통합 뷰가 없으면 취약점 관리가 복잡해집니다. 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 내에서 보안을 자동화하여 취약점 노이즈를 줄이고 게이팅 기준을 설정합니다.
    • 감독 제공: DevOps와 협업하고 관리자가 게이트된 취약점을 추적할 수 있는 대시보드를 제공합니다.
  • 보조 사용자: 애플리케이션 개발자
    • 검토 및 수정: 정책 평가 결과와 상호작용하고, 빌드 결과를 검토하고, 신고된 보안 문제를 수정합니다.
    • 평가: CI 파이프라인의 빌드 프로세스를 통해 간접적으로 정책 평가를 시작합니다.
    • 규정 준수 유지: 개발 워크플로를 중단하지 않고 보안 요구사항을 준수합니다.

주요 용어 및 개념

  • Common Vulnerabilities and Exposures (CVE): 고유 식별자가 할당된 공개적으로 공개된 컴퓨터 보안 취약점입니다. 이러한 식별자는 해결을 위해 취약점을 추적하는 데 도움이 됩니다.
  • 소프트웨어 자재명세서 (SBOM): 소프트웨어 구성요소 및 종속 항목의 기계 판독 가능 인벤토리입니다. SBOM에는 각 구성요소의 버전, 출처, 기타 관련 세부정보에 관한 정보가 포함됩니다. SBOM은 CVE 및 기타 보안 위험을 식별하는 데 사용할 수 있습니다.
  • 아티팩트: 빌드 프로세스 중에 생성된 데이터 또는 항목과 같은 소프트웨어 개발의 버전이 지정되고 검증된 출력입니다.
  • 커넥터: 이미지 태그입니다. CI 파이프라인에서 아티팩트 보호 서비스로 전달되면 커넥터는 빌드 중인 이미지에 대해 실행할 정책을 결정합니다.
  • CI 정책: 환경에서 허용되는 취약점과 패키지를 제어하는 규칙 또는 기준을 정의하는 취약점 정책입니다.

대략적인 워크플로

  1. CI 커넥터 만들기
  2. 이전 단계에서 구성된 커넥터를 사용하여 아티팩트 보호 정책을 만듭니다. 정책 범위를 정의합니다.
  3. 아티팩트 평가 시작

평가 중에 이미지가 빌드되고 사전 정의된 정책에 따라 평가됩니다. 정책이 실패하면 빌드가 실패합니다. 그런 다음 DevOps 또는 애플리케이션 엔지니어가 실패 세부정보를 검사하여 특정 취약점을 찾고, CVE 세부정보에 따라 종속 항목을 업데이트하고, 파이프라인을 다시 실행해야 합니다.

시작하기 전에

CI/CD 통합을 사용하려면 아티팩트 보호를 사용 설정해야 합니다. 자세한 내용은 아티팩트 보호 문서의 시작하기 전에를 참고하세요.

그런 다음 Google Cloud 콘솔에서 또는 Google Cloud CLI를 사용하여 커넥터를 만들 수 있습니다.

Google Cloud 콘솔에서 커넥터 만들기

커넥터를 만들려면 다음 단계를 따르세요.

  1. Google Cloud 콘솔에서 보안 > 설정으로 이동합니다.

  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}

delete

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 파이프라인은 서비스 계정 키 또는 워크로드 아이덴티티 제휴를 사용하여 인증할 수 있습니다. 보안 비밀을 만드는 방법에 관한 자세한 내용은 다음을 참고하세요.

서비스 계정 키

워크로드 아이덴티티 제휴 (GitHub 작업용)

다음 방법 중 하나를 사용하여 CI/CD 환경에 보안 비밀을 추가해야 합니다.

서비스 계정 키 메서드

하나의 보안 비밀:

  • GCP_CREDENTIALS: 다운로드한 서비스 계정 JSON 키 파일의 콘텐츠입니다.

워크로드 아이덴티티 제휴 방법

두 가지 보안 비밀:

  • GCP_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)

이 템플릿은 워크로드 아이덴티티 제휴를 사용하는 GitHub Actions용입니다.

  • GitHub 보안 비밀 (GCP_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 (필수)

  • Google Cloud 조직 ID입니다.

CONNECTOR_ID (필수)

  • 사용할 파이프라인의 커넥터 ID를 지정합니다.

SCANNER_IMAGE (필수)

  • 사전 빌드된 스캐너 이미지는 코드를 분석하고, 빌드 중에 정책에 따라 이미지를 평가하여 취약점을 식별하고, CI/CD 파이프라인 통과 여부를 결정하는 적합성 결과를 생성합니다.

    이미지 세부정보: us-central1-docker.pkg.dev/ci-plugin/ci-images/scc-artifactguard-scan-image:latest

VERBOSITY(선택사항)

  • 스캐너는 스캔 출력에 표시되는 세부정보 수준을 제어하는 선택적 VERBOSITY 플래그를 지원합니다. 스캐너 출력은 세부정보 수준과 적합성 결과 (통과 또는 실패)에 따라 다릅니다.
  • 상세도 플래그는 LOW 또는 HIGH로 설정할 수 있습니다. 제공되지 않으면 기본값은 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=$?

CI/CD 통합은 Docker 컨테이너의 종료 코드를 Jenkins 또는 GitHub Actions 런타임에 전파하며, 그러면 파이프라인의 통과 또는 실패 상태가 결정됩니다.

성능 및 제한사항

  • 아티팩트 평가: 이미지가 다음 기준을 충족하는 경우에만 평가됩니다.
    • 패키지 URL (pURL) 객체 크기는 100MB를 초과할 수 없습니다.
    • 이미지에 포함된 개인화 URL은 500개 이하여야 합니다.
  • 아티팩트 보호 서비스 내 모든 메서드에 대해 소비자 프로젝트당 분당 최대 1,200개의 API 요청 (20QPS)
  • SLO: 아티팩트 평가에는 약 2분이 소요됩니다.

문제 해결

이 섹션에서는 일반적인 오류와 해결 방법을 간략히 설명합니다.

CreateConnector 실패

필드 필수/선택사항 제약조건
name 필수 형식: 정규 표현식 [a-zA-Z0-9\\-\\s_]+$와 일치해야 합니다.
다음 중 하나 이상과 일치해야 합니다.
  • 문자 (대문자 또는 소문자)
  • 숫자
  • 하이픈 -
  • 공백 (\s)
  • 밑줄 _


최대 길이: 64자(영문 기준)
pipeline_type 필수 다음 enum 값 중 하나여야 합니다.
  • 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 요청이 지정된 제약 조건을 충족하는지 확인합니다.