Nastavení OpenVPN pro přístup na stroje za NATem

13. 10. 2022
Doba čtení: 10 minut

Sdílet

 Autor: Depositphotos
Ukážeme si, jak si spustit server OpenVPN a použít ho mimo jiné pro zveřejnění nějaké služby (například SSH) na nějakém stroji za NATem. To je bohužel běžná konfigurace většiny sítí bez veřejných IP adres.

Bohužel mnoho internetových připojení, která potkávám, nepodporuje IPv6, ani neumožňuje přesměrovat port. Proto je potřeba pro dostání se na počítač za NATem udělat nějaký typ tunelu na počítač s veřejnou IP adresou. Takovým počítačem může být například levná VPS u nějakého běžného poskytovatele. Toto téma též pravidelně řeší naši čtenáři na fóru.

Co se dozvíte v článku
  1. Reverzní SSH tunel
  2. Konkurence OpenVPN – Wireguard, IPSec
  3. Certifikační autorita
  4. Přesměrování portu
  5. Řešení problémů
  6. HTTPS

Reverzní SSH tunel

Nejjednodušší možnost, jak takovou věc zařídit, by bylo nastavit SSH klíče a spustit

# ssh nějaký_omezený_uživatel@veřejný_stroj -R 0.0.0.0:2277:localhost:22

To zpřístupní místní port 22 jako port 2277 na vzdáleném stroji. Pokud je navíc ve vzdáleném /etc/ssh/sshd_config nastaveno GatewayPorts clientspecified, je tento port 2277 otevřen do světa a je možno se na něj připojovat i zvenku (jinak bychom používali třeba ssh -J veřejný_stroj -p 2277 localhost). Já jsem s tím měl historicky problémy se stabilitou, jednak protože se jedná o TCP spojení a jsou tam různé timeouty když se to rozpadne (zatímco UDP by pakety posílalo bez ohledu na cokoli), jednak protože docházelo ke stavům, kdy se klient restartoval, ale na serveru spojení stále viselo a tím byl zablokovaný port, který se měl otevřít. Pokud takovou věc chcete používat, tak zkuste tyto věci explicitně nastavit:

# ssh -o "ExitOnForwardFailure yes" -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3"

a případně program autossh.

Omezenému uživateli je nutno zakázat shell, ideálně zakázat forwardovat jiné porty než které mu povolíme aby se nemohl vydávat za jiného uživatele, atd., a už se nám to komplikuje. Většina těchto omezení by měla jít realizovat volbami u klíče v authorized_keys .

command="/bin/false",no-agent-forwarding,no-pty,no-user-rc,no-X11-forwarding,permitopen="127.0.0.1:80"

Dále jsem řešil, že potřebuji propojit sítě trochu komplikovaněji než jen pár vyjmenovanými porty na jeden server.

Konkurence OpenVPN – Wireguard, IPSec

V dnešní době člověk při stavbě VPN možná sáhne po Wireguardu. Pojďme si Wireguard a OpenVPN porovnat.

  • OpenVPN je stará a díky tomu dlouhodobě podporovaná technologie – funguje i na systémech jako Windows XP, Windows 7, Mikrotik RouterOS do verze 6 atd. Wireguard sice nějak podporuje i Windows 7, ale mně se tam nedokázal vzpamatovat z restartu protistrany.
  • OpenVPN má nevýhodu, že je to program běžící v uživatelském prostoru (Wireguard běží v jádře), takže všechna data se musí protlačit z jádra do procesu a zpět. Navíc je OpenVPN jednovláknová, takže se ani nevyužijí vícejádrové procesory a výkon tak dále trpí. Na slabých počítačích (OpenWRT routřících) očekávejte od OpenVPN zhruba 10–30 Mb/s, zatímco Wireguard zvládne 100 Mb/s. Na běžném počítači OpenVPN zvládne stovky Mb/s, zatímco Wireguard gigabit. Záleží pak na vašem použití, jak moc vás tohle trápí (kolik dat přes to přenášíte).
  • OpenVPN má výhodu, že je to běžný program, takže restart znamená skutečně restart, můžete si spustit nezávisle více verzí najednou a celé se to lépe ladí.
  • OpenVPN používá certifikáty a certifikační autoritu, zatímco Wireguardu dáte klíč protistrany (podobně jako u SSH) a to je všechno. Certifikáty jsou zbytečně složité a navíc mají nastavenou dobu platnosti (což znamená, že klienti musí mít hodiny – což se mi rozbilo třeba když došla „baterka v BIOSu“); drobná výhoda je, že certifikát má jméno, zatímco Wireguard ukazuje všude jen klíče a netušíte kdo je kdo, pokud si na to nenapíšete nějakou nadstavbu.
  • OpenVPN používá spojení TLS s TLS handshakem, při kterém se vyjednává verze protokolu (TLS 1.2, TLS 1.3) a použité šifrovací algoritmy. To opět přidává složitost, ale Wireguard má jeden konkrétní šifrovací algoritmus nastavený pevně v protokolu, což do budoucna může přinést problémy s přechodem na novější šifrování.
  • OpenVPN funguje přes UDP i přes TCP, pokud byste někde měli opravdu zásadně omezené připojení, které umí třeba jen TCP na portu 443 nebo nějakou proxy.

V tomto článku popisuji OpenVPN, ale nikomu to nevnucuji a Wireguard bude v některých situacích lepší volbou.

Root.cz pořádá školení, na kterých se naučíte konfigurovat VPN pomocí OpenVPN a WireGuard.

Další možnost je IPSec. Jeho výhoda je vestavěná podpora ve Windows, iPhone a dalších systémech, takže není potřeba instalovat žádný program. Jeho nevýhoda je dle mého názoru velice nepřehledné nastavení a problematický průchod NATy (je to přímo IP protokol, nebalí se do UDP/TCP, u kterých si všichni kontrolují, že jim přes NAT fungují).

Certifikační autorita

Jak jsem napsal, používají se certifikáty, a pro jejich správu slouží program easy-rsa, což je sada skriptů, které spouští OpenSSL a dělají certifikační autoritu. Nástroj easy-rsa se zhruba od roku 2019 používá ve verzi 3, která se mírně liší syntaxí oproti v1/2, zejména tím, že se to volá jako jeden skript někde z /usr/share  místo toho, aby to byly samostatné skripty každý pro jeden účel (např. vystavení certifikátu, vytvoření CA).

Začneme nastavením platnosti všeho na 10 let, protože ve výchozím nastavení to dává jenom něco přes dva roky a nedávno jsme tu řešili, že to někomu expirovalo. Tuto proměnnou prostředí si rovnou dejte do .bashrc, ať na to příště nezapomenete.

# export EASYRSA_CERT_EXPIRE=3650

Nyní si vyrobíme CA. Všechny klíče chceme bez hesla (nopass).

# mkdir /etc/openvpn/easy-rsa
# cd /etc/openvpn/easy-rsa
# /usr/share/easy-rsa/easyrsa init-pki
# /usr/share/easy-rsa/easyrsa build-ca nopass

Budeme potřebovat bezpečné prvočíslo, vyrobíme ho takhle

# /usr/share/easy-rsa/easyrsa gen-dh
[...]
DH parameters of size 2048 created at /etc/openvpn/easy-rsa/pki/dh.pem

Trvá to několik minut, takže já mám prvočísla předpočítaná ( openssl dhparam -out dhparams-4096.pem 4096) a vždycky jenom nějaké vyberu.

Vyrobíme serverový klíč a certifikát. Certifikát má v sobě napsáno, že je serverový, protože kdyby klient při připojování jenom ověřoval, že certifikát vydala správná CA, tak by na něj jiný klient mohl svým certifikátem udělat útok MITM.

# /usr/share/easy-rsa/easyrsa build-server-full server nopass

Tohle mimochodem znamená, že právě vytvořený certifikát se jménem server nemá žádné speciální výsadní postavení. Můžeme si stejným způsobem vytvořit certifikát jinyserver2 a klient se k takovému serveru také bez problému připojí. Toho jsme právě využili ve výše uvedeném vlákně na fóru, kde starý certifikát server expiroval, ale stačilo vytvořit nový a vše zase fungovalo.

Stejně tak vyrobíme i klientský certifikát. Toto musíme spustit pro každého klienta (s různými názvy).

# /usr/share/easy-rsa/easyrsa build-client-full mujklient1 nopass

Nyní si certifikáty můžeme zobrazit, podíváme se třeba na jejich platnost a další hodnoty.

# cat pki/ca.crt | openssl x509 -text -noout
# cat pki/issued/server.crt | openssl x509 -text -noout
# cat pki/issued/mujklient1.crt | openssl x509 -text -noout

Nyní máme certifikáty hotové a musíme vytvořit konfigurační soubor pro OpenVPN server. Vyrobíme třeba /etc/openvpn/server/mujserver.conf s následujícím:

port 1194
proto udp
dev-type tun

dev název_mého_rozhraní_aby_se_nejmenovalo_tun0

ca /etc/openvpn/easy-rsa/pki/ca.crt
cert /etc/openvpn/easy-rsa/pki/issued/server.crt
key /etc/openvpn/easy-rsa/pki/private/server.key
dh /etc/openvpn/easy-rsa/pki/dh.pem

server 10.132.244.0 255.255.255.0
ifconfig-pool-persist ipp.txt
client-to-client
keepalive 19 60
persist-key
persist-tun
status openvpn-status.log
verb 3
client-config-dir /etc/openvpn/ccd
push "route 10.132.244.1 255.255.255.0"

Adresy pro VPN doporučuji zvolit tak, aby bylo naprosto nepravděpodobné, že v nějaké síti budou používat stejné. Takže rozhodně nenastavujte například populární 192.168.1.0/24, ale vymyslete si nějaká vyšší náhodná čísla v přípustných rozsazích.

Nastavili jsme něco, čemu se říká client-config-dir. Tím se dá každému klientovi udělat individuální nastavení – typicky se to používá pro přiřazení pevné IP adresy. Funguje to tak, že v tom adresáři vytvoříme soubor se jménem klienta, např. tedy /etc/openvpn/ccd/mujklient1, a do něj napíšeme:

ifconfig-push 10.132.244.50 10.132.244.1

Nyní můžeme server spustit. Můžeme to udělat interaktivně z konzole, příkazem openvpn /etc/openvpn/server/mujserver.conf, to je dobré že uvidíme hned všechno na co si to případně stěžuje. Až bude vypadat že všechno funguje, tak uděláme

# systemctl enable --now openvpn-server@mujserver

Tím se to spustí a bude spouštět jako unita systemd při startu systému. Teď potřebujeme konfiguraci pro klienta. Ta vypadá takto:

# /etc/openvpn/client/mujnazev.conf

client
dev-type tun
dev název_mého_rozhraní_aby_se_nejmenovalo_tun0
proto udp
remote muj.vpn.server.cz 1194
resolv-retry infinite
nobind
persist-key
persist-tun
ns-cert-type server
verb 3
route-nopull

route 10.132.244.0 255.255.255.0

<ca>
sem vlož obsah souboru /etc/openvpn/easy-rsa/pki/ca.crt
</ca>
<cert>
sem vlož obsah souboru, stačí ta poslední část ohraničená "-----BEGIN CERTIFICATE-----" (včetně), /etc/openvpn/easy-rsa/pki/issued/mujklient1.crt
</cert>
<key>
sem vlož obsah souboru /etc/openvpn/easy-rsa/pki/private/mujklient1.key
</key>

Klient má route-nopull, protože jinak by mu potenciálně zlý server mohl podstrčit routu a ukrást libovolný provoz; takhle je tam ručně napsané, že se to týká jen a pouze sítě 10.132.244.0 a server to nemůže změnit.

Na klientovi uděláme zase openvpn /etc/openvpn/client/mujnazev.conf, mělo by to napsat že se to připojilo, a server by měl odpovídat na ping 10.132.244.1. Pak můžeme zase udělat systemd službu  systemctl enable --now openvpn-client@mujnazev.

Pokud používáte OpenWRT, tak se soubor s konfigurací nebude jmenovat /etc/openvpn/client/mujnazev.conf, ale jen /etc/openvpn/mujnazev.conf, a jeho spuštění proběhne automaticky při /etc/init.d/openvpn restart a pak při každém bootu.

Přesměrování portu

Na serveru jsou potřeba dvě pravidla. Toto pravidlo přesměruje pakety z internetu (TCP/4401) na počítač uvnitř VPN (na jeho SSH server):

# iptables -A PREROUTING -i eth0 -p tcp -m tcp --dport 4401 -j DNAT --to-destination 10.132.244.50:22

ale nepřepíše jim zdrojovou adresu, takže tomu počítači přijde paket s „internetovou“ adresou a on na ni odpoví tak, jak běžně komunikuje do internetu (tj. přes svoji přípojku a ne přes VPN). Protistraně tak přijde paket s úplně jinou zdrojovou adresou, než na kterou se snažila připojit, a tento paket nedokáže spárovat s navazovaným spojením a zahodí ho. Proto potřebujeme ještě překlad adres, který přepíše zdrojovou adresu paketu, a tím se i odpověď bude posílat přes VPN:

# iptables -t nat -A POSTROUTING -o název_mého_rozhraní_aby_se_nejmenovalo_tun0 -j MASQUERADE

Na závěr je ještě potřeba zapnout forwarding paketů

# echo 1 > /proc/sys/net/ipv4/ip_forward

Aby nám pravidla v iptables vydržela i po rebootu, nainstalujeme si balíček iptables-persistent, a kdykoli pak budeme dělat změny, uložíme je příkazem netfilter-persistent save; forwarding paketů zapneme natrvalo nastavením net.ipv4.ip_forward = 1 v souboru  /etc/sysctl.conf.

Na takto zpřístupněný počítač se pak můžeme přihlašovat klasicky ssh -p 4401 muj.vpn.server.cz, ale pamatovat si port pro každý váš počítač a u všech příkazů (ssh, scp, rsync…) ho uvádět je nepohodlné. Proto si můžeme nastavit uživatelský alias v souboru  ~/.ssh/config:

Host blue
  Hostname muj.vpn.server.cz
  Port 4401

Nyní můžeme všude psát jen ssh blue, ssh /tmp/soubor.txt blue: a podobně.

Řešení problémů

Ve /var/log/syslog je vidět kdo se kdy připojil a odpojil, stačí v něm například programem grep vyhledat jméno klienta. To se hodí, když řešíte, že někde vypadl internet/elektřina – kdy se to stalo a co všechno je tím postižené.

Utilita iptables-save vypíše aktuálně nakonfigurovaný obsah iptables, měla by tam být vidět přidaná pravidla a dále že tam není :FORWARD DROP, což některé distribuce ve výchozím nastavení mají.

Pro sledování cesty paketu si na všech bodech po cestě spustíme tcpdump:

server# tcpdump -ni eth0 port 4401
server# tcpdump -ni název_mého_rozhraní_aby_se_nejmenovalo_tun0 port 22
klient# tcpdump -ni název_mého_rozhraní_aby_se_nejmenovalo_tun0 port 22

Sledujeme, jak pakety procházejí a kde se ztrácejí. Sledujeme jejich zdrojovou a cílovou adresu, tím odhalíme, proč se na ně například nedaří odpovědět.

Případně si pustíme # tcpdump -ni název_mého_rozhraní_aby_se_nejmenovalo_tun0 icmp na serveru i klientovi a pingáme si mezi 10.132.244.50 a 10.132.244.1.

Taky můžeme ze serveru udělat telnet 10.132.244.50 22 a sledovat jestli jsme dostali banner sshd.

Když jsem ještě neuměl správně nastavit výše uvedený DNAT + maškarádu + routování na VPN klientovi, tak jsem používal pro přesměrování portu socat:

# socat TCP4-LISTEN:4401,reuseaddr,fork TCP4:10.132.244.50:22

Výhoda je, že to je běžný uživatelský program a líp se to ladí (když se na ten port dokážete připojit telnetem, tak i tohle bude fungovat), nevýhoda je, že když to používá často hodně lidí a různě se připojují a odpojují, tak nějakou záhadou zůstávají osiřelé socat procesy.

HTTPS

Pro publikování webu s HTTPS je jedna z možností terminovat TLS na VPN serveru a mít to na separátním portu. Nainstaloval jsem si HAProxy a zadal jsem do její konfigurace:

frontend publicweb
        bind *:8443 ssl crt /etc/můj_letsencrypt_klíč_a_certifikát.pem
        default_backend web

backend web
        server web1 10.132.244.50:80

Soubor s klíčem a certifikátem je to, co mi vytvořilo acme-tiny, což používám místo Certbota, protože ten mi přišel příliš složitý na správu.

bitcoin_skoleni

Pozor: prohlížeče se odmítají připojit na hodně nestandardní porty; takové ty jako 8443 a 4430 fungují.

Elegantnější řešení by bylo pomocí hlavičky HTTP Host a SNI přesměrovávat podle hostname, ale o tom třeba někdy příště.

Autor článku

Vystudoval informatiku na MFF UK, dělal linuxového admina, vyvíjel elektroniku a nyní vyvíjí meteorologický radar ve společnosti Meteopress.