|
| 1 | +#!/bin/sh |
| 2 | +# pfSense Inventory Collector |
| 3 | +# Collects broad host/network/security context for incident response and |
| 4 | +# competition triage. Output is written to a timestamped directory. |
| 5 | + |
| 6 | +SCRIPT_NAME="$(basename "$0")" |
| 7 | +HOSTNAME_SHORT="$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo unknown)" |
| 8 | +TIMESTAMP="$(date '+%Y%m%d_%H%M%S')" |
| 9 | +DEFAULT_OUTDIR="/tmp/pfsense_inventory_${HOSTNAME_SHORT}_${TIMESTAMP}" |
| 10 | +OUTDIR="${1:-$DEFAULT_OUTDIR}" |
| 11 | + |
| 12 | +umask 077 |
| 13 | +mkdir -p "$OUTDIR" |
| 14 | + |
| 15 | +log() { |
| 16 | + printf '[%s] %s\n' "$(date '+%F %T')" "$*" >&2 |
| 17 | +} |
| 18 | + |
| 19 | +run_cmd() { |
| 20 | + # Usage: run_cmd "description" "command" |
| 21 | + _desc="$1" |
| 22 | + _cmd="$2" |
| 23 | + |
| 24 | + { |
| 25 | + printf '===== %s =====\n' "$_desc" |
| 26 | + printf 'CMD: %s\n' "$_cmd" |
| 27 | + printf 'TIME: %s\n\n' "$(date '+%F %T')" |
| 28 | + sh -c "$_cmd" 2>&1 |
| 29 | + printf '\n' |
| 30 | + } >>"$OUTDIR/inventory.txt" |
| 31 | +} |
| 32 | + |
| 33 | +copy_file_if_exists() { |
| 34 | + # Usage: copy_file_if_exists "/path/to/src" "relative/dest/name" |
| 35 | + _src="$1" |
| 36 | + _dst="$2" |
| 37 | + _dst_dir="$(dirname "$OUTDIR/$_dst")" |
| 38 | + |
| 39 | + if [ -f "$_src" ]; then |
| 40 | + mkdir -p "$_dst_dir" |
| 41 | + cp "$_src" "$OUTDIR/$_dst" |
| 42 | + chmod 600 "$OUTDIR/$_dst" 2>/dev/null || true |
| 43 | + fi |
| 44 | +} |
| 45 | + |
| 46 | +capture_tail_if_exists() { |
| 47 | + # Usage: capture_tail_if_exists "/path/to/log" "relative/dest/name" "lines" |
| 48 | + _src="$1" |
| 49 | + _dst="$2" |
| 50 | + _lines="$3" |
| 51 | + _dst_dir="$(dirname "$OUTDIR/$_dst")" |
| 52 | + |
| 53 | + if [ -f "$_src" ]; then |
| 54 | + mkdir -p "$_dst_dir" |
| 55 | + tail -n "$_lines" "$_src" >"$OUTDIR/$_dst" 2>/dev/null || true |
| 56 | + chmod 600 "$OUTDIR/$_dst" 2>/dev/null || true |
| 57 | + fi |
| 58 | +} |
| 59 | + |
| 60 | +log "Starting collection to: $OUTDIR" |
| 61 | +log "This can take a minute depending on system size and log volume." |
| 62 | + |
| 63 | +# Header summary |
| 64 | +{ |
| 65 | + printf 'pfSense Inventory Collector\n' |
| 66 | + printf 'Script: %s\n' "$SCRIPT_NAME" |
| 67 | + printf 'Collected: %s\n' "$(date '+%F %T %z')" |
| 68 | + printf 'Host: %s\n' "$(hostname 2>/dev/null || echo unknown)" |
| 69 | + printf 'Output Dir: %s\n' "$OUTDIR" |
| 70 | + printf '\n' |
| 71 | +} >"$OUTDIR/README.txt" |
| 72 | + |
| 73 | +# Core system context |
| 74 | +run_cmd "System - OS/Kernel" "uname -a" |
| 75 | +run_cmd "System - pfSense Version Files" "cat /etc/version /etc/version.patch /etc/platform 2>/dev/null" |
| 76 | +run_cmd "System - Uptime/Boot" "uptime && sysctl kern.boottime" |
| 77 | +run_cmd "System - Date" "date" |
| 78 | +run_cmd "System - CPU" "sysctl hw.model hw.ncpu hw.physmem 2>/dev/null" |
| 79 | +run_cmd "System - Full Sysctl (verbose)" "sysctl -a" |
| 80 | +run_cmd "System - Dmesg" "dmesg -a" |
| 81 | +run_cmd "System - Last Logins" "last -n 40 2>/dev/null" |
| 82 | + |
| 83 | +# Filesystems and storage |
| 84 | +run_cmd "Storage - Disk Usage" "df -h" |
| 85 | +run_cmd "Storage - Mounts" "mount" |
| 86 | +run_cmd "Storage - GEOM Disks" "geom disk list 2>/dev/null" |
| 87 | +run_cmd "Storage - GEOM Partitions" "geom part list 2>/dev/null" |
| 88 | +run_cmd "Storage - gpart show" "gpart show 2>/dev/null" |
| 89 | +run_cmd "Storage - swapinfo" "swapinfo -h 2>/dev/null" |
| 90 | +run_cmd "Storage - ZFS status" "zpool status 2>/dev/null" |
| 91 | +run_cmd "Storage - ZFS datasets" "zfs list 2>/dev/null" |
| 92 | + |
| 93 | +# Network and interface context |
| 94 | +run_cmd "Network - Interfaces" "ifconfig -a" |
| 95 | +run_cmd "Network - Routing table" "netstat -rn" |
| 96 | +run_cmd "Network - Listening sockets" "sockstat -4 -6 -l" |
| 97 | +run_cmd "Network - Active sockets" "sockstat -4 -6" |
| 98 | +run_cmd "Network - ARP cache" "arp -an" |
| 99 | +run_cmd "Network - Interface stats" "netstat -i -W" |
| 100 | +run_cmd "Network - Established/All connections" "netstat -an" |
| 101 | +run_cmd "Network - IPv4 stats" "netstat -s -p ip 2>/dev/null" |
| 102 | +run_cmd "Network - TCP stats" "netstat -s -p tcp 2>/dev/null" |
| 103 | +run_cmd "Network - UDP stats" "netstat -s -p udp 2>/dev/null" |
| 104 | + |
| 105 | +# Firewall and pf state |
| 106 | +run_cmd "Firewall - pf info" "pfctl -si 2>/dev/null" |
| 107 | +run_cmd "Firewall - pf rules" "pfctl -sr 2>/dev/null" |
| 108 | +run_cmd "Firewall - pf NAT rules" "pfctl -sn 2>/dev/null" |
| 109 | +run_cmd "Firewall - pf tables" "pfctl -sTables 2>/dev/null" |
| 110 | +run_cmd "Firewall - pf states" "pfctl -ss 2>/dev/null" |
| 111 | +run_cmd "Firewall - pf all status (verbose)" "pfctl -sa 2>/dev/null" |
| 112 | + |
| 113 | +# Service and process context |
| 114 | +run_cmd "Processes - ps auxww" "ps auxww" |
| 115 | +run_cmd "Services - rc status" "service -e 2>/dev/null" |
| 116 | +run_cmd "Services - pkg inventory" "pkg info 2>/dev/null" |
| 117 | +run_cmd "Services - package locks" "pkg lock -l 2>/dev/null" |
| 118 | +run_cmd "Services - cron jobs (root)" "crontab -l -u root 2>/dev/null || crontab -l 2>/dev/null" |
| 119 | +run_cmd "Services - periodic conf" "cat /etc/periodic.conf 2>/dev/null" |
| 120 | + |
| 121 | +# User and auth context |
| 122 | +run_cmd "Users - passwd" "cat /etc/passwd" |
| 123 | +run_cmd "Users - group" "cat /etc/group" |
| 124 | +run_cmd "Users - login classes" "cat /etc/login.conf 2>/dev/null" |
| 125 | +run_cmd "Users - sudoers" "cat /usr/local/etc/sudoers 2>/dev/null" |
| 126 | +run_cmd "Users - authorized_keys files" "find /root /home -maxdepth 4 -type f -name authorized_keys 2>/dev/null | while IFS= read -r f; do echo --- \"\$f\"; cat \"\$f\"; done" |
| 127 | +run_cmd "Users - SSH daemon config" "cat /etc/ssh/sshd_config 2>/dev/null" |
| 128 | + |
| 129 | +# pfSense-specific configuration references |
| 130 | +run_cmd "pfSense - Config history/listing" "ls -lah /cf/conf 2>/dev/null && ls -lah /cf/conf/backup 2>/dev/null" |
| 131 | +run_cmd "pfSense - OpenVPN files" "ls -lah /var/etc/openvpn 2>/dev/null" |
| 132 | +run_cmd "pfSense - IPsec files" "ls -lah /var/etc/ipsec 2>/dev/null" |
| 133 | +run_cmd "pfSense - Unbound DNS files" "ls -lah /var/unbound 2>/dev/null" |
| 134 | +run_cmd "pfSense - DHCP files" "ls -lah /var/dhcpd 2>/dev/null" |
| 135 | +run_cmd "pfSense - Resolver/Services local configs" "find /var/etc -maxdepth 2 -type f 2>/dev/null | head -n 300" |
| 136 | + |
| 137 | +# Copy key configuration files (sensitive) |
| 138 | +mkdir -p "$OUTDIR/config" "$OUTDIR/log_tails" |
| 139 | +copy_file_if_exists "/cf/conf/config.xml" "config/config.xml" |
| 140 | +copy_file_if_exists "/etc/ssh/sshd_config" "config/sshd_config" |
| 141 | +copy_file_if_exists "/etc/rc.conf" "config/rc.conf" |
| 142 | +copy_file_if_exists "/etc/resolv.conf" "config/resolv.conf" |
| 143 | +copy_file_if_exists "/etc/hosts" "config/hosts" |
| 144 | +copy_file_if_exists "/usr/local/etc/sudoers" "config/sudoers" |
| 145 | +copy_file_if_exists "/etc/crontab" "config/crontab" |
| 146 | +copy_file_if_exists "/var/cron/tabs/root" "config/root_crontab" |
| 147 | + |
| 148 | +# Log tails for quick triage |
| 149 | +capture_tail_if_exists "/var/log/system.log" "log_tails/system.log.tail" "400" |
| 150 | +capture_tail_if_exists "/var/log/filter.log" "log_tails/filter.log.tail" "400" |
| 151 | +capture_tail_if_exists "/var/log/auth.log" "log_tails/auth.log.tail" "400" |
| 152 | +capture_tail_if_exists "/var/log/vpn.log" "log_tails/vpn.log.tail" "400" |
| 153 | +capture_tail_if_exists "/var/log/dhcpd.log" "log_tails/dhcpd.log.tail" "400" |
| 154 | +capture_tail_if_exists "/var/log/resolver.log" "log_tails/resolver.log.tail" "400" |
| 155 | +capture_tail_if_exists "/var/log/nginx/error.log" "log_tails/nginx_error.log.tail" "400" |
| 156 | + |
| 157 | +# Permissions and integrity quick checks |
| 158 | +run_cmd "Security - SUID/SGID files" "find / -xdev \\( -perm -4000 -o -perm -2000 \\) -type f 2>/dev/null" |
| 159 | +run_cmd "Security - Writable by others (top level paths)" "find /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin -type f -perm -0002 2>/dev/null" |
| 160 | +run_cmd "Security - rc scripts" "ls -lah /etc/rc* /usr/local/etc/rc.d 2>/dev/null" |
| 161 | +run_cmd "Security - SSH host keys fingerprints" "ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub 2>/dev/null; ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key.pub 2>/dev/null; ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub 2>/dev/null" |
| 162 | + |
| 163 | +# Build a simple file manifest |
| 164 | +( |
| 165 | + cd "$OUTDIR" || exit 1 |
| 166 | + find . -type f -print0 | xargs -0 sha256 >SHA256SUMS.txt 2>/dev/null || true |
| 167 | +) |
| 168 | + |
| 169 | +{ |
| 170 | + printf 'Collection complete: %s\n' "$(date '+%F %T')" |
| 171 | + printf 'Primary report: %s/inventory.txt\n' "$OUTDIR" |
| 172 | + printf 'Sensitive config copies may exist in: %s/config\n' "$OUTDIR" |
| 173 | +} >>"$OUTDIR/README.txt" |
| 174 | + |
| 175 | +log "Collection complete." |
| 176 | +log "Report: $OUTDIR/inventory.txt" |
| 177 | +log "Bundle this directory for offline analysis if needed." |
0 commit comments