Files
local-deep-research/.github/scripts/validate-docker-compose-images.sh
LearningCircuit ea73116db6 feat: add CI validation for Docker image SHA digest pinning
Add comprehensive validation to enforce SHA256 digest pinning across all
Docker image references (Dockerfiles, docker-compose, and workflow files).

New Files:
- .github/scripts/validate-docker-compose-images.sh
  Bash script that validates docker-compose.yml files for unpinned images.
  Allows documented exceptions for own images and templates.

- .github/scripts/validate-workflow-images.py
  Python script with proper YAML parsing to validate GitHub Actions
  workflow service containers and container images.

- .github/workflows/validate-image-pinning.yml
  CI workflow that runs both validators on PR changes. Provides clear
  error messages and fix instructions when violations are found.

Why This Matters:
Image tags are mutable and can be reassigned to malicious images in supply
chain attacks. SHA256 digests are immutable cryptographic identifiers that
guarantee the exact same image bytes every deployment.

This validation:
- Blocks PRs with unpinned images
- Shows violations directly in PR checks (not just Security tab)
- Provides clear fix instructions
- Runs efficiently (only on relevant file changes)

Complements:
- PR #1184 (pins Dockerfile and workflow images)
- PR #1218 (pins docker-compose images)
2025-12-03 12:35:09 +01:00

123 lines
3.9 KiB
Bash
Executable File

#!/bin/bash
# Validates that all docker-compose image references use SHA256 digests
# Prevents supply chain attacks by ensuring immutable image references
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration - images that are allowed without SHA digests
ALLOWED_EXCEPTIONS=(
"localdeepresearch/local-deep-research:latest" # Own image, built by CI
)
# Check if an image reference is in the exceptions list
is_exception() {
local image="$1"
for exception in "${ALLOWED_EXCEPTIONS[@]}"; do
if [[ "$image" == "$exception" ]]; then
return 0
fi
done
return 1
}
# Validate a single docker-compose file
validate_compose_file() {
local file="$1"
local violations=0
local line_num=0
while IFS= read -r line; do
line_num=$((line_num + 1))
# Check if this line contains an image reference
if [[ "$line" =~ ^[[:space:]]*image:[[:space:]]*(.+)$ ]]; then
local image="${BASH_REMATCH[1]}"
image=$(echo "$image" | tr -d '"' | xargs) # Remove quotes and whitespace
# Skip if it's an exception
if is_exception "$image"; then
echo -e "${YELLOW} Line $line_num: $image (exception)${NC}"
continue
fi
# Check if image has SHA digest
if [[ ! "$image" =~ @sha256: ]]; then
echo -e "${RED} ❌ Line $line_num: Missing SHA digest${NC}"
echo -e "${RED} Image: $image${NC}"
violations=$((violations + 1))
else
echo -e "${GREEN} ✓ Line $line_num: $image${NC}"
fi
fi
done < "$file"
return $violations
}
# Main validation logic
main() {
local total_violations=0
local files_checked=0
echo "🔍 Validating docker-compose image pinning..."
echo ""
# Find all docker-compose files
while IFS= read -r compose_file; do
# Skip cookiecutter templates and examples (documentation only)
if [[ "$compose_file" =~ cookiecutter-docker/ ]] || [[ "$compose_file" =~ examples/ ]]; then
echo -e "${YELLOW}⏭ Skipping: $compose_file (template/example)${NC}"
continue
fi
echo "📄 Checking: $compose_file"
if validate_compose_file "$compose_file"; then
: # No violations
else
violations=$?
total_violations=$((total_violations + violations))
fi
files_checked=$((files_checked + 1))
echo ""
done < <(find . -name "docker-compose*.yml" -o -name "docker-compose*.yaml")
# Summary
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 Summary:"
echo " Files checked: $files_checked"
echo " Violations: $total_violations"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [ $total_violations -gt 0 ]; then
echo ""
echo -e "${RED}❌ Found $total_violations unpinned images in docker-compose files${NC}"
echo ""
echo "Images must use SHA256 digests for security and reproducibility."
echo ""
echo "To fix:"
echo " 1. Pull the image: docker pull <image:tag>"
echo " 2. Get digest: docker inspect <image:tag> | jq -r '.[0].RepoDigests[0]'"
echo " 3. Update file: image: <image:tag>@sha256:..."
echo ""
echo "Example:"
echo " # Bad"
echo " image: ollama/ollama:latest"
echo ""
echo " # Good"
echo " image: ollama/ollama:latest@sha256:8850b8b33936b9fb..."
exit 1
fi
echo ""
echo -e "${GREEN}✅ All docker-compose images properly pinned${NC}"
exit 0
}
main "$@"