Офісний VPN: дозвіл доступу лише до одного сервера/порту (найменші привілеї)

Було корисно?

Офісний VPN працює. Усіх влаштовує. Поки хтось не виявляє, що «доступ через VPN» фактично означає «приватна траса до всієї корпоративної мережі».
Потім о 00:30 ви отримуєте повідомлення: «Чому підрядник має доступ до бази даних?» — і розумієте, що ви збудували тунель, а не огорожу.

Мета тут надзвичайно проста: як тільки користувач підключається до VPN, він має мати доступ саме до одного сервера на самому одному порту.
Не «ідеально». Не «якщо не знає підмережу». Саме так. Усе інше — потенційна помилка, що чекає на календарне запрошення.

Що насправді означає «один сервер/порт лише»

Принцип найменших привілеїв у VPN — це не «користувачі бачать тільки ту підмережу, яка їм потрібна». Це ввічлива вигадка. Підмережі — для маршрутизації; привілеї — для безпеки.
Коли ви кажете «один сервер/порт лише», ви насправді маєте на увазі:

  • Клієнт VPN отримує IP (або ні), але надіятись тільки на маршрутизацію для примусового застосування доступу не можна.
  • Шлюз VPN і/або цільовий сервер застосовують правила дозволу і за замовчуванням відхиляють все інше.
  • Усі інші призначення й порти блокуються, включно з «внутрішніми» сервісами: DNS, SMB, SSH, RDP і метрик-ендпоїнтами.
  • Є спостережуваність: логи, лічильники і спосіб довести, що й чому було заблоковано.

Антипатерн — це «ми просто штовхаємо один маршрут». Це не найменші привілеї; це рекомендація. Якщо клієнт може вручну додати маршрути,
або якщо є NAT, або другий інтерфейс на VPN-хості — ви отримаєте непередбачений доступ.

Сильний дизайн зазвичай включає два контрольні плаcи:
(1) мережеві контролі (firewall/маршрутизація на вході VPN), та
(2) сервісні контролі (firewall/автентифікація на призначенні).
Якщо ви робите тільки одне, з часом ви дізнаєтесь, навіщо роблять інше.

Цікаві факти й трохи історії

  • VPN не створювалися як інструмент zero-trust. Ранні корпоративні уявлення про VPN припускали, що «всередині тунелю» — це «всередині офісу». Ця припущення погано збереглося в часі.
  • IPsec існує з часів до сучасних практик безпеки. Він походить з епохи, коли периметрові firewall були основним інструментом, а не внутрішня сегментація.
  • Split tunneling завжди був суперечливим. Воно зменшує навантаження на корпоративну мережу, але створює кінцеві точки зі змішаною довірою (домашній Wi‑Fi + корпоративний тунель).
  • Фаєрволи навчилися станності, щоб вирішити реальні проблеми. Безстанні ACL робили «allow TCP/443» складним без дозволу трафіку-відповіді; stateful-tracking це вирішив.
  • «Користувач VPN = мережевий користувач» — історична спрощеність. Це було зручно оперативно, коли системи ідентифікації були простішими, а віддалена робота — рідкістю.
  • WireGuard має надзвичайно компактний дизайн. Його мінімалізм спростив аудит і розгортання порівняно з розрісшимися VPN-стеками, і це вплинуло на вибір за замовчуванням.
  • NAT став «костилем безпеки». Люди ставилися до NAT як до ізоляції; це не так. NAT — це трансляція адрес, а не авторизація.
  • Мікросегментація не починалася в хмарі. ЦОДи робили це VLAN та ACL задовго до того, як «zero trust» став модним словосполученням.

Модель загроз: що ви запобігаєте (і що — ні)

Ви намагаєтесь запобігти латеральному руху від клієнта VPN у решту мережі. Це включає:

  • Сканування портів внутрішніх діапазонів («просто перевіряю, що там»).
  • Доступ до адміністративних інтерфейсів: SSH, RDP, консоль типу vCenter, BMC, управління NAS, принтери (так), і будь-які веб-інтерфейси.
  • Використання внутрішнього DNS для виявлення імен, які ви не мали на меті публікувати.
  • Доступ до метаданих сервісів, внутрішніх реєстрів або моніторингових ендпоїнтів, що можуть витікати топологію та облікові дані.

Ви не вирішуєте:

  • Скомпрометовані облікові дані для єдиного дозволеного сервісу. (Там потрібна сильна автентифікація.)
  • Шкідливе ПЗ на кінцевій точці, яке ексфільтрує дані через дозволений порт. (Потрібні DLP/моніторинг і контролі на боці додатка.)
  • Інспекцію трафіку на глибокому контентному рівні. (Це окрема система з власними експлуатаційними витратами.)

Одна цитата, щоб зберегти чесну позицію. Як казав Bruce Schneier: Security is a process, not a product. Ви не «налаштовуєте найменші привілеї» один раз.
Ви підтримуєте їх у процесі, поки вимоги змінюються і персонал змінюється.

Короткий жарт #1: Якщо ви дозволяєте «будь-які внутрішні» з VPN, ви не робите безпеку — ви граєте в офісний косплей.

Три патерни, які справді працюють

Патерн A: Шлюз VPN застосовує суворий список дозволів (найпоширеніший)

Концентратор VPN (або Linux-хост з WireGuard/OpenVPN) діє як контрольна точка. Клієнти VPN опиняються на виділеному інтерфейсі/підмережі.
Політика firewall каже: з VPN-підмережі → дозволити до target_ip:target_port, відхилити все інше.

Переваги: проста ментальна модель, централізований контроль, хороший логінг. Недоліки: якщо шлюз помилково налаштований або обхід можливий (dual-homed шляхи, hairpinning),
ви можете випадково відкрити більше, ніж планували.

Патерн B: Жодного загального мережевого доступу; опублікуйте сервіс через проксі

Замість того, щоб дозволяти клієнтам VPN напряму звертатися до цільового сервера, ви відкриваєте сервіс через зворотний проксі (для HTTP/HTTPS) або TCP-проксі
(для не-HTTP). Клієнт VPN може звертатися лише до проксі; тільки проксі має доступ до цілі.

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

Патерн C: Розмістіть ціль у виділеній «VPN-сервісній мережі»

Створіть ізольований сегмент (VLAN/VRF), де живе цільовий сервер, з суворими вхідними правилами. Клієнти VPN можуть маршрутизувати лише в цей сегмент,
і цей сегмент не може широко маршрутизувати назад.

Переваги: сильне зменшення blast-radius. Недоліки: більше мережевого інжинірингу, і легко помилитися з маршрутами зворотного шляху та звинуватити «VPN».

Мій упереджений погляд: Патерн A плюс обмеження на самому сервері — це базовий рівень. Якщо ви можете реалізувати Патерн B без сліз у команди додатка, робіть це.
Патерн C відмінний, коли у вас є серйозна мережна інфраструктура і дисципліна змін.

Реалізація: WireGuard + firewall (рекомендовано)

WireGuard популярний через простоту експлуатації. Але сам WireGuard не є рушієм контролю доступу.
AllowedIPs — це передусім концепт маршрутизації/асоціації peer, а не «політика безпеки, яку неможливо обійти».
Розглядайте це як корисну інфраструктуру, а політику застосовуйте правилами firewall там, де це важливо.

Дизайн: виділена VPN-підмережа та одна дозволена ціль

  • Інтерфейс VPN: wg0
  • VPN-підмережа: 10.50.0.0/24
  • Цільовий сервер: 10.20.30.40
  • Дозволений порт: 5432/tcp (приклад: Postgres)

Конфіг WireGuard (сервер)

Тримайте конфіг сервера нудним. Нудне — добре. Нудне означає, що вашому майбутньому «я» не доведеться розбиратися в «хитрощах».

cr0x@server:~$ sudo sed -n '1,120p' /etc/wireguard/wg0.conf
[Interface]
Address = 10.50.0.1/24
ListenPort = 51820
PrivateKey = (redacted)
# Optional: save config on runtime changes
SaveConfig = false

[Peer]
PublicKey = (redacted)
AllowedIPs = 10.50.0.10/32

Важливо: Те, що у peer вказано AllowedIPs = 10.50.0.10/32, означає «цей peer володіє тим VPN-IP».
Це не запобігає тому, щоб peer намагався звертатися до 10.20.30.40, щойно тунель піднято. Це — завдання firewall.

Політика firewall з nftables (переважно на сучасному Linux)

Чистий підхід: за замовчуванням відхилити з VPN → куди завгодно, а потім відкрити одну конкретну дірку. Також дозволяйте встановлені/пов’язані відповіді.
І логувати відкидання з обмеженням частоти, щоб не влаштувати DoS на власні диски.

cr0x@server:~$ sudo nft list ruleset
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    iif "lo" accept
    ct state established,related accept
    iif "wg0" tcp dport 22 accept
    iif "eth0" tcp dport 51820 accept
    ip protocol icmp accept
    counter drop
  }

  chain forward {
    type filter hook forward priority 0; policy drop;
    ct state established,related accept

    # Allow VPN clients to reach exactly one server/port
    iif "wg0" ip saddr 10.50.0.0/24 ip daddr 10.20.30.40 tcp dport 5432 accept

    # Optional: allow VPN clients to reach only the VPN gateway DNS forwarder
    # iif "wg0" ip saddr 10.50.0.0/24 ip daddr 10.50.0.1 udp dport 53 accept
    # iif "wg0" ip saddr 10.50.0.0/24 ip daddr 10.50.0.1 tcp dport 53 accept

    # Log and drop everything else from VPN
    iif "wg0" limit rate 10/second burst 20 log prefix "VPN-DROP " flags all counter drop
    counter drop
  }

  chain output {
    type filter hook output priority 0; policy accept;
  }
}

На що звернути увагу:

  • Політика drop для chain forward: VPN-бокс за замовчуванням не маршрутизатор. Це контролер на вході з журналом.
  • Правило дозволу повністю кваліфіковане: інтерфейс, вихідна підмережа, IP призначення, протокол і порт.
  • Ми логгируем відхилення з wg0 з обмеженням частоти. Без обмеження один нудьгуючий інтерн з nmap зробить проблему зберігання логів.

Маршрутизація та NAT: уникайте «допоміжного» NAT, якщо він не потрібен

Якщо ваш цільовий сервер вже маршрутизує назад до VPN-підмережі через VPN-шлюз, NAT не потрібен. Це чистіше і краще для аудиту.
NAT може приховати ідентичність клієнта від цільового сервера і ускладнити серверні allowlist-и.

Якщо ви не можете змінити маршрути на стороні цілі, можете використати source NAT на шлюзі VPN, щоб ціль відповідав шлюзу.
Але ставтесь до цього як до компромісу і логгируйте такі випадки.

cr0x@server:~$ sudo nft list table ip nat
table ip nat {
  chain postrouting {
    type nat hook postrouting priority 100; policy accept;
    oif "eth0" ip saddr 10.50.0.0/24 ip daddr 10.20.30.40 tcp dport 5432 masquerade
  }
}

Рішення: якщо ви бачите правила masquerade, що застосовуються до широких призначень (наприклад «any»), звузьте їх. NAT — не перепустка.

Реалізація: OpenVPN + firewall (досі поширено)

OpenVPN перевірений бойовий інструмент і всюди присутній. Та сама ідея: OpenVPN штовхає маршрути; firewall примушує доступ.
OpenVPN також підтримує per-client config (CCD), яким часто користуються як сегментацією. Це допомагає, але не замінює правила firewall.

Форма конфігу сервера OpenVPN

cr0x@server:~$ sudo sed -n '1,160p' /etc/openvpn/server/server.conf
port 1194
proto udp
dev tun0
server 10.60.0.0 255.255.255.0
topology subnet
persist-key
persist-tun
keepalive 10 60
user nobody
group nogroup

# Don't push broad routes unless you mean it
;push "route 10.0.0.0 255.0.0.0"

# Optionally push a single host route (still not a security boundary)
push "route 10.20.30.40 255.255.255.255"

client-config-dir /etc/openvpn/ccd

Приклад CCD (для конкретного клієнта)

cr0x@server:~$ sudo cat /etc/openvpn/ccd/alice
ifconfig-push 10.60.0.10 255.255.255.0

Добра гігієна. Але все ще не примус. Примус — це firewall у FORWARD або відповідній nftables-ланці.

Приклад iptables (старий, але ще зустрічається)

cr0x@server:~$ sudo iptables -S FORWARD
-P FORWARD DROP
-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A FORWARD -i tun0 -s 10.60.0.0/24 -d 10.20.30.40/32 -p tcp -m tcp --dport 5432 -j ACCEPT
-A FORWARD -i tun0 -j LOG --log-prefix "VPN-DROP " --log-level 4
-A FORWARD -i tun0 -j DROP

Рішення: якщо політика не DROP — виправте. Якщо ви бачите -A FORWARD -i tun0 -j ACCEPT — це правило «відкритий бар». Приберіть його.

Контролі на боці сервера: вважайте, що VPN може брехати

Якщо цільовий сервер важливий, встановіть firewall на ньому також. Шлюз VPN — це чудова одна контрольна точка — поки він не перестає нею бути.
Неправильне правило, друга інстанція VPN, аварійна зміна або виняток у cloud security group можуть обійти ваш шлюз.

На цілі: дозволяйте сервіс лише з VPN-підмережі

Приклад: ціль — 10.20.30.40, сервіс — Postgres 5432/tcp. Дозвольте тільки з VPN-підмережі, інші — відхилити.

cr0x@server:~$ sudo nft list ruleset
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    iif "lo" accept
    ct state established,related accept

    # Allow Postgres only from VPN subnet
    ip saddr 10.50.0.0/24 tcp dport 5432 accept

    # Allow admin SSH only from a management subnet (example)
    ip saddr 10.1.2.0/24 tcp dport 22 accept

    limit rate 5/second burst 10 log prefix "TARGET-DROP " flags all counter drop
  }

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

  chain output {
    type filter hook output priority 0; policy accept;
  }
}

Якщо це веб‑додаток замість БД, можете бути жорсткішими: дозволяти тільки IP проксі/шлюзу, а не всю VPN‑підмережу.

Короткий жарт #2: Найшвидший шлях вивчити «захист в глибину» — покластися тільки на один firewall, а потім зустріти інтерна з sudo.

Практичні завдання: 12+ реальних перевірок з командами, виводом і рішеннями

Ці завдання написані так, ніби ви на виклику і хтось питає: «Чи може користувач VPN дістатися лише до того одного сервісу?»
Кожне завдання містить: команду, реалістичний вивід, що це значить, і яке рішення приймати далі.

Задача 1: Підтвердити, що інтерфейс VPN піднятий і має очікувану підмережу

cr0x@server:~$ ip -brief addr show wg0
wg0             UNKNOWN        10.50.0.1/24

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

Рішення: якщо ім’я інтерфейсу відрізняється (наприклад, wg-office), оновіть правила firewall, щоб вони відповідали реальному інтерфейсу.

Задача 2: Перевірити peers WireGuard і стан handshake

cr0x@server:~$ sudo wg show wg0
interface: wg0
  public key: (redacted)
  listening port: 51820

peer: (redacted)
  endpoint: 203.0.113.10:53422
  allowed ips: 10.50.0.10/32
  latest handshake: 1 minute, 12 seconds ago
  transfer: 18.23 MiB received, 41.77 MiB sent

Значення: тунель активний. Якщо handshake «never», не витрачайте час на політику firewall — виправляйте спочатку доступність/ключі.

Рішення: немає handshake → перевірте UDP‑доступність і ключі; handshake ок → переходьте до маршрутизації й firewall.

Задача 3: Переконатися, що на шлюзі VPN включено IP forwarding (якщо очікується маршрутизація)

cr0x@server:~$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

Значення: ядро дозволяє пересилати пакети між інтерфейсами. Якщо 0 — ваша «список дозволів» ідеально налаштований, але марний.

Рішення: ставте 1 тільки якщо цей хост має маршрутизувати. Якщо ви використовуєте лише проксі‑дизайн, можливо, forwarding має бути вимкнений.

Задача 4: Переглянути політику forward і лічильники в nftables

cr0x@server:~$ sudo nft list chain inet filter forward
table inet filter {
  chain forward {
    type filter hook forward priority filter; policy drop;
    ct state established,related accept
    iif "wg0" ip saddr 10.50.0.0/24 ip daddr 10.20.30.40 tcp dport 5432 accept
    iif "wg0" limit rate 10/second burst 20 log prefix "VPN-DROP " flags all counter drop
    counter packets 1294 bytes 188290 drop
  }
}

Значення: політика за замовчуванням drop присутня, і є явний allow. Лічильники показують, чи трафік потрапляє в відхилення.

Рішення: якщо лічильники drop зростають, коли користувач пробує дозволений сервіс, ваше правило allow неправильне (неправильний IP, порт, інтерфейс або протокол).

Задача 5: Підтвердити маршрут до цільової мережі на шлюзі VPN

cr0x@server:~$ ip route get 10.20.30.40
10.20.30.40 via 10.20.30.1 dev eth0 src 10.20.30.10 uid 0
    cache

Значення: шлюз знає, як дістатися до цілі і який IP використовуватиме як джерело. Якщо маршрут виходить через неправильний інтерфейс — очікування по NAT/firewall ламаються.

Рішення: неправильний шлях → виправте маршрути перед тим, як торкатися firewall. Firewall не лагодять топологію.

Задача 6: Перевірити маршрут звороту (з цілі назад до VPN-підмережі)

cr0x@server:~$ ssh cr0x@10.20.30.40 'ip route get 10.50.0.10'
10.50.0.10 via 10.20.30.10 dev eth0 src 10.20.30.40 uid 0
    cache

Значення: ціль знає, щоб повертати трафік до шлюзу VPN для клієнтів VPN. Якщо ні — ви побачите «SYN sent, no reply».

Рішення: якщо зворотний шлях неправильний і ви не можете його змінити, розгляньте NAT на шлюзі (але тримайте його в суворих межах).

Задача 7: Тест одного дозволеного порту з боку клієнта VPN (за допомогою nc)

cr0x@server:~$ nc -vz 10.20.30.40 5432
Connection to 10.20.30.40 5432 port [tcp/postgresql] succeeded!

Значення: мережевий шлях і правила firewall дозволяють порт. Це не означає, що автентифікація коректна — лише що TCP підключається.

Рішення: якщо це не вдається, перевіряйте відхилення на шлюзі/цілі. Якщо вдається — переконайтеся, що все інше відхилено.

Задача 8: Доведіть відмову: спробуйте заборонений порт на тій же цілі

cr0x@server:~$ nc -vz 10.20.30.40 22
nc: connect to 10.20.30.40 port 22 (tcp) failed: Operation timed out

Значення: таймаут часто означає «блоковано firewall-ом». «Connection refused» означало б, що firewall дозволив трафік, але сервіс не слухає.

Рішення: якщо ви отримуєте «refused» на забороненому порту — ви дозволяєте занадто багато. Виправте правила на шлюзі/цілі.

Задача 9: Доведіть відмову: спробуйте внутрішній хост, який має бути недоступним

cr0x@server:~$ nc -vz 10.20.30.41 5432
nc: connect to 10.20.30.41 port 5432 (tcp) failed: Operation timed out

Значення: ви обмежуєте за IP призначення, а не «будь-який сервер на тому порті». Добре.

Рішення: якщо вдається підключитися — ваше правило allow надто широке (підмережа замість хоста, або пізніше є правило «accept all»).

Задача 10: Перевірити conntrack на незаплановані потоки (щоб помітити «якось підключилось»)

cr0x@server:~$ sudo conntrack -L -p tcp 2>/dev/null | head -n 5
tcp      6 431999 ESTABLISHED src=10.50.0.10 dst=10.20.30.40 sport=49822 dport=5432 src=10.20.30.40 dst=10.50.0.10 sport=5432 dport=49822 [ASSURED] mark=0 use=1
tcp      6 119 SYN_SENT src=10.50.0.10 dst=10.20.30.40 sport=49830 dport=22 src=10.20.30.40 dst=10.50.0.10 sport=22 dport=49830 mark=0 use=1

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

Рішення: якщо бачите встановлені потоки до призначень/портів, які ви не дозволяли — ваша політика протікає. Аудитуйте порядок правил firewall і можливі виключення NAT.

Задача 11: Дивитися логи відхилень в режимі реального часу (без потоплення)

cr0x@server:~$ sudo journalctl -k -f | grep 'VPN-DROP' | head
Dec 28 10:22:01 vpn-gw kernel: VPN-DROP IN=wg0 OUT=eth0 SRC=10.50.0.10 DST=10.20.30.40 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=51123 DF PROTO=TCP SPT=49830 DPT=22 WINDOW=64240 SYN

Значення: firewall активно блокує. Ви отримуєте конкретні докази: вихідний IP, призначення і порт.

Рішення: якщо бачите відхилення до несподіваних внутрішніх IP — ви довели, навіщо потрібні найменші привілеї. Тримайте це заблокованим.

Задача 12: Підтвердити, що цільовий сервер бачить реальні IP клієнтів (або ні)

cr0x@server:~$ ssh cr0x@10.20.30.40 'sudo ss -tnp sport = :5432 | head -n 5'
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB  0      0      10.20.30.40:5432 10.50.0.10:49822 users:(("postgres",pid=1421,fd=7))

Значення: ціль бачить IP клієнта VPN (10.50.0.10). Якщо бачить IP шлюзу VPN — в дії NAT.

Рішення: віддавайте перевагу реальним IP для аудиту і контролів по користувачах. Якщо доводиться NAT, компенсуйте логами шлюзу і сильною ідентифікацією в додатку.

Задача 13: Перевірити поведінку DNS (випадкове виявлення внутрішнього оточення — часте явище)

cr0x@server:~$ resolvectl status | sed -n '1,40p'
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 1.1.1.1
       DNS Servers: 1.1.1.1 8.8.8.8

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

Рішення: якщо ви штовхаєте внутрішній DNS клієнтам VPN, переконайтеся, що ви не дозволяєте доступ до всього, що ці імена розв’язують.

Задача 14: Довести, що правила firewall зберігаються після перезавантаження

cr0x@server:~$ sudo systemctl is-enabled nftables
enabled

Значення: firewall підніметься після рестарту. Якщо disabled — ваша «жорстка політика» може бути тимчасовим дивом.

Рішення: увімкніть її і зберігайте правила в керованому конфігу. Якщо ви робите це вручну на проді — принаймні задокументуйте і зафіксуйте зміни.

Плейбук швидкої діагностики

Коли «користувач VPN не може дістатися до одного дозволеного порту», вам потрібен швидкий спосіб знайти місце блокування без інтерпретаційних танців.
Ось порядок, який витрачає мінімум часу.

1) Підтвердьте, що тунель піднятий (не дебагуйте політику без тунелю)

  • WireGuard: sudo wg show wg0 → шукайте свіжий handshake і зростаючі лічильники переданих даних.
  • OpenVPN: перевірте логи сервера і статус клієнта; переконайтеся, що клієнт отримав IP з очікуваного пулу VPN.

Якщо немає handshake — ви в області «доступність/ключі/автентифікація», а не сегментації.

2) Підтвердьте, що клієнт справді намагається правильне призначення/порт

  • Запустіть nc -vz target port з клієнта.
  • Подивіться, чи час очікування (drop) чи відмова (refuse).

3) Шукайте відхилення спочатку на шлюзі VPN

  • Перегляньте лічильники і логи nftables/iptables для спроби потоку.
  • Якщо шлюз не бачить трафіку, клієнт може не маршрутизувати через тунель або ви тестуєте з неправильного інтерфейсу.

4) Перевірте маршрутизацію і зворотний шлях

  • На шлюзі: ip route get target.
  • На цілі: ip route get vpn_client_ip.

Маршрутизація — причина №1, чому «має працювати» не працює, особливо коли NAT налаштований частково.

5) Нарешті, перевірте локальний firewall і налаштування сервісу на цілі

  • Firewall цілі: підтвердьте, що він дозволяє з VPN-підмережі на порт сервісу.
  • Сервіс: переконайтеся, що він слухає на потрібному інтерфейсі (не лише на localhost).

Типові помилки (симптом → причина → виправлення)

Помилка 1: «Ми штовхнули тільки один маршрут, тож користувачі можуть лише один сервер»

Симптоми: Користувачі можуть дістатися інших внутрішніх IP, додаючи маршрути вручну або через hairpinning/NAT.

Причина: Плутанина між конфігурацією маршрутизації клієнта та авторизацією.

Виправлення: Застосувати allowlist на шляху FORWARD шлюзу VPN (і бажано — на цілі). За замовчуванням — deny. Логувати відхилення.

Помилка 2: Дозволити «VPN підмережа → будь-що» тимчасово, а потім забути

Симптоми: Через тижні аудит виявляє, що користувачі VPN можуть RDP у випадкові сервери. Ніхто не пам’ятає причину.

Причина: Аварійні зміни без відкату або терміну дії.

Виправлення: Обмежуйте винятки часово. Використовуйте управління конфігом і code review для правил firewall. Додавайте алерти на широкі accepts з інтерфейсів VPN.

Помилка 3: Неправильно читати «Connection refused» як «заблоковано»

Симптоми: Ви вірите, що firewall працює, бо тести не проходять, але вони повертають «refused», а не «timeout».

Причина: Firewall дозволяє трафік; сервіс відхиляє його, бо не слухає або є контроль доступу.

Виправлення: Для заборонених шляхів бажані таймаути/падіння (або явні reject, якщо це ваша політика). Перевірте лічильники firewall і ss -lntp на сервері.

Помилка 4: NAT ховає ідентичність клієнта, руйнуючи аудит і серверні контроли

Симптоми: Логи цілі показують підключення від IP шлюзу VPN; ліміти за користувачем і allowlist-и не працюють.

Причина: Широко застосований masquerade, часто як швидке рішення для зворотної маршрутизації.

Виправлення: Віддавайте перевагу реальній маршрутизації. Якщо NAT необхідний, звужуйте його до однієї цілі/порту і компенсуйте логами шлюзу та сильною автентифікацією в додатку.

Помилка 5: Правила firewall у неправильній ланці (INPUT замість FORWARD)

Симптоми: Firewall на шлюзі виглядає суворим, але клієнти VPN все одно дістаються внутрішніх сервісів.

Причина: Правила застосували до INPUT (трафік до самого шлюзу) замість FORWARD (трафік через шлюз).

Виправлення: Розміщуйте allow/deny правила в шляху пересилання. Тримайте INPUT для захисту самого шлюзу.

Помилка 6: IPv6 тихо обходить вашу політику тільки для IPv4

Симптоми: Ви заблокували IPv4, але користувачі все ще мають доступ. Захоплення пакетів показує IPv6 потоки.

Причина: Двоїста стекова мережа, а правила написані лише для IPv4.

Виправлення: Використовуйте table inet в nftables і явно контролюйте IPv6. Або вимкніть IPv6 на VPN‑інтерфейсі, якщо це припустимо.

Помилка 7: DNS витікає внутрішню топологію

Симптоми: Навіть при блокуванні користувачі VPN можуть запитувати внутрішній DNS і переліковувати імена сервісів.

Причина: DNS-сервер доступний з VPN-підмережі, або DNS дозволений «бо не шкідливо».

Виправлення: Не дозволяйте внутрішній DNS без потреби. Якщо потрібно — дозволяйте DNS лише до контролюємого резолвера зі split-horizon та без надмірного розкриття.

Помилка 8: Логувати кожен відкинутий пакет без обмеження частоти

Симптоми: Kernellog заповнює диск; диски заповнюються; реагування на інцидент перетворюється в «чому впав syslog?»

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

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

Чеклісти / покроковий план

Покроковий план: від «відкритого VPN» до «один сервер/порт лише»

  1. Виберіть точку примусу. Використовуйте шлюз VPN як основну контрольну точку. Вирішіть, чи також застосовуватимете обмеження на цілі (рекомендується).
  2. Визначте точний кортеж: джерело (VPN-підмережа), IP призначення, порт призначення, протокол. Запишіть це. Якщо «те, що використовує додаток» — ви ще не закінчили.
  3. Створіть виділену VPN-підмережу. Не змішуйте її з існуючими внутрішніми діапазонами. Сегментація починається з чітких адресних меж.
  4. Встановіть за замовчуванням deny у шляху пересилання. На шлюзі chain forward має бути DROP за замовчуванням.
  5. Додайте одне правило allow. Повністю кваліфіковане. Без підмереж, якщо вимога — одна ціль.
  6. Опрацюйте зворотну маршрутизацію. Віддавайте перевагу маршруту на цілі (або upstream маршрутизатору) назад до VPN‑підмережі через шлюз. NAT — тільки якщо необхідно.
  7. Заблокуйте цільовий сервер. Дозвольте лише VPN‑підмережі (або IP шлюзу/проксі) на порт сервісу.
  8. Вирішіть питання DNS. Якщо клієнту не потрібен внутрішній DNS — не надавайте його. Якщо потрібен — фільтруйте і обмежуйте.
  9. Зробіть систему спостережуваною. Лічильники, лімітувані логи і звичка перевіряти їх після змін.
  10. Тестуйте відмови, а не лише дозволи. Спробуйте підключитися до заборонених портів і хостів і переконайтеся, що це провалюється очікуваним способом.
  11. Забезпечте стійкість конфігурацій. Сервіси systemd увімкнені, конфіги під контролем версій, зміни перевіряються.
  12. Проводьте періодичний перегляд доступу. Вимоги змінюються; ваш firewall не повинен тихо «повзти» разом з ними.

Чекліст змін (для дорослих у кімнаті)

  • Є явне правило allowlist для одного сервера/порту? (Не «розмита» підмережа.)
  • Політика forward за замовчуванням DROP?
  • Логи/лічильники підтверджують, що лише цей потік успішний?
  • Є термін дії на будь-який тимчасовий виняток?
  • Можемо довести зворотну маршрутизацію без широкого NAT?
  • IPv6 опрацьовано свідомо?
  • Цільовий сервер також обмежений?

Три корпоративні історії з практики

Історія 1: Інцидент через неправильне припущення

Середня компанія мала «vendor VPN» для інтеграції з payroll. Постачальнику потрібен був HTTPS‑доступ до одного внутрішнього ендпоїнта.
Мережна команда запхала один маршрут до того хоста і була горда своєю стриманістю. Вони навіть записали це в зміну.

Через кілька місяців внутрішній аудит показав, що акаунт постачальника може дістатися файлового сервера. Ніхто не вірив. Таблиця маршрутів на ноуті постачальника мала чистий вигляд.
Але шлюз VPN також робив NAT, щоб «полегшити життя», а політика forward була надто ліберальною: вона дозволяла VPN-підмережі дістатися внутрішніх мереж, бо
«інакше важко тестувати».

Постачальник ніколи не планував доступ до файлового сервера. Суть у тому, що скомпрометована кінцева точка постачальника мала вимощений шлях до внутрішніх активів.
Припущення було «маршрутизація = обмеження». Реальність: «маршрутизація — пропозиція; firewall — примус».

Виправлення було нудним: default drop у forward, одне правило allow і firewall на сервері. Складніше було визнати організаційно: «ми штовхали один маршрут» — це театральна безпека.

Історія 2: Оптимізація, що повернулась боком

Інша організація хотіла зменшити навантаження на шлюз VPN. Вони ввімкнули split tunneling і штовхнули внутрішній DNS, щоб ноутбуки могли розв’язувати внутрішні сервіси.
Ідея: лише трафік додатка йде через тунель; усе інше залишається локальним. Графіки пропускної здатності покращилися. Усі були задоволені.

Потім з’явився дивний баг: частина користувачів періодично отримувала доступ до «одного дозволеного сервісу», а іноді — таймаути.
Канали інцидентів заповнилися впевненими припущеннями: «WireGuard нестабільний», «база даних перевантажена», «напевно MTU».

Насправді причина була прозаїчна. З split tunneling і внутрішнім DNS клієнти розв’язували внутрішні імена поза мережею,
і деякі ноутбуки мали локальні конфлікти адрес (домашні роутери використовували ті самі приватні діапазони що й корпоративні). Клієнти іноді маршрутизували «внутрішній» трафік локально,
ніколи не входячи в тунель. Отже firewall шлюзу не був у шляху.

Оптимізація зекономила полосу, але створила лотерею маршрутизації. Рішення: фіксувати IP цілі для доступу через VPN, виправити накладки адрес і примусити трафік одного сервісу через тунель. Нудно, передбачувано, правильно.

Історія 3: Нудна, але правильна практика, що врятувала ситуацію

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

Одного дня під час рутинного оновлення OS на шлюзі VPN бекенд firewall змінився. Правила завантажилися, але одне ім’я ланки змінилось,
і їхня політика «лише один порт» фактично перетворилась на «дозволяти все встановлене», з надто широкими accept правилами, доданими старим скриптом.

Ніхто не помітив одразу, бо дозволений сервіс все ще працював. Так ці помилки ховаються: успіх не доводить обмеження.
Але їхній тестовий набір після зміни відразу зреагував. Спроби заборонених підключень перестали таймитись — вони підключалися.

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

FAQ

1) Чи можу я забезпечити «один сервер/порт лише» лише за допомогою WireGuard AllowedIPs?

Ні. AllowedIPs — це маршрутизація та асоціація peer. Це допомагає, але не є межою примусу, яку ви хочете.
Використовуйте правила firewall на шлюзі (а бажано — і на цілі).

2) Краще NAT чи правильна маршрутизація?

Віддавайте перевагу правильній маршрутизації, щоб ціль бачила реальні IP клієнтів і ви могли робити аудит і серверні allowlist-и.
Використовуйте NAT лише якщо не виходить виправити зворотні шляхи, і звужуйте його до однієї цілі/порту.

3) Чи сумісний split tunnel із найменшими привілеями?

Інколи, але легко помилитися. Split tunnel підвищує залежність від правильних маршрутних рішень на клієнті і зменшує вашу здатність централізовано спостерігати шляхи.
Для «один сервер/порт лише» зазвичай чистіше примусити трафік цього сервісу через тунель.

4) Де застосовувати обмеження: на шлюзі, на цілі чи обидва?

Обидва. Примус на шлюзі зменшує blast-radius і дає централізований логінг. Примус на цілі захищає від шляхів обходу і помилкових налаштувань.
Захист у глибину, без маркетингового відтінку.

5) Як обмежити по користувачу, а не лише по VPN-підмережі?

Найпростіший шлях — виділяти IP для кожного користувача і правила, які матимуть ip saddr 10.50.0.10 замість всієї підмережі.
Краще: розмістити сервіс за identity-aware proxy, щоб доступ прив’язувався до автентифікації, а не лише до IP.

6) Щодо доступу до DNS і часу (NTP)?

Не робіть їх дозволом за замовчуванням. Якщо ваш сервіс вказано за IP, можливо вам і не потрібен внутрішній DNS.
Якщо потрібно надати DNS, дозволяйте лише контролюємому резолверу і ставтесь до DNS як до конфіденційних метаданих.

7) Чи краще відхиляти трафік чи скидати його?

Скидання (таймаут) менше розкриває про наявність сервісів, але може уповільнити діагностику.
Відмова (reject) може зробити досвід користувача зрозумілішим. Для сегментації VPN я зазвичай віддаю перевагу drop за замовчуванням з лімітованим логуванням, а explicit reject — лише коли потрібно.

8) Як довести аудиторам, що доступ обмежено?

Покажіть правила firewall (default deny + single allow), лічильники/логи відхилень, і результати тестів:
підключення до дозволеного порту проходить, підключення до заборонених портів/хостів таймиться. Якщо ви не можете швидко показати це — ви фактично не контролюєте доступ.

9) А IPv6 — чи треба цим перейматись?

Так, бо ваші клієнти вже можуть його використовувати. Якщо ви фаєрволите лише IPv4, випадково можна дозволити шляхи по IPv6.
Використовуйте nftables inet таблиці або явно вимкніть IPv6 на VPN‑інтерфейсі, якщо це підходить.

10) Якщо сервіс — HTTPS, чи все одно робити «один порт лише»?

Так, але також розгляньте публікацію сервісу через reverse proxy, де можна застосувати ідентичність, mTLS, логування запитів і ліміти.
Мережеве обмеження необхідне, але недостатнє.

Висновок: наступні кроки, які працюють

«Доступ через офісний VPN» — це не бінарний вибір між «немає доступу» і «все всередині». Ви можете — і повинні — спроектувати доступ до одного сервера і одного порту.
Так виглядають найменші привілеї, коли їх можна виміряти.

  1. Виберіть точку примусування: реалізуйте forwarding з default‑deny на шлюзі VPN.
  2. Напишіть одне точне правило allow: інтерфейс + джерело + IP призначення + протокол + порт.
  3. Перевірте зворотну маршрутизацію: спочатку виправте маршрути; використовуйте суворо звужений NAT лише якщо неможливо інакше.
  4. Додайте обмеження на боці сервера: ціль повинна приймати порт сервісу лише з VPN-підмережі (або IP шлюзу/проксі).
  5. Після кожної зміни доводьте відмову: тестуйте заборонені порти/хости і стежте за лічильниками/логами з обмеженням частоти.

Зробіть це — і ваш VPN перестане бути коридором у будівлю. Він стане зачиненими дверима з конкретним ключем.
Це не параноя; це професійна гігієна.

← Попередня
ZFS: Використання NVMe як SLOG — коли це ідеально і коли надмірно
Наступна →
Помилка WordPress REST API: що ламає REST і як усувати неполадки

Залишити коментар