IT Pro Expert
Search
IT · 5 Dec 2025 · 47 min read

VitalPBX Enhanced Firewall Security

If you need to add an inbound IP abuse protection layer to your VitalPBX/Asterisk server then here is a guide.Note: This is specific to VitalPBX but should work on Asterisk/Freepbx…

VitalPBX Security Suite Abuse IPAPI Ban Blocking Firewall Protection

Add an inbound IP abuse protection layer to your VitalPBX or Asterisk server. The suite protects SIP and SSH from brute-force attacks and scanners using AbuseIPDB and APIBan.

It's written for VitalPBX but should also work on Asterisk and FreePBX if you adjust a few items. Once it's installed, check that it keeps working for 24 hours and after a reboot, and that the firewall rules are in the correct order.

VitalPBX Security Suite: AbuseIPDB and APIBan blocking with firewall protection

Master Installation Guide

Version 2.0 (self-healing and auto-sync) for Debian and VitalPBX. The suite is built from four layers, and each one is installed in its own phase below.

Golden Whitelist

Puts the IPs from your VitalPBX whitelist at Rule #1, above every blocklist, so you can't lock yourself out.

APIBan VoIP Shield

Downloads active VoIP attacker IPs and blocks them with a self-healing script.

AbuseIPDB General Shield

Blocks the top 10,000 worst IPs at a confidence score of 100.

Fail2Ban Reporting

Reports local brute-force attempts back to AbuseIPDB.

Prerequisites

Before you start, make sure you have:

  • Root access via SSH.
  • An AbuseIPDB API key, free from abuseipdb.com.
  • An APIBan API key, free from apiban.org.

Phase 1: System Preparation

Install the tools for managing IP lists, keeping firewall rules persistent and downloading data.

apt update
apt install ipset ipset-persistent iptables-persistent curl tcpdump -y

If you're prompted to save the current IPv4 rules during the install, select Yes.

Phase 2: The Golden Whitelist (Priority System)

This script reads the IPs from the VitalPBX web interface (Admin > Firewall > Whitelist) and forces them to the very top of the firewall at Rule #1, where they bypass every blocklist. This stops you locking yourself out.

1. Create the script:

nano /usr/local/bin/whitelist-update.sh

2. Paste the code:

#!/bin/bash
# ==============================================================================
# "Golden Whitelist" - Syncs VitalPBX GUI to Top of Firewall
# ==============================================================================
CHAIN_NAME="custom-whitelist"
VPBX_CHAIN="vpbx_white_list"

# 1. Create/Flush Chain
iptables -N $CHAIN_NAME 2>/dev/null
iptables -F $CHAIN_NAME

# 2. Hardcoded Safety Nets (Localhost & Server IP)
iptables -A $CHAIN_NAME -s 127.0.0.1 -j ACCEPT
# Add your Server Public IP below to prevent locking yourself out
# iptables -A $CHAIN_NAME -s YOUR_SERVER_IP -j ACCEPT

# 3. AUTO-SYNC: Import IPs from VitalPBX Web Interface
if iptables -L $VPBX_CHAIN -n >/dev/null 2>&1; then
    # Robust parsing to extract IPs from VitalPBX chain
    IP_LIST=$(iptables -S $VPBX_CHAIN | awk '$3 == "-s" {print $4}' | grep -v "0.0.0.0/0")
    for ip in $IP_LIST; do
        if [ -n "$ip" ]; then iptables -A $CHAIN_NAME -s $ip -j ACCEPT; fi
    done
fi

# 4. ENFORCE POSITION (King of the Hill)
# Ensure this chain is always Rule #1
if ! iptables -C INPUT -j $CHAIN_NAME 2>/dev/null; then
    iptables -I INPUT 1 -j $CHAIN_NAME
fi

# Double check priority: If Rule 1 is NOT our whitelist, fix it.
FIRST_RULE=$(iptables -L INPUT -n --line-numbers | head -n 3 | grep "1" | awk '{print $2}')
if [ "$FIRST_RULE" != "$CHAIN_NAME" ]; then
    iptables -D INPUT -j $CHAIN_NAME 2>/dev/null
    iptables -I INPUT 1 -j $CHAIN_NAME
fi

3. Make it executable:

chmod +x /usr/local/bin/whitelist-update.sh

Phase 3: APIBan VoIP Shield (Inbound Block)

Downloads active VoIP attacker IPs and blocks them using a self-healing script.

1. Create the script:

nano /usr/local/bin/apiban-update.sh

2. Paste the code, replacing YOUR_APIBAN_KEY with your key:

#!/bin/bash
# APIBan Self-Healing Updater
APIKEY="YOUR_APIBAN_KEY"
IPSET_NAME="apiban"

# 1. Download List
JSON_OUTPUT=$(curl -s "https://apiban.org/api/$APIKEY/banned/0")
IP_LIST=$(echo "$JSON_OUTPUT" | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')

# 2. Create IPSet
ipset create $IPSET_NAME hash:ip hashsize 4096 2>/dev/null

if [ -z "$IP_LIST" ]; then exit 1; fi

# 3. Load Firewall
( echo "flush $IPSET_NAME"; for ip in $IP_LIST; do echo "add $IPSET_NAME $ip -exist"; done ) | ipset restore

# 4. Self-Healing Chain & Rule
if ! iptables -C INPUT -j apiban 2>/dev/null; then
    iptables -N apiban 2>/dev/null
    iptables -A apiban -m set --match-set apiban src -j DROP
    iptables -I INPUT -j apiban
fi

# 5. Re-Apply Whitelist to ensure it stays on top
/usr/local/bin/whitelist-update.sh >/dev/null 2>&1

3. Make it executable:

chmod +x /usr/local/bin/apiban-update.sh

Phase 4: AbuseIPDB General Shield (Inbound Block)

Blocks the top 10,000 worst IPs, at a confidence score of 100.

1. Create the script:

nano /usr/local/bin/abuseipdb-update.sh

2. Paste the code, replacing YOUR_ABUSEIPDB_KEY with your key:

#!/bin/bash
# AbuseIPDB Self-Healing Updater
API_KEY="YOUR_ABUSEIPDB_KEY"
IPSET_NAME="abuseipdb"
CONFIDENCE=100
LIMIT=10000

# 1. Create IPSet
ipset create $IPSET_NAME hash:ip hashsize 4096 2>/dev/null

# 2. Download List (IPv4 Only)
IP_LIST=$(curl -G https://api.abuseipdb.com/api/v2/blacklist \
  -d confidenceMinimum=$CONFIDENCE -d limit=$LIMIT -d ipVersion=4 -d plaintext \
  -H "Key: $API_KEY" -H "Accept: text/plain")

if [[ -z "$IP_LIST" ]] || [[ "$IP_LIST" == *"errors"* ]]; then exit 1; fi

# 3. Load Firewall
( echo "flush $IPSET_NAME"; for ip in $IP_LIST; do echo "add $IPSET_NAME $ip -exist"; done ) | ipset restore

# 4. Self-Healing Rule
if ! iptables -C INPUT -m set --match-set abuseipdb src -j DROP 2>/dev/null; then
    iptables -I INPUT -m set --match-set abuseipdb src -j DROP
fi

# 5. Re-Apply Whitelist to ensure it stays on top
/usr/local/bin/whitelist-update.sh >/dev/null 2>&1

3. Make it executable:

chmod +x /usr/local/bin/abuseipdb-update.sh

Phase 5: Fail2Ban (Outbound Reporting)

Configures the system to report local brute-force attempts back to AbuseIPDB.

1. Edit the config:

nano /etc/fail2ban/jail.local

2. Paste the config, replacing YOUR_ABUSEIPDB_KEY with your key:

[DEFAULT]
bantime = 8600
findtime = 700
maxretry = 7
chain = vpbx_fail2ban
banaction = firewallcmd-ipset
banaction_allports = firewallcmd-ipset

# --- ABUSEIPDB SETTINGS ---
abuseipdb_apikey = YOUR_ABUSEIPDB_KEY

action = %(action_mw)s
ignoreip = 127.0.0.1 192.168.123.0/24

[sshd]
enabled = true
action = %(action_mw)s
         %(action_abuseipdb)s[abuseipdb_apikey="%(abuseipdb_apikey)s", abuseipdb_category="22"]

[asterisk]
enabled  = true
filter   = asterisk
logpath  = /var/log/asterisk/fail2ban tail
maxretry = 3
action = %(action_mw)s
         %(action_abuseipdb)s[abuseipdb_apikey="%(abuseipdb_apikey)s", abuseipdb_category="18"]

[vitalpbx-gui]
enabled  = true
filter   = vitalpbx-gui
logpath  = /var/log/vitalpbx/authentications.log
action = %(action_mw)s
         %(action_abuseipdb)s[abuseipdb_apikey="%(abuseipdb_apikey)s", abuseipdb_category="18"]

[nginx-botsearch]
enabled  = true
filter   = nginx-botsearch
logpath  = /var/log/nginx/access.log
maxretry = 20
action = %(action_mw)s
         %(action_abuseipdb)s[abuseipdb_apikey="%(abuseipdb_apikey)s", abuseipdb_category="19"]

[nginx-bad-request]
enabled = true
filter = nginx-bad-request
logpath = /var/log/nginx/access.log
maxretry = 50
action = %(action_mw)s
         %(action_abuseipdb)s[abuseipdb_apikey="%(abuseipdb_apikey)s", abuseipdb_category="21"]

[manual-ban]
enabled = true
bantime = -1

3. Restart Fail2Ban:

touch /var/log/fail2ban.log

systemctl restart fail2ban

Phase 6: Automation (Cron)

Runs the scripts on schedule and restores the firewall after a reboot.

1. Edit the crontab:

crontab -e

2. Add these lines at the bottom:

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# APIBan (Every 10 mins) + Sync Whitelist
*/10 * * * * /usr/local/bin/apiban-update.sh >/dev/null 2>&1

# AbuseIPDB (Every 6 hours) + Sync Whitelist
0 */6 * * * /usr/local/bin/abuseipdb-update.sh >/dev/null 2>&1

# Boot Persistence (Restore rules 60s after reboot)
@reboot sleep 60 && /usr/local/bin/apiban-update.sh >/dev/null 2>&1
@reboot sleep 60 && /usr/local/bin/abuseipdb-update.sh >/dev/null 2>&1
@reboot sleep 65 && /usr/local/bin/whitelist-update.sh >/dev/null 2>&1

# Restore Geo-Firewall rules on boot
@reboot sleep 40 && for set in $(ipset list -n | grep "blacklist_"); do iptables -I INPUT -m set --match-set "$set" src -j DROP; done

Phase 7: Security Monitor (Dashboard v11)

A dashboard that visualises blocked packets and service status.

1. Create the script:

nano /usr/local/bin/security-monitor

2. Paste the code:

#!/bin/bash

# ==============================================================================
# VITALPBX SECURITY DASHBOARD (v11 - itproexpert.com)
# Run with: watch -n 2 -c /usr/local/bin/security-monitor
# ==============================================================================

LOG_FILE="/var/log/fail2ban.log"
TODAY=$(date +%Y-%m-%d)

# --- Colors ---
GREEN=$(printf '\033[32m')
RED=$(printf '\033[31m')
YELLOW=$(printf '\033[33m')
CYAN=$(printf '\033[36m')
WHITE=$(printf '\033[97m')
GREY=$(printf '\033[90m')
RESET=$(printf '\033[0m')
BOLD=$(printf '\033[1m')

# --- Snapshots & System Data ---
IPTABLES_SNAPSHOT=$(iptables -L INPUT -v -n)
LOAD=$(cat /proc/loadavg | awk '{print $1" "$2" "$3}')
UPTIME=$(uptime -p | cut -d " " -f 2-)

# --- Helper Functions ---
get_ipset_count() {
    if ipset list -n | grep -q "^$1$"; then
        ipset list "$1" | grep "Number of entries" | cut -d: -f2 | tr -d ' '
    else
        echo "0"
    fi
}

get_drop_count() {
    # Check Main Input
    CNT=$(echo "$IPTABLES_SNAPSHOT" | grep "match-set $1" | awk '{print $1}' | head -n 1)
    # Check Sub-Chains
    if [ -z "$CNT" ]; then
        # If we can't find it in snapshot, check the chain directly
        CNT=$(iptables -L $1 -v -n 2>/dev/null | grep "DROP" | awk '{print $1}' | head -n 1)
    fi
    if [ -z "$CNT" ]; then echo "0"; else echo "$CNT"; fi
}

clear
echo -e "${CYAN}==============================================================================${RESET}"
echo -e "${BOLD}   🛡️   VITALPBX SECURITY DEFENCE CENTER  ${RESET}"
echo -e "   ${WHITE}Load:${RESET} $LOAD   |   ${WHITE}Uptime:${RESET} $UPTIME"
echo -e "${CYAN}==============================================================================${RESET}"

# -----------------------------
# 1. INTEGRATION STATUS
# -----------------------------
if systemctl is-active --quiet fail2ban; then F2B_ICON="${GREEN}● ACTIVE${RESET}"; else F2B_ICON="${RED}● DOWN${RESET}"; fi
ADB_SENT=$(grep "Reported" "$LOG_FILE" | grep "$TODAY" | wc -l)
ADB_ICON="${GREEN}● ACTIVE${RESET} (Sent Today: ${WHITE}${ADB_SENT}${RESET})"
if ipset list -n | grep -q "apiban"; then API_ICON="${GREEN}● ACTIVE${RESET}"; else API_ICON="${RED}● DOWN${RESET}"; fi

printf " Fail2Ban Engine:  %-45s\n" "$F2B_ICON"
printf " AbuseIPDB Report: %-45s\n" "$ADB_ICON"
printf " APIBan Service:   %-45s\n" "$API_ICON"

# -----------------------------
# 2. DEFENSE METRICS
# -----------------------------
echo ""
echo -e "${CYAN}--- 🛡️  DEFENSE METRICS (Global Stats) -------------------------------------${RESET}"

API_DB=$(get_ipset_count "apiban")
API_DROPS=$(get_drop_count "apiban")
ADB_DB=$(get_ipset_count "abuseipdb")
ADB_DROPS=$(get_drop_count "abuseipdb")

printf " ${WHITE}%-20s${RESET} | ${GREY}%-25s${RESET} | ${GREY}%-20s${RESET}\n" "SERVICE" "TOTAL BAD IPs" "PACKETS BLOCKED"
echo " -------------------------------------------------------------------------"
printf " APIBan (VoIP)        | ${WHITE}%-25s${RESET} | ${YELLOW}${BOLD}%-20s${RESET}\n" "$API_DB IPs" "$API_DROPS"
printf " AbuseIPDB (General)  | ${WHITE}%-25s${RESET} | ${YELLOW}${BOLD}%-20s${RESET}\n" "$ADB_DB IPs" "$ADB_DROPS"

# -----------------------------
# 3. GEO-FIREWALL
# -----------------------------
echo ""
echo -e "${CYAN}--- 🌐 GEO-FIREWALL (Active Blocks) ---------------------------------------${RESET}"

GEO_LIST=$(ipset list -n | grep "blacklist_")
TOTAL_GEO_RANGES=0
ACTIVE_BLOCKS=""

if [ -z "$GEO_LIST" ]; then
    echo -e " ${YELLOW}No Country Blocking detected.${RESET}"
else
    for set in $GEO_LIST; do
        CNT=$(ipset list "$set" -t | grep "Number of entries" | cut -d: -f2 | tr -d ' ')
        TOTAL_GEO_RANGES=$((TOTAL_GEO_RANGES + CNT))
        DROPS=$(echo "$IPTABLES_SNAPSHOT" | grep "match-set $set" | awk '{print $1}' | head -n 1)
        
        if [[ "$DROPS" != "0" && ! -z "$DROPS" ]]; then
             NAME=$(echo "$set" | sed 's/blacklist_//g' | tr '[:lower:]' '[:upper:]')
             ACTIVE_BLOCKS+="${CYAN}${NAME}:${RESET} ${YELLOW}${DROPS}${RESET}   "
        fi
    done
    
    echo -e " Total Geo-Fenced IP Ranges: ${WHITE}${TOTAL_GEO_RANGES}${RESET}"
    
    if [ -z "$ACTIVE_BLOCKS" ]; then
        echo -e " ${GREY}(0 Active attacks from Geo-Fenced countries since last reboot)${RESET}"
    else
        echo -e " Top Blocked Countries:"
        echo -e " $ACTIVE_BLOCKS"
    fi
fi

# -----------------------------
# 4. FAIL2BAN JAILS
# -----------------------------
echo ""
echo -e "${CYAN}--- 🔒 FAIL2BAN JAILS & ACTIVE BANS ---------------------------------------${RESET}"
printf " ${BOLD}%-20s %-15s %-15s${RESET}\n" "Jail Name" "Active Bans" "Total Bans"
echo " --------------------------------------------------------"

JAILS=$(fail2ban-client status | grep "Jail list:" | sed 's/.*Jail list://g' | sed 's/,//g')
for jail in $JAILS; do
    STATUS_OUTPUT=$(fail2ban-client status "$jail")
    CURRENT=$(echo "$STATUS_OUTPUT" | grep "Currently banned:" | grep -o '[0-9]*')
    TOTAL=$(echo "$STATUS_OUTPUT" | grep "Total banned:" | grep -o '[0-9]*')
    IP_LIST=$(echo "$STATUS_OUTPUT" | grep "Banned IP list:" | sed 's/.*Banned IP list://g' | tr -s ' ')
    
    if [ "$CURRENT" -gt 0 ]; then C_COL=$RED; else C_COL=$GREEN; fi
    
    # FIXED PRINTF LINE (Space at start, arguments separated)
    printf " %-20s %b%-15s%b %-15s\n" "$jail" "$C_COL" "$CURRENT" "$RESET" "$TOTAL"
    
    if [ "$CURRENT" -gt 0 ]; then
        for ip in $IP_LIST; do
            TIME_BANNED=$(grep "Ban $ip" /var/log/fail2ban.log /var/log/fail2ban.log.1 2>/dev/null | tail -n 1 | awk '{print $1, $2}' | cut -d, -f1)
            if [ -z "$TIME_BANNED" ]; then TIME_BANNED="Unknown Time"; fi
            echo -e "      ${RED}>${RESET} ${WHITE}$ip${RESET}   ${GREY}($TIME_BANNED)${RESET}"
        done
    fi
done

# -----------------------------
# 5. LATEST LOG ACTIVITY
# -----------------------------
echo ""
echo -e "${CYAN}--- 📄 LATEST LOG ACTIVITY (Stream) ---------------------------------------${RESET}"
if [ -f "$LOG_FILE" ]; then
    LAST_LOGS=$(grep " Ban " "$LOG_FILE" | tail -n 5)
    if [ -z "$LAST_LOGS" ]; then
        echo -e "${GREY}   (No bans in current log file)${RESET}"
    else
        echo "$LAST_LOGS" | awk '{printf "  %s %s   %-15s  %-10s  %s\n", $1, $2, $5, "Ban", $NF}' | sed "s/Ban/${RED}Ban${RESET}/g"
    fi
else
    echo -e "${RED}Log file not found.${RESET}"
fi

echo -e "${CYAN}==============================================================================${RESET}"

3. Make it executable:

chmod +x /usr/local/bin/security-monitor

Phase 8: Persistence and Verification

1. Save the initial configuration:

ipset save > /etc/iptables/ipsets
netfilter-persistent save

2. Check everything is working. Run the dashboard to see live statistics:

watch -n 2 -c /usr/local/bin/security-monitor

3. Testing:

  • Check an IP in AbuseIPDB: ipset test abuseipdb 1.2.3.4
  • Check an IP in APIBan: ipset test apiban 1.2.3.4
  • Monitor specific traffic: tcpdump -n -i any src net 1.2.3.0/24

Diagnostic Cheat Sheet

CommandDescription
watch -n 2 -c /usr/local/bin/security-monitorLaunch the dashboard
ipset list abuseipdb | head -n 10View the top AbuseIPDB entries
ipset list apiban | head -n 10View the top APIBan entries
iptables -L custom-whitelist -nView currently whitelisted IPs
fail2ban-client set asterisk banip 1.2.3.4Manually ban an IP
fail2ban-client set asterisk unbanip 1.2.3.4Manually unban an IP
/usr/local/bin/apiban-update.shManually update the APIBan list
/usr/local/bin/abuseipdb-update.shManually update the AbuseIPDB list
ipset list -n | while read setname; do echo -n "$setname: "; ipset list $setname -t | grep "Number of entries" | cut -d: -f2; doneManually check block counts

Disabling the Suite Temporarily

  1. Pause the Cron Jobs

    Run crontab -e and add a # to the start of these two lines:

    # */10 * * * * /usr/local/bin/apiban-update.sh >/dev/null 2>&1
    # 0 */6 * * * /usr/local/bin/abuseipdb-update.sh >/dev/null 2>&1
  2. Remove the Blocking Rules

    Delete the APIBan and AbuseIPDB rules from the INPUT chain:

    iptables -D INPUT -j apiban
    iptables -D INPUT -m set --match-set abuseipdb src -j DROP
  3. Flush the Lists (Troubleshooting Only)

    A full flush isn't normally needed. Only use it when troubleshooting.

    ipset flush apiban
    ipset flush abuseipdb
    ipset list apiban | grep "Number of entries"
    # Entries should be 0 if disabled

Restoring the Suite

Run crontab -e again and remove the # from the two lines you edited, then update the lists manually:

/usr/local/bin/apiban-update.sh
/usr/local/bin/abuseipdb-update.sh

Troubleshooting

Section A: After a GUI Firewall Change

If you update the firewall in the GUI, you must run this immediately afterwards to restore your shields:

/usr/local/bin/whitelist-update.sh && /usr/local/bin/apiban-update.sh

Section B: Repair the Firewall, Fail2Ban and Shields

1. Fix the Native Firewall (If the vpbx_ Chains Are Missing)

This forces VitalPBX to rebuild the standard firewall structure.

# Ensure firewall is enabled in settings (just in case)
vitalpbx build-firewall

# Force apply the rules
vitalpbx apply-firewall

2. Fix Fail2Ban (If You Get Socket or Log File Errors)

This fixes the crash caused by the firewall restart.

# Create the missing log file that causes the crash
touch /var/log/fail2ban.log

# Force stop the service
systemctl stop fail2ban

# Delete the stale socket file
rm -rf /var/run/fail2ban/fail2ban.sock

# Restart the service
systemctl restart fail2ban

# Verify it is alive (Should say "pong")
fail2ban-client ping

3. Restore Custom Shields (If the Whitelist or APIBan Disappeared)

This puts your protection back on top (Rules #1 and #2) after a firewall reload has wiped it.

# Restore Golden Whitelist (Rule #1)
/usr/local/bin/whitelist-update.sh

# Restore APIBan / AbuseIPDB (Rule #2)
/usr/local/bin/apiban-update.sh

4. Final Verification

Check that everything is in the Ironclad order:

iptables -L INPUT -n --line-numbers | head -n 10

Look for: custom-whitelist first, apiban second, and the vpbx_ chains lower down.

Section C: Emergency Restore Command

If your firewall crashes, Fail2Ban fails to start, or you've accidentally wiped your rules through the GUI, run this one-liner to fix the entire stack:

touch /var/log/fail2ban.log && systemctl restart fail2ban && /usr/local/bin/whitelist-update.sh && /usr/local/bin/apiban-update.sh

Section D: AbuseIPDB Isn't Receiving Reports

Log in to the AbuseIPDB website to check. Blocks from brute-force attempts on Asterisk, SSH and so on should appear in your reports and in the public IP search. If they don't, the Fail2Ban jail is set up incorrectly or has been overwritten.

1. Fix Your jail.local

Open the file:

nano /etc/fail2ban/jail.local

Compare it with the config in Phase 5, step 2. It should contain your AbuseIPDB API key, and each jail (such as asterisk and sshd) should have an action that reports to AbuseIPDB. Correct anything that's missing, save, then restart Fail2Ban:

systemctl restart fail2ban

You can confirm it's working in the log, but wait until an event happens first:

grep "AbuseIPDB" /var/log/fail2ban.log

Section E: Why Fail2Ban Ban Counts Are Low

You might notice that the Fail2Ban numbers are very low, perhaps only one ban. This is actually a good thing.

Because APIBan and AbuseIPDB sit at the front door (Rule #2), you're blocking the 100,000 known attackers before they can even reach Asterisk to guess a password.

Without APIBan

Your server logs would be full of thousands of failed logins, and Fail2Ban would be working overtime.

With APIBan

The noise is blocked silently at the firewall. Fail2Ban sits comfortably in the background, waiting for any new attacker who isn't on the global blacklists yet.

How to Check the Brute-Force Status

To see exactly what the Asterisk protection is doing right now, run:

fail2ban-client status

This lists all the jails (for example asterisk-udp, asterisk-tcp, sshd and vitalpbx-gui), like this:

# THIS IS AN EXAMPLE RESULT - DO NOT TYPE THIS IN
Status
|- Number of jail:      8
`- Jail list:   asterisk, manual-ban, nginx-bad-request, nginx-botsearch, recidive, sshd, sshd-ddos, vitalpbx-gui

To look inside the Asterisk UDP jail, the most common attack vector, run:

fail2ban-client status asterisk-udp

What to look for:

  • Status: should say Currently failed: 0, or a small number.
  • File list: should show the log file it's watching, usually /var/log/asterisk/security or /var/log/asterisk/full.
  • Banned IP list: any IPs listed here are in the penalty box for guessing passwords.

If that command returns status information, your standard brute-force protection is healthy.

Section F: Changed the Whitelist in the GUI and Fail2Ban Was Reset?

1. Fix jail.local. Put the settings back as described in Phase 5, including your AbuseIPDB API key, then restart Fail2Ban:

nano /etc/fail2ban/jail.local
systemctl restart fail2ban

2. Sync everything again:

/usr/local/bin/whitelist-update.sh && /usr/local/bin/apiban-update.sh

3. Run the security audit:

/usr/local/bin/security-audit.sh

The audit should show that everything is working again.

Three-Point Health Check

To be sure your system is healthy and the Ironclad order is correct, run these three checks. If the outputs match the examples below, the suite is working as it should.

Check 1: The Firewall Order (Most Critical)

This shows the top 10 rules, which decide who gets in and who gets dropped.

iptables -L INPUT -n --line-numbers | head -n 10

What you must see:

  • Line 1: custom-whitelist. Your trusted IPs must be first.
  • Line 2: apiban or abuseipdb. Your shields must be second.
  • Line 3 onwards: vpbx_... or fail2ban. The standard VitalPBX rules must be below your shields.

Check 2: The Blocklist Capacity

Confirm your buckets of banned IPs are actually full.

ipset list -n | while read setname; do 
    echo -n "$setname: "
    ipset list $setname -t | grep "Number of entries" | cut -d: -f2
done

What you must see:

  • apiban: more than 0, usually 200–500.
  • abuseipdb: 10,000 (full).
  • vpbx_...: numbers for country blocks, if you've enabled them.

Check 3: The Ping Test

Make sure the dynamic guard, Fail2Ban, is awake and listening.

fail2ban-client ping

What you must see: Server replied: pong

Summary

  • Whitelist on top? Yes.
  • Lists full? Yes.
  • Fail2Ban replying with pong? Yes.

If all three are yes, your system is healthy, secure and ready for production.

Security Audit Tool

This tool automatically checks that all the firewalls, updates and blocklists are working.

1. Create the script:

nano /usr/local/bin/security-audit.sh

2. Paste the following:

#!/bin/bash

# ==============================================================================
# VITALPBX SECURITY AUDIT TOOL (v2)
# Verifies Firewall Order, Shield Health, Fail2Ban Config, and Reporting.
# ==============================================================================

# --- Colors ---
PASS=$(printf '\033[32m✔ PASS\033[0m')
FAIL=$(printf '\033[31m✖ FAIL\033[0m')
WARN=$(printf '\033[33m! WARN\033[0m')
BOLD=$(printf '\033[1m')
RESET=$(printf '\033[0m')

echo -e "\n${BOLD}🔒 STARTING SECURITY AUDIT...${RESET}"
echo "=========================================================="

# ------------------------------------------------------------------------------
# 1. FIREWALL HIERARCHY CHECK (The "Ironclad" Test)
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[1] FIREWALL HIERARCHY CHECK${RESET}"

# Get the first 3 input rules
RULES=$(iptables -L INPUT -n --line-numbers | head -n 5)

# Parse Rule 1
RULE_1_TARGET=$(echo "$RULES" | grep "^1" | awk '{print $2}')

# Parse Rule 2 (Capture Target AND Options to detect "match-set")
RULE_2_LINE=$(echo "$RULES" | grep "^2")
RULE_2_TARGET=$(echo "$RULE_2_LINE" | awk '{print $2}')

# Check Rule 1 (Must be Whitelist)
if [[ "$RULE_1_TARGET" == "custom-whitelist" ]]; then
    echo -e " $PASS Rule #1 is Golden Whitelist"
else
    echo -e " $FAIL Rule #1 is '$RULE_1_TARGET' (Expected: custom-whitelist)"
    echo -e "       ${WARN} Run /usr/local/bin/whitelist-update.sh to fix."
fi

# Check Rule 2 (Accepts 'apiban' Chain OR 'DROP' with abuseipdb match)
if [[ "$RULE_2_TARGET" == "apiban" ]]; then
    echo -e " $PASS Rule #2 is Shield (APIBan Chain)"
elif [[ "$RULE_2_TARGET" == "DROP" ]] && echo "$RULE_2_LINE" | grep -q "abuseipdb"; then
    echo -e " $PASS Rule #2 is Shield (AbuseIPDB Direct Drop)"
elif [[ "$RULE_2_TARGET" == "abuseipdb" ]]; then
    echo -e " $PASS Rule #2 is Shield (AbuseIPDB Chain)"
else
    echo -e " $FAIL Rule #2 is '$RULE_2_TARGET' (Expected: apiban or abuseipdb)"
    echo -e "       ${WARN} Run /usr/local/bin/apiban-update.sh to fix."
fi

# ------------------------------------------------------------------------------
# 2. BLOCKLIST CAPACITY CHECK (Are lists empty?)
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[2] BLOCKLIST CAPACITY CHECK${RESET}"

# Function to check ipset count
check_ipset() {
    NAME=$1
    EXPECTED=$2
    if ipset list -n | grep -q "^$NAME$"; then
        COUNT=$(ipset list $NAME -t | grep "Number of entries" | cut -d: -f2 | tr -d ' ')
        if [ "$COUNT" -gt "$EXPECTED" ]; then
             echo -e " $PASS $NAME: Active with ${BOLD}$COUNT${RESET} blocked IPs."
        else
             echo -e " $FAIL $NAME: Empty or too low ($COUNT entries)."
        fi
    else
        echo -e " $FAIL $NAME: Chain missing from Kernel."
    fi
}

check_ipset "apiban" 50
check_ipset "abuseipdb" 1000

# ------------------------------------------------------------------------------
# 3. FAIL2BAN & REPORTING CHECK
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[3] FAIL2BAN & REPORTING CHECK${RESET}"

# Service Status
if systemctl is-active --quiet fail2ban; then
    echo -e " $PASS Fail2Ban Service is RUNNING."
else
    echo -e " $FAIL Fail2Ban Service is DEAD."
fi

# Socket Ping
if fail2ban-client ping | grep -q "pong"; then
    echo -e " $PASS Fail2Ban Socket is responsive."
else
    echo -e " $FAIL Fail2Ban Socket is unreachable."
fi

# Reporting Configuration Check
if grep -q "action_abuseipdb" /etc/fail2ban/jail.local; then
     echo -e " $PASS AbuseIPDB Reporting Logic found in config."
else
     echo -e " $FAIL AbuseIPDB Reporting Logic MISSING from jail.local."
fi

# API Key Check
API_KEY=$(grep "abuseipdb_apikey =" /etc/fail2ban/jail.local | cut -d= -f2 | tr -d ' ')
if [[ ${#API_KEY} -gt 10 ]]; then
    echo -e " $PASS Reporting API Key detected."
else
    echo -e " $FAIL Reporting API Key is missing or empty."
fi

# ------------------------------------------------------------------------------
# 4. WHITELIST INTEGRITY (Sync Check)
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[4] WHITELIST SYNC CHECK${RESET}"

VPBX_WL_COUNT=$(iptables -S vpbx_white_list 2>/dev/null | grep "\-s" | wc -l)
CUSTOM_WL_COUNT=$(iptables -S custom-whitelist 2>/dev/null | grep "\-s" | wc -l)

# We expect Custom to satisfy VitalPBX + 1 (Localhost)
EXPECTED=$((VPBX_WL_COUNT + 1))

if [ "$CUSTOM_WL_COUNT" -ge "$EXPECTED" ]; then
    echo -e " $PASS Whitelist Synced (VitalPBX: $VPBX_WL_COUNT | Active: $CUSTOM_WL_COUNT)"
else
    echo -e " $WARN Whitelist Mismatch! VitalPBX has $VPBX_WL_COUNT, Active has $CUSTOM_WL_COUNT."
    echo -e "       ${WARN} Run /usr/local/bin/whitelist-update.sh to re-sync."
fi

echo -e "\n=========================================================="
echo -e "${BOLD}AUDIT COMPLETE.${RESET}\n"

3. Save, then make it executable:

chmod +x /usr/local/bin/security-audit.sh

4. Run it:

/usr/local/bin/security-audit.sh
Example output from the VitalPBX security audit script

CIDR Range Blocking Upgrade

Block whole IP ranges in CIDR notation, such as 107.189.0.0/19, because the VitalPBX blacklist doesn't always work with CIDR blocks.

Version 1.0, compatible with VitalPBX Security Suite v2.0 and later.

The original suite uses hash:ip ipsets, which only support individual IP addresses. This upgrade adds a new manual_cidr ipset using hash:net, which supports CIDR notation natively, so you can block entire IP ranges.

What This Upgrade Adds

  • CIDR range blocking. Block entire subnets like 107.189.0.0/19 (8,192 IPs) with a single entry.
  • Management script. Simple add, remove, list and test commands.
  • Boot persistence. Survives reboots automatically.
  • Self-healing. Recovers after VitalPBX firewall reloads.
  • Dashboard integration. See blocked ranges in the Security Monitor.
  • Audit integration. The security audit recognises the new shield.

Why CIDR Support Is Needed

The default ipsets use the hash:ip type:

ipset list abuseipdb -t | grep "Type:"
# Output: Type: hash:ip

When you try to add a range like 107.189.0.0/19, it fails silently or only adds the base IP. The hash:net type is needed for CIDR support.

Installation

Step 1: Create the Manual Blacklist Script

nano /usr/local/bin/manual-blacklist.sh

Paste the following:

#!/bin/bash
# ==============================================================================
# Manual CIDR Blacklist Manager v1.0
# Supports individual IPs and CIDR ranges (e.g., 107.189.0.0/19)
# ==============================================================================

IPSET_NAME="manual_cidr"
BLACKLIST_FILE="/etc/security/manual-blacklist.txt"
WHITELIST_SCRIPT="/usr/local/bin/whitelist-update.sh"

# Ensure directory and file exist
mkdir -p /etc/security
touch "$BLACKLIST_FILE"

# Ensure ipset exists (hash:net for CIDR support)
if ! ipset list -n | grep -q "^${IPSET_NAME}$"; then
    ipset create $IPSET_NAME hash:net hashsize 4096 maxelem 65536
    echo "Created ipset: $IPSET_NAME"
fi

# Ensure iptables rule exists
ensure_iptables_rule() {
    if ! iptables -C INPUT -m set --match-set $IPSET_NAME src -j DROP 2>/dev/null; then
        # Insert at position 2 (after whitelist)
        iptables -I INPUT 2 -m set --match-set $IPSET_NAME src -j DROP
        echo "Added iptables rule for $IPSET_NAME"
    fi
    # Re-apply whitelist to maintain priority
    [ -x "$WHITELIST_SCRIPT" ] && $WHITELIST_SCRIPT >/dev/null 2>&1
}

# Validate IP/CIDR format
validate_entry() {
    local entry="$1"
    if [[ "$entry" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$ ]]; then
        return 0
    else
        echo "Invalid format: $entry"
        echo "Use: x.x.x.x or x.x.x.x/xx (e.g., 1.2.3.4 or 10.0.0.0/8)"
        return 1
    fi
}

case "$1" in
    add)
        if [ -z "$2" ]; then
            echo "Usage: $0 add <IP or CIDR>"
            echo "Examples:"
            echo "  $0 add 107.189.0.0/19"
            echo "  $0 add 45.155.205.100"
            exit 1
        fi
        validate_entry "$2" || exit 1
        ensure_iptables_rule
        
        if ipset test $IPSET_NAME "$2" 2>/dev/null; then
            echo "$2 is already in blacklist"
        else
            ipset add $IPSET_NAME "$2"
            echo "$2" >> "$BLACKLIST_FILE"
            sort -u "$BLACKLIST_FILE" -o "$BLACKLIST_FILE"
            echo "✓ Added $2 to blacklist"
        fi
        ;;
        
    remove|del)
        if [ -z "$2" ]; then
            echo "Usage: $0 remove <IP/CIDR>"
            exit 1
        fi
        if ipset del $IPSET_NAME "$2" 2>/dev/null; then
            sed -i "\|^${2}$|d" "$BLACKLIST_FILE"
            echo "✓ Removed $2 from blacklist"
        else
            echo "$2 was not in blacklist"
        fi
        ;;
        
    test|check)
        if [ -z "$2" ]; then
            echo "Usage: $0 test <IP>"
            exit 1
        fi
        if ipset test $IPSET_NAME "$2" 2>/dev/null; then
            echo "✓ $2 IS blocked by manual blacklist"
        else
            echo "✗ $2 is NOT in manual blacklist"
        fi
        ;;
        
    list)
        COUNT=$(ipset list $IPSET_NAME 2>/dev/null | grep -c "^[0-9]")
        echo "=== Manual Blacklist ($COUNT entries) ==="
        ipset list $IPSET_NAME | grep -E "^[0-9]" | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n
        ;;
        
    count)
        COUNT=$(ipset list $IPSET_NAME 2>/dev/null | grep -c "^[0-9]")
        echo "$COUNT entries in manual blacklist"
        ;;
        
    stats)
        echo "=== Manual Blacklist Statistics ==="
        COUNT=$(ipset list $IPSET_NAME 2>/dev/null | grep -c "^[0-9]")
        DROPS=$(iptables -L INPUT -v -n | grep "match-set ${IPSET_NAME}" | awk '{print $1}')
        echo "Entries: $COUNT"
        echo "Packets blocked: ${DROPS:-0}"
        ;;
        
    flush|clear)
        read -p "Are you sure you want to remove ALL entries? (y/N): " confirm
        if [[ "$confirm" =~ ^[Yy]$ ]]; then
            ipset flush $IPSET_NAME
            > "$BLACKLIST_FILE"
            echo "✓ Blacklist cleared"
        else
            echo "Cancelled"
        fi
        ;;
        
    import)
        # Re-import from file (used for boot persistence)
        ensure_iptables_rule
        if [ -f "$BLACKLIST_FILE" ] && [ -s "$BLACKLIST_FILE" ]; then
            IMPORTED=0
            while IFS= read -r entry || [ -n "$entry" ]; do
                entry=$(echo "$entry" | tr -d '[:space:]')
                if [ -n "$entry" ] && [[ ! "$entry" =~ ^# ]]; then
                    ipset add $IPSET_NAME "$entry" -exist 2>/dev/null && ((IMPORTED++))
                fi
            done < "$BLACKLIST_FILE"
            echo "✓ Imported $IMPORTED entries from $BLACKLIST_FILE"
        else
            echo "No entries to import"
        fi
        ;;
        
    export)
        echo "# Manual blacklist export - $(date)"
        ipset list $IPSET_NAME | grep -E "^[0-9]"
        ;;
        
    bulk)
        # Bulk add from file argument
        if [ -z "$2" ] || [ ! -f "$2" ]; then
            echo "Usage: $0 bulk <filename>"
            echo "File should contain one IP/CIDR per line"
            exit 1
        fi
        ensure_iptables_rule
        ADDED=0
        while IFS= read -r entry || [ -n "$entry" ]; do
            entry=$(echo "$entry" | tr -d '[:space:]')
            if [ -n "$entry" ] && [[ ! "$entry" =~ ^# ]]; then
                if ipset add $IPSET_NAME "$entry" -exist 2>/dev/null; then
                    echo "$entry" >> "$BLACKLIST_FILE"
                    ((ADDED++))
                fi
            fi
        done < "$2"
        sort -u "$BLACKLIST_FILE" -o "$BLACKLIST_FILE"
        echo "✓ Added $ADDED entries from $2"
        ;;
        
    *)
        echo "Manual CIDR Blacklist Manager"
        echo ""
        echo "Usage: $0 <command> [argument]"
        echo ""
        echo "Commands:"
        echo "  add <IP/CIDR>      Add IP or range to blacklist"
        echo "  remove <IP/CIDR>   Remove IP or range from blacklist"
        echo "  test <IP>          Check if IP is blocked"
        echo "  list               Show all blacklisted entries"
        echo "  count              Show number of entries"
        echo "  stats              Show entries and packets blocked"
        echo "  flush              Remove all entries (with confirmation)"
        echo "  import             Reload entries from file (boot recovery)"
        echo "  export             Output entries for backup"
        echo "  bulk <file>        Add multiple entries from file"
        echo ""
        echo "Examples:"
        echo "  $0 add 107.189.0.0/19"
        echo "  $0 add 45.155.205.100"
        echo "  $0 test 107.189.10.70"
        echo "  $0 remove 1.2.3.0/24"
        ;;
esac

Make it executable:

chmod +x /usr/local/bin/manual-blacklist.sh

Step 2: Add Boot Persistence to Crontab

crontab -e

Add this line after your existing @reboot entries:

@reboot sleep 35 && /usr/local/bin/manual-blacklist.sh import >/dev/null 2>&1

Your crontab should now look similar to this:

# Define the PATH so Cron can find 'iptables' and 'ipset'
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# 1. APIBan Updater (Self-Healing)
*/10 * * * * /usr/local/bin/apiban-update.sh >/dev/null 2>&1

# 2. AbuseIPDB Updater (Self-Healing)
0 */6 * * * /usr/local/bin/abuseipdb-update.sh >/dev/null 2>&1

# 3. Boot Safety (Persistence)
@reboot sleep 30 && /usr/local/bin/apiban-update.sh >/dev/null 2>&1
@reboot sleep 30 && /usr/local/bin/abuseipdb-update.sh >/dev/null 2>&1
@reboot sleep 35 && /usr/local/bin/manual-blacklist.sh import >/dev/null 2>&1

Step 3: Add Self-Healing to the APIBan Script

When VitalPBX reloads its firewall, for example when you click Apply Changes in the GUI, it wipes custom iptables rules. apiban-update.sh runs every 10 minutes, so it can restore the manual_cidr rule automatically.

Edit the script:

nano /usr/local/bin/apiban-update.sh

Add this block before the final whitelist-update.sh call:

# 6. Self-Healing for Manual CIDR Blacklist
MANUAL_SET="manual_cidr"
if ipset list -n | grep -q "^${MANUAL_SET}$"; then
    if ! iptables -C INPUT -m set --match-set $MANUAL_SET src -j DROP 2>/dev/null; then
        iptables -I INPUT 2 -m set --match-set $MANUAL_SET src -j DROP
    fi
fi

# 7. Self-Healing for Geo-Firewall
for set in $(ipset list -n | grep "blacklist_"); do
    if ! iptables -C INPUT -m set --match-set "$set" src -j DROP 2>/dev/null; then
        iptables -I INPUT -m set --match-set "$set" src -j DROP
    fi
done

# 8. Re-Apply Whitelist to ensure it stays on top
/usr/local/bin/whitelist-update.sh >/dev/null 2>&1

Step 4: Update the Security Audit Script

The audit script needs to recognise manual_cidr as a valid shield at Rule #2.

nano /usr/local/bin/security-audit.sh

Replace all of the code with:

#!/bin/bash

# ==============================================================================
# VITALPBX SECURITY AUDIT TOOL (v2 - Fixed Drop Detection)
# Verifies Firewall Order, Shield Health, Fail2Ban Config, and Reporting.
# ==============================================================================

# --- Colors ---
PASS=$(printf '\033[32m✔ PASS\033[0m')
FAIL=$(printf '\033[31m✖ FAIL\033[0m')
WARN=$(printf '\033[33m! WARN\033[0m')
BOLD=$(printf '\033[1m')
RESET=$(printf '\033[0m')

echo -e "\n${BOLD}🔒 STARTING SECURITY AUDIT...${RESET}"
echo "=========================================================="

# ------------------------------------------------------------------------------
# 1. FIREWALL HIERARCHY CHECK (The "Ironclad" Test)
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[1] FIREWALL HIERARCHY CHECK${RESET}"

# Get the first 3 input rules
RULES=$(iptables -L INPUT -n --line-numbers | head -n 5)

# Parse Rule 1
RULE_1_TARGET=$(echo "$RULES" | grep "^1" | awk '{print $2}')

# Parse Rule 2 (Capture Target AND Options to detect "match-set")
RULE_2_LINE=$(echo "$RULES" | grep "^2")
RULE_2_TARGET=$(echo "$RULE_2_LINE" | awk '{print $2}')

# Check Rule 1 (Must be Whitelist)
if [[ "$RULE_1_TARGET" == "custom-whitelist" ]]; then
    echo -e " $PASS Rule #1 is Golden Whitelist"
else
    echo -e " $FAIL Rule #1 is '$RULE_1_TARGET' (Expected: custom-whitelist)"
    echo -e "       ${WARN} Run /usr/local/bin/whitelist-update.sh to fix."
fi

# Check Rule 2 (Accepts shields: apiban, abuseipdb, manual_cidr, or geo-firewall)
if [[ "$RULE_2_TARGET" == "apiban" ]]; then
    echo -e " $PASS Rule #2 is Shield (APIBan Chain)"
elif [[ "$RULE_2_TARGET" == "DROP" ]] && echo "$RULE_2_LINE" | grep -q "abuseipdb"; then
    echo -e " $PASS Rule #2 is Shield (AbuseIPDB Direct Drop)"
elif [[ "$RULE_2_TARGET" == "DROP" ]] && echo "$RULE_2_LINE" | grep -q "manual_cidr"; then
    echo -e " $PASS Rule #2 is Shield (Manual CIDR Blacklist)"
elif [[ "$RULE_2_TARGET" == "DROP" ]] && echo "$RULE_2_LINE" | grep -q "blacklist_"; then
    echo -e " $PASS Rule #2 is Shield (Geo-Firewall)"
elif [[ "$RULE_2_TARGET" == "abuseipdb" ]]; then
    echo -e " $PASS Rule #2 is Shield (AbuseIPDB Chain)"
else
    echo -e " $FAIL Rule #2 is '$RULE_2_TARGET' (Expected: apiban, abuseipdb, manual_cidr, or blacklist_)"
    echo -e "       ${WARN} Run /usr/local/bin/apiban-update.sh to fix."
fi

# ------------------------------------------------------------------------------
# 2. BLOCKLIST CAPACITY CHECK (Are lists empty?)
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[2] BLOCKLIST CAPACITY CHECK${RESET}"

# Function to check ipset count
check_ipset() {
    NAME=$1
    EXPECTED=$2
    if ipset list -n | grep -q "^$NAME$"; then
        COUNT=$(ipset list $NAME -t | grep "Number of entries" | cut -d: -f2 | tr -d ' ')
        if [ "$COUNT" -gt "$EXPECTED" ]; then
             echo -e " $PASS $NAME: Active with ${BOLD}$COUNT${RESET} blocked IPs."
        else
             echo -e " $FAIL $NAME: Empty or too low ($COUNT entries)."
        fi
    else
        echo -e " $FAIL $NAME: Chain missing from Kernel."
    fi
}

check_ipset "apiban" 50
check_ipset "abuseipdb" 1000

# ------------------------------------------------------------------------------
# 3. FAIL2BAN & REPORTING CHECK
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[3] FAIL2BAN & REPORTING CHECK${RESET}"

# Service Status
if systemctl is-active --quiet fail2ban; then
    echo -e " $PASS Fail2Ban Service is RUNNING."
else
    echo -e " $FAIL Fail2Ban Service is DEAD."
fi

# Socket Ping
if fail2ban-client ping | grep -q "pong"; then
    echo -e " $PASS Fail2Ban Socket is responsive."
else
    echo -e " $FAIL Fail2Ban Socket is unreachable."
fi

# Reporting Configuration Check
if grep -q "action_abuseipdb" /etc/fail2ban/jail.local; then
     echo -e " $PASS AbuseIPDB Reporting Logic found in config."
else
     echo -e " $FAIL AbuseIPDB Reporting Logic MISSING from jail.local."
fi

# API Key Check
API_KEY=$(grep "abuseipdb_apikey =" /etc/fail2ban/jail.local | cut -d= -f2 | tr -d ' ')
if [[ ${#API_KEY} -gt 10 ]]; then
    echo -e " $PASS Reporting API Key detected."
else
    echo -e " $FAIL Reporting API Key is missing or empty."
fi

# ------------------------------------------------------------------------------
# 4. WHITELIST INTEGRITY (Sync Check)
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[4] WHITELIST SYNC CHECK${RESET}"

VPBX_WL_COUNT=$(iptables -S vpbx_white_list 2>/dev/null | grep "\-s" | wc -l)
CUSTOM_WL_COUNT=$(iptables -S custom-whitelist 2>/dev/null | grep "\-s" | wc -l)

# We expect Custom to satisfy VitalPBX + 1 (Localhost)
EXPECTED=$((VPBX_WL_COUNT + 1))

if [ "$CUSTOM_WL_COUNT" -ge "$EXPECTED" ]; then
    echo -e " $PASS Whitelist Synced (VitalPBX: $VPBX_WL_COUNT | Active: $CUSTOM_WL_COUNT)"
else
    echo -e " $WARN Whitelist Mismatch! VitalPBX has $VPBX_WL_COUNT, Active has $CUSTOM_WL_COUNT."
    echo -e "       ${WARN} Run /usr/local/bin/whitelist-update.sh to re-sync."
fi

# ------------------------------------------------------------------------------
# 5. GEO-FIREWALL RULE CHECK
# ------------------------------------------------------------------------------
echo -e "\n${BOLD}[5] GEO-FIREWALL RULE CHECK${RESET}"

GEO_SETS=$(ipset list -n | grep "blacklist_")
if [ -z "$GEO_SETS" ]; then
    echo -e " ${GREY}No Geo-Firewall sets detected.${RESET}"
else
    GEO_OK=0
    GEO_MISSING=0
    for set in $GEO_SETS; do
        if iptables -C INPUT -m set --match-set "$set" src -j DROP 2>/dev/null; then
            ((GEO_OK++))
        else
            ((GEO_MISSING++))
            echo -e " $FAIL Missing iptables rule for $set"
        fi
    done
    if [ "$GEO_MISSING" -eq 0 ]; then
        echo -e " $PASS All $GEO_OK Geo-Firewall rules active"
    else
        echo -e "       ${WARN} Run: for set in \$(ipset list -n | grep blacklist_); do iptables -I INPUT -m set --match-set \"\$set\" src -j DROP; done"
    fi
fi

echo -e "\n=========================================================="
echo -e "${BOLD}AUDIT COMPLETE.${RESET}\n"

Step 5: Update the Security Monitor Dashboard

Add the manual_cidr statistics to the dashboard.

nano /usr/local/bin/security-monitor

Find the DEFENSE METRICS section and update it to include:

MANUAL_DB=$(get_ipset_count "manual_cidr")
MANUAL_DROPS=$(get_drop_count "manual_cidr")

Then add this line after the AbuseIPDB printf:

printf " Manual CIDR          | ${WHITE}%-25s${RESET} | ${YELLOW}${BOLD}%-20s${RESET}\n" "$MANUAL_DB ranges" "$MANUAL_DROPS"

Usage Examples

Adding IP Ranges

# Block a /19 range (8,192 IPs)
manual-blacklist.sh add 107.189.0.0/19

# Block a /24 range (256 IPs)
manual-blacklist.sh add 45.155.205.0/24

# Block a single IP
manual-blacklist.sh add 107.54.1.123

Testing Whether an IP Is Blocked

# Test if a specific IP within a range is blocked
manual-blacklist.sh test 107.189.10.70
# Output: ✓ 107.189.10.70 IS blocked by manual blacklist

Viewing Blocked Entries

# List all entries
manual-blacklist.sh list

# Show count only
manual-blacklist.sh count

# Show statistics with packet counts
manual-blacklist.sh stats

Removing Entries

# Remove a specific range
manual-blacklist.sh remove 107.189.0.0/19

Bulk Import from a File

Create a file with one entry per line:

# /tmp/bad-ranges.txt
107.189.0.0/19
45.155.205.0/24
193.32.162.0/24
# Comments are ignored

Import it:

manual-blacklist.sh bulk /tmp/bad-ranges.txt

Firewall Hierarchy

After installation, your firewall rules are ordered like this:

PositionRulePurpose
1custom-whitelistTrusted IPs bypass all blocks
2manual_cidrYour custom CIDR ranges
3abuseipdbTop 10,000 worst IPs
4apibanActive VoIP attackers
5+vpbx_*VitalPBX native rules

Verify with:

iptables -L INPUT -n --line-numbers | head -n 10

Verification Commands

CommandDescription
ipset list manual_cidr -t | grep "Type:"Verify the ipset type is hash:net
manual-blacklist.sh test 107.189.10.70Test whether an IP is blocked
manual-blacklist.sh statsView the entry count and blocked packets
/usr/local/bin/security-audit.shRun the full security audit
watch -n 2 -c /usr/local/bin/security-monitorLive security dashboard

CIDR Quick Reference

CIDRIPs BlockedExample
/321Single host
/24256192.168.1.0/24
/198,192107.189.0.0/19
/1665,53610.0.0.0/16
/816,777,21610.0.0.0/8

Troubleshooting

Rule Disappears After VitalPBX GUI Changes?

This is expected. The self-healing in apiban-update.sh restores it within 10 minutes. To restore it immediately, run:

manual-blacklist.sh import

Security Audit Shows FAIL for Rule #2?

If manual_cidr is at position 2, update the audit script as described in Step 4. This is a false positive: the rule is working correctly.

Entries Not Surviving a Reboot?

Check that your crontab has the @reboot entry:

crontab -l | grep manual-blacklist

It should show:

@reboot sleep 35 && /usr/local/bin/manual-blacklist.sh import >/dev/null 2>&1

Whitelist Manager Upgrade

Whitelist a trusted IP or range in one command. It goes into the firewall at Rule #1 and into Fail2Ban's ignoreip, with no VitalPBX GUI reload needed.

Version 1.0, compatible with VitalPBX Security Suite v2.0 and later. Tested on VitalPBX 4.

The Golden Whitelist (Phase 2) is meant to copy the IPs from the VitalPBX GUI whitelist to Rule #1. In production we found three problems with this.

On VitalPBX 4 the Sync Imports Nothing

VitalPBX 4 stores the GUI whitelist in an ipset called vpbx_white_list. The chain of the same name holds one --match-set rule instead of one -s rule per IP. The Phase 2 script only looks for -s rules, so only your hardcoded IPs reach Rule #1. The audit still shows PASS because it counts the -s inside --match-set.

Using the GUI Wipes Your Shields

Adding an IP in the GUI means clicking Apply Changes. That wipes the custom whitelist and shields, and can reset jail.local.

The GUI Whitelist Doesn't Reach Fail2Ban

ignoreip in jail.local is maintained by hand. A trusted IP can still be banned, and reported to AbuseIPDB, if a device behind it fails authentication.

Check Whether Your Server Is Affected

iptables -S vpbx_white_list

If you see -A vpbx_white_list -m set --match-set vpbx_white_list src -j ACCEPT, your GUI whitelist has not been reaching Rule #1.

What This Upgrade Adds

  • One-command whitelisting. manual-whitelist.sh add 1.2.3.4 updates the firewall and Fail2Ban together.
  • VitalPBX 4 sync fix. GUI whitelist entries now reach Rule #1.
  • File-based whitelist. Entries live in /etc/security/*-whitelist.txt, with comments, so there's no GUI reload.
  • Bulk import. Paste a provider's published IP list, such as the Acrobits push servers, and import it in one go.
  • IP status check. See whether an IP is whitelisted, ignored by Fail2Ban, banned, or listed on a blocklist.
  • Safe Fail2Ban edits. jail.local is backed up and the config is tested before reload. If the test fails, the backup is restored.
  • Accurate audit. Section 4 checks that every whitelisted IP is actually present at Rule #1.

Installation

Step 1: Fix the VitalPBX 4 Whitelist Sync

Back up the script, then open it:

cp /usr/local/bin/whitelist-update.sh /root/whitelist-update.sh.orig
nano /usr/local/bin/whitelist-update.sh

In section 3. AUTO-SYNC, find this line:

    IP_LIST=$(iptables -S $VPBX_CHAIN | awk '$3 == "-s" {print $4}' | grep -v "0.0.0.0/0")

Replace it with this. It reads both the old -s format and the VitalPBX 4 ipset:

    IP_LIST=$( { iptables -S $VPBX_CHAIN | awk '$3 == "-s" {print $4}'; ipset list $VPBX_CHAIN 2>/dev/null | sed -n '/^Members:/,$p' | grep -Eo '^[0-9.]+(/[0-9]+)?'; } | grep -v "0.0.0.0/0" | sort -u )

Step 2: Add Whitelist File Support

In the same file, find the 2. Hardcoded Safety Nets section. Directly after your server IP line (iptables -A $CHAIN_NAME -s YOUR_SERVER_IP -j ACCEPT), add:

# 2b. MANUAL WHITELIST FILES (managed by manual-whitelist.sh)
for ip in $(grep -Eho '^[0-9.]+(/[0-9]+)?' /etc/security/*-whitelist.txt 2>/dev/null | sort -u); do
    iptables -A $CHAIN_NAME -s "$ip" -j ACCEPT
done

This loads every file in /etc/security/ whose name ends in -whitelist.txt. You can keep groups separate, for example manual-whitelist.txt and sipis-whitelist.txt. The manual blacklist file (manual-blacklist.txt) is not matched.

Save, then check the syntax and run it:

bash -n /usr/local/bin/whitelist-update.sh && echo "syntax OK"
/usr/local/bin/whitelist-update.sh
iptables -L custom-whitelist -n | grep -c ACCEPT

The count should now include your GUI whitelist entries. If the syntax check fails, restore the backup with cp /root/whitelist-update.sh.orig /usr/local/bin/whitelist-update.sh.

Step 3: Create the Whitelist Manager Script

nano /usr/local/bin/manual-whitelist.sh

Paste the following:

#!/bin/bash
# ==============================================================================
# Manual Whitelist Manager v1.0 (itproexpert.com)
# Adds trusted IPs / CIDR ranges to the Golden Whitelist (Rule #1)
# and to Fail2Ban ignoreip with a single command.
# ==============================================================================

WL_FILE="/etc/security/manual-whitelist.txt"
WL_GLOB="/etc/security/*-whitelist.txt"
JAIL="/etc/fail2ban/jail.local"
WHITELIST_SCRIPT="/usr/local/bin/whitelist-update.sh"
CHAIN_NAME="custom-whitelist"
F2B_RELOAD=0
BACKUP=""

mkdir -p /etc/security
touch "$WL_FILE"

# ------------------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------------------
valid() {
    local re='^[0-9]{1,3}(\.[0-9]{1,3}){3}(/[0-9]{1,2})?$'
    [[ "$1" =~ $re ]] && return 0
    echo "Invalid format: $1"
    echo "Use: x.x.x.x or x.x.x.x/xx (e.g. 1.2.3.4 or 10.0.0.0/24)"
    return 1
}

esc()         { printf '%s' "$1" | sed 's/\./\\./g'; }
norm()        { [[ "$1" == */* ]] && echo "$1" || echo "$1/32"; }
in_files()    { grep -El "^$(esc "$1")([[:space:]]|$)" $WL_GLOB 2>/dev/null; }
all_entries() { grep -Eho '^[0-9.]+(/[0-9]+)?' $WL_GLOB 2>/dev/null | sort -u; }
f2b_tokens()  { grep -m1 '^ignoreip' "$JAIL" | cut -d= -f2- | tr -s ' \t' '\n' | sed '/^$/d'; }
fw_has()      { iptables -S "$CHAIN_NAME" 2>/dev/null | grep -Fq -- "-s $(norm "$1") "; }

backup_jail() {
    [ -n "$BACKUP" ] && return 0
    BACKUP="/root/jail.local.bak-$(date +%F-%H%M%S)"
    cp "$JAIL" "$BACKUP"
}

f2b_add() {
    [ $# -gt 0 ] || return 0
    if ! grep -q '^ignoreip' "$JAIL" 2>/dev/null; then
        echo "[!!] No ignoreip line in $JAIL - add these manually: $*"
        return 1
    fi
    local TOKENS ADD="" e
    TOKENS=$(f2b_tokens)
    for e in "$@"; do grep -Fxq -- "$e" <<< "$TOKENS" || ADD+=" $e"; done
    if [ -z "$ADD" ]; then
        echo "[OK] Fail2Ban ignoreip already contains these entries"
        return 0
    fi
    backup_jail
    sed -i "0,/^ignoreip *=/ s|^ignoreip *=.*|&$ADD|" "$JAIL"
    echo "[OK] Added to Fail2Ban ignoreip:$ADD"
    F2B_RELOAD=1
}

f2b_remove() {
    grep -Fxq -- "$1" <<< "$(f2b_tokens)" || return 0
    backup_jail
    local E
    E=$(esc "$1")
    for _ in 1 2 3 4 5; do
        grep -Eq "^ignoreip *=.* ${E}( |$)" "$JAIL" || break
        sed -i -E "/^ignoreip *=/ s#( )${E}( |$)#\2#" "$JAIL"
    done
    echo "[OK] Removed $1 from Fail2Ban ignoreip"
    F2B_RELOAD=1
}

f2b_apply() {
    [ "$F2B_RELOAD" = 1 ] || return 0
    if fail2ban-client --help 2>&1 | grep -q -- '--test' && ! fail2ban-client -t >/dev/null 2>&1; then
        echo "[!!] Fail2Ban config test failed - restoring $BACKUP"
        cp "$BACKUP" "$JAIL"
        return 1
    fi
    if fail2ban-client reload >/dev/null 2>&1; then
        echo "[OK] Fail2Ban reloaded"
    else
        systemctl restart fail2ban
        sleep 10
        if fail2ban-client ping >/dev/null 2>&1; then
            echo "[OK] Fail2Ban restarted"
        else
            echo "[!!] Fail2Ban not responding - check: journalctl -u fail2ban -n 30"
        fi
    fi
}

fw_apply() {
    "$WHITELIST_SCRIPT" >/dev/null 2>&1
    if [ "$(iptables -S INPUT | sed -n '2p')" = "-A INPUT -j $CHAIN_NAME" ]; then
        echo "[OK] Golden Whitelist rebuilt at Rule #1 ($(iptables -S "$CHAIN_NAME" | grep -c ' -j ACCEPT') entries)"
    else
        echo "[!!] $CHAIN_NAME is not Rule #1 - run $WHITELIST_SCRIPT and check the firewall order"
    fi
}

# ------------------------------------------------------------------------------
# Commands
# ------------------------------------------------------------------------------
case "$1" in
    add)
        [ -n "$2" ] || { echo "Usage: $0 add <IP/CIDR> [\"comment\"]"; exit 1; }
        valid "$2" || exit 1
        FOUND=$(in_files "$2")
        if [ -n "$FOUND" ]; then
            echo "[OK] $2 is already in: $(echo $FOUND)"
        else
            echo "$2${3:+ # $3}" >> "$WL_FILE"
            echo "[OK] Added $2 to $WL_FILE"
        fi
        f2b_add "$2"
        f2b_apply
        fail2ban-client unban "$2" >/dev/null 2>&1
        fw_apply
        ;;

    remove|del)
        [ -n "$2" ] || { echo "Usage: $0 remove <IP/CIDR>"; exit 1; }
        valid "$2" || exit 1
        FOUND=$(in_files "$2")
        if [ -z "$FOUND" ]; then
            echo "$2 is not in any whitelist file (if it is in the VitalPBX GUI whitelist, remove it there)"
            exit 0
        fi
        for f in $FOUND; do
            sed -i -E "\#^$(esc "$2")([[:space:]]|$)#d" "$f"
            echo "[OK] Removed $2 from $f"
        done
        f2b_remove "$2"
        f2b_apply
        fw_apply
        ;;

    test|check)
        [ -n "$2" ] || { echo "Usage: $0 test <IP/CIDR>"; exit 1; }
        valid "$2" || exit 1
        FOUND=$(in_files "$2")
        if [ -n "$FOUND" ]; then echo "[OK] Whitelist file : $(echo $FOUND)"; else echo "[--] Whitelist file : not listed"; fi
        if fw_has "$2"; then echo "[OK] Firewall       : ACCEPT at Rule #1"; else echo "[--] Firewall       : not in $CHAIN_NAME"; fi
        if grep -Fxq -- "$2" <<< "$(f2b_tokens)"; then echo "[OK] Fail2Ban       : in ignoreip"; else echo "[--] Fail2Ban       : not in ignoreip"; fi
        if [[ "$2" != */* ]]; then
            HITS=""
            for s in $(ipset list -n 2>/dev/null); do
                ipset test "$s" "$2" >/dev/null 2>&1 && HITS+=" $s"
            done
            if [ -n "$HITS" ]; then echo "[!!] Found in ipsets:$HITS"; else echo "[OK] Not in any ipset"; fi
            for j in $(fail2ban-client status 2>/dev/null | sed -n 's/.*Jail list://p' | tr -d ','); do
                fail2ban-client status "$j" | grep -Fqw -- "$2" && echo "[!!] Currently banned in Fail2Ban jail: $j"
            done
        fi
        ;;

    list)
        for f in $WL_GLOB; do
            [ -f "$f" ] || continue
            echo "=== $f ($(grep -Ec '^[0-9]' "$f") entries) ==="
            grep -E '^[0-9]' "$f"
        done
        ;;

    bulk)
        if [ -z "$2" ] || [ ! -f "$2" ]; then
            echo "Usage: $0 bulk <file> [\"comment\"]"
            exit 1
        fi
        ADDED=0
        NEW=()
        while IFS= read -r line || [ -n "$line" ]; do
            first=$(awk '{print $1}' <<< "$line")
            [[ "$first" == *:* ]] && continue      # skip IPv6 addresses
            e=$(grep -Eo '^[0-9.]+(/[0-9]+)?' <<< "$first")
            [ -z "$e" ] && continue                # skip headers, comments, blank lines
            valid "$e" >/dev/null || { echo "Skipped invalid: $line"; continue; }
            if [ -z "$(in_files "$e")" ]; then
                echo "$e${3:+ # $3}" >> "$WL_FILE"
                ADDED=$((ADDED+1))
            fi
            NEW+=("$e")
        done < "$2"
        echo "[OK] $ADDED new entries added to $WL_FILE"
        if [ ${#NEW[@]} -gt 0 ]; then
            mapfile -t NEW < <(printf '%s\n' "${NEW[@]}" | sort -u)
            f2b_add "${NEW[@]}"
            f2b_apply
            fail2ban-client unban "${NEW[@]}" >/dev/null 2>&1
        fi
        fw_apply
        ;;

    sync)
        mapfile -t ALL < <(all_entries)
        f2b_add "${ALL[@]}"
        f2b_apply
        fw_apply
        echo "[OK] ${#ALL[@]} whitelist file entries checked"
        ;;

    *)
        echo "Manual Whitelist Manager"
        echo ""
        echo "Usage: $0 <command> [argument]"
        echo ""
        echo "Commands:"
        echo "  add <IP/CIDR> [\"comment\"]   Whitelist an IP or range (firewall + Fail2Ban)"
        echo "  remove <IP/CIDR>            Remove an IP or range"
        echo "  test <IP>                   Show whitelist, Fail2Ban and blocklist status"
        echo "  list                        Show all whitelist file entries"
        echo "  bulk <file> [\"comment\"]     Whitelist every IPv4 address in a file"
        echo "  sync                        Re-apply everything (e.g. after a GUI change)"
        echo ""
        echo "Examples:"
        echo "  $0 add 203.0.113.10 \"Office static IP\""
        echo "  $0 add 198.51.100.0/24 \"SIP trunk provider\""
        echo "  $0 test 203.0.113.10"
        echo "  $0 remove 203.0.113.10"
        ;;
esac

Make it executable:

chmod +x /usr/local/bin/manual-whitelist.sh

Step 4: Fix the Security Audit Whitelist Check

The original Section 4 of the audit counts -s rules, which isn't meaningful on VitalPBX 4. This version checks that every IP in the GUI whitelist and the whitelist files is actually active at Rule #1.

nano /usr/local/bin/security-audit.sh

Replace everything in the 4. WHITELIST INTEGRITY (Sync Check) section, from the [4] WHITELIST SYNC CHECK echo down to its closing fi, with:

echo -e "\n${BOLD}[4] WHITELIST SYNC CHECK${RESET}"

EXPECTED_LIST=$( { iptables -S vpbx_white_list 2>/dev/null | awk '$3 == "-s" {print $4}'; ipset list vpbx_white_list 2>/dev/null | sed -n '/^Members:/,$p' | grep -Eo '^[0-9.]+(/[0-9]+)?'; grep -Eho '^[0-9.]+(/[0-9]+)?' /etc/security/*-whitelist.txt 2>/dev/null; } | grep -v "0.0.0.0/0" | sort -u )
ACTIVE_RULES=$(iptables -S custom-whitelist 2>/dev/null)
WL_TOTAL=0; WL_MISSING=0

for ip in $EXPECTED_LIST; do
    WL_TOTAL=$((WL_TOTAL+1))
    [[ "$ip" == */* ]] || ip="$ip/32"
    grep -Fq -- "-s $ip " <<< "$ACTIVE_RULES" || WL_MISSING=$((WL_MISSING+1))
done

if [ "$WL_MISSING" -eq 0 ]; then
    echo -e " $PASS Whitelist Synced ($WL_TOTAL entries active at Rule #1)"
else
    echo -e " $WARN Whitelist Mismatch! $WL_MISSING of $WL_TOTAL whitelisted entries missing from Rule #1."
    echo -e "       ${WARN} Run /usr/local/bin/whitelist-update.sh to re-sync."
fi

Step 5: Verify

/usr/local/bin/manual-whitelist.sh sync
/usr/local/bin/security-audit.sh

Expected output:

  • sync shows [OK] Golden Whitelist rebuilt at Rule #1.
  • The audit shows Rule #1 is Golden Whitelist and Whitelist Synced (N entries active at Rule #1).

Usage Examples

Whitelisting an IP or Range

# Single IP with a note
manual-whitelist.sh add 203.0.113.10 "Office static IP"

# Whole range (use the network address, e.g. .0/24 not .5/24)
manual-whitelist.sh add 198.51.100.0/24 "SIP trunk provider"

Each add does four things:

  1. Saves the entry to /etc/security/manual-whitelist.txt.
  2. Adds it to Fail2Ban ignoreip, backing up jail.local first.
  3. Reloads Fail2Ban and lifts any existing ban.
  4. Rebuilds the Golden Whitelist at Rule #1.

Checking an IP

manual-whitelist.sh test 203.0.113.10

Example output for an IP that is being blocked:

[--] Whitelist file : not listed
[--] Firewall       : not in custom-whitelist
[--] Fail2Ban       : not in ignoreip
[!!] Found in ipsets: voipbl

Any IP showing [OK] Firewall : ACCEPT at Rule #1 bypasses every blocklist below it, including AbuseIPDB, APIBan, voipbl and Geo-Firewall.

Listing and Removing

# Show all whitelist files and entries
manual-whitelist.sh list

# Remove an entry (firewall + Fail2Ban)
manual-whitelist.sh remove 203.0.113.10

remove only affects IPs in the whitelist files. If the IP is also in the VitalPBX GUI whitelist, remove it there as well, or it will stay at Rule #1.

Bulk Import

Paste a list into a file, one IP per line. Header lines, blank lines, comments and IPv6 addresses are skipped automatically.

nano /tmp/new-ips.txt
manual-whitelist.sh bulk /tmp/new-ips.txt "Provider name"

After Any VitalPBX GUI Firewall Change

If you do use the GUI whitelist or click Apply Changes, restore everything with:

/usr/local/bin/whitelist-update.sh && /usr/local/bin/apiban-update.sh
/usr/local/bin/manual-whitelist.sh sync

sync re-adds any file-managed IPs missing from Fail2Ban ignoreip, which covers the case where a GUI change reset jail.local. If your AbuseIPDB settings were also lost, follow Troubleshooting, Section F above.

Real-World Example: Groundwire Push Notifications Not Arriving

Symptom: Acrobits Groundwire (or Acrobits Softphone and Cloud Softphone) rings when the app is open, but calls never arrive when it's in the background. Some users are affected and others aren't.

Cause: In the background, the phone doesn't register directly. Acrobits' push servers (SIPIS) register to your PBX on the phone's behalf and send the push when a call arrives. Each SIPIS server handles thousands of users, so these IPs regularly get reported to public VoIP blacklists. On our server, two SIPIS IPs were listed in voipbl, the VoIP blacklist VitalPBX ships and applies by default. Every user assigned to those two servers silently lost push calls.

Check

manual-whitelist.sh test 165.227.190.186

Fix

  1. Copy the current IPv4 SIPIS list from the Acrobits allowlist page. The list changes over time, so always use the live page.

  2. Import it:

    nano /tmp/acrobits-sipis.txt        # paste the IP list
    manual-whitelist.sh bulk /tmp/acrobits-sipis.txt "Acrobits SIPIS"
  3. Ask the user to turn their account off and on in the app, then confirm traffic is being accepted:

    iptables -L custom-whitelist -n -v | grep 165.227.190.186

    A packet counter above 0 means the push server is getting through.

Also check the Asterisk log for the user whose credentials are failing through the SIPIS IP. Whitelisting stops the ban, but it won't fix a wrong password in the app.

Troubleshooting

Failed to access socket path After a Fail2Ban Change?

Fail2Ban can take a few seconds to start. Wait 10 seconds and run fail2ban-client ping. If there's still no pong, follow Troubleshooting, Section B, step 2 above.

[!!] Fail2Ban config test failed?

The script has already restored the previous jail.local. Check the ignoreip line with grep '^ignoreip' /etc/fail2ban/jail.local. Backups are kept in /root/jail.local.bak-*.

Audit Shows Whitelist Mismatch for a CIDR Entry?

iptables stores ranges by network address. An entry like 10.0.0.5/24 becomes 10.0.0.0/24 and won't match. Remove it and add it again using the network address.

Removed an IP but It's Still Accepted?

It's also in the VitalPBX GUI whitelist or another *-whitelist.txt file. Run manual-whitelist.sh test <IP> to see where.

Verification Commands

CommandDescription
manual-whitelist.sh test 1.2.3.4Full whitelist, Fail2Ban and blocklist status
manual-whitelist.sh listShow all file-managed whitelist entries
manual-whitelist.sh syncRe-apply the firewall and Fail2Ban whitelist
iptables -L custom-whitelist -n -vLive Rule #1 entries with packet counters
grep '^ignoreip' /etc/fail2ban/jail.localView the Fail2Ban ignore list
/usr/local/bin/security-audit.shRun the full security audit

Feedback on the Security Suite?

If something behaves differently on your server, let us know.

Get in touch