OpenVPN

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:

/etc/openvpn/client.ovpn
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:

/etc/openvpn/server/server.conf
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.

/etc/openvpn/server/server.conf
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:

sudo dnf -y install 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.

cd ~/.local/bin
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:

am Ende von /etc/NetworkManager/system-connections/openvpn-config.nmconnection hinzufügen
[vpn-secrets]
cert-pass=any

Dann importieren und aktivieren:

sudo chmod 600 /etc/NetworkManager/system-connections/*
sudo nmcli con load /etc/NetworkManager/system-connections/*

Verbindungsaufbau über das GUI oder per:

nmcli con up openvpn-config.nmconnection  # name from "id="

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:

/etc/openvpn/server/oath-user-pass.sh
#!/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
/etc/openvpn/server/oath-secret-gen.sh
#!/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:

/path/to/openvpn-client.ovpn
auth-user-pass

Der Benutzer kann sich wie folgt einloggen:

  1. sudo openvpn --config /path/to/my.ovpn

  2. Enter Auth Username: alice

  3. 🔐 Enter Auth Password: linuxfabrik:123456 (Eingabe also Passwort, gefolgt von Doppelpunkt, gefolgt von OTP-Code)

Und so funktioniert das ganze:

  1. Benutzernamen und gehashte Passwörter finden sich in /etc/openvpn/server/oath.secret

  2. Benutzer verbindet sich inkl. Client-Zertifikat (und unter Umständen einem Zertifikats-Passwort). Soweit bekannt.

  3. Wegen auth-user-pass wird der Benutzer nach seinen Credentials gefragt (Benutzername und „Passwort:OTP“)

  4. 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.

  5. 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

  6. 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 2024-04-18