#!/bin/bash ############################################################# #### fapolicyd Log Analyzer Script #### #### Parses denial logs and suggests fix commands #### #### #### #### Author: Phil Connor #### #### Contact: contact@mylinux.work #### #### License: MIT #### #### Version 1.00 #### #### #### #### To use this script chmod it to 755 #### #### or simply type bash #### ############################################################# # ── Colors ──────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # No Color # ── Defaults ────────────────────────────────────────────── MODE="recent" OUTPUT_FILE="" QUIET=0 TOTAL_DENIALS=0 UNIQUE_FILES=0 SUGGESTED_FIXES=0 # ── Functions ───────────────────────────────────────────── usage() { echo -e "${BOLD}fapolicyd Log Analyzer${NC}" echo "" echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --help Show this help message" echo " --recent Analyze denials from the last hour only (default)" echo " --all Analyze all denials in the log" echo " --output FILE Save suggested fixes to FILE" echo " --quiet Show suggestions only, suppress raw denial lines" echo "" echo "Examples:" echo " sudo bash $0 --recent" echo " sudo bash $0 --all --output fixes.txt" echo " sudo bash $0 --quiet --output /tmp/fixes.txt" exit 0 } check_root() { if [[ $EUID -ne 0 ]]; then echo -e "${RED}Error: This script must be run as root.${NC}" echo "Please run with: sudo bash $0" exit 1 fi } check_fapolicyd() { if ! command -v fapolicyd-cli &>/dev/null; then echo -e "${RED}Error: fapolicyd does not appear to be installed.${NC}" echo -e "${YELLOW}Install with: dnf install fapolicyd${NC}" exit 1 fi if ! systemctl is-active --quiet fapolicyd 2>/dev/null; then echo -e "${YELLOW}Warning: fapolicyd service is not currently running.${NC}" echo -e "${CYAN}Continuing to analyze existing log entries...${NC}" echo "" fi } output_line() { local line="$1" echo -e "$line" if [[ -n "$OUTPUT_FILE" ]]; then echo -e "$line" | sed 's/\x1b\[[0-9;]*m//g' >> "$OUTPUT_FILE" fi } # ── fapolicyd Analysis ─────────────────────────────────── parse_fapolicyd_denial() { local line="$1" local dec perm fname exe trust pid dec=$(echo "$line" | grep -oP 'dec=\K[^ ]+') perm=$(echo "$line" | grep -oP 'perm=\K[^ ]+') fname=$(echo "$line" | grep -oP 'fname=\K[^ ]+') exe=$(echo "$line" | grep -oP 'exe=\K[^ ]+') trust=$(echo "$line" | grep -oP 'trust=\K[^ ]+') pid=$(echo "$line" | grep -oP 'pid=\K[^ ]+') if [[ $QUIET -eq 0 ]]; then output_line "${RED}DENIAL:${NC} $line" fi [[ -n "$dec" ]] && output_line "${CYAN} Decision:${NC} $dec" [[ -n "$perm" ]] && output_line "${CYAN} Permission:${NC} $perm" [[ -n "$fname" ]] && output_line "${CYAN} File:${NC} $fname" [[ -n "$exe" ]] && output_line "${CYAN} Executable:${NC} $exe" [[ -n "$trust" ]] && output_line "${CYAN} Trust status:${NC} $trust" [[ -n "$pid" ]] && output_line "${CYAN} PID:${NC} $pid" suggest_fapolicyd_fix "$fname" "$exe" "$perm" "$trust" output_line "" } suggest_fapolicyd_fix() { local fname="$1" exe="$2" perm="$3" trust="$4" ((SUGGESTED_FIXES++)) if [[ -n "$fname" ]]; then # Check current trust status output_line "${GREEN} Suggested fixes:${NC}" # Trust the file output_line "${GREEN} 1. Add file to trust database:${NC}" output_line "${GREEN} fapolicyd-cli --file add ${fname}${NC}" output_line "${GREEN} fapolicyd-cli --update${NC}" # Check trust output_line "${GREEN} 2. Verify trust status:${NC}" output_line "${GREEN} fapolicyd-cli --check-path ${fname}${NC}" # If the file is a script or binary from a known package if command -v rpm &>/dev/null; then local pkg pkg=$(rpm -qf "$fname" 2>/dev/null) if [[ $? -eq 0 && -n "$pkg" ]]; then output_line "${CYAN} Note: File belongs to package: ${pkg}${NC}" output_line "${YELLOW} If the file was modified after install, consider:${NC}" output_line "${GREEN} rpm --restore ${pkg}${NC}" output_line "${GREEN} fapolicyd-cli --update${NC}" fi fi # If it looks like a shared library if [[ "$fname" == *.so* ]]; then output_line "${YELLOW} Library denial — also check:${NC}" output_line "${GREEN} ldconfig${NC}" output_line "${GREEN} fapolicyd-cli --update${NC}" fi fi # Suggest rule-based approach if [[ -n "$exe" && -n "$perm" ]]; then output_line "${GREEN} 3. Or add a custom rule in /etc/fapolicyd/rules.d/:${NC}" output_line "${GREEN} allow ${perm} exe=${exe} : all${NC}" fi } categorize_fapolicyd_denial() { local line="$1" local perm fname perm=$(echo "$line" | grep -oP 'perm=\K[^ ]+') fname=$(echo "$line" | grep -oP 'fname=\K[^ ]+') case "$perm" in execute) if [[ "$fname" == *.so* ]]; then echo "library" else echo "execute" fi ;; open) echo "open" ;; *) echo "other" ;; esac } analyze_fapolicyd() { output_line "${BOLD}═══════════════════════════════════════════════════${NC}" output_line "${BOLD} fapolicyd Log Analysis${NC}" output_line "${BOLD}═══════════════════════════════════════════════════${NC}" output_line "" # Show daemon status local daemon_status daemon_status=$(systemctl is-active fapolicyd 2>/dev/null) output_line "${CYAN}fapolicyd status:${NC} $daemon_status" # Show integrity setting if [[ -f /etc/fapolicyd/fapolicyd.conf ]]; then local integrity integrity=$(grep -oP '^\s*integrity\s*=\s*\K.*' /etc/fapolicyd/fapolicyd.conf 2>/dev/null) [[ -n "$integrity" ]] && output_line "${CYAN}Integrity mode:${NC} $integrity" fi # Show trust database stats if command -v fapolicyd-cli &>/dev/null; then local trust_count trust_count=$(fapolicyd-cli --dump-db 2>/dev/null | wc -l) [[ -n "$trust_count" ]] && output_line "${CYAN}Trusted files:${NC} $trust_count" fi output_line "" # Gather denials from audit log local denials="" if [[ ! -f /var/log/audit/audit.log ]]; then output_line "${RED}Error: Cannot find /var/log/audit/audit.log${NC}" output_line "${YELLOW}Ensure auditd is running: systemctl start auditd${NC}" return fi if [[ "$MODE" == "recent" ]]; then if command -v ausearch &>/dev/null; then denials=$(ausearch -m FANOTIFY -ts recent 2>/dev/null | grep "type=FANOTIFY") fi # Fallback to manual log parsing if [[ -z "$denials" ]]; then local one_hour_ago one_hour_ago=$(date -d '1 hour ago' '+%s' 2>/dev/null) if [[ -n "$one_hour_ago" ]]; then denials=$(awk -v cutoff="$one_hour_ago" ' /type=FANOTIFY/ && /dec=deny/ { match($0, /msg=audit\(([0-9]+)\./, arr) if (arr[1] >= cutoff) print } ' /var/log/audit/audit.log) fi fi else denials=$(grep "type=FANOTIFY" /var/log/audit/audit.log | grep "dec=deny") fi if [[ -z "$denials" ]]; then output_line "${GREEN}No fapolicyd denials found.${NC}" output_line "" return fi # Group denials by category declare -A categories local denial_count=0 local -A seen_files while IFS= read -r line; do [[ -z "$line" ]] && continue ((denial_count++)) local category category=$(categorize_fapolicyd_denial "$line") categories["$category"]+="$line"$'\n' local f f=$(echo "$line" | grep -oP 'fname=\K[^ ]+') [[ -n "$f" ]] && seen_files["$f"]=1 done <<< "$denials" TOTAL_DENIALS=$denial_count UNIQUE_FILES=${#seen_files[@]} # Display grouped results for category in "execute" "library" "open" "other"; do if [[ -n "${categories[$category]}" ]]; then local label case "$category" in execute) label="Execution Denials" ;; library) label="Library Load Denials" ;; open) label="File Open Denials" ;; other) label="Other Denials" ;; esac output_line "${BOLD}── ${label} ──────────────────────────────────${NC}" output_line "" while IFS= read -r denial_line; do [[ -z "$denial_line" ]] && continue parse_fapolicyd_denial "$denial_line" done <<< "${categories[$category]}" fi done # Show bulk fix suggestions if [[ ${#seen_files[@]} -gt 0 ]]; then output_line "${BOLD}── Bulk Fix Commands ────────────────────────────${NC}" output_line "" output_line "${YELLOW}To trust all denied files at once:${NC}" for f in "${!seen_files[@]}"; do output_line "${GREEN} fapolicyd-cli --file add ${f}${NC}" done output_line "${GREEN} fapolicyd-cli --update${NC}" output_line "" fi # Rule file reference output_line "${BOLD}── Rule File Reference ──────────────────────────${NC}" output_line "" output_line "${CYAN}Rules are loaded from:${NC}" if [[ -d /etc/fapolicyd/rules.d ]]; then output_line " /etc/fapolicyd/rules.d/ (drop-in directory)" local rule_files rule_files=$(ls /etc/fapolicyd/rules.d/ 2>/dev/null) if [[ -n "$rule_files" ]]; then output_line "${CYAN} Current rule files:${NC}" while IFS= read -r rf; do output_line " $rf" done <<< "$rule_files" fi fi if [[ -f /etc/fapolicyd/fapolicyd.rules ]]; then output_line " /etc/fapolicyd/fapolicyd.rules (compiled rules)" fi output_line "" output_line "${YELLOW}After making changes, restart the daemon:${NC}" output_line "${GREEN} systemctl restart fapolicyd${NC}" output_line "" } # ── Summary ─────────────────────────────────────────────── print_summary() { output_line "${BOLD}═══════════════════════════════════════════════════${NC}" output_line "${BOLD} Summary${NC}" output_line "${BOLD}═══════════════════════════════════════════════════${NC}" output_line "" output_line " Total denials found: ${BOLD}${TOTAL_DENIALS}${NC}" output_line " Unique files denied: ${BOLD}${UNIQUE_FILES}${NC}" output_line " Suggested fixes: ${BOLD}${SUGGESTED_FIXES}${NC}" output_line "" if [[ -n "$OUTPUT_FILE" ]]; then output_line "${GREEN}Suggestions saved to: ${OUTPUT_FILE}${NC}" output_line "" fi } # ── Parse Arguments ─────────────────────────────────────── while [[ $# -gt 0 ]]; do case "$1" in --help|-h) usage ;; --recent) MODE="recent" shift ;; --all) MODE="all" shift ;; --output) if [[ -z "$2" || "$2" == --* ]]; then echo -e "${RED}Error: --output requires a filename argument.${NC}" exit 1 fi OUTPUT_FILE="$2" shift 2 ;; --quiet|-q) QUIET=1 shift ;; *) echo -e "${RED}Unknown option: $1${NC}" echo "Use --help for usage information." exit 1 ;; esac done # ── Main ────────────────────────────────────────────────── check_root # Clear output file if specified if [[ -n "$OUTPUT_FILE" ]]; then > "$OUTPUT_FILE" fi echo -e "${BOLD}fapolicyd Log Analyzer v1.00${NC}" echo -e "${CYAN}Mode: ${MODE}${NC}" echo "" check_fapolicyd analyze_fapolicyd print_summary