OpenVPN
Siehe auch
Das EPEL-Repo stellt für gewöhnlich eine sehr aktuelle OpenVPN-Version bereit.
OpenVPN ist nicht multi-threaded. Damit ist die maximale Bandbreite durch die Leistungsfähigkeit eines CPU-Cores begrenzt.
Die Systemd-Unit-Files erwarten, dass die (Server-)Konfigurationsdateien auf .conf
statt auf .ovpn
enden. Beispielkonfigurationen für OpenVPN finden sich in /usr/share/doc/openvpn*
.
OpenVPN-Client installieren und konfigurieren
Eine zum hier gezeigten Server passende Konfigurationsdatei:
auth SHA384
auth-nocache
client
data-ciphers-fallback AES-256-GCM
dev tun98
keepalive 10 60
nobind
persist-key
persist-tun
pkcs12 /etc/openvpn/myself.p12 # use double slashes on Windows clients, for example c://path//to//myself.p12
proto udp
remote vpn.example.com 12345
remote-cert-tls server
tls-cipher TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA
tls-version-min 1.2
verb 1
verify-x509-name vpn.example.com name
Client unter RHEL installieren und starten:
# from EPEL-Repository
dnf -y install openvpn
openvpn --config /etc/openvpn/client.ovpn &
Client unter Windows:
Client unter Apple macOS:
OpenVPN-Server installieren und konfigurieren
IP-Forwarding aktivieren:
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
sysctl -p
Dann:
# from EPEL-Repository
dnf -y install openvpn
Diffie Hellmann-Parameter erstellen:
openssl dhparam -out /etc/openvpn/dh4096.pem 4096
chmod 0400 /etc/openvpn/server/server.p12
Auf gehärteten Maschinen (z.B. CIS), oder bei Erscheinen der Fehlermeldung „RTNETLINK answers: Operation not permitted“ im openvpn.log
:
cat > /etc/sudoers.d/openvpn << EOF
openvpn ALL=(ALL) NOPASSWD: /sbin/ip
Defaults:openvpn !requiretty
EOF
Konfiguration eines OpenVPN-Servers, der auf Port 12345 hört, ein VPN-Netz „192.0.2.0“ anbietet, und dieses ins interne Netz „10.80.109.0“ routet:
auth SHA384
cipher AES-256-GCM
# crl file (or directory) is read every time a peer connects,
# and it has to contain at least one item
#crl-verify /etc/openvpn/server/crl.pem
#client-config-dir /etc/openvpn/ccd
daemon
dev tun
dh /etc/openvpn/dh4096.pem
#duplicate-cn
group openvpn
keepalive 10 60
log-append /var/log/openvpn.log
persist-key
persist-tun
pkcs12 /etc/openvpn/server/server.p12
port 12345
proto udp
push "route 10.80.109.0 255.255.255.0"
server 192.0.2.0 255.255.255.0
status /var/log/openvpn-status.log
syslog
tls-cipher TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA
tls-server
tls-version-min 1.2
topology subnet
user openvpn
verb 4
semanage port -a -t openvpn_port_t -p udp 12345
OpenVPN-Server starten:
# if config file is named "linuxfabrik.conf", run "...@linuxfabrik":
systemctl enable --now openvpn-server@server
Eine Firewall muss, wenn sie Regeln auf das VPN-Interface anwenden möchte, auf dieses beim Hochfahren warten. Ausserhalb von Systemd lässt sich das so umsetzen:
### Wait for OpenVPN interface ###
device="tun0"
t=60
while [[ $t -gt 0 ]]; do
if [[ $(ip link show $device 2> /dev/null) ]]; then
break
else
((t-=1));
sleep 1;
fi
done
if [[ $t -le 0 ]]; then
echo "Cannot find $device."
logger "Cannot find $device."
exit 1;
fi
echo "$device found."
logger "$device found."
Logrotate:
cat >/etc/logrotate.d/openvpn << EOF
/var/log/openvpn.log {
# The log is kept open *even if* I send a
# SIGUSR1 or SIGHUP signal to the OpenVPN process.
# Also, if I move away the logfile it is never
# recreated, just because the
# daemon keeps writing on the renamed file.
# The copytruncate fixes this.
compress
copytruncate
daily
dateext
missingok
rotate 14
size 1
}
EOF
Clients fixe IP zuweisen (CCD)
Auf einem Server mit gesetzter Client Config Dir-Option (CCD) hat es sich bewährt, die CCDs von hinten anfangen zu lassen, damit der IP-Adresspool (Angabe server
) sich nicht so schnell mit den statischen IPs aus ccd
überschneidet. Die Dateinamen im ccd-Verzeichnis müssen denen des CommonName im Client-Zertifkat entsprechen.
client-config-dir /etc/openvpn/ccd
Im Beispiel erhält der Client mit dem Zertifikat „myhost“ bei der Einwahl die statische IP „192.0.2.47“:
mkdir -p /etc/openvpn/ccd
cat > /etc/openvpn/ccd/myhost << EOF
ifconfig-push 192.0.2.47 255.255.255.0
EOF
TUN vs. TAP
TUN = Punkt-zu-Punkt Netzwerkgerät, sendet und empfängt IP-Pakete auf Layer 3. Routed Network.
TAP = virtuelle Ethernet-Schnittstelle, sendet und empfängt Ethernet-Frames auf Layer 2, ist also unabhängig vom verwendeten Protokoll (IP, IPX, etc.). In erster Linie Bridged Network (Routing wäre auch möglich). TAP benötigt Netzwerkkarten im Promiscuous Mode.
Schlüsselstärken
Liste verfügbarer Chiffren anzeigen:
openvpn --show-ciphers
Liste möglicher TLS-Ciphers:
openvpn --show-tls
Liste unterstützter Message Authentications (HMACs):
openvpn --show-digests
OpenVPN und NetworkManager - PKCS#12-Zertifikate ohne Passwort
Der NetworkManager unterstützt generell OpenVPN-Verbindungen. Dazu werden zwei Pakete benötigt:
NetworkManager-openvpn
NetworkManager-openvpn-gnome
Der NetworkManager hat jedoch Probleme mit dem Import von vorgefertigten OpenVPN-Konfigurationen, die PKCS#12 SSL-Zertifikate ohne Passwort verwenden.
Damit das am Ende doch funktioniert, muss die OpenVPN-Konfiguration zunächst in das NetworkManager-Format konvertiert werden. Für die Konvertierung gibt es ein lua-Script auf NetworkManager github contrib/scripts page.
wget https://raw.githubusercontent.com/NetworkManager/NetworkManager/main/contrib/scripts/nm-import-openvpn
chmod +x ./nm-import-openvpn
sudo ./nm-import-openvpn openvpn-config.conf /etc/NetworkManager/system-connections/openvpn-config.nmconnection
Anschliessend das Passwort für das Zertifikat auf any
setzen:
openvpn-config.nmconnection
hinzufügen[vpn-secrets]
cert-pass=any
Dann importieren und aktivieren:
sudo chmod 600 /etc/NetworkManager/system-connections/openvpn-config.nmconnection
sudo nmcli con load /etc/NetworkManager/system-connections/openvpn-config.nmconnection
Verbindungsaufbau über das GUI oder per:
nmcli con up openvpn-config.nmconnection
OpenVPN und 2FA (TOTP)
So wird der OpenVPN-Server konfiguriert, falls 2FA umgesetzt werden soll (hier mit TOTP, aber andere Verfahren funktionieren analog).
Auf dem OpenVPN-Server zusätzlich installieren:
dnf -y install oathtool
dnf -y install qrencode
OpenVPN server.conf ergänzen:
# https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/
script-security 2
auth-user-pass-verify ./oath-user-pass.sh via-file
Auf dem OpenVPN-Server zwei Skripte ablegen:
#!/usr/bin/env bash
# Author: Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
# https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.
# Script to verify OTP using oathtool.
# Will be called from openvpn server like so:
# `$0 /tmp/openvpn_up_53ebbfc3ee15299f67dcff30b05d87ad.tmp`
# (where openvpn will write the username and password to the first two lines of the temporary file)
# Get the user/pass from the openvpn tmp passfile
openvpn_passfile=$1
openvpn_username=$(head -1 "$openvpn_passfile")
openvpn_secret=$(tail -1 "$openvpn_passfile") # expecting "password:otp"
# Find the entry in our oath.secrets file, ignore case
pwd_stored=$(grep -i -m 1 "$openvpn_username:" oath.secrets | cut -d: -f2)
# Calculate the code we should expect
otp_calculated=$(oathtool --totp "$pwd_stored")
if [ "$otp_calculated" = "$openvpn_secret" ];
then
# we already got a hex encoded secret key
exit 0
fi
# See if we have password and OTP, or just OTP
echo -n "$openvpn_secret" | grep -q -i :
if [ $? -eq 0 ];
then
pwd_given=$(echo -n "$openvpn_secret" | cut -d: -f1)
otp_given=$(echo -n "$openvpn_secret" | cut -d: -f2)
hashed_pwd_given=$(echo -n "$pwd_given" | sha256sum | cut -b 1-30)
if [ "$pwd_stored" = "$hashed_pwd_given" ] && [ "$otp_calculated" = "$otp_given" ];
then
# credentials match
exit 0
fi
fi
# If we make it here, auth hasn't succeeded, don't grant access
exit 1
#!/usr/bin/env bash
# Author: Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
# https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.
# Set your URL-encoded issuer string here
issuer='Linuxfabrik%20OpenVPN'
user=$1
pwd=$2
hex_pwd=$(echo -n "$pwd" | sha256sum | cut -b 1-30)
base32_pwd=$(/usr/bin/oathtool --totp -v "$hex_pwd" | grep Base32 | awk '{print $3}')
echo "$user:$hex_pwd" >> /etc/openvpn/server/oath.secrets
qrencode --type ansi "otpauth://totp/$issuer:$user?secret=$base32_pwd"
echo "User Credentials: $user / $pwd"
echo "User String: otpauth://totp/$issuer:$user?secret=$base32_pwd"
Benutzer anlegen:
# /etc/openvpn/server/oath-secret-gen.sh $USERNAME $PASSWORD
/etc/openvpn/server/oath-secret-gen.sh alice linuxfabrik
QR-Code für Apps wie FreeOTP und andere wird im Terminal geprintet. QR-Code, Benutzername und Passwort den Benutzern auf sicheren Kanälen zukommen lassen.
OpenVPN Client-Config um den Eintrag ergänzen:
auth-user-pass
Der Benutzer kann sich wie folgt einloggen:
sudo openvpn --config /path/to/my.ovpn
Enter Auth Username: alice
🔐 Enter Auth Password: linuxfabrik:123456 (Eingabe also Passwort, gefolgt von Doppelpunkt, gefolgt von OTP-Code)
Und so funktioniert das ganze:
Benutzernamen und gehashte Passwörter finden sich in
/etc/openvpn/server/oath.secret
Benutzer verbindet sich inkl. Client-Zertifikat (und unter Umständen einem Zertifikats-Passwort). Soweit bekannt.
Wegen
auth-user-pass
wird der Benutzer nach seinen Credentials gefragt (Benutzername und „Passwort:OTP“)Durch die Direktive
auth-user-pass-verify ./oath-user-pass.sh
legt der OpenVPN-Server die Credentials in einer temporären Datei ab und ruft das angegebene Skript auf.Das Skript …
liest die übergebenen Credentials aus
liest das gespeicherte Passwort des Benutzers aus
/etc/openvpn/server/oath.secret
kalkuliert basierend auf dem gespeicherten Passwort das theoretische OTP
prüft, ob das eingegebene mit dem gespeicherten Passwort sowie das eingegebene OTP mit dem kalkulierten OTP übereinstimmen
Nach dem Skript-Run wird die temporäre Datei gelöscht.
Troubleshooting
- VPN-Client: „Authenticate/Decrypt packet error: missing authentication info“
Liegt an einer der folgenden fehlenden Client-Settings: nobind, remote-cert-tls server - oder meist an der veralteten Einstellung „ns-cert-type server“ statt „remote-type-tls“. Wird letztere durch „remote-cert-tls server“ ersetzt, ist der Client auch in der Lage, „extended key usage“ der Zertifikate zu validieren.
- VPN-Client: Server-Maschine hinter dem VPN-Server ist nicht erreichbar, VPN-Server aber schon
Rückroute auf der Server-Maschine hinter dem OpenVPN-Server fehlt. Entweder auf dem zentralen Gateway die Route hin zum OpenVPN-Server eintragen, oder jedem per VPN zu erreichenden System einzeln.
Built on 2023-09-21