【Fail2ban 進階實戰】效能與通知雙重升級:導入 ipset 與 Telegram 靜音防轟炸設定

在上一篇 [【Fail2ban 進階實戰】設定 recidive 累犯監獄,將惡意 IP 永久打入冷宮] 中,我們成功建立了一套能自動揪出死不悔改的攻擊者,並給予永久封鎖的防禦機制。

但隨著伺服器穩定運作,你可能會遇到兩個「甜蜜的煩惱」:

  1. 效能隱憂recidive 監獄累積了數十甚至數百個 IP,傳統的 iptables 一次寫入一條規則,當名單越來越長,防火牆比對封包的負擔也會隨之增加。
  2. 重啟轟炸:每次重開機或重啟 Fail2ban 服務時,系統會從資料庫把舊的黑名單重新載入,導致連動的 Telegram 機器人瞬間連發數十上百條「封鎖通知」,讓人不堪其擾。

這篇進階教學將帶你透過三個步驟,徹底解決這些痛點,讓你的伺服器防禦系統不僅強大,而且更輕量、更安靜!

步驟一:導入 ipset,將 O(n) 線性比對升級為 O(1) 哈希查詢

預設的 Fail2ban 會為每一個被封鎖的 IP 建立一條 iptables 規則。當封包進入伺服器時,系統必須由上而下逐一比對(這就是 O(n) 的線性搜尋)。當黑名單累積到上千筆時,會白白消耗 CPU 資源。

解決方案是改用 ipset。它會在核心建立一個哈希集合(Hash Table),不管集合裡有 10 個還是 10,000 個 IP,防火牆都只需要比對一次(O(1) 複雜度),效能極高。

1. 安裝 ipset

首先,確保你的 Ubuntu 伺服器已經安裝了該工具:

sudo apt update && sudo apt install ipset -y

2. 修改 recidive 監獄設定

開啟你的 /etc/fail2ban/jail.local,找到 [recidive] 區段,將 banaction 改為支援 ipset 的版本:

[recidive]
enabled = true
# 啟用 ipset 並封鎖所有通訊埠 (IPv4 & IPv6)
banaction = iptables-ipset-proto6-allports

# 直接呼叫 banaction 與自定義的 telegram 動作,並傳入監獄名稱參數
action = %(banaction)s
         telegram[name=recidive]

備註:這裡我們使用了 telegram[name=recidive],將監獄名稱當作參數傳遞給下一步的腳本,這對後續的客製化訊息非常重要。

步驟二:防重啟轟炸與動態訊息 (Telegram 腳本魔改)

接下來我們要解決「重開機就被 Telegram 洗版」的問題。

核心邏輯是:利用「時間差」來判斷。 當 Fail2ban 重啟時載入的舊 IP,其發生攻擊的時間(<time>)會是好幾個小時或幾天前;而真正新鮮的攻擊,時間差一定在幾秒鐘之內。

同時,我們也可以利用剛才傳入的 <name> 參數,讓 recidive(永久封鎖)和一般監獄(如 sshd)發出不同側重點的警報訊息。

修改 Telegram Action 設定

開啟你的 Telegram 動作設定檔(例如 /etc/fail2ban/action.d/telegram.conf),將 actionban 的區段修改如下:

[Definition]

actionban = now=$(date +%%s) \
t_int=$(echo "" | cut -d. -f1) \
diff=$(($now - $t_int)) \
if [ "$diff" -le 60 ]; then \
if [ "" = "recidive" ]; then \
MSG="[Fail2ban 永久封鎖]%%0A監獄:%%0A目標:%%0A結果:該 IP 已多次累犯,執行永久禁封"; \
else \
MSG="[Fail2ban 警報]%%0A監獄:%%0A目標:%%0A次數:第 次嘗試"; \
fi; \
curl -s -X POST "https://api.telegram.org/bot/sendMessage" \
-d chat_id= \
-d text="$MSG" -d parse_mode="HTML"; \
fi

(行尾的 \ 是續行符號,確保這些判斷式被視為同一道指令執行,後面絕對不能有空格。)

步驟三:關鍵程式碼解析 (為什麼要用 cut?)

在上面的腳本中,有一行非常關鍵的處理: t_int=$(echo "<time>" | cut -d. -f1)

為什麼需要這行? <time> 是 Fail2ban 傳遞給腳本的變數,代表攻擊發生的時間戳記(Timestamp)。但在某些 Fail2ban 版本中,這個時間戳會帶有小數點(包含毫秒),例如 1714521600.123

問題在於,Linux Shell 內建的數學運算 $(()) 不支援浮點數(小數)計算。如果不先處理,系統就會報錯導致通知發送失敗。

因此,我們引入了 cut 這個強大的文字處理工具:

  • echo "<time>":將帶有小數點的時間字串印出。
  • | (Pipe):將印出的字串丟給下一個指令處理。
  • cut -d.:告訴系統以「小數點 (.)」作為分隔符號(Delimiter)。
  • -f1:取得分隔後的第一個欄位(Field 1),也就是小數點前面的「整數」部分。

處理過後,純整數的 $t_int 就能順利與當前時間 $now 進行相減。只要時間差 ($diff) 小於等於 60 秒,系統才會判定這是「即時的新封鎖」並發送通知,完美過濾掉重啟時載入的舊資料!

驗證與總結

設定完成後,重新啟動 Fail2ban:

sudo systemctl restart fail2ban

你可以觀察到兩個明顯的改變:

  1. 你的 Telegram 靜悄悄的,再也不會因為重啟服務而遭受數十條訊息轟炸。
  2. 在終端機輸入 sudo ipset list,你會看到系統已經自動建立了一個名為 f2b-recidive 的集合,並且悄悄地將所有的惡意 IP 都關進了這個高效能的牢籠中。而在輸入 sudo iptables -L -n 時,原本落落長的規則也變得乾淨俐落。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料