Last active
December 23, 2025 08:58
-
-
Save rafliiar17/a18f8519c9689e525a4ebaea92b0b085 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Script Inventarisasi Server + Firewall Audit | |
| # Output: Format TSV untuk Google Sheets | |
| # Compatible: CentOS 5, 6, 7, 8 + Ubuntu + Debian | |
| print_line() { | |
| echo "==================================================================================" | |
| } | |
| print_header() { | |
| echo "" | |
| print_line | |
| echo " $1" | |
| print_line | |
| } | |
| clear | |
| echo "INVENTARISASI SERVER & FIREWALL AUDIT" | |
| echo "Generated: $(date '+%Y-%m-%d %H:%M:%S')" | |
| echo "Hostname: $(hostname)" | |
| print_line | |
| # Check if running as root | |
| if [ "$(id -u)" -ne 0 ]; then | |
| echo "" | |
| echo "WARNING: Not running as root. Some info may be incomplete." | |
| echo " Run as root: sudo su -" | |
| echo "" | |
| sleep 2 | |
| fi | |
| # ============================================================================ | |
| # COLLECT BASIC SERVER INFO | |
| # ============================================================================ | |
| print_header "COLLECTING SERVER INFORMATION" | |
| # Kode Area / Nama Server | |
| NAMA_SERVER="${NAMA_SERVER:-$(hostname)}" | |
| KODE_AREA="${KODE_AREA:-AUTO-$(hostname -s | tr '[:lower:]' '[:upper:]')}" | |
| # Produk | |
| PRODUK="${PRODUK:-General Server}" | |
| # Nama Daerah | |
| NAMA_DAERAH="${NAMA_DAERAH:-$(hostname -d 2>/dev/null || echo 'N/A')}" | |
| if [ "$NAMA_DAERAH" = "localdomain" ] || [ "$NAMA_DAERAH" = "(none)" ]; then | |
| NAMA_DAERAH="N/A" | |
| fi | |
| # Tipe Server | |
| if [ -d /etc/vmware-tools ] || [ -d /usr/lib/vmware-tools ]; then | |
| SERVER_TYPE="VM IDC" | |
| elif [ -f /.dockerenv ]; then | |
| SERVER_TYPE="Docker" | |
| elif [ -d /proc/vz ]; then | |
| SERVER_TYPE="VM (OpenVZ)" | |
| elif [ -f /proc/xen/capabilities ]; then | |
| SERVER_TYPE="VM (Xen)" | |
| elif command -v systemd-detect-virt > /dev/null 2>&1; then | |
| VIRT_TYPE=$(systemd-detect-virt 2>/dev/null) | |
| if [ "$VIRT_TYPE" != "none" ]; then | |
| SERVER_TYPE="VM ($VIRT_TYPE)" | |
| else | |
| SERVER_TYPE="Physical" | |
| fi | |
| else | |
| SERVER_TYPE="Physical" | |
| fi | |
| # OS Version | |
| if [ -f /etc/redhat-release ]; then | |
| OS_VERSION=$(cat /etc/redhat-release) | |
| # Detect Major Version for logic branching | |
| if grep -q "release 7" /etc/redhat-release; then | |
| OS_MAJOR=7 | |
| elif grep -q "release 6" /etc/redhat-release; then | |
| OS_MAJOR=6 | |
| elif grep -q "release 5" /etc/redhat-release; then | |
| OS_MAJOR=5 | |
| elif grep -q "release 8" /etc/redhat-release; then | |
| OS_MAJOR=8 | |
| else | |
| OS_MAJOR=0 # Unknown | |
| fi | |
| elif [ -f /etc/os-release ]; then | |
| OS_VERSION=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2) | |
| # Try to detect version from os-release | |
| if grep -q "VERSION_ID=\"7" /etc/os-release; then | |
| OS_MAJOR=7 | |
| elif grep -q "VERSION_ID=\"8" /etc/os-release; then | |
| OS_MAJOR=8 | |
| else | |
| OS_MAJOR=0 | |
| fi | |
| else | |
| OS_VERSION=$(uname -s) | |
| OS_MAJOR=0 | |
| fi | |
| # Kernel | |
| KERNEL=$(uname -r) | |
| # ============================================================================ | |
| # DETAILED CPU INFORMATION | |
| # ============================================================================ | |
| CPU_CORES=$(grep -c processor /proc/cpuinfo) | |
| CPU_MODEL=$(grep "model name" /proc/cpuinfo | head -1 | cut -d':' -f2 | sed 's/^[ \t]*//' | sed 's/ */ /g') | |
| if [ -z "$CPU_MODEL" ]; then | |
| CPU_MODEL="Unknown CPU" | |
| fi | |
| # Physical CPU count | |
| PHYSICAL_CPU=$(grep "physical id" /proc/cpuinfo | sort -u | wc -l) | |
| if [ "$PHYSICAL_CPU" -eq 0 ]; then | |
| PHYSICAL_CPU=1 | |
| fi | |
| CPU_INFO="${CPU_CORES} Core (${PHYSICAL_CPU} CPU) - ${CPU_MODEL}" | |
| # ============================================================================ | |
| # DETAILED RAM INFORMATION | |
| # ============================================================================ | |
| # Total RAM in GB | |
| RAM_TOTAL_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}') | |
| RAM_TOTAL_GB=$(echo "scale=2; $RAM_TOTAL_KB / 1024 / 1024" | bc) | |
| RAM_TOTAL="${RAM_TOTAL_GB} GB" | |
| # Try to get RAM details from dmidecode | |
| RAM_DETAIL="" | |
| if command -v dmidecode > /dev/null 2>&1 && [ "$(id -u)" -eq 0 ]; then | |
| RAM_MODULES=$(dmidecode -t memory 2>/dev/null | grep -A 20 "Memory Device" | grep "Size:" | grep -v "No Module" | grep -v "^Size: 0" | wc -l) | |
| RAM_SPEEDS=$(dmidecode -t memory 2>/dev/null | grep -A 20 "Memory Device" | grep "Speed:" | grep -v "Unknown" | grep -v "^Speed: 0" | head -1 | cut -d':' -f2 | sed 's/^[ \t]*//') | |
| RAM_TYPE=$(dmidecode -t memory 2>/dev/null | grep -A 20 "Memory Device" | grep "Type:" | grep -v "Unknown" | grep -v "Type: <OUT OF SPEC>" | head -1 | cut -d':' -f2 | sed 's/^[ \t]*//') | |
| if [ ! -z "$RAM_MODULES" ] && [ "$RAM_MODULES" -gt 0 ]; then | |
| RAM_DETAIL="${RAM_MODULES}x modules" | |
| if [ ! -z "$RAM_TYPE" ]; then | |
| RAM_DETAIL="${RAM_DETAIL}, ${RAM_TYPE}" | |
| fi | |
| if [ ! -z "$RAM_SPEEDS" ]; then | |
| RAM_DETAIL="${RAM_DETAIL}, ${RAM_SPEEDS}" | |
| fi | |
| fi | |
| fi | |
| if [ ! -z "$RAM_DETAIL" ]; then | |
| RAM_INFO="${RAM_TOTAL} (${RAM_DETAIL})" | |
| else | |
| RAM_INFO="${RAM_TOTAL}" | |
| fi | |
| # ============================================================================ | |
| # DETAILED DISK INFORMATION | |
| # ============================================================================ | |
| DISK_INFO="" | |
| DISK_TOTAL_GB=0 | |
| # Get all disk devices | |
| if command -v lsblk > /dev/null 2>&1; then | |
| # Using lsblk (modern systems) | |
| DISK_DEVICES=$(lsblk -d -n -o NAME,TYPE,SIZE,MODEL 2>/dev/null | grep disk) | |
| while IFS= read -r line; do | |
| DISK_NAME=$(echo "$line" | awk '{print $1}') | |
| DISK_SIZE=$(echo "$line" | awk '{print $3}') | |
| DISK_MODEL=$(echo "$line" | cut -d' ' -f4- | sed 's/^[ \t]*//') | |
| if [ -z "$DISK_MODEL" ] || [ "$DISK_MODEL" = "disk" ]; then | |
| DISK_MODEL="Unknown" | |
| fi | |
| # Convert size to GB for total calculation | |
| SIZE_NUM=$(echo "$DISK_SIZE" | sed 's/[^0-9.]//g') | |
| SIZE_UNIT=$(echo "$DISK_SIZE" | sed 's/[0-9.]//g') | |
| case "$SIZE_UNIT" in | |
| T|TB) | |
| SIZE_GB=$(echo "scale=0; $SIZE_NUM * 1024" | bc) | |
| ;; | |
| G|GB) | |
| SIZE_GB=$(echo "scale=0; $SIZE_NUM / 1" | bc) | |
| ;; | |
| *) | |
| SIZE_GB=0 | |
| ;; | |
| esac | |
| DISK_TOTAL_GB=$(echo "$DISK_TOTAL_GB + $SIZE_GB" | bc) | |
| if [ -z "$DISK_INFO" ]; then | |
| DISK_INFO="${DISK_SIZE} ${DISK_MODEL}" | |
| else | |
| DISK_INFO="${DISK_INFO}, ${DISK_SIZE} ${DISK_MODEL}" | |
| fi | |
| done <<< "$DISK_DEVICES" | |
| else | |
| # Fallback: use fdisk | |
| if command -v fdisk > /dev/null 2>&1 && [ "$(id -u)" -eq 0 ]; then | |
| DISK_DEVICES=$(fdisk -l 2>/dev/null | grep "^Disk /dev/" | grep -v "loop\|ram\|dm-") | |
| while IFS= read -r line; do | |
| DISK_NAME=$(echo "$line" | awk '{print $2}' | tr -d ':') | |
| DISK_SIZE=$(echo "$line" | grep -oP '\d+(\.\d+)?\s*(G|T)B' | head -1) | |
| if [ -z "$DISK_SIZE" ]; then | |
| DISK_SIZE=$(echo "$line" | awk '{print $3$4}') | |
| fi | |
| # Try to get model from smartctl | |
| DISK_MODEL="Unknown" | |
| if command -v smartctl > /dev/null 2>&1; then | |
| DISK_MODEL=$(smartctl -i "$DISK_NAME" 2>/dev/null | grep "Device Model\|Product:" | cut -d':' -f2 | sed 's/^[ \t]*//' | head -1) | |
| fi | |
| if [ -z "$DISK_MODEL" ] || [ "$DISK_MODEL" = "Unknown" ]; then | |
| DISK_MODEL=$(basename "$DISK_NAME") | |
| fi | |
| if [ -z "$DISK_INFO" ]; then | |
| DISK_INFO="${DISK_SIZE} ${DISK_MODEL}" | |
| else | |
| DISK_INFO="${DISK_INFO}, ${DISK_SIZE} ${DISK_MODEL}" | |
| fi | |
| done <<< "$DISK_DEVICES" | |
| fi | |
| fi | |
| # Fallback if no disk info found | |
| if [ -z "$DISK_INFO" ]; then | |
| TOTAL_DISK=$(df -h --total 2>/dev/null | grep total | awk '{print $2}') | |
| if [ -z "$TOTAL_DISK" ]; then | |
| TOTAL_DISK=$(df -h / | tail -1 | awk '{print $2}') | |
| fi | |
| DISK_INFO="Total: $TOTAL_DISK" | |
| else | |
| # Add total | |
| if [ $(echo "$DISK_TOTAL_GB > 1024" | bc) -eq 1 ]; then | |
| DISK_TOTAL_TB=$(echo "scale=2; $DISK_TOTAL_GB / 1024" | bc) | |
| DISK_INFO="${DISK_INFO} | Total: ${DISK_TOTAL_TB} TB" | |
| else | |
| DISK_INFO="${DISK_INFO} | Total: ${DISK_TOTAL_GB} GB" | |
| fi | |
| fi | |
| # IP Address | |
| if command -v ip > /dev/null 2>&1; then | |
| PRIMARY_IP=$(ip addr show | grep "inet " | grep -v "127.0.0.1" | awk '{print $2}' | cut -d'/' -f1 | head -1) | |
| else | |
| PRIMARY_IP=$(ifconfig | grep "inet " | grep -v "127.0.0.1" | awk '{print $2}' | cut -d':' -f2 | head -1) | |
| fi | |
| # SSH Port | |
| SSH_PORT=$(ss -tlnp 2>/dev/null | grep sshd | awk '{print $4}' | rev | cut -d':' -f1 | rev | head -1) | |
| if [ -z "$SSH_PORT" ]; then | |
| SSH_PORT=$(netstat -tlnp 2>/dev/null | grep sshd | awk '{print $4}' | rev | cut -d':' -f1 | rev | head -1) | |
| fi | |
| if [ -z "$SSH_PORT" ]; then | |
| SSH_PORT="22" | |
| fi | |
| echo "Server Name : $NAMA_SERVER" | |
| echo "Type : $SERVER_TYPE" | |
| echo "OS : $OS_VERSION" | |
| echo "CPU : $CPU_INFO" | |
| echo "RAM : $RAM_INFO" | |
| echo "Disk : $DISK_INFO" | |
| echo "IP : $PRIMARY_IP" | |
| echo "SSH Port : $SSH_PORT" | |
| echo "" | |
| # ============================================================================ | |
| # DETECT FIREWALL & GET ALLOWED PORTS | |
| # ============================================================================ | |
| print_header "ANALYZING FIREWALL CONFIGURATION" | |
| FIREWALL_TYPE="None" | |
| ALL_PUBLIC_PORTS="" | |
| ALL_RESTRICTED_PORTS="" | |
| # Check Firewall based on OS Version | |
| if [ "$OS_MAJOR" -eq 7 ] || [ "$OS_MAJOR" -eq 8 ]; then | |
| # CentOS 7/8 - Default firewalld | |
| if command -v firewall-cmd > /dev/null 2>&1 && systemctl is-active firewalld > /dev/null 2>&1; then | |
| FIREWALL_TYPE="firewalld" | |
| ACTIVE_ZONES=$(firewall-cmd --get-active-zones 2>/dev/null | grep -v "interfaces:" | grep -v "sources:") | |
| echo "Firewall: firewalld (Active)" | |
| echo "Active zones: $ACTIVE_ZONES" | |
| ALLOWED_PORTS="" | |
| # Loop through each active zone | |
| for zone in $ACTIVE_ZONES; do | |
| # Get ports from zone (preserve protocol) | |
| ZONE_PORTS_RAW=$(firewall-cmd --zone=$zone --list-ports 2>/dev/null | tr ' ' '\n') | |
| # Expand ranges if any | |
| for zp in $ZONE_PORTS_RAW; do | |
| if echo "$zp" | grep -q "-"; then | |
| RANGE=${zp%/*} | |
| PROTO=${zp#*/} | |
| START=${RANGE%-*} | |
| END=${RANGE#*-} | |
| if command -v seq > /dev/null 2>&1; then | |
| for p in $(seq $START $END); do | |
| ALLOWED_PORTS="${ALLOWED_PORTS} ${p}/${PROTO}" | |
| done | |
| else | |
| # Fallback if seq missing (rare on EL7) | |
| ALLOWED_PORTS="${ALLOWED_PORTS} ${zp}" | |
| fi | |
| else | |
| ALLOWED_PORTS="${ALLOWED_PORTS} ${zp}" | |
| fi | |
| done | |
| # Get services from zone and convert to ports | |
| SERVICES=$(firewall-cmd --zone=$zone --list-services 2>/dev/null) | |
| for service in $SERVICES; do | |
| P="" | |
| # Try dynamic lookup first | |
| DYN_PORTS=$(firewall-cmd --service="$service" --get-ports 2>/dev/null) | |
| if [ ! -z "$DYN_PORTS" ]; then | |
| # Remove newlines and add /protocol if missing (default tcp) | |
| # Output is like "22/tcp" or "80/tcp 443/tcp" or just "123" (rare) | |
| for dp in $DYN_PORTS; do | |
| if echo "$dp" | grep -q "/"; then | |
| ALLOWED_PORTS="${ALLOWED_PORTS} ${dp}" | |
| else | |
| ALLOWED_PORTS="${ALLOWED_PORTS} ${dp}/tcp" | |
| fi | |
| done | |
| else | |
| # Fallback to hardcoded defaults | |
| case $service in | |
| ssh) P="22/tcp" ;; | |
| http) P="80/tcp" ;; | |
| https) P="443/tcp" ;; | |
| ftp) P="21/tcp" ;; | |
| smtp) P="25/tcp" ;; | |
| dns) P="53/udp" ;; | |
| mysql) P="3306/tcp" ;; | |
| postgresql) P="5432/tcp" ;; | |
| ntp) P="123/udp" ;; | |
| dhcpv6-client) P="546/udp" ;; | |
| *) P="" ;; | |
| esac | |
| if [ ! -z "$P" ]; then ALLOWED_PORTS="${ALLOWED_PORTS} ${P}"; fi | |
| fi | |
| done | |
| done | |
| ALL_PUBLIC_PORTS=$(echo "$ALLOWED_PORTS" | tr ' ' '\n' | sort -u | uniq | tr '\n' ' ') | |
| elif command -v iptables > /dev/null 2>&1; then | |
| # Fallback to iptables if firewalld matches "iptables" usage (rare but possible) | |
| # Using logic below | |
| : | |
| fi | |
| fi | |
| # CentOS 5/6 Logic (IPTables) | |
| if [ "$OS_MAJOR" -eq 6 ] || [ "$OS_MAJOR" -eq 5 ]; then | |
| # Older systems use iptables service | |
| IPT_RUNNING=0 | |
| if service iptables status 2>/dev/null | grep -q "Table: filter"; then | |
| IPT_RUNNING=1 | |
| elif service iptables status 2>/dev/null | grep -q "num target"; then | |
| IPT_RUNNING=1 | |
| fi | |
| if [ $IPT_RUNNING -eq 1 ] || command -v iptables > /dev/null 2>&1; then | |
| FIREWALL_TYPE="iptables" | |
| INPUT_POLICY=$(iptables -L INPUT -n | head -1 | grep -o "policy [A-Z]*" | awk '{print $2}') | |
| if [ -z "$INPUT_POLICY" ]; then INPUT_POLICY="UNKNOWN"; fi | |
| echo "Firewall: iptables (Active, Policy: $INPUT_POLICY)" | |
| # Parse rules (Old Style for CentOS 6) | |
| iptables -L INPUT -n 2>/dev/null | grep ACCEPT | grep tcp | while IFS= read -r rule; do | |
| # Example: ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 | |
| PORT=$(echo "$rule" | grep -o "dpt:[0-9]*" | cut -d: -f2) | |
| if [ ! -z "$PORT" ]; then | |
| SOURCE=$(echo "$rule" | awk '{print $4}') | |
| if [ "$SOURCE" = "0.0.0.0/0" ] || [ "$SOURCE" = "anywhere" ]; then | |
| echo "${PORT}/tcp|PUBLIC" >> /tmp/fw_tmp.$$ | |
| else | |
| echo "${PORT}/tcp|RESTRICTED" >> /tmp/fw_tmp.$$ | |
| fi | |
| fi | |
| done | |
| if [ -f /tmp/fw_tmp.$$ ]; then | |
| # Flatten with tr to ensure space separated list for grep checks | |
| ALL_PUBLIC_PORTS=$(grep "|PUBLIC" /tmp/fw_tmp.$$ | cut -d'|' -f1 | sort -u | tr '\n' ' ') | |
| rm -f /tmp/fw_tmp.$$ | |
| fi | |
| # If Policy is ACCEPT, everything listening is technically open... | |
| # ...UNLESS there is a catch-all REJECT/DROP rule at the end. | |
| IS_PERMISSIVE=0 | |
| if [ "$INPUT_POLICY" = "ACCEPT" ]; then | |
| IS_PERMISSIVE=1 | |
| # Check for catch-all reject/drop in modern syntax (-S) | |
| # Looking for rules like "-A INPUT -j REJECT" with no source/dest restrictions | |
| # We grep for REJECT/DROP, then ensure it's not specific (-s, -d, -i, -p) | |
| # Note: This is an approximation. A rule at the end is usually the catch-all. | |
| if iptables -S INPUT 2>/dev/null | grep -E "^-A INPUT -j (REJECT|DROP)" >/dev/null; then | |
| IS_PERMISSIVE=0 | |
| fi | |
| # Check for catch-all in old syntax (-L) | |
| # Looking for target REJECT/DROP with source/dest 0.0.0.0/0 or anywhere | |
| if [ $IS_PERMISSIVE -eq 1 ]; then | |
| if iptables -L INPUT -n 2>/dev/null | grep -E "^(REJECT|DROP).*(0.0.0.0/0|anywhere).*(0.0.0.0/0|anywhere)" >/dev/null; then | |
| IS_PERMISSIVE=0 | |
| fi | |
| fi | |
| fi | |
| if [ $IS_PERMISSIVE -eq 1 ]; then | |
| # Get all listening TCP ports (requires netstat, which we used for LISTENING earlier but maybe not available here) | |
| # Use generic approach compatible with older CentOS | |
| LISTENING_PORTS="" | |
| if command -v netstat > /dev/null 2>&1; then | |
| LISTENING_PORTS=$(netstat -tln | grep -E "^tcp" | awk '{print $4}' | awk -F: '{print $NF}' | sort -u) | |
| elif command -v ss > /dev/null 2>&1; then | |
| LISTENING_PORTS=$(ss -tlnH | awk '{print $4}' | awk -F: '{print $NF}' | sort -u) | |
| fi | |
| for lp in $LISTENING_PORTS; do | |
| if echo " $ALL_PUBLIC_PORTS " | grep -qv " ${lp}/tcp "; then | |
| ALL_PUBLIC_PORTS="${ALL_PUBLIC_PORTS} ${lp}/tcp" | |
| fi | |
| done | |
| # Re-sort and uniq | |
| ALL_PUBLIC_PORTS=$(echo "$ALL_PUBLIC_PORTS" | tr ' ' '\n' | sort -u | tr '\n' ' ') | |
| fi | |
| if [ "$INPUT_POLICY" = "ACCEPT" ] && [ -z "$ALL_PUBLIC_PORTS" ]; then | |
| ALL_PUBLIC_PORTS="ALL (Policy ACCEPT)" | |
| fi | |
| fi | |
| fi | |
| # General Fallback / Unknown OS (Debian/Ubuntu/Etc) | |
| if [ "$OS_MAJOR" -eq 0 ]; then | |
| # Check UFW | |
| if command -v ufw > /dev/null 2>&1; then | |
| if ufw status 2>/dev/null | grep -q "Status: active"; then | |
| FIREWALL_TYPE="ufw" | |
| echo "Firewall: UFW (Active)" | |
| ALL_PUBLIC_PORTS=$(ufw status 2>/dev/null | grep ALLOW | awk '{print $1}' | grep -E "^[0-9]+" | sort -u) | |
| fi | |
| fi | |
| # Check raw iptables if no UFW | |
| if [ "$FIREWALL_TYPE" = "None" ] && command -v iptables > /dev/null 2>&1; then | |
| # Reuse iptables logic (assume modern syntax check first) | |
| if iptables -S INPUT 2>/dev/null > /dev/null; then | |
| FIREWALL_TYPE="iptables" | |
| # ... reused parsing logic omitted for brevity, keeping simple for unknown ... | |
| fi | |
| fi | |
| fi | |
| if [ "$FIREWALL_TYPE" = "None" ]; then | |
| echo "Firewall: Not Detected" | |
| PORT_FW_OPEN="No Firewall" | |
| else | |
| # Format firewall ports | |
| if [ "$ALL_PUBLIC_PORTS" = "ALL (Policy ACCEPT)" ]; then | |
| PORT_FW_OPEN=" ALL PORTS OPEN (Policy ACCEPT)" | |
| else | |
| # Format as Table | |
| HEADER=$(printf " %-15s %s" "PORT/PROTO" "SERVICE") | |
| SEPARATOR=$(printf " %-15s %s" "----------" "-------") | |
| PORT_FW_OPEN="${HEADER}\n${SEPARATOR}" | |
| for pp in $ALL_PUBLIC_PORTS; do | |
| P_NUM=${pp%/*} | |
| P_PROTO=${pp#*/} | |
| SNAME="Custom" | |
| case $P_NUM in | |
| 22) SNAME="SSH" ;; | |
| 80) SNAME="HTTP" ;; | |
| 443) SNAME="HTTPS" ;; | |
| 21) SNAME="FTP" ;; | |
| 25) SNAME="SMTP" ;; | |
| 53) SNAME="DNS" ;; | |
| 123) SNAME="NTP" ;; | |
| 546) SNAME="DHCPv6" ;; | |
| 110) SNAME="POP3" ;; | |
| 143) SNAME="IMAP" ;; | |
| 3306) SNAME="MySQL/MariaDB" ;; | |
| 5432) SNAME="PostgreSQL" ;; | |
| 8080|8888|9000|9090) SNAME="Web Alternative" ;; | |
| esac | |
| ROW=$(printf " %-15s %s" "$pp" "$SNAME") | |
| PORT_FW_OPEN="${PORT_FW_OPEN}\n${ROW}" | |
| done | |
| if [ -z "$ALL_PUBLIC_PORTS" ]; then # Check if ALL_PUBLIC_PORTS is empty after loop | |
| PORT_FW_OPEN="None" | |
| fi | |
| fi | |
| fi | |
| echo "Public Ports : $PORT_FW_OPEN" | |
| echo "" | |
| # ============================================================================ | |
| # DETECT WEB SERVICES & URLs | |
| # ============================================================================ | |
| print_header "DETECTING WEB SERVICES" | |
| URL_LOCAL="" | |
| URL_PUBLIC="" | |
| # Check web server | |
| # Get listening ports for verification | |
| if command -v ss > /dev/null 2>&1; then | |
| LISTENING=$(ss -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | rev | cut -d':' -f1 | rev | sort -nu) | |
| else | |
| LISTENING=$(netstat -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | rev | cut -d':' -f1 | rev | sort -nu) | |
| fi | |
| # Check Web Service based on OS | |
| WEB_RUNNING=0 | |
| # Define status check function based on OS | |
| check_web_status() { | |
| local service_name=$1 | |
| if [ "$OS_MAJOR" -eq 7 ] || [ "$OS_MAJOR" -eq 8 ]; then | |
| systemctl is-active "$service_name" > /dev/null 2>&1 | |
| return $? | |
| elif [ "$OS_MAJOR" -eq 6 ] || [ "$OS_MAJOR" -eq 5 ]; then | |
| if service "$service_name" status 2>/dev/null | grep -q "running"; then | |
| return 0 | |
| fi | |
| return 1 | |
| else | |
| # Fallback | |
| if command -v systemctl > /dev/null 2>&1; then | |
| systemctl is-active "$service_name" > /dev/null 2>&1 | |
| elif service "$service_name" status 2>/dev/null | grep -q "running"; then | |
| return 0 | |
| fi | |
| fi | |
| } | |
| if check_web_status "httpd"; then | |
| WEB_RUNNING=1 | |
| elif check_web_status "apache2"; then | |
| WEB_RUNNING=1 | |
| elif check_web_status "nginx"; then | |
| WEB_RUNNING=1 | |
| fi | |
| if [ $WEB_RUNNING -eq 1 ]; then | |
| # Check if port 80 or 443 is listening | |
| if command -v ss > /dev/null 2>&1; then | |
| HTTP_LISTENING=$(ss -tlnp 2>/dev/null | grep ":80 " | wc -l) | |
| HTTPS_LISTENING=$(ss -tlnp 2>/dev/null | grep ":443 " | wc -l) | |
| else | |
| HTTP_LISTENING=$(netstat -tlnp 2>/dev/null | grep ":80 " | wc -l) | |
| HTTPS_LISTENING=$(netstat -tlnp 2>/dev/null | grep ":443 " | wc -l) | |
| fi | |
| # Initialize basic URLs (will be appended/replaced by detailed ones if found) | |
| BASE_URL_LOCAL="" | |
| if [ $HTTPS_LISTENING -gt 0 ]; then | |
| BASE_URL_LOCAL="https://${PRIMARY_IP}" | |
| URL_PUBLIC="https://$(hostname -f 2>/dev/null || hostname)" | |
| elif [ $HTTP_LISTENING -gt 0 ]; then | |
| BASE_URL_LOCAL="http://${PRIMARY_IP}" | |
| URL_PUBLIC="http://$(hostname -f 2>/dev/null || hostname)" | |
| fi | |
| fi | |
| # Extra check for httpd.conf defined ports AND VHosts | |
| NB_APACHE_PORTS="" | |
| APACHE_CMD="" | |
| if command -v httpd > /dev/null 2>&1; then | |
| APACHE_CMD="httpd" | |
| elif command -v apache2 > /dev/null 2>&1; then | |
| APACHE_CMD="apache2" | |
| elif command -v apachectl > /dev/null 2>&1; then | |
| APACHE_CMD="apachectl" | |
| elif [ -x /usr/sbin/httpd ]; then | |
| APACHE_CMD="/usr/sbin/httpd" | |
| elif [ -x /usr/sbin/apache2 ]; then | |
| APACHE_CMD="/usr/sbin/apache2" | |
| fi | |
| if [ ! -z "$APACHE_CMD" ] && [ "$(id -u)" -eq 0 ]; then | |
| # Parse VHost output | |
| # Format: *:9061 PALEMBANG-PBB-BPHTB (/etc/httpd/conf/httpd.conf:362) | |
| # _default_:443 DEMO_V-TAX (/etc/httpd/conf.d/ssl.conf:74) | |
| # We want port (9061) and name (PALEMBANG-PBB-BPHTB) | |
| # Parse VHost output using simplest grep to avoid version issues | |
| $APACHE_CMD -S 2>/dev/null | grep ":[0-9]" | while read -r line; do | |
| # Clean line | |
| line=$(echo "$line" | sed 's/^[ \t]*//') | |
| # Skip informational lines | |
| if echo "$line" | grep -q "is a NameVirtualHost"; then continue; fi | |
| if echo "$line" | grep -q "default server"; then continue; fi | |
| # Extract port (col 1, strip anything before last :) | |
| COL1=$(echo "$line" | awk '{print $1}') | |
| # Handle cases like "port 80 namevhost" -> COL1="port", COL2="80" | |
| if [ "$COL1" = "port" ]; then | |
| APORT=$(echo "$line" | awk '{print $2}') | |
| else | |
| APORT=$(echo "$COL1" | rev | cut -d':' -f1 | rev) | |
| fi | |
| # Validate integer | |
| if ! [[ "$APORT" =~ ^[0-9]+$ ]]; then continue; fi | |
| # Try to get App Name from DocumentRoot | |
| # Config info is usually the last field: (/etc/httpd/conf/httpd.conf:123) | |
| CONFIG_DATA=$(echo "$line" | awk '{print $NF}' | tr -d '()') | |
| CONFIG_FILE=${CONFIG_DATA%:*} | |
| START_LINE=${CONFIG_DATA##*:} | |
| ANAME="Unknown" | |
| FOUND_DOCROOT=0 | |
| if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ] && [[ "$START_LINE" =~ ^[0-9]+$ ]]; then | |
| # Read 50 lines from start line to look for DocumentRoot | |
| DOCROOT_LINE=$(sed -n "${START_LINE},$(($START_LINE + 100))p" "$CONFIG_FILE" | grep -i "DocumentRoot" | head -1) | |
| if [ -n "$DOCROOT_LINE" ]; then | |
| # Extract path. Format: DocumentRoot "/var/www/html/pbb" | |
| DOCROOT_PATH=$(echo "$DOCROOT_LINE" | awk '{print $2}' | tr -d '"' | tr -d "'") | |
| if [ -n "$DOCROOT_PATH" ]; then | |
| ANAME=$(basename "$DOCROOT_PATH") | |
| FOUND_DOCROOT=1 | |
| fi | |
| fi | |
| fi | |
| # Fallback to ServerName if DocRoot retrieval failed | |
| if [ $FOUND_DOCROOT -eq 0 ]; then | |
| if [ "$COL1" = "port" ]; then | |
| ANAME=$(echo "$line" | awk '{print $3}') | |
| else | |
| ANAME=$(echo "$line" | awk '{print $2}') | |
| fi | |
| if [ -z "$ANAME" ] || [[ "$ANAME" == \(* ]]; then ANAME="default"; fi | |
| fi | |
| # Check if allowed in firewall | |
| IS_ALLOWED=0 | |
| if echo " $ALL_PUBLIC_PORTS " | grep -q " ${APORT}/"; then | |
| IS_ALLOWED=1 | |
| elif [ "$ALL_PUBLIC_PORTS" = "ALL OPEN" ] || [ "$FIREWALL_TYPE" = "None" ]; then | |
| IS_ALLOWED=1 | |
| fi | |
| # Trust httpd -S configuration if firewall allows it (removed IS_LISTENING check for VHosts) | |
| if [ $IS_ALLOWED -eq 1 ]; then | |
| # Construct URL Table Row | |
| PROTO="http" | |
| if [ "$APORT" = "443" ] || [ "$APORT" = "8443" ]; then PROTO="https"; fi | |
| URL="${PROTO}://${PRIMARY_IP}:${APORT}" | |
| # Format: Port Name URL | |
| # Use a fixed width simple table | |
| # PORT(5) NAME(20) URL(...) | |
| # Use printf -v to store in variable instead of subshell capturing if possible, | |
| # but here standard assignment is fine. | |
| # Avoid duplicates in URL_LOCAL | |
| if echo "$URL_LOCAL" | grep -qv "$APORT"; then | |
| # Prepare row content | |
| ROW=$(printf " %-8s %-25s %s" "$APORT" "${ANAME:0:24}" "$URL") | |
| if [ -z "$URL_LOCAL" ] || [ "$URL_LOCAL" = "N/A" ]; then | |
| HEADER=$(printf " %-8s %-25s %s" "PORT" "APP NAME" "URL") | |
| SEPARATOR=$(printf " %-8s %-25s %s" "----" "--------" "---") | |
| URL_LOCAL="${HEADER}\n${SEPARATOR}\n${ROW}" | |
| else | |
| URL_LOCAL="${URL_LOCAL}\n${ROW}" | |
| fi | |
| fi | |
| fi | |
| # Write to temp file to persist variable change outside pipe | |
| echo "$URL_LOCAL" > /tmp/url_local_tmp.$$ | |
| done | |
| if [ -f /tmp/url_local_tmp.$$ ]; then | |
| URL_LOCAL=$(cat /tmp/url_local_tmp.$$ | tail -1) | |
| rm -f /tmp/url_local_tmp.$$ | |
| fi | |
| fi | |
| # Use base if detailed is empty, otherwise check for duplicates | |
| if [ -z "$URL_LOCAL" ]; then | |
| URL_LOCAL="$BASE_URL_LOCAL" | |
| elif [ ! -z "$BASE_URL_LOCAL" ]; then | |
| # If URL_LOCAL contains detailed info, we might want to skip the generic BASE_URL_LOCAL if it's redundant | |
| # Check if BASE_URL_LOCAL is already covered (e.g., https://IP is covered by https://IP:443...) | |
| # For now, just prepend base if not present? Or relies on VHosts providing all? | |
| # If 2 out of 3 Vhosts are found, we still want those. | |
| # User complained about duplication: "https://IP, https://IP:443..." | |
| # If we have VHOST defined for 443, we don't need generic https://IP. | |
| # Simple heuristic: If URL_LOCAL has content, don't show generic BASE_URL unless it's on a port not covered? | |
| # Actually, let's just NOT add BASE_URL_LOCAL if URL_LOCAL was populated by VHosts/Ports. | |
| : | |
| fi | |
| # Fallback to simple port parsing if no VHost detected or cmd failed, but keeping it simple for now as requested specific vhost logic | |
| # If simple ports were added previously, we might want to keep the loop for non-vhost ports too? | |
| # The user wants "localhost:9080 name-server", so we prioritize VHost name. | |
| # If URL is still empty/NA, try basic config scrape again for ports not in VHost? | |
| # For now, let's mix the logic: | |
| if [ -z "$URL_LOCAL" ] || [ "$URL_LOCAL" = "N/A" ]; then | |
| # Logic from before (fallback) | |
| if [ -f /etc/httpd/conf/httpd.conf ]; then | |
| NB_APACHE_PORTS=$(grep -E "^Listen [0-9]+" /etc/httpd/conf/httpd.conf | awk '{print $2}') | |
| elif [ -f /etc/apache2/ports.conf ]; then | |
| NB_APACHE_PORTS=$(grep -E "^Listen [0-9]+" /etc/apache2/ports.conf | awk '{print $2}') | |
| fi | |
| # ... (rest of old loop could go here if really needed, but VHost -S is comprehensive if apache is running) | |
| # Re-implementing the simple loop just in case: | |
| for aport in $NB_APACHE_PORTS; do | |
| aport=$(echo $aport | tr -d ' ') | |
| if [ -z "$aport" ]; then continue; fi | |
| if [ "$aport" = "80" ] || [ "$aport" = "443" ]; then continue; fi | |
| IS_ALLOWED=0 | |
| if echo " $ALL_PUBLIC_PORTS " | grep -q " ${aport}/"; then IS_ALLOWED=1; fi | |
| if [ "$ALL_PUBLIC_PORTS" = "ALL OPEN" ] || [ "$FIREWALL_TYPE" = "None" ]; then IS_ALLOWED=1; fi | |
| if [ $IS_ALLOWED -eq 1 ] && echo "$LISTENING" | grep -qw "$aport"; then | |
| if [ -z "$URL_LOCAL" ] || [ "$URL_LOCAL" = "N/A" ]; then | |
| URL_LOCAL="http://${PRIMARY_IP}:${aport}" | |
| else | |
| URL_LOCAL="${URL_LOCAL}, http://${PRIMARY_IP}:${aport}" | |
| fi | |
| fi | |
| done | |
| fi | |
| if [ -z "$URL_LOCAL" ]; then | |
| URL_LOCAL="N/A" | |
| fi | |
| if [ -z "$URL_PUBLIC" ]; then | |
| URL_PUBLIC="N/A" | |
| fi | |
| echo "Web Service : $([ $WEB_RUNNING -eq 1 ] && echo 'Running' || echo 'Not Running')" | |
| echo "URL Local : $URL_LOCAL" | |
| echo "URL Public : $URL_PUBLIC" | |
| echo "" | |
| # ============================================================================ | |
| # CHECK CONNECTIVITY (INBOUND/OUTBOUND) | |
| # ============================================================================ | |
| print_header "CHECKING CONNECTIVITY" | |
| # Outbound Internet (Public) | |
| OUTBOUND_PUBLIC="NO" | |
| if ping -c 1 -W 2 8.8.8.8 > /dev/null 2>&1; then | |
| OUTBOUND_PUBLIC="YES" | |
| fi | |
| # Database Driven Connectivity Check | |
| DB_CONNECTIVITY_REPORT="" | |
| if command -v mysql > /dev/null 2>&1; then | |
| # Database Credentials (Change here to avoid prompt) | |
| DB_USER="sw_user" | |
| DB_PASS="sw_pwd" | |
| # Try to connect | |
| echo "Database: Connectivity Check..." | |
| # Prompt for password if not set | |
| if [ -z "$DB_PASS" ]; then | |
| echo "Please enter password for MySQL user '$DB_USER' (press Enter if none or ~/.my.cnf exists):" | |
| read -s -p "Password: " DB_PASS | |
| echo "" | |
| fi | |
| QUERY="SELECT DISTINCT SUBSTRING_INDEX(CTR_AC_VALUE, '/', 3) FROM SW_PBB.CENTRAL_APP_CONFIG a WHERE a.CTR_AC_KEY IN ('ESIGN_URL_CALLBACK', 'ESIGN_API_URL', 'PORTLET_LINK', 'URL_PUBLIK', 'ESIGN_MASSAL_API_URL', 'ESIGN_SKNJOP_URL', 'qris', 'va') AND CTR_AC_VALUE IS NOT NULL AND CTR_AC_VALUE <> '' AND (CTR_AC_VALUE LIKE 'http://%' OR CTR_AC_VALUE LIKE 'https://%') AND CTR_AC_VALUE NOT LIKE 'http://localhost%' AND CTR_AC_VALUE NOT LIKE 'https://localhost%' AND CTR_AC_VALUE NOT LIKE 'http://127.0.0.1%' AND CTR_AC_VALUE NOT LIKE 'https://127.0.0.1%';" | |
| # Get URLs (Raw mode, No Headers) with explicit credentials | |
| if [ -z "$DB_PASS" ]; then | |
| # Try without -p first if empty (socket/config) | |
| DB_URLS=$(mysql -u"$DB_USER" -N -B -e "$QUERY" 2>/dev/null) | |
| else | |
| DB_URLS=$(mysql -u"$DB_USER" -p"$DB_PASS" -N -B -e "$QUERY" 2>/dev/null) | |
| fi | |
| if [ ! -z "$DB_URLS" ]; then | |
| DB_CONNECTIVITY_REPORT=" Database Configured Connectivity:" | |
| for url in $DB_URLS; do | |
| # Check with curl (3s timeout, Head request) | |
| if curl -I -s -m 3 "$url" > /dev/null 2>&1; then | |
| STATUS="OK" | |
| else | |
| STATUS="FAIL/UNREACHABLE" | |
| fi | |
| # Clean URL for display | |
| CLEAN_URL=$(echo "$url" | tr -d '\r') | |
| DB_CONNECTIVITY_REPORT="${DB_CONNECTIVITY_REPORT}\n - $CLEAN_URL : $STATUS" | |
| done | |
| else | |
| DB_CONNECTIVITY_REPORT=" Database Configured Connectivity: No external URLs found (or auth failed)." | |
| fi | |
| else | |
| DB_CONNECTIVITY_REPORT=" Database Configured Connectivity: Skipped (MySQL client not found)" | |
| fi | |
| if [ -z "$DB_CONNECTIVITY_REPORT" ] && command -v mysql > /dev/null 2>&1; then | |
| DB_CONNECTIVITY_REPORT=" Database Configured Connectivity: Skipped (Cannot connect to MySQL - check ~/.my.cnf or root auth)" | |
| fi | |
| # Outbound HTTPS | |
| if command -v curl > /dev/null 2>&1; then | |
| if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 "https://www.google.com" 2>/dev/null | grep -q "^[23]"; then | |
| OUTBOUND_PUBLIC="YES (HTTPS)" | |
| fi | |
| fi | |
| # Inbound Public - check if any public port is accessible | |
| INBOUND_PUBLIC="UNKNOWN" | |
| if [ ! -z "$ALL_PUBLIC_PORTS" ] && [ "$ALL_PUBLIC_PORTS" != "None" ]; then | |
| if [ "$ALL_PUBLIC_PORTS" = "ALL (Policy ACCEPT)" ] || [ "$ALL_PUBLIC_PORTS" = "ALL OPEN" ]; then | |
| INBOUND_PUBLIC="YES (All Ports)" | |
| else | |
| PORT_COUNT=$(echo "$ALL_PUBLIC_PORTS" | wc -w) | |
| if [ $PORT_COUNT -gt 0 ]; then | |
| INBOUND_PUBLIC="YES ($PORT_COUNT ports)" | |
| else | |
| INBOUND_PUBLIC="NO" | |
| fi | |
| fi | |
| else | |
| INBOUND_PUBLIC="NO (Firewall)" | |
| fi | |
| # Inbound Intranet - assume YES if server is accessible locally | |
| INBOUND_INTRA="YES" | |
| # Outbound Intranet - assume YES | |
| OUTBOUND_INTRA="YES" | |
| echo "Outbound Internet : $OUTBOUND_PUBLIC" | |
| echo "Inbound Internet : $INBOUND_PUBLIC" | |
| echo "" | |
| # ============================================================================ | |
| # DETAILED FIREWALL PORT LIST | |
| # ============================================================================ | |
| if [ ! -z "$ALL_PUBLIC_PORTS" ] && [ "$ALL_PUBLIC_PORTS" != "None" ] && [ "$ALL_PUBLIC_PORTS" != "ALL OPEN" ] && [ "$ALL_PUBLIC_PORTS" != "ALL (Policy ACCEPT)" ]; then | |
| print_header "FIREWALL PORT DETAILS" | |
| echo "" | |
| echo "Port Service Status" | |
| print_line | |
| # Get listening ports | |
| if command -v ss > /dev/null 2>&1; then | |
| LISTENING=$(ss -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | rev | cut -d':' -f1 | rev | sort -nu) | |
| else | |
| LISTENING=$(netstat -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | rev | cut -d':' -f1 | rev | sort -nu) | |
| fi | |
| # Echo ALL_PUBLIC_PORTS could be newlines | |
| for item in $ALL_PUBLIC_PORTS; do | |
| PORT=${item%%/*} | |
| PROTO=${item##*/} | |
| # Get service name | |
| case $PORT in | |
| 22) SERVICE="SSH" ;; | |
| 80) SERVICE="HTTP" ;; | |
| 443) SERVICE="HTTPS" ;; | |
| 3306) SERVICE="MySQL" ;; | |
| 5432) SERVICE="PostgreSQL" ;; | |
| 8080) SERVICE="HTTP Alt" ;; | |
| 8443) SERVICE="HTTPS Alt" ;; | |
| *) SERVICE="Custom" ;; | |
| esac | |
| # Check if listening - remove /tcp etc | |
| if echo "$LISTENING" | grep -qw "$PORT"; then | |
| STATUS="Listening" | |
| else | |
| STATUS="Not Listening" | |
| fi | |
| printf "%-9s %-17s %s\n" "$item" "$SERVICE" "$STATUS" | |
| done | |
| echo "" | |
| fi | |
| # ============================================================================ | |
| # SUMMARY | |
| # ============================================================================ | |
| print_header "SUMMARY" | |
| echo "" | |
| echo "Server Information:" | |
| echo " Nama/Hostname : $NAMA_SERVER" | |
| echo " Produk : $PRODUK" | |
| echo " Nama Daerah : $NAMA_DAERAH" | |
| echo " Tipe Server : $SERVER_TYPE" | |
| echo " OS : $OS_VERSION" | |
| echo " IP Address : $PRIMARY_IP" | |
| echo "" | |
| echo "Hardware Specifications:" | |
| echo " CPU : $CPU_INFO" | |
| echo " RAM : $RAM_INFO" | |
| echo " Disk : $DISK_INFO" | |
| echo "" | |
| echo "Network & Firewall:" | |
| echo " SSH Port : $SSH_PORT" | |
| echo " Firewall Type : $FIREWALL_TYPE" | |
| if echo "$PORT_FW_OPEN" | grep -q "\n"; then | |
| echo " FW Open Ports :" | |
| echo -e "$PORT_FW_OPEN" | |
| else | |
| echo " FW Open Ports : $PORT_FW_OPEN" | |
| fi | |
| echo "" | |
| echo "Web Application:" | |
| if echo "$URL_LOCAL" | grep -q "\n"; then | |
| echo " URL Local :" | |
| echo " ---------------" | |
| echo -e "$URL_LOCAL" | |
| else | |
| echo " URL Local : $URL_LOCAL" | |
| fi | |
| echo " URL Public : $URL_PUBLIC" | |
| echo "" | |
| echo "Connectivity:" | |
| echo " Outbound Intra : $OUTBOUND_INTRA" | |
| echo " Outbound Inter : $OUTBOUND_PUBLIC" | |
| echo " Inbound Intra : $INBOUND_INTRA" | |
| echo " Inbound Public : $INBOUND_PUBLIC" | |
| if [ ! -z "$DB_CONNECTIVITY_REPORT" ]; then | |
| echo -e "$DB_CONNECTIVITY_REPORT" | |
| fi | |
| echo "" | |
| print_line | |
| echo "INVENTARISASI SELESAI" | |
| echo "" | |
| echo "Cara menyimpan report:" | |
| echo " 1. Full report : $0 > inventory_$(hostname)_$(date +%Y%m%d).txt" | |
| echo " 2. TSV only : $0 | grep -A 1 'PRODUK.*NAMA DAERAH' > inventory.tsv" | |
| echo "" | |
| print_line |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
first