Translate

неділю, 28 липня 2019 р.

WireGuard VPN. Part II: Setup And Usage

Минулого разу ми говорили про ідеї, що підштовхнули до створення мережевого тунелю WireGuard, та алгоритми, що лежать в його основі. Цього ж разу спробуємо налаштувати WireGuard і пересвідчимось, що VPN is fun again.

1. SETUP


WireGuard ще не включено до кодової бази ядра Linux і наразі розповсюджується як DKMS-модуль. У якості сервера Wireguard працює лише на Linux, а у якості клієнта - під усіма популярними операційними системами, в т.ч. мобільними, в основному як userspace-імплементація на мові Go.

Для демонстрації можливостей WireGuard ми скористаємось Ubuntu 18.04. Для неї пакети розповсюджуються через ppa-репозиторій. На кожному вузлі, що буде з'єднуватись тунелем, необхідно встановити наступні пакети:

# add-apt-repository ppa:wireguard/wireguard
# apt update
# apt install wireguard

Можливо також потрібно буде встановити пакети-заголовки для ядра:

# apt install linux-headers-$(uname -r) linux-headers-generic

Кожен вузол повинен мати свою пару асиметричних ключів, тож згенеруємо їх на кожному хості:

# cd /etc/wireguard/
# umask 077
# wg genkey | tee privatekey | wg pubkey > publickey


2. HOST-HOST CONNECTION


У першому сценарії побачимо як з'єднувати тунелем 2 вузла між собою. На вузлі, що виступатиме сервером, пишемо наступне:

# vim /etc/wireguard/wg0.conf

[Interface]
# server's privatekey
PrivateKey = MIRmd9owAlnKwRjk2RbzjcGvlssB5qmGP0UW4M/0QmA=
Address = 172.21.12.1/32
ListenPort = 51820

[Peer]
# client's publickey
PublicKey = HsG+rLgPRd18DN4lqBN4jyvgnOMOk57jd9zpHkEkUHs=
AllowedIPs = 172.21.12.2/32

Де 172.21.12.1/32 адреса сервера, котру він матиме в тунелі, 51820 - UDP-порт, що буде очікувати на підключення (має бути дозволений на фаерволі), PrivateKey/PublicKey - приватний/публічний ключ сервера/клієнта, вміст файлу privatekey/publickey, що були згенеровані вище на сервері/клієнті відповідно.

На стороні ж клієнта конфігураційний файл матиме наступний вигляд:

# vim /etc/wireguard/wg0.conf

[Interface]
# client's privatekey
PrivateKey = QLnMSq7IaXks08mka1vr0bplWmUJmoCXErcPz56ykmU=
Address = 172.21.12.2/32

[Peer]
# server's publickey
PublicKey = 50TO6tBKW3fWu28f0v3FxVc1oycooD5zlpGd3fbbcjQ=
Endpoint = <server's public ip address>:51820
AllowedIPs = 172.21.12.1/32
PersistentKeepalive = 25

Де, аналогічно, PrivateKey - ключ клієнта privatekey та PublicKey - публічний ключ сервера. Тобто за основу можна взяти наступне: приватний ключ окремої системи не має копіюватись за межі цієї системи.

Мабуть тут буде доречно згадати про те, яким чином в WireGuard працює маршрутизація пакетів (Cryptokey routing). Сенс її полягає в поєднанні публічних ключів та списку IP, що дозволені в тунелі. Кожен мережевий інтерфейс (мається на увазі wg) має приватний ключ та список вузлів (peers), з'єднання з якими може відбуватись. У свою чергу кожен пір має публічний ключ. Публічні ключі використовуються для аутентифікації з'єднань між собою.

Якщо за приклад взяти конфігураційні файли вище (wg0.conf), то все буде виглядати приблизно наступним чином. У конфігурації сервера вказано, що кожен клієнт матиме можливість відправляти пакети до мережевого інтерфейсу сервера з IP адрес (source IPs), що описані в секції AllowedIPs, тобто з 172.21.12.2 (адреса клієнта). Наприклад, якщо пакет буде отримано сервером від вузла HsG+rLgP..., після цього він буде автентифікований і дешифрований, і у разі, якщо його IP адреса буде 172.21.12.2, то пакет буде дозволений на wg0-інтерфейсі і буде маршрутизований далі.

Знову ж, виходячи з конфігурації серверу, якщо мережевий інтерфейс сервера захоче відправити пакет клієнту (peer-у), Wireguard перегляне IP-адресу призначення пакету і порівняє її із кожним списком дозволених адрес кожного піра. У нашому випадку, якщо мережевому інтерфейсу необхідно буде відправити пакет з адресою призначення 172.21.12.2, то сервер зашифрує його використовуючи публічний ключ клієнта HsG+rLgP... і вже потім відправить його на останній відомий публічний ендпоїнт клієнта.

У конфігурації клієнта указано, що сервер може відправляти пакети клієнту з мережевого інтерфейсу із адресою 172.21.12.1 (source IP). Наприклад, якщо пакет буде отримано від вузла 50TO6tBK... (тобто сервера), зможе бути розшифрований та коректно автентифікований і адреса пакета буде 172.21.12.1, то такий пакет буде дозволено на мережевому інтерфейсі клієнта; інакше ж його буде відкинуто.

Також у wg0.conf клієнта вказано, якщо його мережевий інтерфейс захоче відправити пакет серверу, то він спочатку має його зашифрувати лише для сервера із AllowedIPs адресою. Тобто, якщо мережевому інтерфейсу клієнта необхідно відправити пакет на 172.21.12.1, він його зашифрує ключем 50TO6tBK... і потім відправить на останній відомий публічний ендпоїнт сервера.

Саме це і входить до терміну Cryptokey Routing Table: асоціація публічних ключів та дозволених IP-адрес.

Піднімаємо тунель спочатку на сервері, а потім і на клієнті:

# wg-quick up wg0

У результаті чого на сервері буде піднято wg0-інтерфейс із адресою 172.21.12.1, а на клієнті - з адресою 172.21.12.2. Перевіримо роботу тунелю. Для цього запустимо ping на клієнті:

$ ping -c2 172.21.12.1

PING 172.21.12.1 (172.21.12.1) 56(84) bytes of data.
64 bytes from 172.21.12.1: icmp_seq=1 ttl=64 time=39.0 ms
64 bytes from 172.21.12.1: icmp_seq=2 ttl=64 time=37.4 ms

--- 172.21.12.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1ms
rtt min/avg/max/mdev = 37.362/38.189/39.017/0.850 ms

І перевіримо чи приходять пакети на сервері:

# tcpdump -n -i wg0 icmp

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wg0, link-type RAW (Raw IP), capture size 262144 bytes
12:03:58.892503 IP 172.21.12.2 > 172.21.12.1: ICMP echo request, id 662, seq 1, length 64
12:03:58.892556 IP 172.21.12.1 > 172.21.12.2: ICMP echo reply, id 662, seq 1, length 64
12:03:59.893866 IP 172.21.12.2 > 172.21.12.1: ICMP echo request, id 662, seq 2, length 64
12:03:59.893910 IP 172.21.12.1 > 172.21.12.2: ICMP echo reply, id 662, seq 2, length 64

WireGuard розповсюджується із клієнтськими утилітами для діагностики тунелю.

# wg -h

Usage: wg <cmd> [<args>]

Available subcommands:
  show: Shows the current configuration and device information
  showconf: Shows the current configuration of a given WireGuard interface, for use with `setconf'
  set: Change the current configuration, add peers, remove peers, or change peers
  setconf: Applies a configuration file to a WireGuard interface
  addconf: Appends a configuration file to a WireGuard interface
  genkey: Generates a new private key and writes it to stdout
  genpsk: Generates a new preshared key and writes it to stdout
  pubkey: Reads a private key from stdin and writes a public key to stdout
You may pass `--help' to any of these subcommands to view usage.

Переглянемо стан роботи тунелю однією із них:

# wg show
interface: wg0
  public key: 50TO6tBKW3fWu28f0v3FxVc1oycooD5zlpGd3fbbcjQ=
  private key: (hidden)
  listening port: 51820

peer: HsG+rLgPRd18DN4lqBN4jyvgnOMOk57jd9zpHkEkUHs=
  endpoint: <client's public IP address>:49589
  allowed ips: 172.21.12.2/32
  latest handshake: 22 seconds ago
  transfer: 35.78 KiB received, 25.74 KiB sent

Якщо звернутись до початкового конфігураційного файлу клієнта, він має початковий endpoint, тобто публічну IP-адресу та порт Wireguard-сервера. Тож клієнт знатиме на яку адресу відправляти шифровані дані. В конфігурації сервера такий параметр відсутній - сервер знаходить endpoint клієнта за адресою звідки приходять правильно автентифіковані дані. Якщо ж сервер змінить свою адресу - клієнт аналогічно визначить новий endpoint сервера і відповідно змінить конфігурацію. Клієнт та сервер відправляють шифровані дані на останній IP endpoint, котрий коректно дешифрував дані. Саме ця логіка і забезпечує режим роумінгу (roaming) по обидві сторони і це неймовірний функціонал на нестабільних чи динамічних мережах на кшталт мобільних.

На сервері додамо WireGuard в автозавантаження:

# systemctl enable wg-quick@wg0

На клієнті варто виконати аналогічну дію за потреби.

3. ACCESS TO LOCAL NETWORK BEHIND WIREGUARD SERVER


Цього разу спробуємо з'єднати клієнт з сервером та локальною мережею позаду нього. Тобто сервер буде виступати таким собі шлюзом для доступу в приватну мережу (наприклад, офісну) і не більше. Для цього необхідно виконати мінімальні зміни конфігураційних файлів, що були використані в попередньому пункті. На сервері:

# vim /etc/wireguard/wg0.conf

[Interface]
# server's privatekey
PrivateKey = MIRmd9owAlnKwRjk2RbzjcGvlssB5qmGP0UW4M/0QmA=
Address = 172.21.12.1/32
ListenPort = 51820
PostUp   = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -s 172.21.12.0/24 -A POSTROUTING -o ens2 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -s 172.21.12.0/24 -D POSTROUTING -o ens2 -j MASQUERADE

[Peer]
# client's publickey
PublicKey = HsG+rLgPRd18DN4lqBN4jyvgnOMOk57jd9zpHkEkUHs=
AllowedIPs = 172.21.12.2/32

Де ens2 - інтерфейс, що обслуговує внутрішню локальну мережу 10.0.0.0/8. Кожного разу, коли буде піднято інтерфейс wg0 на сервері, буде виконано скрипт PostUp, котрий запустить правило iptables. Це правило активує NAT трансляцію для пакетів із підмережі 172.21.12.0/24 в уже згаданий інтерфейс. Власне нічого особливого в цій логіці - аналогічно транслюються адреси і, наприклад, у OpenVPN.

На клієнті конфігураційний файл має бути наступним:

# vim /etc/wireguard/wg0.conf

[Interface]
# client's privatekey
PrivateKey = QLnMSq7IaXks08mka1vr0bplWmUJmoCXErcPz56ykmU=
Address = 172.21.12.2/32

[Peer]
# server's publickey
PublicKey = 50TO6tBKW3fWu28f0v3FxVc1oycooD5zlpGd3fbbcjQ=
Endpoint = <server's public ip address>:51820
AllowedIPs = 172.21.12.1/32, 10.0.0.0/8
PersistentKeepalive = 25

Тут варто звернути увагу на параметр AllowedIPs. Він вказує на те, що клієнт має право приймати пакети із 172.21.12.1/32, 10.0.0.0/8 підмереж.

Перезапустимо WireGuard на сервері, потім на клієнті та перевіримо зв'язок з клієнта:

$ ping -c2 10.22.57.7

PING 10.22.57.7 (10.22.57.7) 56(84) bytes of data.
64 bytes from 10.22.57.7: icmp_seq=1 ttl=58 time=41.8 ms
64 bytes from 10.22.57.7: icmp_seq=2 ttl=58 time=43.4 ms

--- 10.22.57.7 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 3ms
rtt min/avg/max/mdev = 41.799/42.618/43.438/0.845 ms

Де 10.22.57.7 - вузол, що лежить в тій же мережі, що і WireGuard сервер. Також не варто забувати про попередню активацію ip forwarding в ядрі, що дозволить пересилати пакети між мережевими інтерфейсами:

# vim /etc/sysctl.conf
...
net.ipv4.ip_forward=1
...

# sysctl -p

4. ACCESS THE INTERNET THROUGH WIREGUARD SERVER


Мабуть найбільш класичний спосіб використання VPN. Тобто дані до WireGuard-серверу будуть додатково шифруватись, а вже після відправлятимуться як трафік із цієї локації. Це підвищить рівень безпеки та приховає справжнє місцезнаходження клієнта. Для цього варто виконати мінімальні зміни в порівнянні з попереднім пунктом. На клієнті правимо параметр AllowedIPs:

# vim /etc/wireguard/wg0.conf

[Interface]
# client's privatekey
PrivateKey = QLnMSq7IaXks08mka1vr0bplWmUJmoCXErcPz56ykmU=
Address = 172.21.12.2/32

[Peer]
# server's publickey
PublicKey = 50TO6tBKW3fWu28f0v3FxVc1oycooD5zlpGd3fbbcjQ=
Endpoint = <server's public ip address>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

Тобто джерелом пакетів для клієнта можуть бути будь-які IPv4 адреси.

Це все. Перезапустимо WireGuard та перевіримо публічну IP-адресу на клієнті:

$ sudo wg-quick down wg0
$ sudo wg-quick up wg0

$ curl ifconfig.me
<server's public ip address>

Досить цікавий стан справ з маршрутами. У вже згаданому OpenVPN у клієнта у подібній схемі створюється декілька нових маршрутів із яких простежується логіка роутингу трафіку. Щось схоже на це:

# route -n

Kernel IP routing table
Destination               Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0                   10.8.0.5        128.0.0.0       UG    0      0        0 tun0
0.0.0.0                   192.168.1.1     0.0.0.0         UG    100    0        0 eth0
10.8.0.1                  10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
10.8.0.5                  0.0.0.0         255.255.255.255 UH    0      0        0 tun0
128.0.0.0                 10.8.0.5        128.0.0.0       UG    0      0        0 tun0
169.254.0.0               0.0.0.0         255.255.0.0     U     1000   0        0 tun0
public_ip_of_vpn_server   192.168.1.1     255.255.255.255 UGH   0      0        0 eth0
192.168.1.0               0.0.0.0         255.255.255.0   U     100    0        0 eth0

Тобто для початкового з'єднання на публічну адресу public_ip_of_vpn_server OpenVPN створює окремий маршрут через попередній гейтвей 192.168.1.1 (транспортний канал), після чого додає 2 окремих маршрути на приватну адресу OpenVPN-серверу (10.8.0.5), що описує доступ на всю IPv4 мережу (0.0.0.0/0).

У разі WireGuard маршрути додаються інакше, використовуючи маркування fwmark. Це стосується тунелей, що були підняті утилітою wg-quick.

5. UNBOUND FOR AVOIDING DNS LEAKS


Для уникнення витоку адрес сайтів, що були відвідані, скористаймось власним кешуючим DNS-сервером, котрий встановимо на VPN-сервер. Цей можливий недолік безпеки має назву DNS Leak. Суть його в тому, що дані до DNS-серверів при певних обставинах можуть прямувати не через VPN-сервер, а напряму, по локальним маршрутам і таким чином відслідковуватись третьою стороною. Це може статись, якщо буде використовуватись локальний DNS-сервер, котрий автоматично надає провайдер по DHCP-протоколу і т.п.

У якості вже згаданого DNS-сервера використаємо Unbound. Установимо його на WireGuard сервер та завантажимо адреси кореневих DNS-серверів:

# apt install unbound unbound-host
# curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache

Приберемо попередній вміст конфігураційного файлу Unbound та запишемо наступне:

# vim /etc/unbound/unbound.conf

server:

  num-threads: 4

  # Enable logs
  verbosity: 1

  # list of Root DNS Server
  root-hints: "/var/lib/unbound/root.hints"

  # Use the root servers key for DNSSEC
  auto-trust-anchor-file: "/var/lib/unbound/root.key"

  # Respond to DNS requests on wg interface only
  interface: 172.21.12.1
  max-udp-size: 3072

  # Authorized IPs to access the DNS Server
  access-control: 0.0.0.0/0          refuse
  access-control: 172.21.12.0/24     allow

  # Not allowed to be returned for public internet names
  private-address: 172.21.12.0/24

  # Hide DNS Server info
  hide-identity: yes
  hide-version: yes

  # Limit DNS Fraud and use DNSSEC
  harden-glue: yes
  harden-dnssec-stripped: yes
  harden-referral-path: yes

  # Add an unwanted reply threshold to clean the cache and avoid when possible a DNS Poisoning
  unwanted-reply-threshold: 10000000

  # Have the validator print validation failures to the log.
  val-log-level: 1

  # Minimum lifetime of cache entries in seconds
  cache-min-ttl: 1800

  # Maximum lifetime of cached entries
  cache-max-ttl: 14400
  prefetch: yes
  prefetch-key: yes

Тобто ми дозволили доступ до DNS-серверу лише з адрес підмережі VPN-серверу (172.21.12.0/24) та локального хосту. Зупиняємо локальний systemd-resolved сервіс (якщо його було запущено), додаємо Unbound в автозавантаження та запускаємо:

# systemctl disable systemd-resolved.service
# systemctl stop systemd-resolved

# chown -R unbound:unbound /var/lib/unbound
# systemctl enable unbound
# systemctl start unbound

Перевіримо чи він коректно працює:

$ nslookup www.google.com 172.21.12.1

Server:   172.21.12.1
Address:  172.21.12.1#53

Non-authoritative answer:
Name: www.google.com
Address: 216.58.215.36
Name: www.google.com
Address: 2a00:1450:4007:808::2004

Чудово. Тепер відредагуємо конфігураційний файл WireGuard-клієнта та додамо адресу цього DNS-серверу:

# vim /etc/wireguard/wg0.conf

[Interface]
# client's privatekey
PrivateKey = QLnMSq7IaXks08mka1vr0bplWmUJmoCXErcPz56ykmU=
Address = 172.21.12.2/32
DNS = 172.21.12.1

[Peer]
# server's publickey
PublicKey = 50TO6tBKW3fWu28f0v3FxVc1oycooD5zlpGd3fbbcjQ=
Endpoint = <server's public ip address>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

# wg-quick down wg0
# wg-quick up wg0

Тобто всі DNS-дані будуть йти через адресу, яка в свою чергу лежить в адресному просторі WireGuard серверу і доступна лише в тому випадку, якщо VPN-тунель вже піднято.

Перевірити чи витік DNS-даних не відбувається можна за наступним посиланням http://dnsleak.com/.

6. FIREWALL CONFIGURATION


Останній, але не менш важливий пункт - налаштування фаервола. Для ОС Linux в основному цю роль виконує Iptables або фронтенди на його основі. Дозволимо RELATED трафік на INPUT ланцюжку:

# iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

51820 - UPD-порт, котрий "слухає" WireGuard сервер:

# iptables -A INPUT -p udp -m udp --dport 51820 -m conntrack --ctstate NEW -j ACCEPT

Дозволимо DNS трафік на сервері, якщо запити прийшли із VPN діапазону:

# iptables -A INPUT -s 172.21.12.0/24 -p tcp -m tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
# iptables -A INPUT -s 172.21.12.0/24 -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT

Та SSH:

# iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

Всі інші пакети в ланцюжку INPUT будуть знищені:

# iptables -A INPUT -j DROP

Наступне правило дозволяє NAT-трансляцію:

# iptables -t nat -A POSTROUTING -s 172.21.12.0/24 -o ens2 -j MASQUERADE

У разі, якщо ці правила будуть вказані напряму, їх потрібно видалити із PostUp та PostDown директив wg0.conf сервера.

Робимо правила постійними:

# apt-get install iptables-persistent
# systemctl enable netfilter-persistent
# netfilter-persistent save

Посилання:
https://www.ckn.io/blog/2017/11/14/wireguard-vpn-typical-setup/
https://www.stavros.io/posts/how-to-configure-wireguard/
https://www.digitalocean.com/community/tutorials/how-to-create-a-point-to-point-vpn-with-wireguard-on-ubuntu-16-04
https://wiki.archlinux.org/index.php/WireGuard
https://www.wireguard.com/netns/
https://arstechnica.com/gadgets/2018/08/wireguard-vpn-review-fast-connections-amaze-but-windows-support-needs-to-happen/

Немає коментарів:

Дописати коментар