RT2600acは標準で指定時間中に指定回数OpenVPNへログイン失敗したipがあればそれをBANしてくれますし、モバイル通知を設定しておけばそれを通知してくれます。しかしtls-cryptを設定しているので、意図しないログイン試行が発生した時点で異常事態が起きています。だからといって一回でも失敗したらipBAN設定までいくと今度は実用上の問題が生じるため、標準機能の複数回失敗時のIPBANとは別に、一回でも失敗したら通知するスクリプトを組みました。
同時に意図しない接続車がいれば気付くため、ログイン成功時にも通知するスクリプトを作成しました。以下はその覚え書きです。通知方法はDiscordを使用しています。
まずsshで接続してopenvpn.confに以下を追加します。
script-security 2 #スクリプトの実行を許可するclient-connect /volume1/openvpn/vpn_connect_notify.sh #認証成功後に実行してル通知するスクリプトlog-append /var/log/openvpn.log #ログはデフォルトではコメントアウトされているので有効化する
次に/volume1/openvpnフォルダを作成して、VPNに接続したデバイスがあれば、通知するスクリプトvpn_connect_notify.shを作成します。
#!/bin/sh
WEBHOOK_URL="https://discord.com/api/webhooks/XXX"
CLIENT_IP="${untrusted_ip:-$untrusted_ip6}"
USERNAME="$common_name"
VPN_IP="$ifconfig_pool_remote_ip"
HOSTNAME="$(hostname)"
DATE="$(date)"
MESSAGE="\
VPN Connected
User: $USERNAME
Source IP: $CLIENT_IP
VPN IP: $VPN_IP
Time: $DATE
"
JSON_SAFE_MESSAGE=$(printf '%s' "$MESSAGE" | sed ':a;N;$!ba;s/\n/\\n/g')
curl -s -H "Content-Type: application/json" \
-X POST \
-d "{\"content\":\"$JSON_SAFE_MESSAGE\"}" \
"$WEBHOOK_URL"
exit 0
同様に一度でも認証に失敗したらそのipを通知するスクリプトopenvpn_guard.shを作成します。ただし、ブルーフォースアタック時に大量の通知が来てしまいますので一体時間に一回の通知としています。また、上記で有効化したopenvpn.logの肥大化を防ぐためのログローテーションも設定しています。ただし、ipv6のアドレスは抽出する正規表現作成が困難なのでほぼ機能しません。規格を決めるときに問題視しなかったのだろうか
#!/bin/sh
# OpenVPN 失敗接続通知専用スクリプト
# NOTIFY_INTERVAL間に失敗したIPがあればDiscord Webhookに通知(IPの抽出は正常に動作しない)
# /var/log/openvpn.logのログローテーションも管理
LOG_FILE="/var/log/openvpn.log"
WEBHOOK_URL="https://discord.com/api/webhooks/XXX"
# 通知間隔(秒)
NOTIFY_INTERVAL=300
# openvpn.logの最大サイズ(バイト)
MAX_LOG_SIZE=50*1024*1024 # 50MB
# 最終通知時刻を記録するファイル
NOTIFY_DB="/tmp/ovpn_notify_time"
PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH
# ========= 関数 =========
send_discord() {
MSG="$1"
curl -s -H "Content-Type: application/json" \
-X POST \
-d "{\"content\":\"$MSG\"}" \
"$DISCORD_WEBHOOK" >/dev/null 2>&1
}
should_notify() {
NOW=$(date +%s)
LAST=$(cat "$NOTIFY_DB" 2>/dev/null || echo 0)
if [ $((NOW - LAST)) -ge $NOTIFY_INTERVAL ]; then
echo "$NOW" > "$NOTIFY_DB"
return 0
fi
return 1
}
rotate_log() {
SIZE=$(wc -c < "$LOG_FILE")
if [ "$SIZE" -gt "$MAX_LOG_SIZE" ]; then
# 超過分のバイト数を計算
OVER=$((SIZE - MAX_LOG_SIZE))
# 超過分を削除して残りをLOG_FILEに上書き
# tail -c +N は先頭からNバイト目から最後までを出力
tail -c +$((OVER + 1)) "$LOG_FILE" > "${LOG_FILE}.tmp" && mv "${LOG_FILE}.tmp" "$LOG_FILE"
fi
}
extract_ip() {
LINE="$1"
IP=$(echo "$LINE" | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}')
[ -n "$IP" ] && echo "$IP" && return
IP=$(echo "$LINE" | grep -oE '([0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}')
[ -n "$IP" ] && echo "$IP"
}
# ========= メインループ =========
tail -Fn0 "$LOG_FILE" | while read LINE; do
echo "$LINE" | grep -E "Username/Password verification failed" >/dev/null || continue
IP=$(extract_ip "$LINE")
[ -z "$IP" ] && IP="Unknown"
if should_notify; then
send_discord "🚨 OVPN Auth Failure [$IP] $(date)"
fi
rotate_log
done
再起動時に自動で開始するよう起動スクリプト/usr/local/etc/rc.d/launch_openvpn_guard.shも作成します。
#!/bin/sh
case "$1" in
start)
nohup /bin/sh /volume1/openvpn/openvpn_guard.sh &
;;
stop)
pkill -f /volume1/openvpn/openvpn_guard.sh
;;
esac
exit
実行権限付与
chmod +x /volume1/openvpn/vpn_connect_notify.sh
chmod +x /volume1/openvpn/openvpn_guard.sh
chmod +x /usr/local/etc/rc.d/launch_openvpn_guard.sh