Принцип работы
Мы используем связку ipset + iptables:
- Скрипт регулярно скачивает ваш персональный список IPv4-адресов WAF по URL.
- Добавляет адреса в ipset‑набор
waf_allow4. - Обновляет цепочку iptables
WAF_ALLOWи добавляет переход вINPUTв самое начало. - Затем добавьте правило DROP для портов приложения, чтобы прямой доступ в обход WAF был закрыт.
Установка скрипта
Зависимости:
sudo apt update
sudo apt install -y curl ipset iptables
Создайте файл скрипта
Создайте файл:
sudo nano /usr/local/sbin/waf-ips-update.sh
Вставьте тело скрипта и заполните настройки:
- Укажите URL вашего списка в
URL="...". - Укажите порты, которые должны быть доступны только через WAF, в
PORTS="..."(пример:80 443).
#!/usr/bin/env bash
set -Eeuo pipefail
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
# ====== SETTINGS ======
URL="" # IP/CIDR per line, comments allowed
PORTS="80 443" # TCP ports (can be "80,443")
# ======================
LOCKDIR="/run/waf-ips-update.lock"
SET="waf_allow4"
TMP="waf_allow4_tmp"
CHAIN="WAF_ALLOW"
die(){ echo "ERROR: $*" >&2; exit 1; }
[[ ${EUID:-$(id -u)} -eq 0 ]] || die "run as root (sudo)"
command -v curl >/dev/null 2>&1 || die "missing: curl"
command -v ipset >/dev/null 2>&1 || die "missing: ipset"
command -v iptables >/dev/null 2>&1 || die "missing: iptables"
# lock to prevent concurrent runs
mkdir "$LOCKDIR" 2>/dev/null || exit 0
tmp="$(mktemp)"
cleanup(){ rm -f "$tmp"; rmdir "$LOCKDIR" 2>/dev/null || true; }
trap cleanup EXIT
# normalize ports
PORTS="${PORTS//,/ }"
# download and ensure HTTP 200
code="$(curl -sS -L --connect-timeout 10 --max-time 45 -o "$tmp" -w "%{http_code}" "$URL" || echo 000)"
[[ "$code" == "200" ]] || die "URL must return HTTP 200 (got $code)"
# prevent HTML instead of IP list
if head -c 200 "$tmp" | tr -d '\000' | grep -qiE '<!doctype|<html'; then
die "downloaded content looks like HTML, not an IP list"
fi
# iptables wait (if supported)
IPT_WAIT=()
iptables -w 1 -L >/dev/null 2>&1 && IPT_WAIT=(-w 5)
# ipset
ipset create "$SET" hash:net family inet -exist
ipset create "$TMP" hash:net family inet -exist
ipset flush "$TMP"
# fill temporary set and ensure it is not empty
valid=0
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%%#*}"
line="${line//$'\r'/}"
for tok in $line; do
[[ -z "$tok" ]] && continue
if ipset add "$TMP" "$tok" -exist 2>/dev/null; then
valid=$((valid+1))
fi
done
done < "$tmp"
(( valid > 0 )) || die "no valid IPv4 IPs/CIDRs found (not applying)"
# atomic swap
ipset swap "$TMP" "$SET"
ipset flush "$TMP"
# iptables: dedicated chain + jump at top of INPUT
iptables "${IPT_WAIT[@]}" -N "$CHAIN" 2>/dev/null || true
iptables "${IPT_WAIT[@]}" -F "$CHAIN"
for p in $PORTS; do
[[ "$p" =~ ^[0-9]{1,5}$ ]] || die "bad port: $p"
(( p>=1 && p<=65535 )) || die "bad port: $p"
iptables "${IPT_WAIT[@]}" -A "$CHAIN" -p tcp --dport "$p" -m set --match-set "$SET" src -j ACCEPT
done
iptables "${IPT_WAIT[@]}" -A "$CHAIN" -j RETURN
# ensure exactly one jump in INPUT at the top
while iptables "${IPT_WAIT[@]}" -C INPUT -j "$CHAIN" >/dev/null 2>&1; do
iptables "${IPT_WAIT[@]}" -D INPUT -j "$CHAIN" || break
done
iptables "${IPT_WAIT[@]}" -I INPUT 1 -j "$CHAIN"
echo "OK: updated from $URL (ports: $PORTS, entries: $valid)"
Сделайте скрипт исполняемым:
sudo chmod +x /usr/local/sbin/waf-ips-update.sh
Проверка скачивания и применения списка
Запуск:
sudo /usr/local/sbin/waf-ips-update.sh
Пример успешного вывода:
OK: updated from https://... (ports: 80 443, entries: 123)
Проверка ipset
sudo ipset list waf_allow4
В выводе должны быть IP-адреса/подсети из вашего списка.
Проверка iptables
sudo iptables -S WAF_ALLOW
sudo iptables -L INPUT -n --line-numbers | head -n 30
Ожидаемая логика:
- В
INPUTв самом начале стоит переход вWAF_ALLOW. - В
WAF_ALLOWесть правила ACCEPT на нужные порты для источников изwaf_allow4, затемRETURN.
Автообновление списка через systemd timer
Создайте файл сервиса обновления:
nano /etc/systemd/system/waf-ips-update.service
[Unit]
Description=Update WAF IPv4 allowlist
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/waf-ips-update.sh
Создайте таймер, который будет запускать сервис обновления списка:
nano /etc/systemd/system/waf-ips-update.timer
[Unit]
Description=Periodic WAF allowlist update
[Timer]
OnBootSec=1min
OnUnitActiveSec=10min
Persistent=true
Unit=waf-ips-update.service
[Install]
WantedBy=timers.target
Примените настройки:
sudo systemctl daemon-reload
sudo systemctl enable --now waf-ips-update.timer
Проверка:
systemctl status waf-ips-update.timer
journalctl -u waf-ips-update.service -n 50 --no-pager
Закрытие прямого доступа
Скрипт добавляет ACCEPT для IP WAF. Чтобы запретить прямой доступ, добавьте DROP для портов приложения после перехода (jump) в WAF_ALLOW.
Пример для 80,443:
sudo iptables -I INPUT 2 -p tcp -m multiport --dports 80,443 -j DROP
Включайте DROP только после того, как убедитесь, что WAF действительно проксирует трафик на ваш сервер и скрипт успешно подтягивает список IP.
Проверка порядка:
sudo iptables -L INPUT -n --line-numbers | head -n 30
Откат (удалите правило по номеру строки N из вывода):
sudo iptables -D INPUT N