Implementing Image Scanning in CI/CD Pipelines

Implementing Image Scanning in CI/CD Pipelines

Integration of security scanning into CI/CD pipelines shifts security left, catching vulnerabilities before image distribution. Early scanning reduces remediation costs and prevents vulnerable images from reaching production. However, scanning adds build time and may block deployments, requiring careful implementation to balance security with development velocity.

Pipeline scanning strategies must accommodate different risk tolerances and deployment urgencies. Fail-fast approaches block builds immediately upon finding vulnerabilities. Threshold-based policies allow low-severity vulnerabilities while blocking critical ones. Asynchronous scanning permits deployment while scanning continues, suitable for development environments. Organizations should implement different strategies for different pipeline stages.

# Example: Multi-stage scanning pipeline with Trivy
# .gitlab-ci.yml
stages:
  - build
  - scan-quick
  - test
  - scan-deep
  - deploy

variables:
  IMAGE_NAME: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
  TRIVY_VERSION: "0.45.0"
  GRYPE_VERSION: "0.65.0"

# Build stage
build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build 
        --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) 
        --build-arg VCS_REF=$CI_COMMIT_SHA
        -t $IMAGE_NAME .
    - docker push $IMAGE_NAME
  only:
    - branches
    - merge_requests

# Quick scan for rapid feedback
security-scan-quick:
  stage: scan-quick
  image: aquasec/trivy:$TRIVY_VERSION
  script:
    # Quick scan for critical vulnerabilities only
    - trivy image 
        --severity CRITICAL 
        --exit-code 1 
        --no-progress 
        --format table
        --timeout 5m
        $IMAGE_NAME
  allow_failure: false
  only:
    - branches
    - merge_requests

# Deep scan with multiple tools
security-scan-deep:
  stage: scan-deep
  image: alpine:latest
  before_script:
    - apk add --no-cache curl bash
    # Install multiple scanners
    - curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v$TRIVY_VERSION
    - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v$GRYPE_VERSION
  script:
    # Trivy comprehensive scan
    - |
      trivy image 
        --severity HIGH,CRITICAL,MEDIUM 
        --format json 
        --output trivy-report.json 
        --vuln-type os,library 
        --ignorefile .trivyignore
        $IMAGE_NAME
    
    # Grype scan for comparison
    - |
      grype $IMAGE_NAME 
        -o json 
        --file grype-report.json
        --fail-on high
    
    # Custom vulnerability analysis
    - |
      python3 <<EOF
      import json
      import sys
      
      # Load scan results
      with open('trivy-report.json', 'r') as f:
          trivy_results = json.load(f)
      
      with open('grype-report.json', 'r') as f:
          grype_results = json.load(f)
      
      # Analyze and correlate results
      critical_vulns = []
      high_vulns = []
      
      for result in trivy_results.get('Results', []):
          for vuln in result.get('Vulnerabilities', []):
              if vuln['Severity'] == 'CRITICAL':
                  critical_vulns.append(vuln)
              elif vuln['Severity'] == 'HIGH':
                  high_vulns.append(vuln)
      
      # Generate summary report
      print("=== Vulnerability Summary ===")
      print(f"Critical: {len(critical_vulns)}")
      print(f"High: {len(high_vulns)}")
      
      # Check against policy
      if len(critical_vulns) > 0:
          print("\nBuild FAILED: Critical vulnerabilities found")
          for vuln in critical_vulns[:5]:  # Show first 5
              print(f"- {vuln['VulnerabilityID']}: {vuln['PkgName']} {vuln['InstalledVersion']}")
          sys.exit(1)
      
      if len(high_vulns) > 10:
          print("\nBuild FAILED: Too many high severity vulnerabilities")
          sys.exit(1)
      
      print("\nBuild PASSED: Within acceptable vulnerability thresholds")
      EOF
  artifacts:
    reports:
      container_scanning: trivy-report.json
    paths:
      - trivy-report.json
      - grype-report.json
    expire_in: 1 week
  only:
    - main
    - develop

# License compliance scanning
license-scan:
  stage: scan-deep
  image: docker:latest
  services:
    - docker:dind
  script:
    # Extract and analyze licenses
    - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock 
        licensefinder/license_finder 
        /bin/bash -c "
          cd /app && 
          license_finder report --format json > licenses.json &&
          license_finder action_items
        "
    
    # Check for problematic licenses
    - |
      if grep -E "(GPL|AGPL|SSPL)" licenses.json; then
        echo "WARNING: Copyleft licenses detected"
        exit 1
      fi
  artifacts:
    paths:
      - licenses.json
  allow_failure: true

Scan result management requires processes for vulnerability assessment and remediation tracking. Not all vulnerabilities pose equal risk - environmental factors affect exploitability. Vulnerability management platforms help track remediation progress across multiple images and deployments. Integration with ticketing systems ensures accountability for vulnerability remediation.