Major accomplishments: - ✅ SELinux policy installed and working - ✅ Core Traefik v2.10 deployment running - ✅ Production configuration ready (v3.1) - ✅ Monitoring stack configured - ✅ Comprehensive documentation created - ✅ Security hardening implemented Current status: - 🟡 Partially deployed (60% complete) - ⚠️ Docker socket access needs resolution - ❌ Monitoring stack not deployed yet - ⚠️ Production migration pending Next steps: 1. Fix Docker socket permissions 2. Deploy monitoring stack 3. Migrate to production config 4. Validate full functionality Files added: - Complete Traefik deployment documentation - Production and test configurations - Monitoring stack configurations - SELinux policy module - Security checklists and guides - Current status documentation
327 lines
9.8 KiB
Bash
Executable File
327 lines
9.8 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Automated Image Digest Management Script
|
|
# Optimized version of generate_image_digest_lock.sh with automation features
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
STACKS_DIR="$PROJECT_ROOT/stacks"
|
|
LOCK_FILE="$PROJECT_ROOT/configs/image-digest-lock.yaml"
|
|
LOG_FILE="$PROJECT_ROOT/logs/image-update-$(date +%Y%m%d-%H%M%S).log"
|
|
|
|
# Create directories if they don't exist
|
|
mkdir -p "$(dirname "$LOCK_FILE")" "$PROJECT_ROOT/logs"
|
|
|
|
# Logging function
|
|
log() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
|
}
|
|
|
|
# Function to extract images from stack files
|
|
extract_images() {
|
|
local stack_file="$1"
|
|
|
|
# Use yq to extract image names from Docker Compose files
|
|
if command -v yq >/dev/null 2>&1; then
|
|
yq eval '.services[].image' "$stack_file" 2>/dev/null | grep -v "null" || true
|
|
else
|
|
# Fallback to grep if yq is not available
|
|
grep -E "^\s*image:\s*" "$stack_file" | sed 's/.*image:\s*//' | sed 's/\s*$//' || true
|
|
fi
|
|
}
|
|
|
|
# Function to get image digest from registry
|
|
get_image_digest() {
|
|
local image="$1"
|
|
local digest=""
|
|
|
|
# Handle images without explicit tag (assume :latest)
|
|
if [[ "$image" != *":"* ]]; then
|
|
image="${image}:latest"
|
|
fi
|
|
|
|
log "Fetching digest for $image"
|
|
|
|
# Try to get digest from Docker registry
|
|
if command -v skopeo >/dev/null 2>&1; then
|
|
digest=$(skopeo inspect "docker://$image" 2>/dev/null | jq -r '.Digest' || echo "")
|
|
else
|
|
# Fallback to docker manifest inspect (requires Docker CLI)
|
|
digest=$(docker manifest inspect "$image" 2>/dev/null | jq -r '.config.digest' || echo "")
|
|
fi
|
|
|
|
if [[ -n "$digest" && "$digest" != "null" ]]; then
|
|
echo "$digest"
|
|
else
|
|
log "Warning: Could not fetch digest for $image"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# Function to process all stack files and generate lock file
|
|
generate_digest_lock() {
|
|
log "Starting automated image digest lock generation"
|
|
|
|
# Initialize lock file
|
|
cat > "$LOCK_FILE" << 'EOF'
|
|
# Automated Image Digest Lock File
|
|
# Generated by automated-image-update.sh
|
|
# DO NOT EDIT MANUALLY - This file is automatically updated
|
|
|
|
version: "1.0"
|
|
generated_at: "$(date -Iseconds)"
|
|
images:
|
|
EOF
|
|
|
|
# Find all stack YAML files
|
|
local stack_files
|
|
stack_files=$(find "$STACKS_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null || true)
|
|
|
|
if [[ -z "$stack_files" ]]; then
|
|
log "No stack files found in $STACKS_DIR"
|
|
return 1
|
|
fi
|
|
|
|
declare -A processed_images
|
|
local total_images=0
|
|
local successful_digests=0
|
|
|
|
# Process each stack file
|
|
while IFS= read -r stack_file; do
|
|
log "Processing stack file: $stack_file"
|
|
|
|
local images
|
|
images=$(extract_images "$stack_file")
|
|
|
|
if [[ -n "$images" ]]; then
|
|
while IFS= read -r image; do
|
|
[[ -z "$image" ]] && continue
|
|
|
|
# Skip if already processed
|
|
if [[ -n "${processed_images[$image]:-}" ]]; then
|
|
continue
|
|
fi
|
|
|
|
((total_images++))
|
|
processed_images["$image"]=1
|
|
|
|
local digest
|
|
digest=$(get_image_digest "$image")
|
|
|
|
if [[ -n "$digest" ]]; then
|
|
# Add to lock file
|
|
cat >> "$LOCK_FILE" << EOF
|
|
"$image":
|
|
digest: "$digest"
|
|
pinned_reference: "${image%:*}@$digest"
|
|
last_updated: "$(date -Iseconds)"
|
|
source_stack: "$(basename "$stack_file")"
|
|
EOF
|
|
((successful_digests++))
|
|
log "✅ $image -> $digest"
|
|
else
|
|
# Add entry with warning for failed digest fetch
|
|
cat >> "$LOCK_FILE" << EOF
|
|
"$image":
|
|
digest: "FETCH_FAILED"
|
|
pinned_reference: "$image"
|
|
last_updated: "$(date -Iseconds)"
|
|
source_stack: "$(basename "$stack_file")"
|
|
warning: "Could not fetch digest from registry"
|
|
EOF
|
|
log "❌ Failed to get digest for $image"
|
|
fi
|
|
done <<< "$images"
|
|
fi
|
|
done <<< "$stack_files"
|
|
|
|
# Add summary to lock file
|
|
cat >> "$LOCK_FILE" << EOF
|
|
|
|
# Summary
|
|
total_images: $total_images
|
|
successful_digests: $successful_digests
|
|
failed_digests: $((total_images - successful_digests))
|
|
EOF
|
|
|
|
log "✅ Digest lock generation complete"
|
|
log "📊 Total images: $total_images, Successful: $successful_digests, Failed: $((total_images - successful_digests))"
|
|
}
|
|
|
|
# Function to update stack files with pinned digests
|
|
update_stacks_with_digests() {
|
|
log "Updating stack files with pinned digests"
|
|
|
|
if [[ ! -f "$LOCK_FILE" ]]; then
|
|
log "❌ Lock file not found: $LOCK_FILE"
|
|
return 1
|
|
fi
|
|
|
|
# Create backup directory
|
|
local backup_dir="$PROJECT_ROOT/backups/stacks-$(date +%Y%m%d-%H%M%S)"
|
|
mkdir -p "$backup_dir"
|
|
|
|
# Process each stack file
|
|
find "$STACKS_DIR" -name "*.yml" -o -name "*.yaml" | while IFS= read -r stack_file; do
|
|
log "Updating $stack_file"
|
|
|
|
# Create backup
|
|
cp "$stack_file" "$backup_dir/"
|
|
|
|
# Extract images and update with digests using Python script
|
|
python3 << 'PYTHON_SCRIPT'
|
|
import yaml
|
|
import sys
|
|
import os
|
|
import re
|
|
|
|
stack_file = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
lock_file = os.environ.get('LOCK_FILE', '')
|
|
|
|
if not stack_file or not lock_file or not os.path.exists(lock_file):
|
|
print("Missing required files")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
# Load lock file
|
|
with open(lock_file, 'r') as f:
|
|
lock_data = yaml.safe_load(f)
|
|
|
|
# Load stack file
|
|
with open(stack_file, 'r') as f:
|
|
stack_data = yaml.safe_load(f)
|
|
|
|
# Update images with digests
|
|
if 'services' in stack_data:
|
|
for service_name, service_config in stack_data['services'].items():
|
|
if 'image' in service_config:
|
|
image = service_config['image']
|
|
if image in lock_data.get('images', {}):
|
|
digest_info = lock_data['images'][image]
|
|
if digest_info.get('digest') != 'FETCH_FAILED':
|
|
service_config['image'] = digest_info['pinned_reference']
|
|
print(f"Updated {service_name}: {image} -> {digest_info['pinned_reference']}")
|
|
|
|
# Write updated stack file
|
|
with open(stack_file, 'w') as f:
|
|
yaml.dump(stack_data, f, default_flow_style=False, indent=2)
|
|
|
|
except Exception as e:
|
|
print(f"Error processing {stack_file}: {e}")
|
|
sys.exit(1)
|
|
PYTHON_SCRIPT "$stack_file"
|
|
done
|
|
|
|
log "✅ Stack files updated with pinned digests"
|
|
log "📁 Backups stored in: $backup_dir"
|
|
}
|
|
|
|
# Function to validate updated stacks
|
|
validate_stacks() {
|
|
log "Validating updated stack files"
|
|
|
|
local validation_errors=0
|
|
|
|
find "$STACKS_DIR" -name "*.yml" -o -name "*.yaml" | while IFS= read -r stack_file; do
|
|
# Check YAML syntax
|
|
if ! python3 -c "import yaml; yaml.safe_load(open('$stack_file'))" >/dev/null 2>&1; then
|
|
log "❌ YAML syntax error in $stack_file"
|
|
((validation_errors++))
|
|
fi
|
|
|
|
# Check for digest references
|
|
if grep -q '@sha256:' "$stack_file"; then
|
|
log "✅ $stack_file contains digest references"
|
|
else
|
|
log "⚠️ $stack_file does not contain digest references"
|
|
fi
|
|
done
|
|
|
|
if [[ $validation_errors -eq 0 ]]; then
|
|
log "✅ All stack files validated successfully"
|
|
else
|
|
log "❌ Validation completed with $validation_errors errors"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to create cron job for automation
|
|
setup_automation() {
|
|
local cron_schedule="0 2 * * 0" # Weekly on Sunday at 2 AM
|
|
local cron_command="$SCRIPT_DIR/automated-image-update.sh --auto-update"
|
|
|
|
# Check if cron job already exists
|
|
if crontab -l 2>/dev/null | grep -q "automated-image-update.sh"; then
|
|
log "Cron job already exists for automated image updates"
|
|
else
|
|
# Add cron job
|
|
(crontab -l 2>/dev/null; echo "$cron_schedule $cron_command") | crontab -
|
|
log "✅ Automated weekly image digest updates scheduled"
|
|
fi
|
|
}
|
|
|
|
# Main execution
|
|
main() {
|
|
case "${1:-}" in
|
|
"--generate-lock")
|
|
generate_digest_lock
|
|
;;
|
|
"--update-stacks")
|
|
update_stacks_with_digests
|
|
validate_stacks
|
|
;;
|
|
"--auto-update")
|
|
generate_digest_lock
|
|
update_stacks_with_digests
|
|
validate_stacks
|
|
;;
|
|
"--setup-automation")
|
|
setup_automation
|
|
;;
|
|
"--help"|"-h"|"")
|
|
cat << 'EOF'
|
|
Automated Image Digest Management Script
|
|
|
|
USAGE:
|
|
automated-image-update.sh [OPTIONS]
|
|
|
|
OPTIONS:
|
|
--generate-lock Generate digest lock file only
|
|
--update-stacks Update stack files with pinned digests
|
|
--auto-update Generate lock and update stacks (full automation)
|
|
--setup-automation Set up weekly cron job for automated updates
|
|
--help, -h Show this help message
|
|
|
|
EXAMPLES:
|
|
# Generate digest lock file
|
|
./automated-image-update.sh --generate-lock
|
|
|
|
# Update stack files with digests
|
|
./automated-image-update.sh --update-stacks
|
|
|
|
# Full automated update (recommended)
|
|
./automated-image-update.sh --auto-update
|
|
|
|
# Set up weekly automation
|
|
./automated-image-update.sh --setup-automation
|
|
|
|
NOTES:
|
|
- Requires yq, skopeo, or Docker CLI for fetching digests
|
|
- Creates backups before modifying stack files
|
|
- Logs all operations for auditability
|
|
- Safe to run multiple times (idempotent)
|
|
EOF
|
|
;;
|
|
*)
|
|
log "❌ Unknown option: $1"
|
|
log "Use --help for usage information"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Execute main function with all arguments
|
|
main "$@" |