#!/bin/bash set -euo pipefail # =============================================================================== # Comprehensive Linux System Enumeration and Security Audit Script # For Ubuntu, Debian, and Fedora systems # Created for automated device inventory and security assessment # Version: 2.0 - Enhanced with better error handling and security features # =============================================================================== # Configuration OUTPUT_DIR="/tmp/system_audit_$(hostname)_$(date +%Y%m%d_%H%M%S)" LOG_FILE="${OUTPUT_DIR}/audit.log" RESULTS_FILE="${OUTPUT_DIR}/results.json" START_TIME=$(date +%s) # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Enhanced error handling error_handler() { local exit_code=$1 local line_no=$2 local bash_lineno=$3 local last_command=$4 local func_stack=$5 echo -e "${RED}Error occurred in: $last_command${NC}" | tee -a "$LOG_FILE" echo -e "${RED}Exit code: $exit_code${NC}" | tee -a "$LOG_FILE" echo -e "${RED}Line number: $line_no${NC}" | tee -a "$LOG_FILE" echo -e "${RED}Function stack: $func_stack${NC}" | tee -a "$LOG_FILE" # Cleanup on error if [ -d "$OUTPUT_DIR" ]; then rm -rf "$OUTPUT_DIR" fi exit $exit_code } # Set up error trap trap 'error_handler $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]:-})' ERR # Create output directory mkdir -p "$OUTPUT_DIR" # Enhanced logging function with levels log() { local level=$1 shift local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" } log_info() { log "INFO" "$*"; } log_warn() { log "WARN" "$*"; } log_error() { log "ERROR" "$*"; } log_debug() { log "DEBUG" "$*"; } print_section() { echo -e "\n${BLUE}==== $1 ====${NC}" | tee -a "$LOG_FILE" } print_subsection() { echo -e "\n${GREEN}--- $1 ---${NC}" | tee -a "$LOG_FILE" } print_warning() { echo -e "${YELLOW}WARNING: $1${NC}" | tee -a "$LOG_FILE" } print_error() { echo -e "${RED}ERROR: $1${NC}" | tee -a "$LOG_FILE" } # Input validation and environment checking validate_environment() { log_info "Validating environment and dependencies..." local required_tools=("hostname" "uname" "lsblk" "ip" "cat" "grep" "awk") local missing_tools=() for tool in "${required_tools[@]}"; do if ! command -v "$tool" >/dev/null 2>&1; then missing_tools+=("$tool") fi done if [ ${#missing_tools[@]} -gt 0 ]; then log_error "Missing required tools: ${missing_tools[*]}" exit 1 fi # Check for optional but recommended tools local optional_tools=("jq" "docker" "podman" "nmap" "vnstat" "ethtool") for tool in "${optional_tools[@]}"; do if ! command -v "$tool" >/dev/null 2>&1; then log_warn "Optional tool not found: $tool" fi done log_info "Environment validation completed" } # Check if running as root with enhanced security check_privileges() { if [[ $EUID -ne 0 ]]; then print_warning "Script not running as root. Some checks may be limited." SUDO_CMD="sudo" # Verify sudo access if ! sudo -n true 2>/dev/null; then log_warn "Passwordless sudo not available. Some operations may fail." fi else SUDO_CMD="" log_info "Running with root privileges" fi } # System Information Collection collect_system_info() { print_section "SYSTEM INFORMATION" print_subsection "Basic System Details" { echo "Hostname: $(hostname)" echo "FQDN: $(hostname -f 2>/dev/null || echo 'N/A')" echo "IP Addresses: $(hostname -I 2>/dev/null || echo 'N/A')" echo "Date/Time: $(date)" echo "Uptime: $(uptime)" echo "Load Average: $(cat /proc/loadavg)" echo "Architecture: $(uname -m)" echo "Kernel: $(uname -r)" echo "Distribution: $(lsb_release -d 2>/dev/null | cut -f2 || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '"')" echo "Kernel Version: $(uname -v)" } | tee -a "$LOG_FILE" print_subsection "Hardware Information" { echo "CPU Info:" lscpu | tee -a "$LOG_FILE" echo -e "\nMemory Info:" free -h | tee -a "$LOG_FILE" echo -e "\nDisk Usage:" df -h | tee -a "$LOG_FILE" echo -e "\nBlock Devices:" lsblk | tee -a "$LOG_FILE" echo -e "\nPCI Devices:" lspci | tee -a "$LOG_FILE" echo -e "\nUSB Devices:" lsusb 2>/dev/null | tee -a "$LOG_FILE" } } # Network Information collect_network_info() { print_section "NETWORK INFORMATION" print_subsection "Network Interfaces" { echo "Network Interfaces (ip addr):" if command -v ip >/dev/null 2>&1; then ip addr show | tee -a "$LOG_FILE" else log_warn "ip command not available, using ifconfig fallback" ifconfig 2>/dev/null | tee -a "$LOG_FILE" || echo "No network interface information available" fi echo -e "\nRouting Table:" if command -v ip >/dev/null 2>&1; then ip route show | tee -a "$LOG_FILE" else route -n 2>/dev/null | tee -a "$LOG_FILE" || echo "No routing information available" fi echo -e "\nDNS Configuration:" if [ -f /etc/resolv.conf ]; then cat /etc/resolv.conf | tee -a "$LOG_FILE" else echo "DNS configuration file not found" | tee -a "$LOG_FILE" fi echo -e "\nNetwork Connections:" # Try multiple methods to get listening ports if command -v ss >/dev/null 2>&1; then $SUDO_CMD ss -tuln | tee -a "$LOG_FILE" elif command -v netstat >/dev/null 2>&1; then $SUDO_CMD netstat -tuln 2>/dev/null | tee -a "$LOG_FILE" else echo "No network connection tools available (ss/netstat)" | tee -a "$LOG_FILE" fi echo -e "\nActive Network Connections:" if command -v ss >/dev/null 2>&1; then $SUDO_CMD ss -tupln | tee -a "$LOG_FILE" elif command -v netstat >/dev/null 2>&1; then $SUDO_CMD netstat -tupln 2>/dev/null | tee -a "$LOG_FILE" else echo "No active connection tools available" | tee -a "$LOG_FILE" fi echo -e "\nNetwork Statistics:" if [ -f /proc/net/dev ]; then cat /proc/net/dev | tee -a "$LOG_FILE" else echo "Network statistics not available" | tee -a "$LOG_FILE" fi echo -e "\nNetwork Interface Details:" if command -v ip >/dev/null 2>&1; then for iface in $(ip link show | grep -E '^[0-9]+:' | cut -d: -f2 | tr -d ' '); do if [ "$iface" != "lo" ]; then echo "Interface: $iface" | tee -a "$LOG_FILE" if command -v ethtool >/dev/null 2>&1; then ethtool "$iface" 2>/dev/null | grep -E '(Speed|Duplex|Link detected)' | tee -a "$LOG_FILE" || echo " ethtool info not available" else echo " ethtool not available" | tee -a "$LOG_FILE" fi fi done fi echo -e "\nBandwidth Usage (if available):" if command -v vnstat >/dev/null 2>&1; then # Try multiple interfaces for iface in eth0 eth1 wlan0; do if ip link show "$iface" >/dev/null 2>&1; then echo "Interface $iface:" | tee -a "$LOG_FILE" vnstat -i "$iface" 2>/dev/null | tee -a "$LOG_FILE" || echo "No vnstat data available for $iface" break fi done else echo "vnstat not installed" | tee -a "$LOG_FILE" fi } print_subsection "Firewall Status" { if command -v ufw >/dev/null 2>&1; then echo "UFW Status:" $SUDO_CMD ufw status verbose | tee -a "$LOG_FILE" fi if command -v iptables >/dev/null 2>&1; then echo -e "\nIPTables Rules:" $SUDO_CMD iptables -L -n | tee -a "$LOG_FILE" fi if command -v firewall-cmd >/dev/null 2>&1; then echo -e "\nFirewalld Status:" $SUDO_CMD firewall-cmd --list-all | tee -a "$LOG_FILE" fi } } # Docker and Container Information collect_container_info() { print_section "CONTAINER INFORMATION" if command -v docker >/dev/null 2>&1; then print_subsection "Docker Information" { echo "Docker Version:" docker --version | tee -a "$LOG_FILE" echo -e "\nDocker System Info:" $SUDO_CMD docker system info 2>/dev/null | tee -a "$LOG_FILE" echo -e "\nRunning Containers:" $SUDO_CMD docker ps -a | tee -a "$LOG_FILE" echo -e "\nDocker Images:" $SUDO_CMD docker images | tee -a "$LOG_FILE" echo -e "\nDocker Networks:" $SUDO_CMD docker network ls | tee -a "$LOG_FILE" echo -e "\nDocker Volumes:" $SUDO_CMD docker volume ls | tee -a "$LOG_FILE" echo -e "\nDocker Compose Services:" if command -v docker-compose >/dev/null 2>&1; then find /opt /home -name "docker-compose.yml" -o -name "docker-compose.yaml" 2>/dev/null | head -10 | tee -a "$LOG_FILE" fi echo -e "\nContainer Management Tools:" $SUDO_CMD docker ps --format "table {{.Names}} {{.Image}} {{.Ports}}" | grep -E "(portainer|watchtower|traefik|nginx-proxy|heimdall|dashboard)" | tee -a "$LOG_FILE" || echo "No common management tools detected" echo -e "\nContainer Resource Usage:" $SUDO_CMD docker stats --no-stream --format "table {{.Container}} {{.CPUPerc}} {{.MemUsage}} {{.NetIO}}" 2>/dev/null | head -20 | tee -a "$LOG_FILE" } # Check for docker socket permissions if [ -S /var/run/docker.sock ]; then echo -e "\nDocker Socket Permissions:" | tee -a "$LOG_FILE" ls -la /var/run/docker.sock | tee -a "$LOG_FILE" fi else echo "Docker not installed or not in PATH" | tee -a "$LOG_FILE" fi if command -v podman >/dev/null 2>&1; then print_subsection "Podman Information" { echo "Podman Version:" podman --version | tee -a "$LOG_FILE" echo -e "\nPodman Containers:" podman ps -a | tee -a "$LOG_FILE" echo -e "\nPodman Images:" podman images | tee -a "$LOG_FILE" } fi # Check for container runtime files if [ -f "/.dockerenv" ]; then print_warning "This system appears to be running inside a Docker container" fi } # Software and Package Information collect_software_info() { print_section "SOFTWARE INFORMATION" print_subsection "Installed Packages" # Determine package manager and list packages if command -v dpkg >/dev/null 2>&1; then echo "Installed Debian/Ubuntu packages:" | tee -a "$LOG_FILE" dpkg -l > "${OUTPUT_DIR}/packages_dpkg.txt" echo "Package list saved to packages_dpkg.txt ($(wc -l < "${OUTPUT_DIR}/packages_dpkg.txt") packages)" | tee -a "$LOG_FILE" # Check for security updates if command -v apt >/dev/null 2>&1; then echo -e "\nAvailable Security Updates:" | tee -a "$LOG_FILE" apt list --upgradable 2>/dev/null | grep -i security | tee -a "$LOG_FILE" fi fi if command -v rpm >/dev/null 2>&1; then echo "Installed RPM packages:" | tee -a "$LOG_FILE" rpm -qa > "${OUTPUT_DIR}/packages_rpm.txt" echo "Package list saved to packages_rpm.txt ($(wc -l < "${OUTPUT_DIR}/packages_rpm.txt") packages)" | tee -a "$LOG_FILE" # Check for security updates (Fedora/RHEL) if command -v dnf >/dev/null 2>&1; then echo -e "\nAvailable Security Updates:" | tee -a "$LOG_FILE" dnf check-update --security 2>/dev/null | tee -a "$LOG_FILE" fi fi print_subsection "Running Services" { echo "Systemd Services:" systemctl list-units --type=service --state=running | tee -a "$LOG_FILE" echo -e "\nEnabled Services:" systemctl list-unit-files --type=service --state=enabled | tee -a "$LOG_FILE" } print_subsection "Running Processes" { echo "Process List (top 20 by CPU):" ps aux --sort=-%cpu | head -20 | tee -a "$LOG_FILE" echo -e "\nProcess Tree:" pstree | tee -a "$LOG_FILE" } } # Security Assessment collect_security_info() { print_section "SECURITY ASSESSMENT" print_subsection "User Accounts" { echo "User accounts with shell access:" set +o pipefail cat /etc/passwd | grep -E '/bin/(bash|sh|zsh|fish)' | tee -a "$LOG_FILE" set -o pipefail echo -e "\nUsers with UID 0 (root privileges):" awk -F: '$3 == 0 {print $1}' /etc/passwd | tee -a "$LOG_FILE" echo -e "\nSudo group members:" if [ -f /etc/group ]; then grep -E '^(sudo|wheel):' /etc/group | tee -a "$LOG_FILE" fi echo -e "\nCurrently logged in users:" who | tee -a "$LOG_FILE" echo -e "\nLast logins:" last -10 | tee -a "$LOG_FILE" } print_subsection "SSH Configuration" { if [ -f /etc/ssh/sshd_config ]; then echo "SSH Configuration highlights:" grep -E '^(Port|PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|Protocol)' /etc/ssh/sshd_config | tee -a "$LOG_FILE" fi echo -e "\nSSH Failed Login Attempts (last 50):" $SUDO_CMD grep "Failed password" /var/log/auth.log 2>/dev/null | tail -50 | tee -a "$LOG_FILE" || echo "Auth log not accessible" } print_subsection "File Permissions and SUID" { echo "World-writable files (excluding /proc, /sys, /dev):" # Use parallel processing for better performance find / -type f -perm -002 -not -path "/proc/*" -not -path "/sys/*" -not -path "/dev/*" -not -path "/tmp/*" -not -path "/var/tmp/*" 2>/dev/null | head -20 | tee -a "$LOG_FILE" echo -e "\nSUID/SGID files:" find / -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null | head -30 | tee -a "$LOG_FILE" echo -e "\nSUID/SGID Risk Assessment:" # Check for dangerous SUID binaries local dangerous_suid=("/bin/su" "/usr/bin/sudo" "/usr/bin/passwd" "/usr/bin/chfn" "/usr/bin/chsh" "/usr/bin/gpasswd" "/usr/bin/newgrp" "/usr/bin/mount" "/usr/bin/umount" "/usr/bin/ping" "/usr/bin/ping6") for binary in "${dangerous_suid[@]}"; do if [ -f "$binary" ] && [ -u "$binary" ]; then echo " WARNING: Potentially dangerous SUID binary found: $binary" | tee -a "$LOG_FILE" fi done echo -e "\nWorld-writable directories (excluding /proc, /sys, /dev, /tmp):" find / -type d -perm -002 -not -path "/proc/*" -not -path "/sys/*" -not -path "/dev/*" -not -path "/tmp/*" -not -path "/var/tmp/*" 2>/dev/null | head -10 | tee -a "$LOG_FILE" } print_subsection "Cron Jobs" { echo "Root crontab:" $SUDO_CMD crontab -l 2>/dev/null | tee -a "$LOG_FILE" || echo "No root crontab" echo -e "\nSystem cron jobs:" if [ -d /etc/cron.d ]; then ls -la /etc/cron.d/ | tee -a "$LOG_FILE" fi if [ -f /etc/crontab ]; then echo -e "\n/etc/crontab contents:" cat /etc/crontab | tee -a "$LOG_FILE" fi } print_subsection "Shell History" { echo "Checking for sensitive information in shell history..." local sensitive_patterns=( "password" "passwd" "secret" "token" "key" "api_key" "private_key" "ssh_key" "aws_access" "aws_secret" "database_url" "connection_string" "credential" "auth" "login" ) for histfile in /home/*/.bash_history /root/.bash_history /home/*/.zsh_history /home/*/.fish_history; do if [ -f "$histfile" ] && [ -r "$histfile" ]; then echo "Analyzing: $histfile" | tee -a "$LOG_FILE" local found_sensitive=false for pattern in "${sensitive_patterns[@]}"; do if grep -q -i "$pattern" "$histfile" 2>/dev/null; then echo " WARNING: Pattern '$pattern' found in $histfile" | tee -a "$LOG_FILE" found_sensitive=true fi done if [ "$found_sensitive" = false ]; then echo " No obvious sensitive patterns found" | tee -a "$LOG_FILE" fi fi done # Check for command history in memory if [ -f /proc/$$/environ ]; then echo -e "\nChecking for sensitive environment variables..." if grep -q -i "password\|secret\|token\|key" /proc/$$/environ 2>/dev/null; then print_warning "Sensitive environment variables detected in current process" fi fi } print_subsection "Tailscale Configuration" { if command -v tailscale >/dev/null 2>&1; then echo "Tailscale Status:" tailscale status 2>/dev/null | tee -a "$LOG_FILE" || echo "Tailscale not running" echo -e "\nTailscale IP:" tailscale ip -4 2>/dev/null | tee -a "$LOG_FILE" || echo "No Tailscale IP" else echo "Tailscale not installed" | tee -a "$LOG_FILE" fi } } # Vulnerability Assessment run_vulnerability_scan() { print_section "VULNERABILITY ASSESSMENT" print_subsection "Kernel Vulnerabilities" { echo "Kernel version and potential CVEs:" uname -r | tee -a "$LOG_FILE" # Enhanced kernel vulnerability assessment kernel_version=$(uname -r) kernel_major=$(echo "$kernel_version" | cut -d. -f1) kernel_minor=$(echo "$kernel_version" | cut -d. -f2) echo "Current kernel: $kernel_version" | tee -a "$LOG_FILE" echo "Kernel major version: $kernel_major" | tee -a "$LOG_FILE" echo "Kernel minor version: $kernel_minor" | tee -a "$LOG_FILE" # Enhanced kernel version checking local risk_level="LOW" local risk_message="" if [ "$kernel_major" -lt 4 ]; then risk_level="CRITICAL" risk_message="Kernel version is very outdated and likely has multiple critical vulnerabilities" elif [ "$kernel_major" -eq 4 ] && [ "$kernel_minor" -lt 19 ]; then risk_level="HIGH" risk_message="Kernel version is outdated and may have security vulnerabilities" elif [ "$kernel_major" -eq 5 ] && [ "$kernel_minor" -lt 4 ]; then risk_level="MEDIUM" risk_message="Kernel version may be outdated. Consider updating for security patches" elif [ "$kernel_major" -eq 5 ] && [ "$kernel_minor" -lt 10 ]; then risk_level="LOW" risk_message="Kernel version is relatively recent but may have some vulnerabilities" else risk_level="LOW" risk_message="Kernel version is recent and likely secure" fi echo "Risk Level: $risk_level" | tee -a "$LOG_FILE" echo "Assessment: $risk_message" | tee -a "$LOG_FILE" # Check for specific known vulnerable kernel versions local known_vulnerable_versions=( "4.9.0" "4.9.1" "4.9.2" "4.9.3" "4.9.4" "4.9.5" "4.9.6" "4.9.7" "4.9.8" "4.9.9" "4.14.0" "4.14.1" "4.14.2" "4.14.3" "4.14.4" "4.14.5" "4.14.6" "4.14.7" "4.14.8" "4.14.9" "4.19.0" "4.19.1" "4.19.2" "4.19.3" "4.19.4" "4.19.5" "4.19.6" "4.19.7" "4.19.8" "4.19.9" ) for vulnerable_version in "${known_vulnerable_versions[@]}"; do if [[ "$kernel_version" == "$vulnerable_version"* ]]; then print_warning "Kernel version $kernel_version matches known vulnerable pattern: $vulnerable_version" break fi done # Check kernel configuration for security features echo -e "\nKernel Security Features:" | tee -a "$LOG_FILE" if [ -f /proc/sys/kernel/randomize_va_space ]; then local aslr=$(cat /proc/sys/kernel/randomize_va_space) if [ "$aslr" -eq 2 ]; then echo " ASLR (Address Space Layout Randomization): ENABLED" | tee -a "$LOG_FILE" else print_warning "ASLR is not fully enabled (value: $aslr)" fi fi if [ -f /proc/sys/kernel/dmesg_restrict ]; then local dmesg_restrict=$(cat /proc/sys/kernel/dmesg_restrict) if [ "$dmesg_restrict" -eq 1 ]; then echo " Dmesg restriction: ENABLED" | tee -a "$LOG_FILE" else print_warning "Dmesg restriction is disabled" fi fi } print_subsection "Open Ports Security Check" { echo "Potentially risky open ports:" $SUDO_CMD netstat -tuln 2>/dev/null | awk 'NR>2 {print $4}' | sed 's/.*://' | sort -n | uniq | while read port; do case $port in 21) echo "Port $port (FTP) - Consider secure alternatives" ;; 23) echo "Port $port (Telnet) - Insecure, use SSH instead" ;; 53) echo "Port $port (DNS) - Ensure properly configured" ;; 80) echo "Port $port (HTTP) - Consider HTTPS" ;; 135|139|445) echo "Port $port (SMB/NetBIOS) - Potentially risky" ;; 3389) echo "Port $port (RDP) - Ensure secure configuration" ;; esac done | tee -a "$LOG_FILE" } } # Environment Variables and Configuration collect_env_info() { print_section "ENVIRONMENT AND CONFIGURATION" print_subsection "Environment Variables" { echo "Key environment variables:" env | grep -E '^(PATH|HOME|USER|SHELL|LANG)=' | tee -a "$LOG_FILE" } print_subsection "Mount Points" { echo "Current mounts:" mount | tee -a "$LOG_FILE" echo -e "\nDisk usage by mount point:" df -h | tee -a "$LOG_FILE" } print_subsection "System Limits" { echo "System limits:" ulimit -a | tee -a "$LOG_FILE" } } # Generate JSON summary generate_json_summary() { print_section "GENERATING SUMMARY" log "Generating JSON summary..." # Calculate execution time END_TIME=$(date +%s) EXECUTION_TIME=$((END_TIME - START_TIME)) # Gather data safely, with fallbacks hostname_val=$(hostname) fqdn_val=$(hostname -f 2>/dev/null || echo 'unknown') ip_addresses_val=$(hostname -I 2>/dev/null | tr ' ' ',' || echo 'unknown') os_val=$(lsb_release -d 2>/dev/null | cut -f2 | tr -d '"' || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '"') kernel_val=$(uname -r) arch_val=$(uname -m) uptime_val=$(uptime -p 2>/dev/null || uptime) docker_installed_val=$(command -v docker >/dev/null 2>&1 && echo "true" || echo "false") podman_installed_val=$(command -v podman >/dev/null 2>&1 && echo "true" || echo "false") running_containers_val=$(if command -v docker >/dev/null 2>&1; then $SUDO_CMD docker ps -q 2>/dev/null | wc -l; else echo "0"; fi) ssh_root_login_val=$(grep -E '^PermitRootLogin' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo 'unknown') ufw_status_val=$(command -v ufw >/dev/null 2>&1 && $SUDO_CMD ufw status | head -1 | awk '{print $2}' || echo 'not_installed') failed_ssh_attempts_val=$(grep "Failed password" /var/log/auth.log 2>/dev/null | wc -l || echo "0") # Collect open ports with better fallback if command -v ss >/dev/null 2>&1; then open_ports=$($SUDO_CMD ss -tuln 2>/dev/null | awk 'NR>2 {print $5}' | sed 's/.*://' | sort -n | uniq | tr '\n' ',' | sed 's/,$//') elif command -v netstat >/dev/null 2>&1; then open_ports=$($SUDO_CMD netstat -tuln 2>/dev/null | awk 'NR>2 {print $4}' | sed 's/.*://' | sort -n | uniq | tr '\n' ',' | sed 's/,$//') else open_ports="unknown" fi # Check for jq and use it if available, otherwise generate basic JSON if command -v jq >/dev/null 2>&1; then # Build JSON with jq jq -n \ --arg timestamp "$(date -Iseconds)" \ --arg hostname "$hostname_val" \ --arg scan_duration "${EXECUTION_TIME}s" \ --arg fqdn "$fqdn_val" \ --arg ip_addresses "$ip_addresses_val" \ --arg os "$os_val" \ --arg kernel "$kernel_val" \ --arg architecture "$arch_val" \ --arg uptime "$uptime_val" \ --argjson docker_installed "$docker_installed_val" \ --argjson podman_installed "$podman_installed_val" \ --arg running_containers "$running_containers_val" \ --arg ssh_root_login "$ssh_root_login_val" \ --arg ufw_status "$ufw_status_val" \ --arg failed_ssh_attempts "$failed_ssh_attempts_val" \ --arg open_ports "$open_ports" \ '{ "scan_info": { "timestamp": $timestamp, "hostname": $hostname, "scanner_version": "2.0", "scan_duration": $scan_duration }, "system": { "hostname": $hostname, "fqdn": $fqdn, "ip_addresses": $ip_addresses, "os": $os, "kernel": $kernel, "architecture": $architecture, "uptime": $uptime }, "containers": { "docker_installed": $docker_installed, "podman_installed": $podman_installed, "running_containers": ($running_containers | tonumber) }, "security": { "ssh_root_login": $ssh_root_login, "ufw_status": $ufw_status, "failed_ssh_attempts": ($failed_ssh_attempts | tonumber), "open_ports": ($open_ports | split(",")) } }' > "$RESULTS_FILE" else # Generate basic JSON without jq log_warn "jq not available, generating basic JSON summary" cat > "$RESULTS_FILE" << EOF { "scan_info": { "timestamp": "$(date -Iseconds)", "hostname": "$hostname_val", "scanner_version": "2.0", "scan_duration": "${EXECUTION_TIME}s" }, "system": { "hostname": "$hostname_val", "fqdn": "$fqdn_val", "ip_addresses": "$ip_addresses_val", "os": "$os_val", "kernel": "$kernel_val", "architecture": "$arch_val", "uptime": "$uptime_val" }, "containers": { "docker_installed": $docker_installed_val, "podman_installed": $podman_installed_val, "running_containers": $running_containers_val }, "security": { "ssh_root_login": "$ssh_root_login_val", "ufw_status": "$ufw_status_val", "failed_ssh_attempts": $failed_ssh_attempts_val, "open_ports": ["$open_ports"] } } EOF fi if [ $? -eq 0 ]; then log_info "JSON summary generated successfully: $RESULTS_FILE" else print_error "Failed to generate JSON summary." return 1 fi } # Network scan function (optional - can be run from central location) network_discovery() { if [ "$1" = "--network-scan" ]; then print_section "NETWORK DISCOVERY" if command -v nmap >/dev/null 2>&1; then local_subnet=$(ip route | grep "src $(hostname -I | awk '{print $1}')" | awk '{print $1}' | head -1) if [ ! -z "$local_subnet" ]; then echo "Scanning local subnet: $local_subnet" | tee -a "$LOG_FILE" nmap -sn "$local_subnet" > "${OUTPUT_DIR}/network_scan.txt" 2>&1 echo "Network scan results saved to network_scan.txt" | tee -a "$LOG_FILE" fi else echo "nmap not available for network scanning" | tee -a "$LOG_FILE" fi fi } # Main execution main() { log_info "Starting comprehensive system audit on $(hostname)" log_info "Output directory: $OUTPUT_DIR" log_info "Script version: 2.0" # Validate environment first validate_environment # Check privileges check_privileges # Run audit modules with error handling local modules=( "collect_system_info" "collect_network_info" "collect_container_info" "collect_software_info" "collect_security_info" "run_vulnerability_scan" "collect_env_info" ) for module in "${modules[@]}"; do log_info "Running module: $module" if ! $module; then log_warn "Module $module encountered issues, continuing..." fi done # Run network discovery if requested if [ $# -gt 0 ]; then log_info "Running network discovery with args: $*" network_discovery "$@" fi # Generate JSON summary log_info "Generating JSON summary" if ! generate_json_summary; then log_warn "JSON summary generation failed, but continuing..." fi print_section "AUDIT COMPLETE" END_TIME=$(date +%s) EXECUTION_TIME=$((END_TIME - START_TIME)) log_info "Audit completed successfully in ${EXECUTION_TIME} seconds" log_info "Results available in: $OUTPUT_DIR" # Create an enhanced summary file create_enhanced_summary # Display final information echo -e "\n${GREEN}Audit Complete!${NC}" echo -e "${BLUE}Results directory: $OUTPUT_DIR${NC}" echo -e "${BLUE}Main log file: $LOG_FILE${NC}" echo -e "${BLUE}JSON summary: $RESULTS_FILE${NC}" # Compress results with better error handling compress_results } # Create enhanced summary file create_enhanced_summary() { local summary_file="${OUTPUT_DIR}/SUMMARY.txt" cat > "$summary_file" << EOF === COMPREHENSIVE AUDIT SUMMARY === Generated: $(date) Script Version: 2.0 Hostname: $(hostname) FQDN: $(hostname -f 2>/dev/null || echo 'N/A') IP Addresses: $(hostname -I 2>/dev/null || echo 'N/A') === SYSTEM INFORMATION === OS: $(lsb_release -d 2>/dev/null | cut -f2 || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '"') Kernel: $(uname -r) Architecture: $(uname -m) Uptime: $(uptime -p 2>/dev/null || uptime) === SECURITY STATUS === SSH Root Login: $(grep -E '^PermitRootLogin' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo 'unknown') UFW Status: $(command -v ufw >/dev/null 2>&1 && $SUDO_CMD ufw status | head -1 | awk '{print $2}' || echo 'not_installed') Failed SSH Attempts: $(grep "Failed password" /var/log/auth.log 2>/dev/null | wc -l || echo "0") === CONTAINER STATUS === Docker: $(command -v docker >/dev/null 2>&1 && echo "Installed" || echo "Not installed") Podman: $(command -v podman >/dev/null 2>&1 && echo "Installed" || echo "Not installed") Running Containers: $(if command -v docker >/dev/null 2>&1; then $SUDO_CMD docker ps -q 2>/dev/null | wc -l; else echo "0"; fi) === FILES GENERATED === EOF ls -la "$OUTPUT_DIR" >> "$summary_file" log_info "Enhanced summary created: $summary_file" } # Compress results with better error handling compress_results() { if ! command -v tar >/dev/null 2>&1; then log_warn "tar not available, skipping compression" return 0 fi log_info "Compressing audit results..." local parent_dir=$(dirname "$OUTPUT_DIR") local archive_name=$(basename "$OUTPUT_DIR").tar.gz if cd "$parent_dir" && tar -czf "$archive_name" "$(basename "$OUTPUT_DIR")"; then log_info "Compressed results: $parent_dir/$archive_name" # Verify archive integrity if tar -tzf "$archive_name" >/dev/null 2>&1; then log_info "Archive verified successfully" # Calculate archive size local archive_size=$(du -h "$archive_name" | cut -f1) log_info "Archive size: $archive_size" else log_error "Archive verification failed" return 1 fi else log_error "Compression failed" return 1 fi } # Run main function with all arguments main "$@"