Sync all scripts from website downloads — 352 scripts total
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.
This commit is contained in:
@@ -0,0 +1,620 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
######################################################################################
|
||||
#### firewall-rule-diff.sh — Detect firewall rule drift against a saved baseline ####
|
||||
#### Supports UFW, iptables, and nftables. Saves snapshots, diffs against ####
|
||||
#### baseline, exports Prometheus metrics via textfile collector. ####
|
||||
#### Requires: bash 4+, diff, coreutils ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.00 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### sudo ./firewall-rule-diff.sh --save ####
|
||||
#### sudo ./firewall-rule-diff.sh --check ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
######################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────
|
||||
MODE="" # save or check
|
||||
BACKEND="" # auto-detect: ufw, iptables, nftables
|
||||
BASELINE_DIR="/etc/firewall-baseline"
|
||||
MAX_AGE_DAYS=30
|
||||
TEXTFILE_MODE=false
|
||||
PROM_FILE="/var/lib/node_exporter/firewall_drift.prom"
|
||||
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
|
||||
JUNIT_FILE="${JUNIT_FILE:-firewall-drift-results.xml}"
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
COLOR="${COLOR:-auto}"
|
||||
|
||||
# ── State ─────────────────────────────────────────────────────────────
|
||||
PASS=0
|
||||
FAIL=0
|
||||
WARN=0
|
||||
TOTAL=0
|
||||
RESULTS=()
|
||||
START_TIME=""
|
||||
RULES_ADDED=0
|
||||
RULES_REMOVED=0
|
||||
RULES_TOTAL=0
|
||||
DRIFT_DETECTED=0
|
||||
BASELINE_AGE=0
|
||||
DETECTED_BACKEND=""
|
||||
|
||||
# ── Colors ────────────────────────────────────────────────────────────
|
||||
setup_colors() {
|
||||
if [[ "$COLOR" == "never" ]]; then
|
||||
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
|
||||
return
|
||||
fi
|
||||
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
RESET='\033[0m'
|
||||
else
|
||||
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────
|
||||
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
||||
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
||||
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${BLUE}[DEBUG]${RESET} $*"; fi; }
|
||||
|
||||
# ── Test Result Recording ─────────────────────────────────────────────
|
||||
record_pass() {
|
||||
local name="$1"
|
||||
local detail="${2:-}"
|
||||
((PASS++)) || true
|
||||
((TOTAL++)) || true
|
||||
RESULTS+=("PASS|${name}|${detail}")
|
||||
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
||||
echo "ok ${TOTAL} - ${name}"
|
||||
elif [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo -e " ${GREEN}✓${RESET} ${name}${detail:+ — ${detail}}"
|
||||
fi
|
||||
}
|
||||
|
||||
record_fail() {
|
||||
local name="$1"
|
||||
local detail="${2:-}"
|
||||
((FAIL++)) || true
|
||||
((TOTAL++)) || true
|
||||
RESULTS+=("FAIL|${name}|${detail}")
|
||||
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
||||
echo "not ok ${TOTAL} - ${name}"
|
||||
[[ -n "$detail" ]] && echo " # ${detail}"
|
||||
elif [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo -e " ${RED}✗${RESET} ${name}${detail:+ — ${detail}}"
|
||||
fi
|
||||
}
|
||||
|
||||
record_warn() {
|
||||
local name="$1"
|
||||
local detail="${2:-}"
|
||||
((WARN++)) || true
|
||||
((TOTAL++)) || true
|
||||
RESULTS+=("WARN|${name}|${detail}")
|
||||
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
||||
echo "ok ${TOTAL} - ${name} # SKIP ${detail}"
|
||||
elif [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo -e " ${YELLOW}⊘${RESET} ${name}${detail:+ — ${detail}}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Help ──────────────────────────────────────────────────────────────
|
||||
show_help() {
|
||||
cat <<'EOF'
|
||||
Usage: firewall-rule-diff.sh [OPTIONS]
|
||||
|
||||
Detect firewall rule drift by comparing current state against a saved baseline.
|
||||
Supports UFW, iptables, and nftables with auto-detection.
|
||||
|
||||
Modes:
|
||||
--save Save current firewall rules as new baseline
|
||||
--check Compare current rules against baseline (default)
|
||||
|
||||
Options:
|
||||
--backend BACKEND Force backend: ufw, iptables, nftables (default: auto-detect)
|
||||
--baseline-dir PATH Baseline storage directory (default: /etc/firewall-baseline/)
|
||||
--max-age DAYS Warn if baseline older than N days (default: 30)
|
||||
--textfile Write Prometheus metrics to textfile collector
|
||||
--prom-file PATH Textfile path (default: /var/lib/node_exporter/firewall_drift.prom)
|
||||
--format FORMAT Output: text (default), tap, junit
|
||||
--junit-file FILE JUnit output path (default: firewall-drift-results.xml)
|
||||
--verbose Show debug output
|
||||
--no-color Disable colored output
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
sudo ./firewall-rule-diff.sh --save
|
||||
sudo ./firewall-rule-diff.sh --check
|
||||
sudo ./firewall-rule-diff.sh --check --textfile
|
||||
sudo ./firewall-rule-diff.sh --backend iptables --check
|
||||
sudo ./firewall-rule-diff.sh --check --max-age 7
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Parse Arguments ───────────────────────────────────────────────────
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--save) MODE="save"; shift ;;
|
||||
--check) MODE="check"; shift ;;
|
||||
--backend) BACKEND="$2"; shift 2 ;;
|
||||
--baseline-dir) BASELINE_DIR="$2"; shift 2 ;;
|
||||
--max-age) MAX_AGE_DAYS="$2"; shift 2 ;;
|
||||
--textfile) TEXTFILE_MODE=true; shift ;;
|
||||
--prom-file) PROM_FILE="$2"; shift 2 ;;
|
||||
--format) OUTPUT_FORMAT="$2"; shift 2 ;;
|
||||
--junit-file) JUNIT_FILE="$2"; shift 2 ;;
|
||||
--verbose) VERBOSE=true; shift ;;
|
||||
--no-color) COLOR="never"; shift ;;
|
||||
-h|--help) show_help ;;
|
||||
*) err "Unknown option: $1"; echo "Run with --help for usage."; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$MODE" ]]; then
|
||||
MODE="check"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Detect Backend ────────────────────────────────────────────────────
|
||||
detect_backend() {
|
||||
if [[ -n "$BACKEND" ]]; then
|
||||
DETECTED_BACKEND="$BACKEND"
|
||||
verbose "Backend forced: ${DETECTED_BACKEND}"
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v ufw &>/dev/null && ufw status &>/dev/null; then
|
||||
local ufw_status
|
||||
ufw_status=$(ufw status 2>/dev/null | head -1)
|
||||
if [[ "$ufw_status" == *"active"* ]]; then
|
||||
DETECTED_BACKEND="ufw"
|
||||
verbose "Detected active UFW"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v nft &>/dev/null; then
|
||||
local nft_rules
|
||||
nft_rules=$(nft list ruleset 2>/dev/null | wc -l)
|
||||
if [[ "$nft_rules" -gt 0 ]]; then
|
||||
DETECTED_BACKEND="nftables"
|
||||
verbose "Detected nftables with ${nft_rules} lines"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v iptables-save &>/dev/null; then
|
||||
DETECTED_BACKEND="iptables"
|
||||
verbose "Detected iptables"
|
||||
return
|
||||
fi
|
||||
|
||||
err "No supported firewall backend found (ufw, nftables, iptables)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── Snapshot Functions ────────────────────────────────────────────────
|
||||
snapshot_ufw() {
|
||||
local dir="$1"
|
||||
ufw status numbered > "${dir}/ufw-status.txt" 2>/dev/null || true
|
||||
ufw status verbose > "${dir}/ufw-verbose.txt" 2>/dev/null || true
|
||||
if [[ -f /etc/ufw/user.rules ]]; then
|
||||
cp /etc/ufw/user.rules "${dir}/user.rules"
|
||||
fi
|
||||
if [[ -f /etc/ufw/user6.rules ]]; then
|
||||
cp /etc/ufw/user6.rules "${dir}/user6.rules"
|
||||
fi
|
||||
# count rules from numbered output (skip header lines)
|
||||
RULES_TOTAL=$(grep -cE '^\[' "${dir}/ufw-status.txt" 2>/dev/null) || RULES_TOTAL=0
|
||||
verbose "UFW snapshot: ${RULES_TOTAL} rules"
|
||||
}
|
||||
|
||||
snapshot_iptables() {
|
||||
local dir="$1"
|
||||
iptables-save > "${dir}/iptables-v4.rules" 2>/dev/null || true
|
||||
if command -v ip6tables-save &>/dev/null; then
|
||||
ip6tables-save > "${dir}/iptables-v6.rules" 2>/dev/null || true
|
||||
fi
|
||||
# count non-comment, non-empty lines
|
||||
RULES_TOTAL=$(grep -cvE '^(#|$|\*|COMMIT|:)' "${dir}/iptables-v4.rules" 2>/dev/null) || RULES_TOTAL=0
|
||||
if [[ -f "${dir}/iptables-v6.rules" ]]; then
|
||||
local v6_count
|
||||
v6_count=$(grep -cvE '^(#|$|\*|COMMIT|:)' "${dir}/iptables-v6.rules" 2>/dev/null) || v6_count=0
|
||||
RULES_TOTAL=$((RULES_TOTAL + v6_count))
|
||||
fi
|
||||
verbose "iptables snapshot: ${RULES_TOTAL} rules"
|
||||
}
|
||||
|
||||
snapshot_nftables() {
|
||||
local dir="$1"
|
||||
nft list ruleset > "${dir}/nftables.rules" 2>/dev/null || true
|
||||
RULES_TOTAL=$(grep -cE '^\s+(rule|chain|table)' "${dir}/nftables.rules" 2>/dev/null) || RULES_TOTAL=0
|
||||
verbose "nftables snapshot: ${RULES_TOTAL} lines"
|
||||
}
|
||||
|
||||
take_snapshot() {
|
||||
local dir="$1"
|
||||
case "$DETECTED_BACKEND" in
|
||||
ufw) snapshot_ufw "$dir" ;;
|
||||
iptables) snapshot_iptables "$dir" ;;
|
||||
nftables) snapshot_nftables "$dir" ;;
|
||||
esac
|
||||
echo "$DETECTED_BACKEND" > "${dir}/backend.txt"
|
||||
date +%s > "${dir}/timestamp.txt"
|
||||
date -Is > "${dir}/timestamp-human.txt"
|
||||
}
|
||||
|
||||
# ── Save Mode ─────────────────────────────────────────────────────────
|
||||
do_save() {
|
||||
mkdir -p "${BASELINE_DIR}"
|
||||
local snapshot_dir="${BASELINE_DIR}/baseline"
|
||||
|
||||
# clean up any previous baseline
|
||||
if [[ -d "$snapshot_dir" ]]; then
|
||||
local prev_ts
|
||||
prev_ts=$(cat "${snapshot_dir}/timestamp.txt" 2>/dev/null || echo "unknown")
|
||||
local archive_dir="${BASELINE_DIR}/archive-${prev_ts}"
|
||||
mv "$snapshot_dir" "$archive_dir" 2>/dev/null || rm -rf "$snapshot_dir"
|
||||
verbose "Archived previous baseline"
|
||||
fi
|
||||
|
||||
mkdir -p "$snapshot_dir"
|
||||
|
||||
verbose "Taking ${DETECTED_BACKEND} snapshot to ${snapshot_dir}"
|
||||
take_snapshot "$snapshot_dir"
|
||||
|
||||
if [[ ! -f "${snapshot_dir}/backend.txt" ]]; then
|
||||
err "Snapshot failed — ${snapshot_dir}/backend.txt not created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo -e "${BOLD}Firewall Rule Diff${RESET}"
|
||||
echo "Backend: ${DETECTED_BACKEND}"
|
||||
echo "Baseline: ${snapshot_dir}"
|
||||
echo "Time: $(cat "${snapshot_dir}/timestamp-human.txt")"
|
||||
echo "Rules: ${RULES_TOTAL}"
|
||||
echo ""
|
||||
echo -e " ${GREEN}✓${RESET} Baseline saved — ${RULES_TOTAL} rules (${DETECTED_BACKEND})"
|
||||
elif [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
||||
echo "1..1"
|
||||
echo "ok 1 - Baseline saved (${RULES_TOTAL} rules, ${DETECTED_BACKEND})"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Check Mode ────────────────────────────────────────────────────────
|
||||
do_check() {
|
||||
START_TIME=$(date +%s)
|
||||
local baseline_dir="${BASELINE_DIR}/baseline"
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo -e "${BOLD}Firewall Rule Diff${RESET}"
|
||||
echo "Backend: ${DETECTED_BACKEND}"
|
||||
echo "Baseline: ${baseline_dir}"
|
||||
echo "Time: $(date -Is)"
|
||||
echo ""
|
||||
elif [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
||||
echo "TAP version 13"
|
||||
fi
|
||||
|
||||
# check baseline exists
|
||||
if [[ ! -d "$baseline_dir" ]]; then
|
||||
record_fail "Baseline exists" "no baseline found — run with --save first"
|
||||
DRIFT_DETECTED=1
|
||||
print_summary
|
||||
return
|
||||
fi
|
||||
|
||||
# check backend matches
|
||||
local baseline_backend
|
||||
baseline_backend=$(cat "${baseline_dir}/backend.txt" 2>/dev/null || echo "unknown")
|
||||
if [[ "$baseline_backend" != "$DETECTED_BACKEND" ]]; then
|
||||
record_fail "Backend match" "baseline uses ${baseline_backend}, current is ${DETECTED_BACKEND}"
|
||||
DRIFT_DETECTED=1
|
||||
else
|
||||
record_pass "Backend match" "${DETECTED_BACKEND}"
|
||||
fi
|
||||
|
||||
# check baseline age
|
||||
local baseline_ts
|
||||
baseline_ts=$(cat "${baseline_dir}/timestamp.txt" 2>/dev/null || echo "0")
|
||||
local now
|
||||
now=$(date +%s)
|
||||
BASELINE_AGE=$((now - baseline_ts))
|
||||
local age_days=$((BASELINE_AGE / 86400))
|
||||
|
||||
if [[ $age_days -gt $MAX_AGE_DAYS ]]; then
|
||||
record_warn "Baseline age" "${age_days} days old (threshold: ${MAX_AGE_DAYS})"
|
||||
else
|
||||
record_pass "Baseline age" "${age_days} days old"
|
||||
fi
|
||||
|
||||
# take current snapshot to temp dir
|
||||
local tmp_dir
|
||||
tmp_dir=$(mktemp -d)
|
||||
trap 'rm -rf "'"$tmp_dir"'"' EXIT
|
||||
take_snapshot "$tmp_dir"
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo ""
|
||||
echo -e "${BOLD}Rule Comparison${RESET}"
|
||||
fi
|
||||
|
||||
# diff based on backend
|
||||
case "$DETECTED_BACKEND" in
|
||||
ufw) diff_ufw "$baseline_dir" "$tmp_dir" ;;
|
||||
iptables) diff_iptables "$baseline_dir" "$tmp_dir" ;;
|
||||
nftables) diff_nftables "$baseline_dir" "$tmp_dir" ;;
|
||||
esac
|
||||
|
||||
print_summary
|
||||
}
|
||||
|
||||
# ── Diff Functions ────────────────────────────────────────────────────
|
||||
diff_rules_file() {
|
||||
local label="$1"
|
||||
local baseline_file="$2"
|
||||
local current_file="$3"
|
||||
|
||||
if [[ ! -f "$baseline_file" ]] && [[ ! -f "$current_file" ]]; then
|
||||
verbose "Both files missing for ${label} — skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ! -f "$baseline_file" ]]; then
|
||||
record_fail "${label}" "file missing from baseline but present now"
|
||||
DRIFT_DETECTED=1
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ! -f "$current_file" ]]; then
|
||||
record_fail "${label}" "file present in baseline but missing now"
|
||||
DRIFT_DETECTED=1
|
||||
return
|
||||
fi
|
||||
|
||||
local diff_output
|
||||
diff_output=$(diff --unified=0 "$baseline_file" "$current_file" 2>/dev/null) || true
|
||||
|
||||
if [[ -z "$diff_output" ]]; then
|
||||
record_pass "${label}" "no changes"
|
||||
return
|
||||
fi
|
||||
|
||||
DRIFT_DETECTED=1
|
||||
|
||||
local added removed
|
||||
added=$(echo "$diff_output" | grep -c '^+[^+]' 2>/dev/null) || added=0
|
||||
removed=$(echo "$diff_output" | grep -c '^-[^-]' 2>/dev/null) || removed=0
|
||||
|
||||
RULES_ADDED=$((RULES_ADDED + added))
|
||||
RULES_REMOVED=$((RULES_REMOVED + removed))
|
||||
|
||||
record_fail "${label}" "${added} added, ${removed} removed"
|
||||
|
||||
if [[ "$VERBOSE" == "true" || "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
# show the actual diff lines (limit to 20 lines)
|
||||
local count=0
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == +* && "$line" != +++* ]]; then
|
||||
echo -e " ${GREEN}${line}${RESET}"
|
||||
((count++)) || true
|
||||
elif [[ "$line" == -* && "$line" != ---* ]]; then
|
||||
echo -e " ${RED}${line}${RESET}"
|
||||
((count++)) || true
|
||||
fi
|
||||
[[ $count -ge 20 ]] && { echo " ... (truncated)"; break; }
|
||||
done <<< "$diff_output"
|
||||
fi
|
||||
}
|
||||
|
||||
diff_ufw() {
|
||||
local baseline="$1"
|
||||
local current="$2"
|
||||
|
||||
diff_rules_file "UFW status" "${baseline}/ufw-status.txt" "${current}/ufw-status.txt"
|
||||
diff_rules_file "UFW IPv4 rules" "${baseline}/user.rules" "${current}/user.rules"
|
||||
diff_rules_file "UFW IPv6 rules" "${baseline}/user6.rules" "${current}/user6.rules"
|
||||
|
||||
# rule count comparison
|
||||
local baseline_count current_count
|
||||
baseline_count=$(grep -cE '^\[' "${baseline}/ufw-status.txt" 2>/dev/null) || baseline_count=0
|
||||
current_count=$(grep -cE '^\[' "${current}/ufw-status.txt" 2>/dev/null) || current_count=0
|
||||
|
||||
if [[ $baseline_count -ne $current_count ]]; then
|
||||
record_fail "Rule count" "baseline: ${baseline_count}, current: ${current_count}"
|
||||
DRIFT_DETECTED=1
|
||||
else
|
||||
record_pass "Rule count" "${current_count} rules"
|
||||
fi
|
||||
}
|
||||
|
||||
diff_iptables() {
|
||||
local baseline="$1"
|
||||
local current="$2"
|
||||
|
||||
diff_rules_file "iptables IPv4 rules" "${baseline}/iptables-v4.rules" "${current}/iptables-v4.rules"
|
||||
diff_rules_file "iptables IPv6 rules" "${baseline}/iptables-v6.rules" "${current}/iptables-v6.rules"
|
||||
|
||||
# chain count comparison
|
||||
local baseline_chains current_chains
|
||||
baseline_chains=$(grep -cE '^:' "${baseline}/iptables-v4.rules" 2>/dev/null) || baseline_chains=0
|
||||
current_chains=$(grep -cE '^:' "${current}/iptables-v4.rules" 2>/dev/null) || current_chains=0
|
||||
|
||||
if [[ $baseline_chains -ne $current_chains ]]; then
|
||||
record_fail "Chain count (IPv4)" "baseline: ${baseline_chains}, current: ${current_chains}"
|
||||
DRIFT_DETECTED=1
|
||||
else
|
||||
record_pass "Chain count (IPv4)" "${current_chains} chains"
|
||||
fi
|
||||
}
|
||||
|
||||
diff_nftables() {
|
||||
local baseline="$1"
|
||||
local current="$2"
|
||||
|
||||
diff_rules_file "nftables ruleset" "${baseline}/nftables.rules" "${current}/nftables.rules"
|
||||
|
||||
# table count comparison
|
||||
local baseline_tables current_tables
|
||||
baseline_tables=$(grep -c '^table' "${baseline}/nftables.rules" 2>/dev/null) || baseline_tables=0
|
||||
current_tables=$(grep -c '^table' "${current}/nftables.rules" 2>/dev/null) || current_tables=0
|
||||
|
||||
if [[ $baseline_tables -ne $current_tables ]]; then
|
||||
record_fail "Table count" "baseline: ${baseline_tables}, current: ${current_tables}"
|
||||
DRIFT_DETECTED=1
|
||||
else
|
||||
record_pass "Table count" "${current_tables} tables"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────────
|
||||
print_summary() {
|
||||
local end_time
|
||||
end_time=$(date +%s)
|
||||
local elapsed=$((end_time - START_TIME))
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
||||
echo "1..${TOTAL}"
|
||||
elif [[ "$OUTPUT_FORMAT" == "text" ]]; then
|
||||
echo ""
|
||||
echo "────────────────────────────────────────"
|
||||
echo -e "${BOLD}Summary${RESET} ${DETECTED_BACKEND}"
|
||||
echo -e " ${PASS} passed ${FAIL} failed ${WARN} skipped (${elapsed}s)"
|
||||
if [[ $DRIFT_DETECTED -eq 1 ]]; then
|
||||
echo -e " Rules added: ${RULES_ADDED} removed: ${RULES_REMOVED}"
|
||||
echo -e " ${RED}Drift detected.${RESET}"
|
||||
else
|
||||
echo -e " ${GREEN}No drift detected.${RESET}"
|
||||
fi
|
||||
echo "────────────────────────────────────────"
|
||||
fi
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "junit" ]]; then
|
||||
write_junit
|
||||
fi
|
||||
|
||||
if [[ "$TEXTFILE_MODE" == "true" ]]; then
|
||||
write_prometheus
|
||||
fi
|
||||
}
|
||||
|
||||
# ── JUnit Output ──────────────────────────────────────────────────────
|
||||
write_junit() {
|
||||
local end_time
|
||||
end_time=$(date +%s)
|
||||
local elapsed=$((end_time - START_TIME))
|
||||
|
||||
{
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
echo "<testsuites tests=\"${TOTAL}\" failures=\"${FAIL}\" time=\"${elapsed}\">"
|
||||
echo " <testsuite name=\"firewall-rule-diff\" tests=\"${TOTAL}\" failures=\"${FAIL}\" skipped=\"${WARN}\" time=\"${elapsed}\">"
|
||||
|
||||
for result in "${RESULTS[@]}"; do
|
||||
local status name detail
|
||||
status=$(echo "$result" | cut -d'|' -f1)
|
||||
name=$(echo "$result" | cut -d'|' -f2)
|
||||
detail=$(echo "$result" | cut -d'|' -f3)
|
||||
|
||||
# escape XML
|
||||
name="${name//&/&}"
|
||||
name="${name//</<}"
|
||||
name="${name//>/>}"
|
||||
detail="${detail//&/&}"
|
||||
detail="${detail//</<}"
|
||||
detail="${detail//>/>}"
|
||||
|
||||
echo " <testcase name=\"${name}\">"
|
||||
if [[ "$status" == "FAIL" ]]; then
|
||||
echo " <failure message=\"${detail}\"></failure>"
|
||||
elif [[ "$status" == "WARN" ]]; then
|
||||
echo " <skipped message=\"${detail}\"></skipped>"
|
||||
fi
|
||||
echo " </testcase>"
|
||||
done
|
||||
|
||||
echo " </testsuite>"
|
||||
echo "</testsuites>"
|
||||
} > "$JUNIT_FILE"
|
||||
|
||||
verbose "JUnit report written to ${JUNIT_FILE}"
|
||||
}
|
||||
|
||||
# ── Prometheus Output ─────────────────────────────────────────────────
|
||||
write_prometheus() {
|
||||
local prom_dir
|
||||
prom_dir=$(dirname "$PROM_FILE")
|
||||
if [[ ! -d "$prom_dir" ]]; then
|
||||
warn "Prometheus textfile directory does not exist: ${prom_dir}"
|
||||
return
|
||||
fi
|
||||
|
||||
local tmp_file="${PROM_FILE}.$$"
|
||||
{
|
||||
echo "# HELP firewall_drift_detected Whether firewall rules differ from baseline"
|
||||
echo "# TYPE firewall_drift_detected gauge"
|
||||
echo "firewall_drift_detected ${DRIFT_DETECTED}"
|
||||
echo "# HELP firewall_rules_added Rules added since baseline"
|
||||
echo "# TYPE firewall_rules_added gauge"
|
||||
echo "firewall_rules_added ${RULES_ADDED}"
|
||||
echo "# HELP firewall_rules_removed Rules removed since baseline"
|
||||
echo "# TYPE firewall_rules_removed gauge"
|
||||
echo "firewall_rules_removed ${RULES_REMOVED}"
|
||||
echo "# HELP firewall_rules_total Current total firewall rules"
|
||||
echo "# TYPE firewall_rules_total gauge"
|
||||
echo "firewall_rules_total ${RULES_TOTAL}"
|
||||
echo "# HELP firewall_baseline_age_seconds Seconds since baseline was saved"
|
||||
echo "# TYPE firewall_baseline_age_seconds gauge"
|
||||
echo "firewall_baseline_age_seconds ${BASELINE_AGE}"
|
||||
echo "# HELP firewall_scan_timestamp Unix timestamp of last scan"
|
||||
echo "# TYPE firewall_scan_timestamp gauge"
|
||||
echo "firewall_scan_timestamp $(date +%s)"
|
||||
echo "# HELP firewall_backend Active firewall backend"
|
||||
echo "# TYPE firewall_backend gauge"
|
||||
echo "firewall_backend{backend=\"${DETECTED_BACKEND}\"} 1"
|
||||
} > "$tmp_file"
|
||||
|
||||
mv "$tmp_file" "$PROM_FILE"
|
||||
verbose "Prometheus metrics written to ${PROM_FILE}"
|
||||
}
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────────────────
|
||||
main() {
|
||||
setup_colors
|
||||
parse_args "$@"
|
||||
setup_colors # re-apply after --no-color
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
err "This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
detect_backend
|
||||
|
||||
case "$MODE" in
|
||||
save) do_save ;;
|
||||
check) do_check ;;
|
||||
esac
|
||||
|
||||
if [[ $FAIL -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user