Перейти до вмісту

B4. Файрвол і WireGuard

середнійспирається на модуль 9, модуль 16

Модуль 16 розрізняє файрволи, що пропускають усе крім явно забороненого, і ті, що забороняють усе крім явно дозволеного. Перші зручні, другі безпечні. Тут ви зробите другий: маршрутизатор fw між LAN, DMZ і «інтернетом», де новому з’єднанню потрібен окремий дозвіл, відповіді пропускає таблиця відстеження з’єднань, а все відкинуте лишає слід у журналі. Потім з’єднаєте два офіси тунелем WireGuard і переконаєтеся, що назовні видно лише зашифровані UDP-пакети.

Після цієї роботи ви зможете:

  • написати набір правил nftables із політикою drop у input і forward;
  • застосувати таблицю відстеження з’єднань (ct state) так, щоб відповіді проходили, а нові з’єднання в забороненому напрямку ні;
  • перевірити правила не «мені ніби працює», а з обох боків: що дозволене проходить і що заборонене не проходить;
  • налаштувати тунель WireGuard між двома маршрутизаторами: ключі, allowed-ips, маршрути;
  • довести через tcpdump, що між офісами ходить лише шифрований UDP.

Так виглядає перший робочий тиждень адміністратора малої мережі: закрити те, що не мало бути відкритим, і з’єднати філії, не виставляючи внутрішні адреси в інтернет.

Робота має дві незалежні частини.

lan 10.0.1.2 dmz 10.0.2.2
│ │
│ .1 │ .1
┌────┴─────────────────────┴───┐
│ fw │
└──────────────┬───────────────┘
│ 203.0.113.2
│
out 203.0.113.1
Простір імен Адреса Роль
fw 10.0.1.1/24 (LAN), 10.0.2.1/24 (DMZ), 203.0.113.2/24 (зовні) маршрутизатор-файрвол
lan 10.0.1.2/24, шлюз 10.0.1.1 внутрішня мережа
dmz 10.0.2.2/24, шлюз 10.0.2.1 публічні служби
out 203.0.113.1/24, маршрут у 10.0.0.0/16 через 203.0.113.2 «інтернет»

Політика, яку має реалізувати fw:

Звідки → куди Дозволено (нові з’єднання)
lan → dmz TCP 80, 443
lan → out TCP 80, 443; ping
out → dmz TCP 80, 443
lan → сам fw TCP 22; ping
out → сам fw лише ping
усе інше заборонено, відкинуте пишеться в журнал

Відповіді на дозволені з’єднання проходять автоматично (stateful). Зокрема, out не може сам відкрити з’єднання до lan, а dmz не відкриває нічого нікуди.

pc1 ── o1 ═══ інтернет (198.51.100.0/24) ═══ o2 ── pc2
192.168.1.10 tun 10.200.0.1 10.200.0.2 192.168.2.10
Простір імен Адреси
o1 192.168.1.1/24 (офіс 1), 198.51.100.1/24 («інтернет»), 10.200.0.1/24 (тунель)
o2 192.168.2.1/24 (офіс 2), 198.51.100.2/24 («інтернет»), 10.200.0.2/24 (тунель)
pc1 192.168.1.10/24, шлюз 192.168.1.1
pc2 192.168.2.10/24, шлюз 192.168.2.1

Тунель працює на UDP-порту 51820. Офісні маршрутизатори зовні мають відповідати лише на нього.

Назви просторів імен і адреси зафіксовані, їх перевіряє check.sh. Назви інтерфейсів, таблиць і ланцюжків оберіть самі.

Що зробити:

  1. Побудувати топологію частини 1 (чотири простори імен, три пари veth), маршрути, ip_forward у fw.
  2. Написати в fw таблицю nftables з двома базовими ланцюжками (input і forward), політиками drop і правилами за таблицею вище.
  3. Додати відстеження з’єднань і журнал відкинутого.
  4. Перевірити кожен рядок таблиці двічі: що дозволене проходить і що недозволене ні.
  5. Побудувати топологію частини 2 і підняти тунель WireGuard.
  6. Закрити офісні маршрутизатори: зовні лише UDP 51820.
  7. Показати в tcpdump на «інтернет»-інтерфейсі, що між офісами ходить лише UDP.

Чого робити не треба. NAT у цій роботі не потрібен (out має маршрут до внутрішніх мереж, див. B2). IPv6, iptables, ротація ключів, динамічні списки адрес (set) і автоматизація через wg-quick не потрібні.

Обмеження. Тільки nft (без iptables). Політика drop має бути саме політикою ланцюжка, а не правилом у кінці: тоді додана без уваги служба лишається закритою.

Готово, коли sudo ./check.sh не показує жодного «НІ». Перевірка ганяє і ті з’єднання, що мають проходити, і ті, що ні.

  • Прочитайте модуль 9 (відстеження з’єднань) і модуль 16: розділи про фільтрацію, stateful і WireGuard.
  • Встановіть інструменти: sudo bash setup/provision.sh (потрібні nftables, wireguard-tools, tcpdump, python3).
  • Потрібні права root. Частину 1 можна робити будь-де, де працюють простори імен і nftables. Частина 2 потребує модуля ядра wireguard: у багатьох контейнерах його немає, тоді потрібна віртуальна машина. Перевірте sudo bash setup/probe-net-env.sh, рядок про інтерфейс WireGuard. sudo ./check.sh fw перевіряє лише частину 1.
  • Працюйте в каталозі labs/b4-firewall-vpn.
  1. Топологія частини 1.

    Побудуйте простори й дроти (див. приклад у B2), призначте адреси, додайте маршрути:

    Terminal window
    sudo ip netns exec lan ip route add default via 10.0.1.1
    sudo ip netns exec dmz ip route add default via 10.0.2.1
    sudo ip netns exec out ip route add 10.0.0.0/16 via 203.0.113.2
    sudo ip netns exec fw sysctl -w net.ipv4.ip_forward=1

    Поки правил немає, fw просто маршрутизатор, і все з усім бачиться. Перевірте: lan пінгує out, out пінгує dmz. Це ваша контрольна точка «до».

  2. Політика «за замовчуванням заборонено».

    Скелет таблиці: два базові ланцюжки з політикою drop.

    table inet filter {
    chain input {
    type filter hook input priority 0; policy drop;
    }
    chain forward {
    type filter hook forward priority 0; policy drop;
    }
    }

    Збережіть у файл і завантажте: sudo ip netns exec fw nft -f fw.nft. Тепер не працює нічого, навіть ping на сам fw: так і має бути. Політика drop відкидає все, що не дозволило жодне правило.

  3. Stateful: відповіді.

    Перше правило у двох ланцюжках:

    ct state established,related accept
    ct state invalid drop

    input ще потребує iif lo accept. Без цих правил навіть дозволене з’єднання рвалося б: запит пройде, а відповідь ні, бо нового дозволу для зворотного напрямку ви не писали.

    Запишіть: що означають стани new, established, related і invalid? Чому відповіді не потребують власних правил?

  4. Дозволені сервіси.

    Додайте правила за таблицею політики. Приклад одного:

    ip saddr 10.0.1.0/24 ip daddr 10.0.2.0/24 tcp dport { 80, 443 } accept

    Порядок правил має значення: перше, що збіглося, визначає долю пакета. Перевіряйте кожен крок. Запустіть слухача й під’єднайтеся до нього:

    Terminal window
    sudo ip netns exec dmz python3 -m http.server 80 &
    sudo ip netns exec lan curl -m 3 http://10.0.2.2/

    і так само для порту, який мав бути закритий (python3 -m http.server 8080 у dmz і curl на нього з lan).

  5. Журнал відкинутого.

    Останнім правилом кожного ланцюжка поставте журнал і лічильник:

    log prefix "fw-forward-drop: " counter

    Пакет, що дійшов до цього правила, лишає запис у журналі ядра й збільшує лічильник, а потім відкидається політикою. Зробіть заборонену спробу й подивіться:

    Terminal window
    sudo ip netns exec lan curl -m 2 http://10.0.2.2:8080/
    sudo ip netns exec fw nft list ruleset # лічильник виріс
    sudo dmesg | grep fw-forward-drop

    У журналі буде рядок такого вигляду:

    fw-forward-drop: IN=to-lan OUT=to-dmz SRC=10.0.1.2 DST=10.0.2.2 ... PROTO=TCP SPT=51360 DPT=8080 ... SYN
  6. Перевірка з двох боків.

    Складіть таблицю «дозволено / заборонено»: для кожного рядка політики один пакет, що має пройти, і один, що ні. Не забудьте про негативні випадки з «відповіддю, якої ніхто не чекав»: out намагається з’єднатися з високим портом у lan.

    Запишіть: чим небезпечне правило tcp dport 1024-65535 accept, яке колись називали «для відповідей»?

  7. Топологія частини 2 й ключі.

    Побудуйте o1, o2, pc1, pc2 за таблицею (тунель поки не чіпайте), ip_forward в обох офісах, маршрути на pc. Згенеруйте ключі:

    Terminal window
    umask 077
    wg genkey | tee o1.key | wg pubkey > o1.pub
    wg genkey | tee o2.key | wg pubkey > o2.pub

    Приватні ключі нікому не показуйте й не комітьте в репозиторій.

  8. Тунель.

    Інтерфейс створюйте всередині простору імен:

    Terminal window
    sudo ip netns exec o1 ip link add wg0 type wireguard
    sudo ip netns exec o1 wg set wg0 private-key o1.key listen-port 51820 \
    peer "$(cat o2.pub)" allowed-ips 10.200.0.2/32,192.168.2.0/24 \
    endpoint 198.51.100.2:51820 persistent-keepalive 5
    sudo ip netns exec o1 ip addr add 10.200.0.1/24 dev wg0
    sudo ip netns exec o1 ip link set wg0 up
    sudo ip netns exec o1 ip route add 192.168.2.0/24 dev wg0

    Дзеркально для o2: його ключ, allowed-ips 10.200.0.1/32,192.168.1.0/24, endpoint 198.51.100.1:51820, адреса 10.200.0.2/24, маршрут до 192.168.1.0/24. Перевірте: pc1 пінгує pc2, а sudo ip netns exec o1 wg show показує latest handshake і ненульові лічильники transfer.

    Запишіть: що означає allowed-ips у WireGuard: і «кого пускати з цього вузла», і «куди пакети слати»? Навіщо поруч іще ip route?

  9. Офіс закрито, тунель зашифровано.

    Дайте o1 і o2 файрвол input із політикою drop: дозволено lo, відповіді (ct state), UDP 51820 і все, що прийшло з інтерфейсу тунелю. Потім на «інтернет»-інтерфейсі o1 запустіть захоплення:

    Terminal window
    sudo ip netns exec o1 tcpdump -ni <інтерфейс> 'icmp or udp port 51820'

    і пропінгуйте pc2 з pc1. Ви побачите лише UDP, жодного ICMP. Порівняйте із захопленням на wg0, де пінг видно відкритим: усередині тунелю пакети ті самі, зовні вони вже шифровані.

Terminal window
sudo ./check.sh # обидві частини
sudo ./check.sh fw # лише файрвол
sudo ./check.sh vpn # лише тунель

Скрипт нічого не будує й від назв не залежить. Щоб не вимагати від вас запущених служб, він на кілька секунд сам запускає тимчасові TCP-слухачі на тестових портах (80, 443, 22, 8080, 25 та 50000) у просторах dmz, out, lan і fw і потім зупиняє їх. Перевіряється (частина 1):

  • структура: у input і forward політика drop, у forward є правило ct state ... established, в обох ланцюжках є правило log;
  • що має проходити: усі рядки таблиці політики;
  • що не має проходити (це важливіше): порти, яких немає в таблиці (22 і 8080 у DMZ, 25 назовні), нові з’єднання з out у lan, усі з’єднання з dmz, ssh до fw із зовнішньої сторони й DMZ, і «відповіді, якої ніхто не чекав» на високий порт lan.

Частина 2: адреси, інтерфейс WireGuard і рукостискання в обох офісах, pc1 ↔ pc2, а в каналі між офісами є UDP і немає ICMP, а також те, що офісні маршрутизатори не приймають TCP-з’єднань на своїй «інтернет»-адресі.

Якщо правило надто широке, скрипт покаже, яке саме «заборонене» з’єднання вдалося. Якщо забули ct state, не працюватимуть відповіді, а якщо зробили «відповіді» правилом за номером порту, впаде перевірка високого порту.

Політика drop без ct state. Запит проходить, відповідь ні: curl висить. Перше правило в кожному ланцюжку — ct state established,related accept.

Забули iif lo accept у input. Локальні сервіси на самому fw перестають бачити одне одного.

drop у кінці замість політики. Працює, але порожній ланцюжок або зайва порожня таблиця нічого не закривають. Політика ланцюжка надійніша.

Порядок правил. Якщо правило accept стоїть раніше за те, що мало заборонити, заборона не спрацює. nft list ruleset показує порядок і лічильники.

Журнал порожній. Див. застереження в етапі 5: у просторі імен треба ввімкнути nf_log_all_netns на господарі.

Тунель не піднімається. wg show без latest handshake: перевірте, що публічний ключ сусіда (не свій) стоїть у peer, що endpoint правильний і що файрвол пропускає UDP 51820. Завжди запускайте tcpdump на «інтернет»-інтерфейсі: бачите пакети, що йдуть, але не повертаються, значить, закрито на тому боці.

Пакети в тунель не потрапляють. Без ip route ... dev wg0 ядро не знає, що 192.168.2.0/24 лежить за тунелем: allowed-ips лише каже WireGuard, кого пускати й куди слати, коли пакет уже потрапив в інтерфейс.

Додайте NAT на fw і приберіть маршрут у out. Замініть перелік портів на іменований набір (set) і подивіться, як правила скорочуються. Обмежте швидкість нових з’єднань (limit rate) і подивіться, як log реагує на сканування портів (nmap із out). У тунелі спробуйте змінити MTU й подивіться на фрагментацію: WireGuard додає до кожного пакета власні заголовки.