WireGuard + Pi-hole для корпоративной DNS-фильтрации: как мы защитили 500 устройств школы

Ситуация: школа без контентной фильтрации

Школа «ОбразованиеПлюс» обратилась к нам в itfresh.ru с проблемой: 500 устройств (компьютерные классы, ноутбуки учителей, планшеты) подключены к школьной сети без какой-либо фильтрации контента. По требованиям 436-ФЗ (защита детей от вредоносного контента) школа обязана фильтровать доступ к запрещённому контенту.

Текущая ситуация:

  • Сеть — Mikrotik RB4011 как маршрутизатор, 3 VLAN (учителя, ученики, администрация), 4 точки доступа Ubiquiti
  • DNS — 8.8.8.8 и 1.1.1.1 напрямую, без фильтрации
  • Удалённые учителя — 30 преподавателей работают из дома и используют школьные ресурсы через TeamViewer
  • Бюджет — минимальный, платные решения (Kaspersky Safe Kids, SkyDNS) не рассматриваются

Задача: контентная фильтрация для всех устройств в школе + VPN-доступ для удалённых учителей, с возможностью разных политик для учеников и преподавателей.

Установка Pi-hole в Docker

Pi-hole — DNS-сервер с функцией блокировки рекламы и нежелательного контента. Работает как DNS sinkhole: запросы к заблокированным доменам возвращают 0.0.0.0 вместо реального IP. Установка в Docker на выделенном сервере (Ubuntu 22.04, 2 CPU, 2 GB RAM):

# docker-compose.yml для Pi-hole
version: '3.8'
services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    hostname: pihole
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8080:80/tcp"    # Web-интерфейс
    environment:
      TZ: 'Europe/Moscow'
      WEBPASSWORD: 'SecureP@ssw0rd_PiHole'
      FTLCONF_LOCAL_IPV4: '10.10.0.1'
      PIHOLE_DNS_: '127.0.0.1#5335'  # Unbound как upstream
      DNSSEC: 'true'
      QUERY_LOGGING: 'true'
      # Блокировка IPv6 DNS (предотвращает обход)
      FTLCONF_BLOCK_IPV6: 'true'
    volumes:
      - pihole-data:/etc/pihole
      - pihole-dnsmasq:/etc/dnsmasq.d
    networks:
      dns-net:
        ipv4_address: 10.10.0.2
    restart: unless-stopped
    dns:
      - 127.0.0.1
      - 1.1.1.1
    cap_add:
      - NET_ADMIN

networks:
  dns-net:
    ipam:
      config:
        - subnet: 10.10.0.0/24

volumes:
  pihole-data:
  pihole-dnsmasq:
# Запускаем
docker compose up -d

# Проверяем работу DNS
dig @10.10.0.2 google.com +short
# 142.250.185.78

dig @10.10.0.2 malware-domain.example.com +short
# 0.0.0.0  (заблокирован)

Настройка blocklists и whitelist

Pi-hole по умолчанию включает один blocklist (Steven Black's list). Для школы мы добавили специализированные списки:

# Добавляем blocklists через CLI
pihole -a adlist add https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
pihole -a adlist add https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn/hosts
pihole -a adlist add https://raw.githubusercontent.com/RPiList/specials/master/Blocklisten/gambling
pihole -a adlist add https://raw.githubusercontent.com/RPiList/specials/master/Blocklisten/Streaming
pihole -a adlist add https://raw.githubusercontent.com/hagezi/dns-blocklists/main/domains/pro.txt
pihole -a adlist add https://raw.githubusercontent.com/hagezi/dns-blocklists/main/domains/tif.txt

# Российские blocklists
pihole -a adlist add https://easylist-downloads.adblockplus.org/ruadlist+easylist.txt

# Обновляем Gravity (загружает и обрабатывает все списки)
pihole -g

# Результат:
# [i] Number of domains being blocked: 847,293

Whitelist — критически важен для школы. Образовательные ресурсы не должны блокироваться:

# Whitelist образовательных ресурсов
pihole -w resh.edu.ru
pihole -w uchi.ru
pihole -w foxford.ru
pihole -w stepik.org
pihole -w coursera.org
pihole -w google.com
pihole -w classroom.google.com
pihole -w docs.google.com
pihole -w youtube.com          # нужен для образовательных видео
pihole -w wikipedia.org
pihole -w ru.wikipedia.org

# Regex whitelist для всех поддоменов
pihole --white-regex '(^|\.)edu\.ru$'
pihole --white-regex '(^|\.)school\.mosreg\.ru$'

# Проверяем статус блокировки конкретного домена
pihole -q gambling-site.com
# Match found in https://raw.githubusercontent.com/RPiList/specials/master/Blocklisten/gambling
#  gambling-site.com

Gravity database — SQLite-файл, в котором Pi-hole хранит все blocklists и whitelists. Для 850 000 доменов размер базы — около 40 MB, а запрос занимает менее 1 мс.

WireGuard VPN для удалённых учителей

30 учителей работают из дома и должны использовать школьный DNS для фильтрации (требование закона). Решение — WireGuard VPN: весь DNS-трафик учителей идёт через школьный Pi-hole.

# Установка WireGuard
sudo apt install -y wireguard

# Генерируем ключи сервера
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
chmod 600 /etc/wireguard/server_private.key

# /etc/wireguard/wg0.conf
[Interface]
Address = 10.20.0.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# Учитель 1 — Иванова А.П.
[Peer]
PublicKey = TEACHER1_PUBLIC_KEY
AllowedIPs = 10.20.0.10/32

# Учитель 2 — Петров С.В.
[Peer]
PublicKey = TEACHER2_PUBLIC_KEY
AllowedIPs = 10.20.0.11/32

# ... ещё 28 учителей

Конфигурация клиента (учитель): только DNS идёт через VPN, остальной трафик — напрямую:

# teacher_ivanova.conf
[Interface]
PrivateKey = TEACHER1_PRIVATE_KEY
Address = 10.20.0.10/32
DNS = 10.10.0.2   # Pi-hole через VPN

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = school-vpn.example.ru:51820
AllowedIPs = 10.10.0.0/24, 10.20.0.0/24   # Только школьная сеть
PersistentKeepalive = 25

Для автоматического создания конфигураций учителей написали скрипт:

#!/bin/bash
# generate_teacher_vpn.sh — создание WireGuard-конфига для нового учителя
TEACHER_NAME="$1"
IP_SUFFIX="$2"
SERVER_PUBLIC=$(cat /etc/wireguard/server_public.key)
SERVER_ENDPOINT="school-vpn.example.ru:51820"

# Генерируем ключи
PRIV=$(wg genkey)
PUB=$(echo "$PRIV" | wg pubkey)
PSK=$(wg genpsk)

# Создаём конфиг клиента
cat > "/etc/wireguard/clients/${TEACHER_NAME}.conf" <<EOF
[Interface]
PrivateKey = ${PRIV}
Address = 10.20.0.${IP_SUFFIX}/32
DNS = 10.10.0.2

[Peer]
PublicKey = ${SERVER_PUBLIC}
PresharedKey = ${PSK}
Endpoint = ${SERVER_ENDPOINT}
AllowedIPs = 10.10.0.0/24, 10.20.0.0/24
PersistentKeepalive = 25
EOF

# Добавляем peer на сервер
wg set wg0 peer "${PUB}" preshared-key <(echo "${PSK}") allowed-ips "10.20.0.${IP_SUFFIX}/32"

# Генерируем QR-код для мобильного приложения
qrencode -t ansiutf8 < "/etc/wireguard/clients/${TEACHER_NAME}.conf"

echo "Config saved: /etc/wireguard/clients/${TEACHER_NAME}.conf"
echo "Public key: ${PUB}"

Групповые политики через DHCP и Pi-hole Groups

У школы три группы пользователей с разными требованиями к фильтрации:

  • Ученики — строгая фильтрация: без соцсетей, игр, развлечений, adult-контента
  • Учителя — умеренная: соцсети разрешены, adult заблокирован
  • Администрация — минимальная: только malware и adult

Pi-hole v5+ поддерживает Group Management — разные blocklists для разных клиентов. Настройка через веб-интерфейс или SQLite напрямую:

# Создаём группы в Pi-hole через SQLite
sudo sqlite3 /etc/pihole/gravity.db

-- Создаём группы
INSERT INTO 'group' (name, description, enabled) VALUES
  ('students', 'Ученики — строгая фильтрация', 1),
  ('teachers', 'Учителя — умеренная фильтрация', 1),
  ('admin', 'Администрация — минимальная фильтрация', 1);

-- Привязываем blocklists к группам
-- Все списки — для учеников
INSERT INTO adlist_by_group (adlist_id, group_id)
  SELECT id, (SELECT id FROM 'group' WHERE name='students')
  FROM adlist;

-- Для учителей — только malware и adult
INSERT INTO adlist_by_group (adlist_id, group_id)
  SELECT id, (SELECT id FROM 'group' WHERE name='teachers')
  FROM adlist
  WHERE address LIKE '%malware%' OR address LIKE '%porn%' OR address LIKE '%adult%';

.quit

# Применяем изменения
pihole restartdns

Привязка устройств к группам через DHCP. На Mikrotik настраиваем DHCP с разными DNS для каждого VLAN:

# Mikrotik RouterOS: настройка DNS по VLAN

# VLAN 10 — ученики
/ip dhcp-server network set [find where address="192.168.10.0/24"] dns-server=10.10.0.2

# VLAN 20 — учителя
/ip dhcp-server network set [find where address="192.168.20.0/24"] dns-server=10.10.0.2

# VLAN 30 — администрация
/ip dhcp-server network set [find where address="192.168.30.0/24"] dns-server=10.10.0.2

# Блокируем DNS-запросы к внешним серверам (предотвращаем обход фильтрации)
/ip firewall filter add chain=forward protocol=udp dst-port=53 \
  src-address=!10.10.0.2 action=drop comment="Block external DNS"
/ip firewall filter add chain=forward protocol=tcp dst-port=53 \
  src-address=!10.10.0.2 action=drop comment="Block external DNS"

# Блокируем DoH (DNS over HTTPS) — популярный способ обхода
/ip firewall filter add chain=forward protocol=tcp dst-port=443 \
  tls-host="dns.google" action=drop comment="Block DoH Google"
/ip firewall filter add chain=forward protocol=tcp dst-port=443 \
  tls-host="cloudflare-dns.com" action=drop comment="Block DoH Cloudflare"
/ip firewall filter add chain=forward protocol=tcp dst-port=443 \
  tls-host="mozilla.cloudflare-dns.com" action=drop comment="Block DoH Firefox"

Unbound как рекурсивный DNS-резолвер

По умолчанию Pi-hole пересылает запросы на upstream DNS (Google, Cloudflare). Это означает, что Google видит все DNS-запросы школы. Для приватности мы настроили Unbound — собственный рекурсивный резолвер, который обращается напрямую к корневым DNS-серверам:

# Установка Unbound
sudo apt install -y unbound

# /etc/unbound/unbound.conf.d/pi-hole.conf
server:
    verbosity: 0
    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no

    # Приватность
    hide-identity: yes
    hide-version: yes
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no
    edns-buffer-size: 1232

    # Кэш
    prefetch: yes
    prefetch-key: yes
    num-threads: 2
    msg-cache-slabs: 4
    rrset-cache-slabs: 4
    infra-cache-slabs: 4
    key-cache-slabs: 4
    rrset-cache-size: 128m
    msg-cache-size: 64m
    cache-min-ttl: 300
    cache-max-ttl: 86400

    # DNSSEC валидация
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    val-clean-additional: yes

    # Доступ только от Pi-hole
    access-control: 127.0.0.0/8 allow
    access-control: 0.0.0.0/0 refuse

    # Корневые подсказки
    root-hints: "/var/lib/unbound/root.hints"
# Загружаем root hints
sudo wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache

# Проверяем работу Unbound
sudo systemctl restart unbound
dig @127.0.0.1 -p 5335 google.com +short +dnssec
# 142.250.185.78
# flags: qr rd ra ad  (ad = DNSSEC authenticated)

# Измеряем скорость: первый запрос ~80мс (рекурсия), повторный ~1мс (кэш)
dig @127.0.0.1 -p 5335 yandex.ru | grep "Query time"
# Query time: 82 msec
dig @127.0.0.1 -p 5335 yandex.ru | grep "Query time"
# Query time: 0 msec

Цепочка: устройство → Pi-hole (фильтрация) → Unbound (рекурсивный резолв) → корневые DNS. Никакие сторонние сервисы не видят DNS-запросы школы.

Логирование, аудит и мониторинг

Для школы критично вести журнал DNS-запросов — это требование 436-ФЗ. Pi-hole логирует каждый запрос с указанием IP клиента:

# FTLDNS конфигурация для расширенного логирования
# /etc/pihole/pihole-FTL.conf
PRIVACYLEVEL=0          # Логировать всё (IP + домен + тип)
DBIMPORT=yes             # Импортировать логи в SQLite
MAXDBDAYS=90             # Хранить 90 дней
DBINTERVAL=1.0           # Записывать в БД раз в минуту
RESOLVE_IPV6=no
BLOCK_ICLOUD_PR=true     # Блокируем iCloud Private Relay (обход DNS)
REFRESH_HOSTNAMES=IPV4   # Резолвить hostname клиентов
RATE_LIMIT=0/0           # Без ограничения логирования

Скрипт для генерации еженедельного отчёта директору школы:

#!/bin/bash
# pihole_weekly_report.sh — отчёт для администрации школы

DB="/etc/pihole/pihole-FTL.db"
DATE_FROM=$(date -d '7 days ago' +%s)
DATE_TO=$(date +%s)

# Общая статистика
TOTAL=$(sqlite3 "$DB" "SELECT COUNT(*) FROM queries WHERE timestamp BETWEEN $DATE_FROM AND $DATE_TO;")
BLOCKED=$(sqlite3 "$DB" "SELECT COUNT(*) FROM queries WHERE status IN (1,4,5,6,7,8,9,10,11) AND timestamp BETWEEN $DATE_FROM AND $DATE_TO;")
PERCENT=$(echo "scale=1; $BLOCKED * 100 / $TOTAL" | bc)

# Топ-10 заблокированных доменов
TOP_BLOCKED=$(sqlite3 "$DB" "SELECT domain, COUNT(*) as cnt FROM queries WHERE status IN (1,4,5,6,7,8,9,10,11) AND timestamp BETWEEN $DATE_FROM AND $DATE_TO GROUP BY domain ORDER BY cnt DESC LIMIT 10;")

# Топ-5 клиентов по количеству заблокированных запросов
TOP_CLIENTS=$(sqlite3 "$DB" "SELECT client, COUNT(*) as cnt FROM queries WHERE status IN (1,4,5,6,7,8,9,10,11) AND timestamp BETWEEN $DATE_FROM AND $DATE_TO GROUP BY client ORDER BY cnt DESC LIMIT 5;")

# Отправляем в Telegram
MSG="📊 *DNS-отчёт за неделю*

Всего запросов: ${TOTAL}
Заблокировано: ${BLOCKED} (${PERCENT}%)

*Топ заблокированных доменов:*
${TOP_BLOCKED}

*Топ клиентов (blocked):*
${TOP_CLIENTS}"

curl -s "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
  -d "chat_id=${CHAT_ID}&text=${MSG}&parse_mode=Markdown" >/dev/null

Результат внедрения за первый месяц:

МетрикаЗначение
DNS-запросов/день~180 000
Заблокировано/день~32 000 (18%)
Доменов в blocklist847 293
Среднее время ответа DNS4 мс (кэш) / 85 мс (рекурсия)
VPN-подключений учителей22 из 30 активно
Ложных блокировок/неделю3-5 (добавляются в whitelist)

Pi-hole + WireGuard + Unbound — полностью бесплатное решение, которое обеспечивает школе контентную фильтрацию на уровне DNS, приватность запросов и VPN-доступ для удалённых учителей. Если вашей организации нужна DNS-фильтрация — обращайтесь к нам в itfresh.ru.

Часто задаваемые вопросы

Основные способы обхода: DoH (DNS over HTTPS), VPN-приложения, смена DNS вручную. Мы блокируем все три: DoH — через firewall-правила на Mikrotik (блокируем dns.google и cloudflare-dns.com), VPN-приложения — через блокировку известных VPN-доменов в Pi-hole, смена DNS — через firewall (весь DNS-трафик перенаправляется на Pi-hole).
Легко. Pi-hole обрабатывает до 100 000 запросов в секунду на обычном сервере с 2 CPU и 2 GB RAM. 500 устройств генерируют максимум 500-1000 запросов в секунду в пике. Мы тестировали на 180 000 запросов/день без какой-либо деградации производительности.
Команда pihole -g обновляет Gravity database без остановки DNS-сервера. Pi-hole продолжает использовать старую базу до завершения обновления, затем атомарно переключается на новую. Мы настроили cron на обновление в 03:00 воскресенья: 0 3 * * 0 pihole -g. Время обновления 850 000 доменов — около 2 минут.
На уровне DNS — нет, YouTube использует один домен для всего контента. Для выборочной блокировки нужен proxy с DPI (например, Squid + SquidGuard). Для школы мы приняли компромисс: YouTube разрешён (нужен для образовательных видео), но TikTok, VK Video и другие развлекательные платформы заблокированы.
Unbound не обязателен — Pi-hole работает с любым upstream DNS. Но Unbound даёт три преимущества: приватность (Google не видит запросы), DNSSEC-валидация из коробки, и независимость от внешних сервисов. Минус — первый запрос к новому домену медленнее на 50-100 мс (рекурсия), но при 90% cache hit rate это незаметно.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#WireGuard#Pi-hole#DNS filtering#content filtering#Unbound#blocklist#FTLDNS#Gravity
Комментарии 0

Оставить комментарий

загрузка...