2026 Update – New Feature – Manual CIDR Blocking

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 if you modify a few items slightly.
Once completed make sure it works for 24 hours, after a reboot and that the firewall rules table is in the correct order.

IMPORTANT: This is our beta release, everything worked on perfectly on our test system but make sure you have a full backup before starting.
You should have some Linux knowledge to do this. If you have any feedback, please email us but we assume no risk for this install.

🛡️ VitalPBX/Asterisk Security Suite: Master Installation Guide

Version: 2.0 (Self-Healing + Auto-Sync)

Target OS: Debian/VitalPBX

Role: Protect SIP/SSH from brute force and scanners using AbuseIPDB and APIBan.

⚠️ Prerequisites

Before starting, ensure you have:

  1. Root Access via SSH.
  2. AbuseIPDB API Key (Free from abuseipdb.com).
  3. APIBan API Key (Free from apiban.org).

Phase 1: System Preparation

First, install the required tools for managing IP lists, persistent firewalls, and downloading data.

Bash

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

(If prompted to save current IPv4 rules during install, select YES).


Phase 2: The “Golden Whitelist” (Priority System)

This script reads IPs from the VitalPBX Web Interface (Admin > Firewall > Whitelist) and forces them to the very top of the firewall (Rule #1), bypassing all blocklists. This prevents you from locking yourself out.

1. Create the Script:

Bash

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

2. Paste the Code:

Bash

#!/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 Executable:

Bash

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:

Bash

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

2. Paste the Code (Update YOUR_APIBAN_KEY):

Bash

#!/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 Executable:

Bash

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

Phase 4: AbuseIPDB General Shield (Inbound Block)

Blocks the top 10,000 worst IPs (confidence 100).

1. Create the Script:

Bash

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

2. Paste the Code (Update YOUR_ABUSEIPDB_KEY):

Bash

#!/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 Executable:

Bash

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

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

Phase 5: Fail2Ban (Outbound Reporting)

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

1. Edit Config:

Bash

nano /etc/fail2ban/jail.local

2. Paste Config (Update YOUR_ABUSEIPDB_KEY):

Ini, TOML
Note: Check the IGNOREIP = list and modify to your local network

[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:

Bash

touch /var/log/fail2ban.log

systemctl restart fail2ban

Phase 6: Automation (Cron)

Ensures scripts run on schedule and firewall restores on reboot.

1. Edit Crontab:

Bash

crontab -e

2. Add these lines at the bottom:

Bash

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 to visualize blocked packets and status.

1. Create Script:

Bash

nano /usr/local/bin/security-monitor

2. Paste Code:

Bash

#!/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 Executable:

Bash

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

Phase 8: Persistence & Verification

1. Save initial configuration:

Bash

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

2. Verify everything is working:

Run the dashboard to see live statistics.

Bash

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

3. Testing:

  • Check IP in AbuseIPDB: ipset test abuseipdb 1.2.3.4
  • Check IP in APIBan: iptables -L apiban -n | grep 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 Dashboard.
ipset list abuseipdb | head -n 10View top AbuseIPDB entries.
ipset list apiban | head -n 10View 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.sh
/usr/local/bin/abuseipdb-update.sh
Manually Update Block Lists
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


To Disable Temporally

Edit Crontab

crontab -e

Change the following lines by adding a # at the start

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

Remove Blocking Rules

iptables -D INPUT -j apiban
iptables -D INPUT -m set --match-set abuseipdb src -j DROP

The options below are full a total flush which is not required normally – only for troubleshooting

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

RESTORE CHANGES

Edit the Crontab and remove the # from the two entries edited above.

Manual Update

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

TROUBLESHOOTING


Section A:

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

Section B:

1. Fix Native Firewall (If vpbx_ chains are missing)

This forces VitalPBX to rebuild the standard firewall structure.

Bash

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

# Force apply the rules
vitalpbx apply-firewall
2. Fix Fail2Ban (If getting “Socket” or “Log file” errors)

This fixes the crash caused by the firewall restart.

Bash

# 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 Whitelist/APIBan disappeared)

This puts your protection back on top (Rule #1 & #2) after the firewall reload wiped them.

Bash

# 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:

Bash

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

Look for: custom-whitelist (1st), apiban (2nd), and vpbx_ chains (lower down).


Section C:

🆘 Emergency Restore Command If your firewall crashes, Fail2Ban fails to start, or you accidentally wiped your rules via the GUI, run this one-liner to fix the entire stack:

Bash

touch /var/log/fail2ban.log && systemctl restart fail2ban && /usr/local

Section D:

If you AbuseIPDB is not getting reports from your server (check by logging into their website) – any blocks coming from brute force to Asterisk or SSH etc should be on the log and the public IP search.
The issue would be that the Fail2Ban Jail is setup incorrectly or has been over written.

1. Fix Your jail.local

Open the file:

nano /etc/fail2ban/jail.local

Check the contents of this file against the installation file (from Phase 5 section 2) – it should have the AbuseIPDB API key and each entry (asterisk,ssh) should ‘action’ a report to AduseIPDB.

Correct anything that is missing and then save.

Now restart fail2ban.

systemctl restart fail2ban

You can also verify its working in the log but wait till an event happens.

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

Section E:

You might notice that the Fail2Ban numbers are very low (only 1 ban). This is actually a good thing.

Because you have APIBan and AbuseIPDB at the front door (Rules #2), you are blocking the 100,000 known hackers before they can even touch Asterisk to guess a password.

  • Without APIBan: Your server logs would be screaming with thousands of failed logins, and Fail2Ban would be working overtime.
  • With APIBan: The “Noise” is blocked silently at the firewall. Fail2Ban is now just sitting comfortably in the back, 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 this command:

Bash

fail2ban-client status

This will list all the “Jails” (e.g., asterisk-udp, asterisk-tcp, sshd, vitalpbx-gui).
Like this below:

# 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 deep inside the Asterisk UDP jail (the most common attack vector), run:

Bash

fail2ban-client status asterisk-udp

What to look for:

  • Status: Should say Currently failed: 0 (or a small number).
  • File list: It should show the log file it is watching (usually /var/log/asterisk/security or /var/log/asterisk/full).
  • Banned IP list: Any IPs listed here are currently in the “Penalty Box” for guessing passwords.

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


To be 100% sure your system is healthy and the “Ironclad Order” is correct, run these three checks.

If these three outputs match the examples below, your security is perfect.

Check 1: The Firewall Order (Most Critical)

Run this command to see the top 10 rules. This determines who gets in and who gets dropped.

Bash

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+: vpbx_... or fail2ban (The standard VitalPBX rules must be below your shields).

❌ dangerous Example: If vpbx_firewall is at Line 1 or 2, and custom-whitelist is missing or at the bottom, VitalPBX has wiped your rules. Run your restore scripts immediately.


Check 2: The Blocklist Capacity

Verify that your “buckets” of banned IPs are actually full.

Bash

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: > 0 (Usually 200-500)
  • abuseipdb: 10,000 (Full)
  • vpbx_...: (Should show numbers for country blocks if you enabled them).

Check 3: The “Ping” Test

Ensure the dynamic guard (Fail2Ban) is awake and listening.

Bash

fail2ban-client ping

✅ What you MUST see:

  • Server replied: pong

🟢 Summary
  1. Whitelist is Top? Yes.
  2. Lists are Full? Yes.
  3. Fail2Ban is PONGing? Yes.

If all three represent “Yes”, your system is healthy, secure, and ready for production.


Security Audit Tool

This tool will check all firewalls, updates and block lists are all working – automatically.

Install the script:

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

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"

Save and then chmod

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

Run

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

2026 Update – New Feature – Manual CIDR Blocking


🛡️ VitalPBX Security Suite: CIDR Range Blocking Upgrade

Version: 1.0
Compatibility: VitalPBX Security Suite v2.0+
Purpose: Add support for blocking IP ranges (CIDR notation) like 107.189.0.0/19 because the current vitalpbx black list doesn’t always work with CIDR blocks.


Overview

The original VitalPBX Security Suite uses hash:ip ipsets which only support individual IP addresses. This upgrade adds a new manual_cidr ipset using hash:net which natively supports CIDR notation, allowing you to 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 – Easy add/remove/list/test commands
  • Boot Persistence – Survives reboots automatically
  • Self-Healing – Recovers after VitalPBX firewall reloads
  • Dashboard Integration – See blocked ranges in Security Monitor
  • Audit Integration – Security audit recognizes the new shield

Why CIDR Support is Needed

The default ipsets use 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 required 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:

# 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 APIBan Script

When VitalPBX reloads its firewall (e.g., clicking “Apply Changes” in the GUI), it wipes custom iptables rules. The apiban-update.sh script runs every 10 minutes and 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 Security Audit Script

The audit script needs to recognize 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 Security Monitor Dashboard

Add the manual_cidr statistics to the dashboard display.

nano /usr/local/bin/security-monitor

Find the DEFENSE METRICS section and update to include:

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

And 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 if 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 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:

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

Firewall Hierarchy

After installation, your firewall rules will be ordered:

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 ipset type is hash:net
manual-blacklist.sh test 107.189.10.70Test if IP is blocked
manual-blacklist.sh statsView entry count and blocked packets
/usr/local/bin/security-audit.shRun 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 will restore it within 10 minutes. For immediate restoration:
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 reboot?

Check crontab has the @reboot entry:

crontab -l | grep manual-blacklist

Should show:

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

This upgrade is compatible with VitalPBX Security Suite v2.0

Similar Posts

Leave a Reply