a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
421 lines
13 KiB
Bash
421 lines
13 KiB
Bash
#!/bin/bash
|
|
################################################################################
|
|
# Script Name: openscap-audit.sh
|
|
# Description: OpenSCAP / CIS Benchmark security audit script with HTML and
|
|
# terminal reporting on Ubuntu/Debian and RHEL/Rocky/Alma/Fedora
|
|
#
|
|
# Author: Phil Connor
|
|
# Contact: contact@mylinux.work
|
|
# Website: https://mylinux.work
|
|
# License: MIT
|
|
# Version: 1.0
|
|
#
|
|
# Usage:
|
|
# sudo ./openscap-audit.sh
|
|
# sudo ./openscap-audit.sh --profile cis_server_l1
|
|
# sudo ./openscap-audit.sh --report /tmp/audit-report.html
|
|
# sudo ./openscap-audit.sh --dry-run
|
|
#
|
|
################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# ============================================================================
|
|
# DEFAULTS
|
|
# ============================================================================
|
|
|
|
PROFILE=""
|
|
REPORT_DIR="/var/lib/openscap/reports"
|
|
REPORT_FILE=""
|
|
RESULTS_FILE=""
|
|
TAILORING_FILE=""
|
|
REMEDIATE=false
|
|
DRY_RUN=false
|
|
LIST_PROFILES=false
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
# ============================================================================
|
|
# HELPER FUNCTIONS
|
|
# ============================================================================
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
log_step() { echo -e "${CYAN}[STEP]${NC} $*"; }
|
|
|
|
show_usage() {
|
|
cat <<EOF
|
|
Usage: $0 [OPTIONS]
|
|
|
|
Run an OpenSCAP security audit against CIS or DISA STIG benchmarks.
|
|
|
|
OPTIONS:
|
|
--profile PROFILE SCAP profile to evaluate (auto-detected if omitted)
|
|
--list-profiles List available profiles for this system
|
|
--report PATH HTML report output path (default: auto-generated in $REPORT_DIR)
|
|
--results PATH XCCDF results XML output path
|
|
--tailoring PATH XCCDF tailoring file for custom profile adjustments
|
|
--remediate Apply automatic remediation (USE WITH CAUTION)
|
|
--dry-run Show what would be done without executing
|
|
-h, --help Show this help message
|
|
|
|
PROFILES (common):
|
|
cis_server_l1 CIS Level 1 - Server
|
|
cis_server_l2 CIS Level 2 - Server
|
|
cis_workstation_l1 CIS Level 1 - Workstation
|
|
cis_workstation_l2 CIS Level 2 - Workstation
|
|
stig DISA STIG
|
|
standard Standard System Security Profile
|
|
pci-dss PCI-DSS v3.2.1
|
|
|
|
EXAMPLES:
|
|
$0 # Auto-detect profile and run
|
|
$0 --profile cis_server_l1 # CIS Level 1 Server
|
|
$0 --profile stig --report /tmp/stig.html # STIG with custom report path
|
|
$0 --list-profiles # Show available profiles
|
|
$0 --remediate --profile cis_server_l1 # Auto-fix (careful!)
|
|
$0 --dry-run # Preview
|
|
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help) show_usage ;;
|
|
--profile) PROFILE="$2"; shift 2 ;;
|
|
--list-profiles) LIST_PROFILES=true; shift ;;
|
|
--report) REPORT_FILE="$2"; shift 2 ;;
|
|
--results) RESULTS_FILE="$2"; shift 2 ;;
|
|
--tailoring) TAILORING_FILE="$2"; shift 2 ;;
|
|
--remediate) REMEDIATE=true; shift ;;
|
|
--dry-run) DRY_RUN=true; shift ;;
|
|
*) log_error "Unknown option: $1"; exit 1 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
check_root() {
|
|
if [[ $EUID -ne 0 ]]; then
|
|
log_error "This script must be run as root (sudo)"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
detect_os() {
|
|
if [[ -f /etc/os-release ]]; then
|
|
. /etc/os-release
|
|
OS_ID="$ID"
|
|
OS_VERSION="${VERSION_ID%%.*}"
|
|
OS_FAMILY=""
|
|
case "$OS_ID" in
|
|
ubuntu|debian) OS_FAMILY="debian" ;;
|
|
rhel|centos|rocky|almalinux|fedora) OS_FAMILY="rhel" ;;
|
|
*) log_error "Unsupported OS: $OS_ID"; exit 1 ;;
|
|
esac
|
|
log_info "Detected OS: $PRETTY_NAME"
|
|
else
|
|
log_error "Cannot detect OS"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# INSTALLATION
|
|
# ============================================================================
|
|
|
|
install_openscap() {
|
|
if command -v oscap &>/dev/null; then
|
|
log_info "OpenSCAP already installed: $(oscap --version | head -1)"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Installing OpenSCAP..."
|
|
|
|
case "$OS_FAMILY" in
|
|
debian)
|
|
apt-get update -qq
|
|
apt-get install -y -qq libopenscap8 openscap-utils ssg-debian ssg-base 2>/dev/null || \
|
|
apt-get install -y -qq libopenscap8 openscap-utils scap-security-guide 2>/dev/null || \
|
|
apt-get install -y -qq openscap-scanner openscap-utils scap-security-guide
|
|
;;
|
|
rhel)
|
|
dnf install -y -q openscap-scanner openscap-utils scap-security-guide
|
|
;;
|
|
esac
|
|
|
|
log_info "OpenSCAP installed: $(oscap --version | head -1)"
|
|
}
|
|
|
|
# ============================================================================
|
|
# CONTENT DETECTION
|
|
# ============================================================================
|
|
|
|
find_content() {
|
|
log_step "Finding SCAP content for $PRETTY_NAME..."
|
|
|
|
local content_paths=(
|
|
"/usr/share/xml/scap/ssg/content"
|
|
"/usr/share/scap-security-guide"
|
|
"/usr/share/openscap"
|
|
)
|
|
|
|
SCAP_CONTENT=""
|
|
|
|
# Build expected filename patterns based on OS
|
|
local os_patterns=()
|
|
case "$OS_ID" in
|
|
ubuntu) os_patterns=("ssg-ubuntu${OS_VERSION}04-ds.xml" "ssg-ubuntu${OS_VERSION}-ds.xml" "ssg-ubuntu-ds.xml") ;;
|
|
debian) os_patterns=("ssg-debian${OS_VERSION}-ds.xml" "ssg-debian-ds.xml") ;;
|
|
rhel) os_patterns=("ssg-rhel${OS_VERSION}-ds.xml" "ssg-rhel-ds.xml") ;;
|
|
centos) os_patterns=("ssg-centos${OS_VERSION}-ds.xml" "ssg-rhel${OS_VERSION}-ds.xml") ;;
|
|
rocky) os_patterns=("ssg-rl${OS_VERSION}-ds.xml" "ssg-rhel${OS_VERSION}-ds.xml") ;;
|
|
almalinux) os_patterns=("ssg-almalinux${OS_VERSION}-ds.xml" "ssg-rhel${OS_VERSION}-ds.xml") ;;
|
|
fedora) os_patterns=("ssg-fedora-ds.xml") ;;
|
|
esac
|
|
|
|
for dir in "${content_paths[@]}"; do
|
|
if [[ -d "$dir" ]]; then
|
|
for pattern in "${os_patterns[@]}"; do
|
|
if [[ -f "$dir/$pattern" ]]; then
|
|
SCAP_CONTENT="$dir/$pattern"
|
|
log_info "Found SCAP content: $SCAP_CONTENT"
|
|
return 0
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
|
|
# Fallback: find any matching content
|
|
SCAP_CONTENT=$(find /usr/share -name "ssg-*-ds.xml" -type f 2>/dev/null | head -1)
|
|
|
|
if [[ -z "$SCAP_CONTENT" ]]; then
|
|
log_error "No SCAP content found for $PRETTY_NAME"
|
|
log_error "Install scap-security-guide package"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Using SCAP content: $SCAP_CONTENT"
|
|
}
|
|
|
|
list_available_profiles() {
|
|
log_step "Available profiles for $PRETTY_NAME:"
|
|
echo ""
|
|
oscap info --profiles "$SCAP_CONTENT" 2>/dev/null | while IFS=: read -r id title; do
|
|
printf " %-50s %s\n" "$id" "$title"
|
|
done
|
|
echo ""
|
|
}
|
|
|
|
select_profile() {
|
|
if [[ -n "$PROFILE" ]]; then
|
|
# Match partial profile names
|
|
local full_profile
|
|
full_profile=$(oscap info --profiles "$SCAP_CONTENT" 2>/dev/null | \
|
|
grep -i "$PROFILE" | head -1 | cut -d: -f1)
|
|
|
|
if [[ -n "$full_profile" ]]; then
|
|
PROFILE="$full_profile"
|
|
log_info "Selected profile: $PROFILE"
|
|
else
|
|
log_error "Profile '$PROFILE' not found"
|
|
log_info "Available profiles:"
|
|
list_available_profiles
|
|
exit 1
|
|
fi
|
|
else
|
|
# Auto-select: prefer CIS Level 1 Server, then STIG, then standard
|
|
for pattern in "cis.*server.*l1\|cis_server_l1" "stig\b" "standard"; do
|
|
PROFILE=$(oscap info --profiles "$SCAP_CONTENT" 2>/dev/null | \
|
|
grep -i "$pattern" | head -1 | cut -d: -f1) || true
|
|
if [[ -n "$PROFILE" ]]; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ -z "$PROFILE" ]]; then
|
|
# Use first available profile
|
|
PROFILE=$(oscap info --profiles "$SCAP_CONTENT" 2>/dev/null | head -1 | cut -d: -f1)
|
|
fi
|
|
|
|
log_info "Auto-selected profile: $PROFILE"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# AUDIT EXECUTION
|
|
# ============================================================================
|
|
|
|
run_audit() {
|
|
local timestamp
|
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
|
|
|
mkdir -p "$REPORT_DIR"
|
|
|
|
if [[ -z "$REPORT_FILE" ]]; then
|
|
REPORT_FILE="$REPORT_DIR/openscap-report-${timestamp}.html"
|
|
fi
|
|
|
|
if [[ -z "$RESULTS_FILE" ]]; then
|
|
RESULTS_FILE="$REPORT_DIR/openscap-results-${timestamp}.xml"
|
|
fi
|
|
|
|
log_step "Running OpenSCAP audit..."
|
|
log_info "Profile: $PROFILE"
|
|
log_info "Content: $SCAP_CONTENT"
|
|
log_info "Report: $REPORT_FILE"
|
|
log_info "Results: $RESULTS_FILE"
|
|
echo ""
|
|
|
|
local oscap_cmd=(
|
|
oscap xccdf eval
|
|
--profile "$PROFILE"
|
|
--report "$REPORT_FILE"
|
|
--results "$RESULTS_FILE"
|
|
)
|
|
|
|
if [[ -n "$TAILORING_FILE" ]] && [[ -f "$TAILORING_FILE" ]]; then
|
|
oscap_cmd+=(--tailoring-file "$TAILORING_FILE")
|
|
fi
|
|
|
|
if [[ "$REMEDIATE" == true ]]; then
|
|
oscap_cmd+=(--remediate)
|
|
log_warn "REMEDIATION ENABLED — changes will be applied!"
|
|
fi
|
|
|
|
oscap_cmd+=("$SCAP_CONTENT")
|
|
|
|
# oscap returns exit code 2 for "some checks failed" — that's normal
|
|
local exit_code=0
|
|
"${oscap_cmd[@]}" 2>&1 || exit_code=$?
|
|
|
|
if [[ $exit_code -eq 0 ]]; then
|
|
log_info "All checks passed"
|
|
elif [[ $exit_code -eq 2 ]]; then
|
|
log_warn "Some checks failed (this is normal for a first audit)"
|
|
else
|
|
log_error "OpenSCAP encountered an error (exit code: $exit_code)"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# RESULTS SUMMARY
|
|
# ============================================================================
|
|
|
|
show_summary() {
|
|
log_step "Audit Summary"
|
|
echo ""
|
|
|
|
if [[ -f "$RESULTS_FILE" ]]; then
|
|
local pass fail error notapplicable notchecked total score
|
|
pass=$(grep -c 'result="pass"' "$RESULTS_FILE" 2>/dev/null) || pass=0
|
|
fail=$(grep -c 'result="fail"' "$RESULTS_FILE" 2>/dev/null) || fail=0
|
|
error=$(grep -c 'result="error"' "$RESULTS_FILE" 2>/dev/null) || error=0
|
|
notapplicable=$(grep -c 'result="notapplicable"' "$RESULTS_FILE" 2>/dev/null) || notapplicable=0
|
|
notchecked=$(grep -c 'result="notchecked"' "$RESULTS_FILE" 2>/dev/null) || notchecked=0
|
|
total=$((pass + fail))
|
|
|
|
if [[ $total -gt 0 ]]; then
|
|
score=$(echo "scale=1; $pass * 100 / $total" | bc)
|
|
else
|
|
score=0
|
|
fi
|
|
|
|
echo -e " ${GREEN}Pass:${NC} $pass"
|
|
echo -e " ${RED}Fail:${NC} $fail"
|
|
echo -e " ${YELLOW}Error:${NC} $error"
|
|
echo -e " Not Applicable: $notapplicable"
|
|
echo -e " Not Checked: $notchecked"
|
|
echo ""
|
|
echo -e " ${CYAN}Compliance Score:${NC} ${score}%"
|
|
echo ""
|
|
|
|
# Show top failures
|
|
if [[ $fail -gt 0 ]]; then
|
|
log_info "Top failed rules (first 10):"
|
|
grep -B2 'result="fail"' "$RESULTS_FILE" 2>/dev/null | \
|
|
grep 'idref=' | sed 's/.*idref="\([^"]*\)".*/ - \1/' | head -10
|
|
echo ""
|
|
fi
|
|
fi
|
|
|
|
log_info "HTML report: $REPORT_FILE"
|
|
log_info "XML results: $RESULTS_FILE"
|
|
|
|
if [[ $fail -gt 0 ]]; then
|
|
echo ""
|
|
log_info "To auto-remediate failed checks (USE WITH CAUTION):"
|
|
echo " $0 --profile '$PROFILE' --remediate"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# DRY RUN
|
|
# ============================================================================
|
|
|
|
dry_run() {
|
|
echo ""
|
|
log_info "===== DRY RUN — No changes will be made ====="
|
|
echo ""
|
|
log_info "OS: $PRETTY_NAME"
|
|
log_info "Content: $SCAP_CONTENT"
|
|
log_info "Profile: $PROFILE"
|
|
log_info "Report dir: $REPORT_DIR"
|
|
log_info "Remediate: $REMEDIATE"
|
|
echo ""
|
|
log_info "Actions that would be performed:"
|
|
echo " 1. Install OpenSCAP (if not present)"
|
|
echo " 2. Find SCAP content for $OS_ID $OS_VERSION"
|
|
echo " 3. Evaluate profile: $PROFILE"
|
|
echo " 4. Generate HTML report in $REPORT_DIR/"
|
|
echo " 5. Generate XCCDF results XML"
|
|
if [[ "$REMEDIATE" == true ]]; then
|
|
echo " 6. APPLY REMEDIATION for failed checks"
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# MAIN
|
|
# ============================================================================
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
check_root
|
|
detect_os
|
|
install_openscap
|
|
find_content
|
|
|
|
if [[ "$LIST_PROFILES" == true ]]; then
|
|
list_available_profiles
|
|
exit 0
|
|
fi
|
|
|
|
select_profile
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
dry_run
|
|
exit 0
|
|
fi
|
|
|
|
echo ""
|
|
log_info "===== OpenSCAP Security Audit ====="
|
|
echo ""
|
|
|
|
run_audit
|
|
show_summary
|
|
|
|
echo ""
|
|
log_info "===== Audit Complete ====="
|
|
echo ""
|
|
}
|
|
|
|
main "$@"
|