CI/CD 통합은 CI/CD 파이프라인을 아티팩트 보호에 연결하여 빌드 단계에서 컨테이너 취약점을 감지합니다.
스캐너는 GitHub Actions, Cloud Build, Jenkins를 지원합니다. 파이프라인에 통합되면 이미지가 레지스트리로 푸시되기 전에 아티팩트 가드 정책에 따라 컨테이너 이미지를 평가합니다.
스캐너 기능
CI/CD 통합은 CI/CD 워크플로 내에서 바이너리로 실행되어 다음 작업을 실행합니다.
- 빌드 시간 정책 평가: 구성된 아티팩트 보호 정책에 대해 새로 빌드된 컨테이너 이미지를 스캔합니다.
- 빌드 차단: 보안 정책을 위반하는 빌드를 차단하기 위해 CI/CD 러너 (GitHub Actions, Cloud Build 또는 Jenkins)에 종료 코드를 반환합니다.
- 스캔 보고: 취약점 스캔 결과를 Security Command Center로 전송하고 JSON 또는 SARIF 형식으로 로컬 보고서를 출력합니다.
타겟 역할 및 책임
CI/CD 통합은 다음 팀을 위해 설계되었습니다.
- DevOps 또는 플랫폼 엔지니어링팀: 파이프라인 커넥터를 만들고 CI/CD 워크플로 구성에 스캐너 실행 단계를 추가합니다.
- 보안 관리자: 스캐너가 적용하는 빌드 시간 취약점 정책을 정의합니다.
- 애플리케이션 개발자: CI/CD 빌드 출력에서 스캔 실행 로그를 검토하여 정책 위반을 식별하고 수정합니다.
주요 용어 및 개념
- Common Vulnerabilities and Exposures (CVE): 고유 식별자가 할당된 공개적으로 공개된 컴퓨터 보안 취약점입니다.
- 소프트웨어 자재명세서 (SBOM): CVE 및 기타 보안 위험을 식별하는 데 사용되는 소프트웨어 구성요소 및 종속 항목의 머신 판독 가능 인벤토리입니다.
- 아티팩트: 아티팩트 보호의 컨텍스트에서 CI/CD 파이프라인으로 빌드된 컨테이너 이미지입니다.
- 커넥터: CI/CD 파이프라인을 아티팩트 보호에 연결하는 리소스입니다. 스캔 중에 CI 파이프라인에서 전달되면 커넥터 ID에 따라 이미지를 평가하는 정책이 결정됩니다.
- CI 정책: CI/CD 빌드 중에 적용되는 특정 CVE, 심각도, 패키지 규칙을 정의하는 취약점 정책입니다.
대략적인 워크플로
CI/CD 취약점 스캔을 구성하고 실행하려면 다음 워크플로를 완료하세요.
- 타겟 CI/CD 플랫폼의 CI 커넥터를 만듭니다.
- 파이프라인 범위에 커넥터 ID가 포함된 아티팩트 보호 정책을 만듭니다.
- Google Cloud로 인증하고 컨테이너 이미지를 빌드하고 스캐너 컨테이너를 실행하도록 CI/CD 파이프라인을 구성합니다.
파이프라인이 실행되면 스캐너가 정책에 따라 컨테이너 이미지를 평가합니다. 이미지가 정책을 위반하면 스캐너는 빌드를 실패시키는 실패 종료 코드를 반환합니다. 그러면 개발자는 빌드 로그를 검사하고 취약한 종속 항목을 업데이트하고 빌드를 다시 실행할 수 있습니다.
시작하기 전에
CI/CD 통합을 사용하려면 아티팩트 보호를 사용 설정해야 합니다. 자세한 내용은 아티팩트 보호 문서의 시작하기 전에를 참고하세요.
커넥터 만들기
콘솔
Google Cloud 콘솔에서 커넥터를 만들려면 다음 단계를 따르세요.
- Google Cloud 콘솔에서 보안 > 설정으로 이동합니다.
- 아티팩트 보호 카드에서 설정 관리를 클릭합니다.
- 커넥터 만들기를 클릭하고 커넥터에 대한 다음 세부정보를 입력합니다.
- 커넥터 ID: 커넥터의 ID를 추가합니다.
- 설명: 커넥터에 대한 설명을 입력합니다.
- CI/CD 플랫폼: 목록에서 해당 CI/CD 플랫폼을 선택합니다. 선택한 CI/CD 플랫폼으로 빌드된 파이프라인에서만 이 커넥터를 사용하세요.
- 만들기를 클릭합니다.
커넥터가 생성되었음을 확인하는 알림이 표시됩니다. 커넥터 표에는 사용 가능한 커넥터가 나열되어 있습니다.
gcloud CLI
gcloud CLI를 사용하여 커넥터를 만들려면 다음 명령어를 실행합니다.
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: 커넥터의 식별자입니다 (예:
my-connector). - LOCATION: 데이터가 저장된 Security Command Center 위치입니다.
global만 지원됩니다. - ORGANIZATION_ID: 조직 ID
- PROJECT_NUMBER: 숫자 프로젝트 번호입니다.
- PIPELINE_TYPE: CI/CD 파이프라인의 유형입니다. 다음 중 하나여야 합니다.
GOOGLE_CLOUD_BUILDGITHUB_ACTIONSJENKINS_PIPELINE
- DESCRIPTION: 커넥터의 텍스트 설명입니다.
- DISPLAY_NAME: 커넥터의 사용자 친화적인 표시 이름입니다.
커넥터 업데이트
콘솔
Google Cloud 콘솔에서 정책을 커넥터에 연결하려면 다음 단계를 따르세요.
- Google Cloud 콘솔에서 보안 > 설정으로 이동합니다.
- 아티팩트 보호 카드에서 설정 관리를 클릭합니다.
- 커넥터 표에서 커넥터 옆에 있는 를 클릭하고 정책 추가를 선택합니다.
- 단계에 따라 아티팩트 보호 정책을 만들거나 업데이트합니다. 자세한 내용은 정책 만들기를 참고하세요.
gcloud CLI
gcloud CLI를 사용하여 기존 커넥터를 확인하고 정책 연결을 업데이트하려면 다음 명령어를 사용하세요.
커넥터 나열
gcloud alpha scc artifact-guard connectors list PARENT
다음을 바꿉니다.
- PARENT: 조직 또는 프로젝트 상위 리소스에 허용되는 형식은 다음과 같습니다.
organizations/ORGANIZATION_ID/locations/LOCATIONprojects/PROJECT_NUMBER/locations/LOCATION
커넥터 설명
gcloud alpha scc artifact-guard connectors describe CONNECTOR_ID \ --location=LOCATION \ (--organization=ORGANIZATION_ID | --project=PROJECT_NUMBER)
커넥터 정책 연결 업데이트
커넥터를 정책에 연결하려면 정책 정의 파일의 파이프라인 범위에 커넥터 ID를 포함하고 gcloud alpha scc artifact-guard policies update 명령어를 사용하여 정책을 업데이트합니다. 자세한 내용은 정책 만들기를 참고하세요.
커넥터 삭제
콘솔
Google Cloud 콘솔에서 커넥터를 삭제하려면 다음 단계를 따르세요.
- Google Cloud 콘솔에서 보안 > 설정으로 이동합니다.
- 아티팩트 보호 카드에서 설정 관리를 클릭합니다.
- 커넥터 표에서 커넥터 옆에 있는 를 클릭하고 커넥터 삭제를 선택합니다.
- 확인 메시지에 따라 커넥터를 삭제합니다. 취소를 클릭하여 중단합니다.
gcloud CLI
gcloud CLI를 사용하여 커넥터를 삭제하려면 다음 명령어를 실행합니다.
gcloud alpha scc artifact-guard connectors delete CONNECTOR_ID \ --location=LOCATION \ (--organization=ORGANIZATION_ID | --project=PROJECT_NUMBER)
평가 실행하기
GitHub Actions 및 Jenkins 파이프라인은 취약점 스캔을 지원합니다. 평가를 수행하려면 다음 단계를 따라야 합니다.
보안 비밀 구성
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 적합성: 통과
- 정책 이름을 자세히 설명합니다.
- 심각도별 CVE의 요약된 개수만 제공합니다.
ArtifactGuard 적합성: 실패
- 실패 이유를 자세히 설명합니다.
- 실패를 일으킨 특정 CVE만 나열합니다.
- 심각도별로 분류된 CVE의 요약된 수를 제공합니다.
높은 상세 출력 (자세함)
발견된 모든 취약점의 전체 목록을 제공합니다.
ArtifactGuard 적합성: 통과
- 정책에서 감지된 모든 CVE 목록을 제공합니다.
- 심각도별로 분류된 CVE의 요약 개수를 제공합니다.
- 감지된 모든 취약점의 전체 목록을 제공합니다.
ArtifactGuard 적합성: 실패
- 실패 이유가 포함됩니다.
- 실패를 일으킨 CVE를 나열합니다.
- 정책에서 감지된 모든 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 런타임에 전파하며, 그러면 파이프라인의 통과 또는 실패 상태가 결정됩니다.
문제 해결
이 섹션에서는 일반적인 오류와 해결 방법을 간략히 설명합니다.
CreateConnector 실패
| 필드 | 필수/선택사항 | 제약조건 |
|---|---|---|
name |
필수 | 형식: 정규 표현식 [a-zA-Z0-9\\-\\s_]+$와 일치해야 합니다. 다음 중 하나 이상과 일치해야 합니다.
최대 길이: 64자(영문 기준) |
pipeline_type |
필수 | 다음 enum 값 중 하나여야 합니다.
|
description |
선택사항 | 256자(영문 기준) 이하여야 합니다. |
display_name |
선택사항 | 256자(영문 기준) 이하여야 합니다. |
기타 오류
다음 표에는 일반적인 오류와 해결 방법이 나와 있습니다.
| 오류 메시지 | 원인 | 조치/해결 방법 |
|---|---|---|
리소스에 대한 artifactscanguard.connectors.create 권한이 거부됨 |
사용자 또는 서비스 계정에 리소스 (프로젝트, 폴더 또는 조직)에 대한 artifactscanguard.connectors.create IAM 권한이 없습니다. |
호출자에게 artifactscanguard.connectors.create 권한이 포함된 IAM 역할을 부여합니다. |
status.ErrFailedPrecondition |
Google Cloud 콘솔에 서비스가 사용 설정된 것으로 표시되더라도 온보딩이 진행 중일 수 있습니다. | 지원팀에 문제를 신고합니다. |
status.ErrInvalidArgument |
필드 유효성 검사 실패 | CreateConnector 요청이 지정된 제약 조건을 충족하는지 확인합니다. |