J'utilisais depuis longtemps le VPN PPTP de Windows au boulot, mais il y a 3 semaines, j'ai changé pour OpenVPN, qui est considéré comme un des protocoles les plus sécurisés en point-à-point. Il est disponible pour Windows et Linux, mais le pare-feu Windows me semble faire trop de choses seul pour avoir une vision claire des règles et du trafic. Linux ne requiert qu'une poignée de règles iptables...

Je fais donc Linux. Sur mon Raspberry Pi 3B+ et Ubuntu Server. 😉 C'est donc du Debian-like, mais à part l'installation des binaires en elle-même, c'est agnostique, ça devrait être facilement adaptable à d'autres distributions.

L'installation est toute simple :

apt install openvpn easy-rsa

Ça ramène quelques dépendances, normal. Et voilà, fini pour les binaires !

Il faut maintenant préparer le système à router des paquets :

sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

La ligne avec ipv6 n'est bien sûr nécessaire que si votre réseau est compatible IPV6.

On va pérenniser ces paramètres en décommentant les lignes suivantes dans /etc/sysctl.conf (/etc/sysctl.d/99-sysctl.conf pour Debian) :

net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1

On va créer maintenant l'infrastructure de gestion de clés. Copions les fichiers d'Easy RSA dans /etc/openvpn :

cp -R /usr/share/easy-rsa /etc/openvpn

On renomme le fichier vars.example en vars, et on le personnalise avec nos informations :

set_var EASYRSA_REQ_COUNTRY "FR"
set_var EASYRSA_REQ_PROVINCE "IdF"
set_var EASYRSA_REQ_CITY "Paris"
set_var EASYRSA_REQ_ORG "Celestial Being"
set_var EASYRSA_REQ_EMAIL "Cette adresse e-mail est protégée contre les robots spammeurs. Vous devez activer le JavaScript pour la visualiser.";
set_var EASYRSA_REQ_OU "Celestial Being Certificate Infra"

Mettez ce que vous voulez, c'est juste pour identifier les certificats ensuite. Ne mettez pas n'importe quoi si c'est pour le boulot, ça peut faire désordre ? 😅

On passe à l'initialisation de l'infrastructure :

./easy-rsa init-pki

Ça créée les répertoires idoine, initialise les index, tout ça. On crée ensuite le certificat racine :

./easy-rsa build-ca

La commande se sert des paramètres du fichiers vars, vous pouvez les modifier à la création de la racine si vous le souhaitez. Le mot de passe de la clé du certificat racine sera demandé à chaque opération de certificat, choisissez judicieusement !

On va créer maintenant un fichier de paramètres Diffie-Hellman pour sécuriser l'échange de clés (ça prend du temps, c'est normal) :

./easy-rsa gen-dh

Et une clé partagée pour être sûr qu'on est potes entre serveur et clients :

openvpn --genkey --secret server/ta.key

On peut alors créer le certificat du serveur :

./easy-rsa build-server-full vpn.celestialbeing.org nopass

Remplacez vpn.celestialbeing.org par le nom de votre serveur. Ce n'est pas grave s'il ne s'agit pas d'un vrai nom de domaine, c'est pour l'identifier à la connexion. Le paramètre nopass sert à créer une clé sans mot de passe pour le certificat. C'est gênant de devoir taper un mot de passe à chaque démarrage du serveur OpenVPN... 😁 Assurez-vous que la clé est bien à l'abri des yeux indiscrets.

On est presque prêts ! On va copier les fichiers nécessaires dans /etc/openvpn/server.

cp /etc/openvpn/easy-rsa/pki/issued/vpn.celestialbeing.org.crt /etc/openvpn/server
cp /etc/openvpn/easy-rsa/pki/private/vpn.celestialbeing.org.key /etc/openvpn/server
cp /etc/openvpn/easy-rsa/pki/ca.crt /etc/openvpn/server

Ce document suppose que cette machine est dédiée à OpenVPN et que personne d'autre que les admins autorisés n'ont accès au système de fichiers. Sinon il faut bien tout sécuriser, encore une fois.

Passons à la rédaction du fichier de configuration du serveur. Il existe un exemple, server.conf, dans /etc/openvpn/server. On va s'intéresser aux valeurs suivantes :

port : le port réseau utilisé. Vous pouvez avoir plusieurs serveurs OpenVPN sur la même machine, vous les distinguerez avec son port réseau. 1194 par défaut.

proto : le protocole à utiliser. TCP ou UDP. udp par défaut, vous pouvez le remplacer par udp6 ou tcp6 si vous voulez que le serveur écoute en IPV6.

ca : l'emplacement du certificat racine. /etc/openvpn/server/ca.crt dans ce document.

cert : l'emplacement du certificat du serveur. /etc/openvpn/server/vpn.celestialbeing.org.crt ici.

key : la clé du certificat du serveur. /etc/openvpn/server/vpn.celestialbeing.org.key pour nous, et il ne devrait pas être accessible à tous les utilisateurs du système.

crl-verify : ce paramètre permet de spécifier un fichier de vérification de validité de certificat. /etc/openvpn/easy-rsa/pki/crl.pem dans notre cas, et on en parle plus tard.

dh : l'emplacement du fichier Diffie-Hellman. /etc/openvpn/server/dh.pem ici.

Les directives server et server-ipv6 servent à définir l'adressage utilisé par les clients :

server 10.8.0.0 255.255.255.0
server-ipv6 fdfd:dead:dead::/64

Par défaut c'est un /24 préfixe 10.8 pour IPV4. Pour IPV6, c'est libre tant que c'est du fd::. Changez quand même le réseau par défaut, si vos clients doivent utiliser plusieurs VPN différents, qu'ils ne tombent pas sur une collision d'adresses...

topology : permet de spécifier la méthode d'attribution des IP aux clients. subnet ne donne qu'une adresse par client, et net30 distribue un /30 (réduisant substantiellement le nombre de clients possibles). subnet par défaut.

Le paramètre push permet de passer diverses directives aux clients. Une des plus importantes est la route :

push "route 192.168.0.0 255.255.255.0" : on indique quel réseau est desservi par le VPN.

Il est aussi important d'indiquer si le serveur VPN fait partie du réseau desservi, pour ne pas que les paquets fassent des tours infinis, ici on considère que le serveur OpenVPN a l'adresse 192.168.0.254 :

push "route 192.168.0.254 255.255.255.255 net_gateway"

push permet d'envoyer d'autres informations utiles, comme le DNS local et le nom de domaine local :

push "dhcp-option DNS 192.168.0.2"
push "dhcp-option DOMAIN celestialbeing.org"

Il est toujours prudent de limiter les droits des daemons, le process sera lancé par nobody:nogroup :

user nobody
group nogroup

Enfin, pour avoir une interface de gestion (un peu rudimentaire par telnet, port que vous voulez, ici 7505) :

management localhost 7505

Et c'est fini pour la configuration ! Lancez le serveur par :

openvpn /etc/openvpn/server/server.conf

Et s'il n'y a pas d'erreurs, ça donne quelque chose comme ça :

Wed May 20 18:33:08 2020 OpenVPN 2.4.7 aarch64-unknown-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Sep 5 2019
Wed May 20 18:33:08 2020 library versions: OpenSSL 1.1.1g 21 Apr 2020, LZO 2.10
Wed May 20 18:33:08 2020 MANAGEMENT: TCP Socket listening on [AF_INET]127.0.0.1:7505
Wed May 20 18:33:08 2020 NOTE: your local LAN uses the extremely common subnet address 192.168.0.x or 192.168.1.x. Be aware that this might create routing conflicts if you connect to the VPN server from public locations such as internet cafes that use the same subnet.
Wed May 20 18:33:08 2020 Diffie-Hellman initialized with 2048 bit key
Wed May 20 18:33:08 2020 Outgoing Control Channel Authentication: Using 160 bit message hash 'SHA1' for HMAC authentication
Wed May 20 18:33:08 2020 Incoming Control Channel Authentication: Using 160 bit message hash 'SHA1' for HMAC authentication
Wed May 20 18:33:08 2020 TUN/TAP device tun0 opened
Wed May 20 18:33:08 2020 TUN/TAP TX queue length set to 100
Wed May 20 18:33:08 2020 /sbin/ip link set dev tun0 up mtu 1500
Wed May 20 18:33:08 2020 /sbin/ip addr add dev tun0 10.8.0.1/24 broadcast 10.8.0.255
Wed May 20 18:33:08 2020 /sbin/ip -6 addr add fdfd:dead:dead::1/64 dev tun0
Wed May 20 18:33:08 2020 Socket Buffers: R=[212992->212992] S=[212992->212992]
Wed May 20 18:33:08 2020 setsockopt(IPV6_V6ONLY=0)
Wed May 20 18:33:08 2020 UDPv6 link local (bound): [AF_INET6][undef]:1194
Wed May 20 18:33:08 2020 UDPv6 link remote: [AF_UNSPEC]
Wed May 20 18:33:08 2020 GID set to nogroup
Wed May 20 18:33:08 2020 UID set to nobody
Wed May 20 18:33:08 2020 MULTI: multi_init called, r=256 v=256
Wed May 20 18:33:08 2020 IFCONFIG POOL IPv6: (IPv4) size=252, size_ipv6=65536, netbits=64, base_ipv6=fdfd:dead:dead::1000
Wed May 20 18:33:08 2020 IFCONFIG POOL: base=10.8.0.2 size=252, ipv6=1
Wed May 20 18:33:08 2020 succeeded -> ifconfig_pool_set()
Wed May 20 18:33:08 2020 IFCONFIG POOL LIST
Wed May 20 18:33:08 2020 Initialization Sequence Completed

Là il me dit de ne pas utiliser 192.168.0.0/24 ou 192.168.1.0/24 parce que c'est très courant et que ça peut faire des collisions, mais SFR ne me donne pas le choix. 😑

Notre serveur marche, il faut maintenant créer des règles iptables pour faire passer les paquets sur les bonnes interfaces. Coupez le serveur par Ctrl-C. On va le faire démarrer au boot :

systemctl enable openvpn-server@server
systemctl start openvpn-server@server

server est bien entendu le nom de votre fichier de configuration (sans .conf) dans /etc/openvpn/server.

Dans le cas d'un Debian, c'est juste openvpn au lieu de openvpn-server.

Là je prends un cas ultra-simple, on n'avait pas du tout de règles de pare-feu, on ajoute juste ce qu'il faut pour le routage du VPN dans les fichiers /etc/iptables/rules.v4 et /etc/iptables/rules.v6 (créez-les au besoin) :

*filter
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
-A INPUT -i tun+ -j ACCEPT
-A FORWARD -i tun+ -o eth0 -j ACCEPT
-A FORWARD -i eth0 -o tun+ -j ACCEPT
COMMIT
# Completed
# Generated by webmin
*mangle
COMMIT
# Completed
# Generated by webmin
*nat
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o eth0 -j MASQUERADE
COMMIT

MASQUERADE nous évite de de voir configurer des règles de routage sur le réseau, les autres machines verront les paquets émanant de l'IP locale du serveur OpenVPN et non des adresses des clients OpenVPN. Sans ça, il faut ajouter les règles adéquates soit sur le routeur soit sur les clients, mais c'est plus fastidieux. Les autres règles sont assez simples à comprendre, on accepte les paquets émanant du tunnel tun+, et on accepte de router les paquets de tun+ à eth0 et vice-versa.

Il vous faut le paquet iptables-persistent pour l'activer au boot :

apt install iptables-persistent

Chargez les règles écrites (utilisez celles qui vous intéressent entre IPV4/IPV6/les deux) :

iptables-restore < /etc/iptables/rules.v4 
ip6tables-restore < /etc/iptables/rules.v6

 Vous pouvez voir si elles ont bien été prises en compte avec la commande suivante :

iptables -L

 Créons maintenant un client, on retourne dans /etc/openvpn/easy-rsa :

./easy-rsa build-client-full setsuna

setsuna est le nom du certificat. Selon votre politique, ça sera le nom de la personne ou le nom de la machine, c'est votre choix. Le mot de passe demandé est le mot de passe de la clé du certificat que l'utilisateur devra renseigner pour se connecter.

Un client aura besoin de ses fichiers de certificat (clé /etc/openvpn/easy-rsa/pki/private/setsuna.key, certificat /etc/openvpn/easy-rsa/pki/issued/setsuna.crt), du certificat du serveur /etc/openvpn/easy-rsa/pki/ca.crt et de la clé partagée /etc/openvpn/server/ta.key, et du fichier de configuration .ovpn.

Je ne sais plus où j'ai trouvé le mien, mais le voici, l'important est en rouge :

##############################################
# Sample client-side OpenVPN 2.0 config file #
# for connecting to multi-client server. #
# #
# This configuration can be used by multiple #
# clients, however each client should have #
# its own cert and key files. #
# #
# On Windows, you might want to rename this #
# file so it has a .ovpn extension #
##############################################

# Specify that we are a client and that we
# will be pulling certain config file directives
# from the server.
client # On indique qu'on est en mode client

# Use the same setting as you are using on
# the server.
# On most systems, the VPN will not function
# unless you partially or fully disable
# the firewall for the TUN/TAP interface.
;dev tap
dev tun

# Windows needs the TAP-Win32 adapter name
# from the Network Connections panel
# if you have more than one. On XP SP2,
# you may need to disable the firewall
# for the TAP adapter.
;dev-node MyTap

# Are we connecting to a TCP or
# UDP server? Use the same setting as
# on the server.
;proto tcp
proto udp

# The hostname/IP and port of the server.
# You can have multiple remote entries
# to load balance between the servers.
remote vpn.celestialbeing.org 1194 # le serveur à joindre. On peut mettre plusieurs adresses pour faire du load-balancing, OpenVPN en prend un au hasard.
;remote my-server-2 1194

# Choose a random host from the remote
# list for load-balancing. Otherwise
# try hosts in the order specified.
;remote-random

# Keep trying indefinitely to resolve the
# host name of the OpenVPN server. Very useful
# on machines which are not permanently connected
# to the internet such as laptops.
resolv-retry infinite

# Most clients don't need to bind to
# a specific local port number.
nobind

# Downgrade privileges after initialization (non-Windows only)
;user nobody
;group nogroup

# Try to preserve some state across restarts.
persist-key
persist-tun

# If you are connecting through an
# HTTP proxy to reach the actual OpenVPN
# server, put the proxy server/IP and
# port number here. See the man page
# if your proxy server requires
# authentication.
;http-proxy-retry # retry on connection failures
;http-proxy [proxy server] [proxy port #]

# Wireless networks often produce a lot
# of duplicate packets. Set this flag
# to silence duplicate packet warnings.
;mute-replay-warnings

# SSL/TLS parms.
# See the server config file for more
# description. It's best to use
# a separate .crt/.key file pair
# for each client. A single ca
# file can be used for all clients.
ca ca.crt # Votre certificat racine.
cert setsuna.crt # Le certificat client.
key setsuna.key # La clé client.

# Verify server certificate by checking that the
# certicate has the correct key usage set.
# This is an important precaution to protect against
# a potential attack discussed here:
# http://openvpn.net/howto.html#mitm
#
# To use this feature, you will need to generate
# your server certificates with the keyUsage set to
# digitalSignature, keyEncipherment
# and the extendedKeyUsage to
# serverAuth
# EasyRSA can do this for you.
remote-cert-tls server

# If a tls-auth key is used on the server
# then every client must also have the key.
tls-auth ta.key 1 # Notre clé partagée.

# Select a cryptographic cipher.
# If the cipher option is used on the server
# then you must also specify it here.
# Note that v2.4 client/server will automatically
# negotiate AES-256-GCM in TLS mode.
# See also the ncp-cipher option in the manpage
cipher AES-256-CBC

# Enable compression on the VPN link.
# Don't enable this unless it is also
# enabled in the server config file.
comp-lzo
compress lz4-v2

# Set log file verbosity.
verb 3

# Silence repeating messages
;mute 20

Pour un client Windows, téléchargez le programme chez OpenVPN. Installez-le, et lancez-le une première fois. Il va se plaindre comme quoi il n'y a pas de fichier de configuration. Placez les fichiers clients (il y en 5 en tout) dans le répertoire OpenVPN\config du profil utilisateur. Double-cliquez sur l'icône du tray OpenVPN GUI, et si tout va bien, vous aurez une notification de connection au VPN ! 👍

Je ne m'attarde pas sur les clients, il y en a de divers et variés sur toutes plate-formes, l'important ce sont les fichiers du profil utilisateur. 😉

Il est aussi possible que vous souhaitiez révoquer un certificat client. Il faut employer la commande suivante :

./easy-rsa revoke setsuna

Où setsuna est le nom du certificat que vous voulez supprimer. Ensuite, il faut générer une liste de révocation :

./easy-rsa gen-crl

Ce qui génère le fichier /etc/openvpn/easy-rsa/pki/crl.pem. De base il n'est lisible que par root, mais OpenVPN tourne avec nobody... On va changer les droits dessus, ce n'est pas un secret militaire :

chmod +r /etc/openvpn/easy-rsa/pki/crl.pem

Ce fichier est lu toutes les heures et à chaque nouvelle connexion de client. Les connexions utilisant des certificats révoqués seront déconnectés à ces occasions.

Mais si vous voulez les déconnecter à la main, il est possible de le faire depuis l'interface de gestion :

telnet localhost 7505

Ce qui donne cette invite peu locace :

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
>INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info

Demandez l'aide avec help, et vous avez la liste des commandes disponibles :

Commands:
auth-retry t : Auth failure retry mode (none,interact,nointeract).
bytecount n : Show bytes in/out, update every n secs (0=off).
echo [on|off] [N|all] : Like log, but only show messages in echo buffer.
exit|quit : Close management session.
forget-passwords : Forget passwords entered so far.
help : Print this message.
hold [on|off|release] : Set/show hold flag to on/off state, or
release current hold and start tunnel.
kill cn : Kill the client instance(s) having common name cn.
kill IP:port : Kill the client instance connecting from IP:port.
load-stats : Show global server load stats.
log [on|off] [N|all] : Turn on/off realtime log display
+ show last N lines or 'all' for entire history.
mute [n] : Set log mute level to n, or show level if n is absent.
needok type action : Enter confirmation for NEED-OK request of 'type',
where action = 'ok' or 'cancel'.
needstr type action : Enter confirmation for NEED-STR request of 'type',
where action is reply string.
net : (Windows only) Show network info and routing table.
password type p : Enter password p for a queried OpenVPN password.
remote type [host port] : Override remote directive, type=ACCEPT|MOD|SKIP.
proxy type [host port flags] : Enter dynamic proxy server info.
pid : Show process ID of the current OpenVPN process.
pkcs11-id-count : Get number of available PKCS#11 identities.
pkcs11-id-get index : Get PKCS#11 identity at index.
client-auth CID KID : Authenticate client-id/key-id CID/KID (MULTILINE)
client-auth-nt CID KID : Authenticate client-id/key-id CID/KID
client-deny CID KID R [CR] : Deny auth client-id/key-id CID/KID with log reason
text R and optional client reason text CR
client-kill CID [M] : Kill client instance CID with message M (def=RESTART)
env-filter [level] : Set env-var filter level
client-pf CID : Define packet filter for client CID (MULTILINE)
rsa-sig : Enter an RSA signature in response to >RSA_SIGN challenge
Enter signature base64 on subsequent lines followed by END
certificate : Enter a client certificate in response to >NEED-CERT challenge
Enter certificate base64 on subsequent lines followed by END
signal s : Send signal s to daemon,
s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2.
state [on|off] [N|all] : Like log, but show state history.
status [n] : Show current daemon status info using format #n.
test n : Produce n lines of output for testing/debugging.
username type u : Enter username u for a queried OpenVPN username.
verb [n] : Set log verbosity level to n, or show if n is absent.
version : Show current version number.
END

C'est assez explicite pour ne pas devoir tout expliquer, je pense ? 😉 Les plus importantes à mon avis sont status pour avoir les clients connectés, et kill, pour déconnecter les clients.

Voilà, je crois avoir fait le tour, à vous ! 😆