name: Validate Docker Image Pinning on: pull_request: branches: [main] paths: - '**/Dockerfile*' - '**/docker-compose*.yml' - '**/docker-compose*.yaml' - '.github/workflows/*.yml' - '.github/workflows/*.yaml' - '.github/workflows/validate-image-pinning.yml' - '.github/scripts/validate-docker-compose-images.sh' - '.github/scripts/validate-workflow-images.py' workflow_call: # Called by ci-gate.yml for release pipeline workflow_dispatch: # No concurrency group — intentionally omitted. # This workflow triggers on both pull_request and workflow_call (from # ci-gate.yml / release-gate.yml). A shared concurrency key would cause # direct PR runs and workflow_call runs to cancel each other mid-flight. # See #3554 (reverted in #3599) for context. permissions: {} # Minimal permissions for OSSF Scorecard jobs: validate-docker-compose: name: Validate docker-compose Images runs-on: ubuntu-latest permissions: contents: read steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Validate docker-compose image pinning run: | chmod +x .github/scripts/validate-docker-compose-images.sh .github/scripts/validate-docker-compose-images.sh validate-workflow-images: name: Validate Workflow Service Containers runs-on: ubuntu-latest permissions: contents: read steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Set up Python uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: '3.12' - name: Install PyYAML run: pip install pyyaml==6.0.2 - name: Validate workflow image pinning run: | chmod +x .github/scripts/validate-workflow-images.py python .github/scripts/validate-workflow-images.py summary: name: Image Pinning Validation Summary needs: [validate-docker-compose, validate-workflow-images] runs-on: ubuntu-latest if: always() permissions: contents: read steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit # zizmor: ignore[template-injection] - values passed via env vars, not interpolated in shell - name: Generate summary env: DOCKER_COMPOSE_RESULT: ${{ needs.validate-docker-compose.result }} WORKFLOW_IMAGES_RESULT: ${{ needs.validate-workflow-images.result }} run: | { echo "## 🔒 Docker Image Pinning Validation" echo "" echo "### What is Image Pinning?" echo "" echo "Pinning Docker images with SHA256 digests ensures:" echo "" echo "- 🔒 **Security**: Protection against supply chain attacks" echo "- 🔄 **Reproducibility**: Exact same image bytes every time" echo "- 🛡️ **Immutability**: Tags like \`:latest\` can be changed, but SHA digests cannot" echo "" echo "### Validation Results" echo "" if [ "$DOCKER_COMPOSE_RESULT" = "success" ] && \ [ "$WORKFLOW_IMAGES_RESULT" = "success" ]; then echo "✅ **All images properly pinned with SHA256 digests**" echo "" echo "All docker-compose and workflow files pass validation." else echo "❌ **Image pinning violations found**" echo "" if [ "$DOCKER_COMPOSE_RESULT" != "success" ]; then echo "- ❌ docker-compose files have unpinned images" fi if [ "$WORKFLOW_IMAGES_RESULT" != "success" ]; then echo "- ❌ Workflow files have unpinned service containers" fi echo "" echo "See job logs above for details and fix instructions." fi echo "" echo "### How to Fix Unpinned Images" echo "" echo "\`\`\`bash" echo "# 1. Pull the image" echo "docker pull " echo "" echo "# 2. Get the SHA digest" echo "docker inspect | jq -r '.[0].RepoDigests[0]'" echo "" echo "# 3. Update your file" echo "image: @sha256:..." echo "\`\`\`" echo "" echo "### Resources" echo "" echo "- [OSSF Scorecard - Pinned Dependencies](https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies)" echo "- [Docker Image Digests Documentation](https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier)" } >> "$GITHUB_STEP_SUMMARY" - name: Fail if validation found issues env: DOCKER_COMPOSE_RESULT: ${{ needs.validate-docker-compose.result }} WORKFLOW_IMAGES_RESULT: ${{ needs.validate-workflow-images.result }} run: | if [ "$DOCKER_COMPOSE_RESULT" != "success" ] || \ [ "$WORKFLOW_IMAGES_RESULT" != "success" ]; then echo "::error::Image pinning validation failed" exit 1 fi