diff --git a/roles/hardening/tasks/container-scanning.yml b/roles/hardening/tasks/container-scanning.yml new file mode 100644 index 0000000..8317448 --- /dev/null +++ b/roles/hardening/tasks/container-scanning.yml @@ -0,0 +1,70 @@ +--- +# Container Security Scanning with Trivy + +- name: Install Trivy scanner + apt: + name: + - apt-transport-https + - gnupg + - lsb-release + state: present + update_cache: true + +- name: Add Trivy GPG key + apt_key: + url: https://aquasecurity.github.io/trivy-repo/deb/public.key + state: present + +- name: Add Trivy repository + apt_repository: + repo: "deb https://aquasecurity.github.io/trivy-repo/deb {{ ansible_distribution_release }} main" + state: present + update_cache: true + +- name: Install Trivy + apt: + name: trivy + state: present + update_cache: true + +- name: Create Trivy report directory + file: + path: /var/log/trivy-scans + state: directory + owner: root + group: root + mode: "0750" + +- name: Install container scan script + copy: + src: container-security-scan.sh + dest: /usr/local/sbin/container-security-scan + owner: root + group: root + mode: "0750" + +- name: Install systemd service for container scanning + template: + src: trivy-scan.service.j2 + dest: /etc/systemd/system/trivy-scan.service + owner: root + group: root + mode: "0644" + +- name: Install systemd timer for weekly scans + template: + src: trivy-scan.timer.j2 + dest: /etc/systemd/system/trivy-scan.timer + owner: root + group: root + mode: "0644" + +- name: Reload systemd + systemd: + daemon_reload: true + +- name: Enable and start weekly scan timer + systemd: + name: trivy-scan.timer + enabled: true + state: started diff --git a/roles/hardening/templates/trivy-scan.service.j2 b/roles/hardening/templates/trivy-scan.service.j2 new file mode 100644 index 0000000..6b6d3f5 --- /dev/null +++ b/roles/hardening/templates/trivy-scan.service.j2 @@ -0,0 +1,10 @@ +[Unit] +Description=Container Security Scan with Trivy +After=docker.service + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/container-security-scan --severity HIGH,CRITICAL +StandardOutput=journal +StandardError=journal +SyslogIdentifier=trivy-scan diff --git a/roles/hardening/templates/trivy-scan.timer.j2 b/roles/hardening/templates/trivy-scan.timer.j2 new file mode 100644 index 0000000..dfdd904 --- /dev/null +++ b/roles/hardening/templates/trivy-scan.timer.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=Weekly Container Security Scan with Trivy + +[Timer] +OnCalendar=Sun 03:00 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/scripts/container-security-scan.sh b/scripts/container-security-scan.sh new file mode 100644 index 0000000..0539219 --- /dev/null +++ b/scripts/container-security-scan.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Container Image Security Scanner +# Uses Trivy to scan images for CVEs + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VAULT_PASSWORD_FILE="${VAULT_PASSWORD_FILE:-secrets/.vault_pass}" +REPORT_DIR="${REPORT_DIR:-/var/log/trivy-scans}" +SEVERITY="${SEVERITY:-HIGH,CRITICAL}" + +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " --scan-all Scan all running containers (default)" + echo " --scan-image IMAGE Scan specific image" + echo " --severity LEVEL Severity filter (default: HIGH,CRITICAL)" + echo " --report-only Exit 0 even if CVEs found (for CI reporting)" + echo " --help Show this help" + exit 1 +} + +SCAN_MODE="all" +TARGET_IMAGE="" +REPORT_ONLY=false + +while [[ $# -gt 0 ]]; do + case $1 in + --scan-all) SCAN_MODE="all"; shift ;; + --scan-image) + SCAN_MODE="single" + TARGET_IMAGE="$2" + shift 2 + ;; + --severity) SEVERITY="$2"; shift 2 ;; + --report-only) REPORT_ONLY=true; shift ;; + --help) usage ;; + *) echo "Unknown option: $1"; usage ;; + esac +done + +# Check if Trivy is installed +if ! command -v trivy &> /dev/null; then + echo "Installing Trivy..." + curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin +fi + +# Create report directory +mkdir -p "$REPORT_DIR" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +REPORT_FILE="$REPORT_DIR/trivy-scan-$TIMESTAMP.json" +SUMMARY_FILE="$REPORT_DIR/trivy-scan-$TIMESTAMP.summary" + +scan_image() { + local image="$1" + echo "Scanning: $image" + + # Scan with Trivy + if trivy image \ + --severity "$SEVERITY" \ + --format json \ + --output "$REPORT_FILE.tmp" \ + --scanners vuln,config \ + "$image" 2>/dev/null; then + + # Check for vulnerabilities + if command -v jq &> /dev/null; then + CRITICAL=$(jq '[.Results[]?.Vulnerabilities? // empty | .[] | select(.Severity == "CRITICAL")] | length' "$REPORT_FILE.tmp" 2>/dev/null || echo "0") + HIGH=$(jq '[.Results[]?.Vulnerabilities? // empty | .[] | select(.Severity == "HIGH")] | length' "$REPORT_FILE.tmp" 2>/dev/null || echo "0") + + echo " CRITICAL: $CRITICAL, HIGH: $HIGH" + + if [[ "$CRITICAL" -gt 0 ]] && [[ "$REPORT_ONLY" == "false" ]]; then + echo "ERROR: CRITICAL vulnerabilities found in $image" >&2 + return 1 + fi + fi + + cat "$REPORT_FILE.tmp" >> "$REPORT_FILE" + rm -f "$REPORT_FILE.tmp" + return 0 + else + echo "WARNING: Failed to scan $image" >&2 + return 0 # Continue scanning other images + fi +} + +# Main scanning logic +echo "Starting Trivy container scan at $(date)" +echo "Severity filter: $SEVERITY" +echo "" + +if [[ "$SCAN_MODE" == "single" ]]; then + scan_image "$TARGET_IMAGE" +else + # Get all unique images from running containers + IMAGES=$(docker ps --format '{{.Image}}' | sort -u) + + TOTAL=0 + FAILED=0 + + for image in $IMAGES; do + TOTAL=$((TOTAL + 1)) + if ! scan_image "$image"; then + FAILED=$((FAILED + 1)) + fi + done + + echo "" + echo "Scan complete: $TOTAL images scanned, $FAILED failed" +fi + +# Generate summary +echo "" +echo "Report saved to: $REPORT_FILE" +echo "To view: trivy convert --format table $REPORT_FILE" + +# Cleanup old reports (keep last 30 days) +find "$REPORT_DIR" -name "trivy-scan-*.json" -mtime +30 -delete 2>/dev/null || true + +if [[ "$FAILED" -gt 0 ]] && [[ "$REPORT_ONLY" == "false" ]]; then + exit 1 +fi + +exit 0