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

Файрволи і VPN

Ви піднімаєте новий сервер, і за годину в журналі автентифікації з’являються сотні невдалих спроб зайти по SSH із незнайомих адрес. Ви нічого не оголошували: адресу знайшов сканер, що перебирає весь IPv4-простір за лічені години. Або обернена ситуація: у колеги правило «відкрити порт 443» є, а сайт не відкривається, і треба зрозуміти, на якому кроці шляху пакет зник.

Файрвол (firewall, мережевий екран) вирішує, кого пустити і чого не пускати, навіть якщо кажуть, що можна. Практично це код, який для кожного пакета вирішує: пропустити, відкинути чи змінити. У Linux цей код живе всередині ядра й називається netfilter; правила для нього пишуть інструментом nft.

Друга половина модуля про VPN: як з’єднати дві мережі через ворожий інтернет, щоб вони поводилися як одна.

Передумови. Шлях пакета крізь ядро (модуль 3), маршрутизація (модуль 7), NAT і conntrack (модуль 9), TCP (модуль 11), TLS (модуль 15).

Netfilter складається з точок у мережевому стеку ядра, які називаються хуками (hooks). Модуль ядра може зареєструватися на хук і отримувати кожен пакет, що через нього проходить. Побачити, коли саме пакет потрапляє до якого хука, допомагає рисунок (сам шлях через драйвер і sk_buff описано в модулі 3).

Шлях пакета і п’ять хуків netfilterмережаpreroutingмаршрутforwardpostroutingмережаinputлокальний процесoutputадреса призначення наша?до маршрутизації: DNAT, rawтранзит: правила між мережамипісля маршрутизації: SNATзахист самого хостащо хост шле самтранзитний пакет: prerouting → forward → postrouting. пакет до цього хоста і від нього: prerouting → input; output → postrouting.
П'ять хуків. Пакет, що прийшов ззовні, спочатку проходить prerouting, потім маршрутизацію, яка розгалужує шлях. Транзитний іде через forward, місцевий через input. Усе вихідне, і транзитне теж, проходить postrouting.
  • prerouting. Перший хук для кожного пакета, що прийшов із мережі, ще до вибору маршруту. Тут змінюють адресу призначення (DNAT, модуль 9), бо від неї залежить, куди піде пакет. Тут же зручно відкидати явне сміття.
  • input. Пакети, призначені самому цьому хосту. Правила для захисту самої машини: хто може зайти по SSH.
  • forward. Транзитні пакети, коли Linux працює маршрутизатором. На звичайному сервері net.ipv4.ip_forward=0, і цей хук порожній.
  • output. Пакети, створені локальними процесами.
  • postrouting. Останній хук перед виходом у мережу. Тут підміняють адресу відправника (SNAT, masquerade), бо інтерфейс виходу вже відомий.

Розгалуження — ключ до більшості помилок: пакет із адресою призначення самого хоста не потрапляє в forward, транзитний не потрапляє в input. Правило «закрити порт 22» у forward не захистить сам маршрутизатор, а правило в input не зупинить трафік через нього.

Кілька ланцюжків на одному хуці

Section titled “Кілька ланцюжків на одному хуці”

На один хук можуть зареєструватися багато ланцюжків із різними пріоритетами (число; менше означає раніше). Зарезервовані імена в nftables: raw (-300), mangle (-150), dstnat (-100), filter (0), srcnat (100). Спостереження conntrack теж стоїть на prerouting, з пріоритетом -200, тому правило в raw виконується до нього (на цьому тримається notrack, спосіб не стежити за пакетом).

Ланцюжки одного хука пакет проходить по черзі за пріоритетом, і будь-який із них може його відкинути: accept закінчує розгляд лише в цьому ланцюжку, а наступний на тому самому хуці ще може сказати drop. Тобто «дозволив у своїй таблиці» не означає «пакет пройде», якщо Docker, firewalld чи інша таблиця поставили власні правила.

nftables замінив iptables як основний інструмент (старий синтаксис iptables тепер часто лише оболонка над тим самим ядровим механізмом). Модель складається з кількох рівнів:

  • таблиця (table) має сімейство адрес: ip, ip6, inet (обидва одночасно), arp, bridge, netdev;
  • ланцюжок (chain) містить правила. Базовий ланцюжок зареєстрований на хук (тип, хук, пріоритет, політика); звичайний викликається командою jump;
  • правило (rule) — умови плюс вердикт: accept, drop, reject, jump, return та інші. Правила виконуються згори вниз, перший вердикт зупиняє розгляд;
  • набір (set) — іменований список адрес, портів чи пар, у якому пошук швидкий незалежно від розміру.

Ось повний робочий приклад файрвола для простої топології: клієнт 10.16.1.0/24 — Linux-файрвол — сервер 10.16.2.2.

table inet filter {
set web_ok {
type ipv4_addr
elements = { 10.16.1.2 }
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
icmp type echo-request limit rate 5/second accept
ip saddr 10.16.1.0/24 tcp dport 22 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
ct state invalid drop
ip saddr @web_ok ip daddr 10.16.2.2 tcp dport 80 counter accept
icmp type echo-request counter accept
}
chain output {
type filter hook output priority filter; policy accept;
}
}

Завантажується командою nft -f файл, перегляд — nft list ruleset.

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

Section titled “Політика «за замовчуванням заборонено»”

Можна дозволяти все й перелічувати заборонене (policy accept плюс правила drop) — так ви ніколи не закриєте всіх небезпечних шляхів, бо їх не всі знаєте. Або заборонити все й перелічувати дозволене: policy drop на базовому ланцюжку, а далі лише те, що потрібно. Помилка в такому файрволі робить щось недоступним, що швидко помічають і виправляють, а не відкриває тихо. У прикладі вище input і forward закриті політикою, output відкритий, бо хост довіряє власним процесам (на суворіших вузлах закривають і його).

Перше правило в обох ланцюжках — ct state established,related accept. Без нього файрвол довелося б писати у двох напрямках: дозволити запит клієнта на порт 80 і окремо відповіді сервера з порту 80 на довільний порт клієнта. Так працювали ранні фільтри пакетів без пам’яті (stateless), і вони були або дірявими, або виснажливими. Відстеження з’єднань (connection tracking, conntrack) дає ядру таблицю з’єднань. Для кожного пакета відомо, чи належить він до вже відомого з’єднання, а правила вказують лише напрямок ініціювання.

Стани ct state:

  • new: перший пакет, що відкриває з’єднання (для TCP це SYN);
  • established: з’єднання бачили в обидва боки;
  • related: новий потік, пов’язаний зі старим (ICMP-помилка про наше з’єднання, вторинне з’єднання протоколу на кшталт FTP);
  • invalid: пакет, який не вкладається в жоден відомий стан (запізнілий сегмент, порушення послідовності).

Ось як виглядає таблиця після одного запиту клієнта:

Terminal window
curl http://10.16.2.2/ # з клієнта
conntrack -L # на файрволі
tcp 6 114 TIME_WAIT src=10.16.1.2 dst=10.16.2.2 sport=59938 dport=80 src=10.16.2.2 dst=10.16.1.2 sport=80 dport=59938 [ASSURED] mark=0 use=1
icmp 1 29 src=10.16.1.2 dst=10.16.2.2 type=8 code=0 id=13055 src=10.16.2.2 dst=10.16.1.2 type=0 code=0 id=13055 mark=0 use=1

Кожен запис має два набори адрес: «туди» і «назад». Він зберігає, як мала б виглядати відповідь, і тому відповідь пропускають без окремого правила. Число після 6 (114) — секунди до забування запису. Для UDP, де з’єднання немає, conntrack теж веде записи за таймером, типово 30 секунд без відповіді, а для встановленого TCP-з’єднання за замовчуванням аж п’ять діб (nf_conntrack_tcp_timeout_established), щоб тихе з’єднання не розірвалося між пакетами.

Якщо це правило прибрати, запит проходить, а відповідь сервера в forward не відповідає жодному дозволу й відкидається політикою. Клієнт бачить лише таймаут, а лічильник правила для порту 80 зростає: проходить лише SYN, що щоразу повторюється.

Terminal window
nft delete rule inet filter forward handle 10 # прибрали established (handle видно в nft -a list ruleset)
curl -m3 http://10.16.2.2/ # rc=28: таймаут

Таблиця conntrack — скінченний ресурс (nf_conntrack_max, типово десятки чи сотні тисяч записів залежно від пам’яті), і це слабке місце: надмір потоків, зокрема підроблених, її заповнює, а нові з’єднання відкидаються. Про це далі й у модулі 9.

Діагностика: де зник пакет

Section titled “Діагностика: де зник пакет”

Коли «правило є, а не працює», найкорисніше запитати в самого ядра. Почніть із лічильників: додайте counter до правила й подивіться, чи зростає він. Якщо цього мало, вмикайте трасування. Правило з meta nftrace set 1 на ранньому хуці вмикає запис кожного кроку пакета, який nft monitor trace показує в реальному часі:

Terminal window
nft add table inet trace
nft "add chain inet trace pre { type filter hook prerouting priority -301; }"
nft add rule inet trace pre ip saddr 10.16.1.2 icmp type echo-request meta nftrace set 1
nft monitor trace # в іншому терміналі
trace id 12772c1f inet trace pre packet: iif "m16fw-c" ... ip saddr 10.16.1.2 ip daddr 10.16.2.2 ip ttl 64 ...
trace id 12772c1f inet trace pre rule ip saddr 10.16.1.2 icmp type echo-request meta nftrace set 1 (verdict continue)
trace id 12772c1f inet trace pre policy accept
trace id 12772c1f inet filter forward packet: iif "m16fw-c" oif "m16fw-s" ... ip saddr 10.16.1.2 ip daddr 10.16.2.2 ...
trace id 12772c1f inet filter forward rule icmp type echo-request counter packets 1 bytes 84 accept (verdict accept)

Тут видно весь шлях: пакет пройшов prerouting, потім став транзитним, і в forward його впіймало конкретне правило з вердиктом accept. Пріоритет -301 потрібен, щоб трасувальне правило стояло перед усіма іншими на цьому хуці. Після налагодження таблицю trace видаліть.

Обидва відкидають пакет. drop мовчить: клієнт нічого не чує й чекає таймаут. reject повертає відповідь (TCP RST або ICMP «port unreachable»), і клієнт дізнається одразу.

Terminal window
nc -zv -w3 10.16.2.2 8080 # політика drop
# nc: connect to 10.16.2.2 port 8080 (tcp) timed out
nft add rule inet filter forward tcp dport 8080 reject with tcp reset
nc -zv -w3 10.16.2.2 8080 # те саме, але з reject
# nc: connect to 10.16.2.2 port 8080 (tcp) failed: Connection refused (за 5 мс)

Для внутрішньої мережі reject зручніший: застосунок одразу бачить помилку й не висить на таймауті. Для зовнішньої зазвичай обирають drop: сканер не отримує підтвердження, що хост живий, і витрачає час на кожен порт. Проте не переоцінюйте цю «невидимість»: вона лише ускладнює розвідку, а захищає вас закритий порт, а не мовчання.

Сканер шле SYN на кожен порт цілі. Відповідь SYN+ACK означає «відкрито», RST означає «закрито», а мовчання означає «відфільтровано» (у файрволі політика drop). Саме по собі сканування нічого не ламає: воно лише збирає карту того, що доступне. Захищає від нього мінімум відкритих портів: усе, що ніхто не просив, закрите політикою. Обмеження швидкості нових з’єднань (ct state new limit rate) лише уповільнює перебір, а повністю його не зупиняє.

Звичайний connect() починається з SYN (модуль 11). Отримавши його, сервер для кожного з’єднання в процесі встановлення резервує місце в черзі напіввідкритих з’єднань і чекає на третій пакет рукостискання. Якщо слати тисячі SYN із підроблених адрес, де нікого не чекає відповіді SYN+ACK, черга заповнюється, і справжні клієнти отримують відмову.

Захист із ядра — SYN cookies. Коли черга переповнюється, сервер перестає зберігати стан: у SYN+ACK він кодує параметри з’єднання в початковому номері послідовності (це і є cookie), а сам нічого не пам’ятає. Справжній клієнт відповість ACK із числом, яке містить це значення, а відповідь підробленого джерела не прийде ніколи й нічого не коштуватиме. Ціна в тому, що під cookie не можна повністю погодити деякі параметри TCP (частина опцій втрачається), тому механізм вмикається лише при переповненні.

Перевіримо. Слухач із мізерною чергою (listen(1)) у просторі імен сервера, а з файрвола надсилаємо 500 SYN з випадкових підроблених адрес. Для цього потрібен сирий сокет (IPPROTO_RAW) і вручну пораховані контрольні суми; ось скрипт syn.py:

import socket, struct, random, sys
def csum(b):
if len(b) % 2: b += b'\0'
s = sum(struct.unpack('!%dH' % (len(b) // 2), b)); s = (s >> 16) + (s & 0xffff); s += s >> 16
return ~s & 0xffff
dst, port, n = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
for i in range(n):
src = '10.99.%d.%d' % (random.randint(0, 255), random.randint(1, 254))
tcp = struct.pack('!HHIIBBHHH', random.randint(1024, 65535), port, random.getrandbits(32), 0, 5 << 4, 0x02, 64240, 0, 0)
ph = socket.inet_aton(src) + socket.inet_aton(dst) + struct.pack('!BBH', 0, 6, len(tcp))
tcp = tcp[:16] + struct.pack('!H', csum(ph + tcp)) + tcp[18:]
ip = struct.pack('!BBHHHBBH4s4s', 0x45, 0, 20 + len(tcp), random.getrandbits(16), 0, 64, 6, 0, socket.inet_aton(src), socket.inet_aton(dst))
ip = ip[:10] + struct.pack('!H', csum(ip)) + ip[12:]
s.sendto(ip + tcp, (dst, 0))

Слухач, що нічого не приймає, запускають у просторі імен сервера (Python: s.bind(('10.16.2.2', 8081)); s.listen(1) і далі time.sleep(60)), а потім:

Terminal window
ip netns exec srv sysctl net.ipv4.tcp_syncookies
ip netns exec fw python3 syn.py 10.16.2.2 8081 500 # 500 SYN зі спуфінгом
ip netns exec srv nstat -az TcpExtSyncookiesSent TcpExtTCPReqQFullDoCookies TcpExtTCPReqQFullDrop
ip netns exec fw nc -zv -w3 10.16.2.2 8081 # справжній клієнт
net.ipv4.tcp_syncookies = 1
TcpExtSyncookiesSent 496
TcpExtTCPReqQFullDoCookies 496
TcpExtTCPReqQFullDrop 0
Connection to 10.16.2.2 8081 port [tcp/tproxy] succeeded!

З tcp_syncookies=1 ядро відправило 496 cookie, жоден SYN не відкинуто, і справжній клієнт підключився. Тепер вимкнемо механізм (sysctl -w net.ipv4.tcp_syncookies=0) і повторимо з новим слухачем:

TcpExtTCPReqQFullDrop 496
nc: connect to 10.16.2.2 port 8082 (tcp) timed out: Operation now in progress

Тепер 496 SYN відкинуті як переповнення черги, і клієнт не підключився: це і є відмова в обслуговуванні. Зверніть увагу й на conntrack на файрволі: кожен підроблений SYN лишив у ньому запис SYN_SENT ... [UNREPLIED]. Якщо файрвол стоїть перед сервером, він сам стає вузьким місцем, поки ці записи не протухнуть, тому для великих потоків використовують правила, що не створюють стану (notrack у raw), або окремі пристрої.

Справжні DDoS-атаки вимірюються гігабітами, і на вашому ядрі їх не зупинити: канал забито раніше, ніж сервер встигне щось відкинути. Там допомагають провайдерська фільтрація й спеціалізовані сервіси, а не nftables.

Підміна адреси відправника (спуфінг)

Section titled “Підміна адреси відправника (спуфінг)”

IP-адреса відправника в заголовку нічим не захищена: її просто пишуть. Пряма відповідь піде на підроблену адресу, тому спуфінг корисний, коли відповідь не потрібна (SYN flood) або, навпаки, має прийти жертві (ампліфікація нижче).

Захищаються від цього на вході в мережу, перевіряючи, чи могла адреса відправника звідти прийти. У ядрі для цього є зворотна перевірка маршруту (reverse path filter): якщо відповідь на цю адресу пішла б через інший інтерфейс, пакет підозрілий. У nftables це виражає fib:

Terminal window
nft add rule inet raw pre fib saddr . iif oif missing counter drop

Правило відкидає пакет, для адреси відправника якого немає маршруту назад через вхідний інтерфейс. На тесті 50 SYN із випадкових адрес 10.99.x.x, яких файрвол не знає, уся партія потрапила під нього: counter packets 50 bytes 2000. Справжній трафік із 10.16.1.2 проходить. У промислових мережах те саме називають BCP 38, або фільтрацією на вході, і чим частіше провайдери її вмикають, тим менше спуфінгу в інтернеті.

Тут спуфінг поєднують із протоколом, у якому відповідь сервера набагато більша за запит. Зловмисник шле дрібний запит до відкритого DNS- чи NTP-сервера (або memcached) з підробленою адресою жертви як відправника. Сервер чесно відповідає, але жертві й у набагато більшому обсязі: в окремих протоколів відповідь більша за запит у десятки разів, а в найгірших випадках і значно більше. Так зловмисник множить власну смугу, а жертва отримує відповіді, яких не просила й які нема за чим відфільтрувати.

Проти цього працює лише фільтрація спуфінгу на вході (вона взагалі не дасть таких запитів) і налаштування власних серверів так, щоб вони не відповідали кому завгодно: рекурсивний DNS лише для власних клієнтів, обмеження швидкості, відповіді не більші за запит. «Відкритий рекурсивний резолвер» (модуль 13) через це й вважається помилкою налаштування.

VPN (virtual private network) пересилає пакети IP між двома мережами поверх третьої, яку ви не контролюєте, упаковуючи їх у шифрований тунель: внутрішній пакет шифрується і стає тілом зовнішнього, що йде через інтернет. Зв’язок виглядає як звичайний мережевий інтерфейс із власною адресою, і через нього ходять звичайні маршрути. Застосунки про тунель нічого не знають.

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

WireGuard з’явився в ядрі Linux з версії 5.6, і простота в ньому навмисна: замість довгого переліку опцій і переговорів про алгоритми він має один фіксований набір криптографії (Curve25519 для обміну ключами, ChaCha20-Poly1305 для шифрування, BLAKE2s для хешів, протокол рукостискання Noise). Якщо виявиться вразливість у примітиві, замінюють версію протоколу, а не домовляються наново.

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

Terminal window
wg genkey | tee private.key | wg pubkey > public.key

Інтерфейс як інтерфейс. wg0 — звичайний мережевий пристрій ядра: він має адресу, до нього ведуть маршрути, його видно в ip a. Тунель не постійне з’єднання, а перелік «довірених співрозмовників» (peer). Кожен записаний у конфігурації публічним ключем, необов’язковою адресою endpoint (IP:порт, куди слати) і списком AllowedIPs.

AllowedIPs — це і маршрут, і фільтр. WireGuard визначає, кому слати пакет, за цим полем, а не за конфігурацією маршрутизації ОС.

AllowedIPs у WireGuard: маршрут на відправленні, фільтр на прийняттітаблиця інтерфейсу wg0AllowedIPspeer (відкритий ключ)10.8.0.2/32, 192.168.20.0/24peer B (KB…=)10.8.0.3/32peer C (KC…=)відправленняадресу призначення пакета шукають у таблиці: знайшли, то шифрують ключем цього peerі шлють на його endpoint; не знайшли, то пакет відкидаютьприйняттяпакет розшифровано й автентифіковано певним peer; внутрішня адреса відправникамусить входити в AllowedIPs саме цього peer, інакше пакет відкидають
Одна таблиця «префікс до ключа» обслуговує два напрямки. На відправленні за адресою призначення вибирається peer, і це функція таблиці маршрутів. На прийнятті внутрішню адресу відправника порівнюють з AllowedIPs того peer, який пакет автентифікував: це функція списку доступу.

Це називають cryptokey routing. Пакет, який ви відправляєте на 10.8.0.3, шукають серед усіх AllowedIPs; знайшли peer C, то шифрують ключем C і шлють на його endpoint. У зворотний бік пакет приходить по UDP, розшифровується і перевіряється автентичність відправника (тільки володар приватного ключа peer міг його створити), а потім внутрішня адреса відправника має потрапити в AllowedIPs саме цього peer. Інакше пакет відкидається. Тому скомпрометований peer B не може надіслати пакет з адресою peer C, бо його AllowedIPs цього не охоплюють. Окремих правил файрвола для цього писати не треба: фільтр адрес уже вбудований.

Ось мінімальний тунель між двома «офісами» (прикладна адресація: 10.8.0.1 і 10.8.0.2 на тунелі; 192.168.10.0/24 і 192.168.20.0/24 за ним). Лабораторна B4 збирає саме таку схему в просторах імен.

Terminal window
# вузол A
ip link add wg0 type wireguard
wg set wg0 private-key ./a.key listen-port 51820 \
peer <ПУБЛІЧНИЙ_КЛЮЧ_B> allowed-ips 10.8.0.2/32,192.168.20.0/24 endpoint 198.51.100.2:51820
ip addr add 10.8.0.1/24 dev wg0
ip link set wg0 up
ip route add 192.168.20.0/24 dev wg0 # не обов'язково: wg-quick робить це за AllowedIPs
Terminal window
wg show
interface: wg0
public key: <ключ A>
private key: (hidden)
listening port: 51820
peer: <ключ B>
endpoint: 198.51.100.2:51820
allowed ips: 10.8.0.2/32, 192.168.20.0/24
latest handshake: 14 seconds ago
transfer: 1.21 KiB received, 1.05 KiB sent

Деякі особливості, які відрізняють WireGuard від решти:

  • Мовчить без запрошення. На пакет від невідомого ключа він не відповідає нічим, тож сканер бачить порт закритим.
  • Без стану сеансу. latest handshake — не «з’єднання встановлено», а час останнього обміну ключами. Ключі оновлюються автоматично, а нової сесії не потрібно. Поки немає трафіку, не летить нічого.
  • Роумінг. Якщо клієнт змінив IP (телефон перейшов на іншу мережу), сервер оновлює endpoint за останнім автентифікованим пакетом.
  • За NAT. Щоб пакети від сервера знаходили шлях назад через NAT клієнта, клієнт може слати порожні пакети раз на кілька секунд (PersistentKeepalive).
  • MTU. Інкапсуляція збільшує пакет, тож MTU тунелю менше за MTU каналу (у wg-quick за замовчуванням 1420). Path MTU розбирали в модулі 6.

IPsec — старіша й ширша родина протоколів, яка вбудована в переважну більшість мережевого обладнання, тому її й досі часто вимагають для з’єднання з чужими маршрутизаторами. Пакети в ній шифрує й автентифікує ESP (RFC 4303): це окремий протокол IP із номером 50, тож у tcpdump такий пакет видно як ESP(spi=…), а не як TCP чи ICMP. Перед тим сторони мають домовитися про алгоритми й ключі та переконатися, що говорять саме одна з одною (сертифікатами чи заздалегідь узгодженим спільним ключем, PSK). Є два режими: транспортний (шифрується лише вміст пакета) і тунельний (пакет інкапсулюється цілком, це і є «VPN»).

Порівняно з WireGuard, IPsec гнучкіший, але величезний за кількістю комбінацій параметрів, і саме неузгодженість налаштувань двох кінців найчастіше ламає з’єднання. У Linux пакети шифрує ядровий механізм XFRM, його стан показують ip xfrm state і ip xfrm policy. Для цього курсу досить знати, що він є і що під ним лежать протокол ESP та домовленість про параметри.

Проксі й VPN часто плутають, хоч працюють на різних рівнях.

Проксі (HTTP, SOCKS) VPN
Рівень Прикладний, для конкретного застосунку Мережевий, для всього трафіку через інтерфейс
Кому треба знати Застосунок налаштований на проксі Застосунок нічого не знає
Що переноситься З’єднання (TCP-потоки, запити) IP-пакети, зокрема UDP та ICMP
Шифрування Залежить від протоколу Є за визначенням

Обидва переносять довіру з мережі на вузол, що стоїть посередині. Комерційний VPN не робить вас анонімним: провайдер замінюється на VPN-сервіс, який бачить те саме, що бачив провайдер. Він захищає від локальної мережі й провайдера, але не від сайту, на який ви заходите, і не від власного браузера.

Усі приклади з файрвола відтворюються в трьох просторах імен: клієнт, файрвол із ip_forward=1, сервер. Складіть їх руками, як у модулі 3 чи B2, і працюйте з файрвола:

Terminal window
ip netns exec fw nft -f fw.nft
ip netns exec fw nft list ruleset
ip netns exec fw conntrack -L
ip netns exec fw nft list chain inet filter forward # лічильники

Що дивитися:

  • nft list ruleset разом із лічильниками правил показує, яке правило ловить трафік.
  • conntrack -L і conntrack -E (потік подій) показують записи з’єднань у реальному часі. Пакет conntrack ставиться окремо.
  • nft monitor trace із правилом meta nftrace set 1 відповідає, який саме ланцюжок і правило вирішили долю пакета.
  • ss -ltn показує, на яких адресах слухають процеси (перша лінія захисту: те, що не слухає, атакувати нічим), а Recv-Q слухача — довжину черги прийнятих з’єднань.
  • nstat -az | grep -i -E 'syn|listen' показує лічильники ядра: TcpExtSyncookiesSent, TcpExtListenOverflows, TcpExtListenDrops (модуль 3).
  • sysctl net.netfilter.nf_conntrack_max і nf_conntrack_count для стеження за заповненням таблиці.

Побачити, що проходить крізь правила, корисно й tcpdump на обох боках файрвола: пакет, що є на вході й нема на виході, відкинуто всередині.

Типові помилки розуміння

Section titled “Типові помилки розуміння”

«Закрив порт у файрволі, значить, сервіс недоступний». Ні: ви закрили його на одному хуці одного ланцюжка. Сервіс, який слухає 0.0.0.0, доступний через усі інтерфейси, і шлях, що оминає ваш ланцюжок (інша таблиця, IPv6, інший інтерфейс), лишається відкритим. Надійніше не слухати там, де не треба.

«accept у моєму ланцюжку гарантує, що пакет пройде». accept закінчує лише цей ланцюжок. Інший ланцюжок на тому самому хуці (з іншого пріоритету чи з іншої таблиці) ще може відкинути пакет.

«Правила в input захищають мережу за цим файрволом». Трафік через файрвол іде в forward, а не в input. input стосується лише самої машини.

«NAT — це файрвол». NAT змінює адреси й створює записи conntrack. Те, що ззовні не можна відкрити нове з’єднання до приватної адреси, — лише побічний ефект, і ненадійний: усе залежить від маршрутизації й конфігурації. Захищають правила forward.

«SYN cookies роблять сервер невразливим до SYN flood». Вони знімають проблему переповнення черги напіввідкритих з’єднань. Канал, процесор і conntrack усе ще можуть вичерпатися.

«AllowedIPs у WireGuard — це список адрес, з яких можна підключатися». Це адреси всередині тунелю, які належать цьому peer: маршрут на відправленні й фільтр на прийнятті. Звідки він фізично приходить, визначає endpoint.

«VPN робить мене анонімним». VPN переносить довіру з одного посередника на іншого. Сайт бачить адресу VPN-сервера й усе, що ви самі йому скажете (cookie, вхід в обліковий запис).

Перевір себе

1. Пакет приходить на Linux-маршрутизатор і призначений для хоста за ним. Які хуки netfilter він проходить?
2. Чому в обох ланцюжках нашого файрвола стоїть правило ct state established,related accept?
3. Політика drop на ланцюжку input, і ви додали accept для порту 22 лише для адрес 10.16.1.0/24. Що побачить сканер з іншої мережі на порту 22?
4. Як SYN cookies рятують сервер під час SYN flood?
5. Що робить правило fib saddr . iif oif missing drop?
6. У WireGuard peer B автентифікований, але його внутрішня адреса відправника не входить у його AllowedIPs. Що буде з пакетом?
7. Чим VPN принципово відрізняється від HTTP-проксі?

B4. Файрвол і WireGuard: політика nftables «за замовчуванням заборонено» для двох офісів і тунель WireGuard між ними. Тут усе, що розібрано вище, збирається в одну топологію просторів імен.

Матеріал цього модуля знадобиться й у налагодженні зламаної мережі (B7), де одна з несправностей — неправильне правило файрвола.

  • Документація nftables (wiki.nftables.org): «Quick reference», «Netfilter hooks», «Connection tracking system»
  • man nft, man conntrack, man 8 wg, man 8 wg-quick
  • Donenfeld J., WireGuard: Next Generation Kernel Network Tunnel (NDSS 2017), а також документація на wireguard.com
  • RFC 4303 (ESP)
  • RFC 4987 (TCP SYN flooding attacks and common mitigations)
  • BCP 38 (RFC 2827, Network Ingress Filtering)
  • Документація ядра: Documentation/networking/ у вихідному коді Linux