Принцип работы

Мы используем связку 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

Вставьте тело скрипта и заполните настройки:

  1. Укажите URL вашего списка в URL="...".
  2. Укажите порты, которые должны быть доступны только через 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