Ce document est une copie de https://wiki.ldn-fai.net/wiki/Tuto_Serveur_OpenVPN puisque nos deux FAI ont travaillé ensemble pour produire une configuration OpenVPN. Cette page n'a pas vocation à être actualisée en même temps que celle de LDN puisque la version actuelle décrit parfaitement notre config'.
Notre configuration est disponible sur notre dépôt git.
Ce document documente la mise en place d'un service de VPN basé sur OpenVPN.
Il se base sur ce qui a été mise en place pour LDN et ARN.
Il contient un certain nombre d'informations qui ne sont pas explicitées dans le manuel d'OpenVPN.
Objectif :
Couche protocolaire retenue :
Réseau dans le VPN (L3) | IPv4 et IPv6 (TUN) |
---|---|
VPN | OpenVPN (avec authentification TLS) |
Transport sous-jacent (L4) | TCP ou UDP |
Réseau sous-jacente (L3) | IPv4 ou IPv6 |
Adresses utilisées :
IPv6 | IPv4 | |
---|---|---|
IP externe | 2001:DB8:1::1 | 198.51.100.240 |
IP interco VPN | 2001:DB8:2::/64 | 198.51.100.126/25 |
IP interco utilisateur i | 2001:DB8:2::i/64 | 198.51.100.i/25 |
Préfixe délégue utilisateur i | 2001:DB8:i::/48 (tiré du /40) |
Ces IPs sont tirées des ranges dédiés aux exemples, pas les vraies IP utilisées par notre service.
Remarque: nous avons un même /64 IPv6 d'interco pour tous les utilisateurs du VPN. Pareil qu'en IPv4. De plus, en IPv4 chaque utilisateur a un préfixe délégué qu'il peut attribuer librement pour son réseau interne.
Voici les empilement protocolaires que l'on peut envisager avec OpenVPN :
Contenu du VPN |
---|
VPN |
Transport sous-jacent (L4) |
Réseau sous-jacente (L3) |
Le VPN est transporté sur UDP ou TCP. OpenVPN supporte IPv4 et IPv6 (aussi bien pour le réseau qui transporte le VPN qu'au sein du VPN) dans ses versions récentes.
On configure ici les deux couches inférieurs du diagramme précédent (L3, L4).
OpenVPN peut fonctionner sur TCP ou sur UDP (port 1194 par défaut) :
Une instance donnée d'OpenVPN ne permet d'utiliser qu'un seul protocole de transport. Pour écouter, à la fois sur UDP et sur TCP, il faut donc utiliser deux instances d'OpenVPN :
proto udp6 port 1194
proto tcp6-server port 1194
On utilise IPv6 ce qui permet de faire tourner le service sur les deux versions d'IP (v4 et v6).
OpenVPN peut transporter deux types de VPN :
Dans le premier cas (TAP), le VPN transporte des trames Ethernet qui peuvent contenir des paquets IP ou d'autres types payloads. Dans le second cas (TUN), le VPN transporte directement les paquets IP : il n'y a donc pas de notion d'ARP.
Type d'interface | Couche | Messages qui passent dans le VPN |
---|---|---|
TUN | 3 (réseau) | Paquets IP (v4 et/ou v6) |
TAP | 2 (liaison) | Trames Ethernet |
La solution TAP peut être intéressante afin d'intégrer directement une machine distante dans un réseau Ethernet existant ou pour utiliser des protocoles qui ne tournent pas sur IP. Nous voulons fournir de l'IP et avons donc choisi de faire du TUN ce qui nous permet de ne pas avoir à gérer les attaques de couche 2.
Pour le serveur UDP :
mode server ; Mode serveur (clients multiples) dev tunopenvpnudp ; Nom du périphérique (et type TUN) tun-ipv6 ; Active IPv6 dans le tunnel
Pour le serveur TCP :
mode server ; Mode serveur (clients multiples) dev tunopenvpntcp ; Nom du périphérique (et type TUN) tun-ipv6 ; Active IPv6 dans le tunnel
OpenVPN supporte trois topologies :
La dernière solution consomme le moins d'adresses IP. De plus, certains OS (Windows) ne supportent pas les /30.
Nous utilisons donc la solution subnet :
topology subnet push "topology subnet" ; Informe le client de topologie utilisée
On peut activer ou non l'utilisation de TLS pour le VPN. La sécurité est plus importante avec l'utilisation de TLS et sera donc utilisée.
Le mode TLS nécessite de créer une paire clé privée/certificat pour le serveur OpenVPN. Par défaut, l'authentification mutuelle TLS est utilisée et le client utilise lui aussi une paire de clé pour s'authentifier auprès du serveur.
tls-server ; Active le mode SSL ca /etc/openvpn/keys/ca.crt ; Un ou plusieurs certificats de CA pour les certificats clients. ; Ce n'est pas forcément le même que le CA pour le certificat serveur. remote-cert-tls client ; Les certificats clients doivent avoir le bit "client" (et donc, ne sont pas des certificats serveur). cert /etc/openvpn/keys/server.crt ; Certificat serveur (public) key /etc/openvpn/keys/server.key ; Clé privée correspondant (secret !) dh /etc/openvpn/keys/dh2048.pem ; Paramètres des Diffie-Hellman (publics) crl-verify /etc/openvpn/crl.pem ; liste des certificats révoqués
Le certificat d'AC (CA) est public et doit être communiqué aux utilisateurs. Le fichier server.key est la clé privée qui doit rester secrète. Les paramètres de Diffie-Hellman sont publics mais n'ont pas besoin d'être communiqués aux utilisateurs.
Le protocole SSL/TLS est encapsulé au dessus du protocole VPN et pas l'inverse. Il est donc possible à un équipement intermédiaire
Le fichier de paramètres Diffie-Hellman peut être généré par OpenSSL :
openssl dhparam -out dh2048.pem 2048
Pour la génération et la révocation (en cas de compromission ou perte de la clé privée) des certificats des utilisateurs ou des serveurs (ainsi que la clé privée), voir la partie « Mise en place d'un CA avec easy-rsa » ci-dessous.
OpenVPN ne gère pas la récupération automatique des CRLs depuis une URL donnée. Il lit le fichier CRL spécifié dans la configuration (depuis le système de fichier local) à chaque connexion d'un client.
Donc OpenVPN doit avoir les droits sur ce fichier (notamment si OpenVPN réduit ses droits à l'exécution). Le meilleur moyen est de copier la CRL dans un dossier, par exemple /etc/openvpn. Il faut bien penser à copier le fichier au bon endroit à chaque révocation.
Résumé :
ip -6 route add blackhole 2001:DB8::/48 dev dummy0
# IPv4: ifconfig 192.51.100.126 255.255.255.128 ; Addresse+netmask de l'interface tun push "route-gateway 192.51.100.127" ; Indique au client l'addresse de la gateway du VPN # IPv6: ifconfig-ipv6 2001:DB8:2::0/64 2001:DB8:2::0 ; Addresse+netmask de l'interface tun ; Le second paramètre est inutile pour nous. ; Pas d'équivalent IPv6 de route-gateway, il faudra le spécifier dans le paramètre ; ifconfig-ipv6-push de la configuration spécifique à chaque client.
Nous avons donc deux interfaces associées aux mêmes plages d'IP :
198.51.100.126/25 dev tunopenvpntcp 198.51.100.126/25 dev tunopenvpnudp # idem en IPv6
Il y a donc ambiguïté pour router un paquet à destination des addresses du VPN. Pour résoudre cette ambiguïté nous allons aouter dynamiquement des routes plus spécifiques sur l'une des interfaces lors de la connection de l'utilisateur afin de router le paquet vers la bonne interface :
ip route add 198.51.100.1 dev tunopenvpnudp
Nous avons demandé à OpenVPN un VPN de couche 3 basé sur une interface TUN.
Une interface TUN ne gère pas de couche couche 2 mais directement les couches de couche 3 (IPv4 et IPv6) :
$ ip addr [[...]] 173: tunudp: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100 link/none inet 8192.51.100.126/25 brd 80.67.188.127 scope global tunudp inet6 2001:DB8:2::/64 scope global valid_lft forever preferred_lft forever
Cependant, le serveur OpenVPN ne crée pas une interface par client mais utilise une seule interface TUN pour tous les clients. Comme il s'agit d'une interface TUN, le serveur reçoit des paquets IP du noyau sans indication de couche 2 pour déterminer à quel client les envoyer : le serveur OpenVPN utilise l'adresse IP de destination pour déterminer à quel client envoyer le paquet. Il y a donc une sorte de table de routage interne (dont les entrées sont appelées iroute) à OpenvPN qui associe un ensemble d'addresses IP à un client :
L'ajout de ces routes internes se fait :
Ce dernier cas est utile si des adresses IP doivent être routées vers un client mais ne sont pas directement associées au client (cas du préfixe IPv6 délégué).
Par défaut, un paquet en provenance du VPN (à condition que l'IP source du paquet corresponde bien à un IP associée au client qui a envoyé le paquet) remonte toujours à la couche IP du système et est ensuite routée. Dans le cas d'échanges entre deux clients, il est possible d'éviter de remonter à la pile IP du système et de router directement le paquet dans le serveur OpenVPN : il faut utiliser la directive “client-to-client”.
Cette option n'est cependant pas utilisable dans notre configuration où deux instances d'OpenVPN (l'une pour UDP et l'autre pour TCP) utilisent les même addresses IP. Si deux clients, l'un connecté sur l'instance TCP et l'autre sur l'instance UDP, tentent de communiquer entre eux avec « client-to-client » cela ne va pas fonctionner :
Sans « client-to-client », le paquet est remonté systématiquement à la pile IP du noyau qui va le router sur la bonne interface TUN et donc et à la bonne instance d'OpenVPN. Cette dernière transfèrera alors le paquet par le VPN au bon client.
Les interfaces TUN des clients du VPN sont configurées avec des IP/netmask de la forme 198.51.100.x/25 (IPv4) et 2001:DB8:2::0/64 (IPv6). Les clients devraient donc pouvoir communiquer “directement”. Cependant, comme nous n'avons pas activé l'option “client-to-client”, les paquets passent toujours par la couche IP du serveur OpenVPN où ils sont routés. Un traceroute entre deux clients comprendra donc l'adresse IP du serveur OpenVPN.
TODO, revoir la suite (mettre dans une “note” ?)
Nous n'avons pas activé cette option afin de pouvoir voir le trafic entre clients au niveau du serveur (avec tcpdump par exemple) pour debug en cas de problèmes. Sans cette option, le noyau ne voit que le trafic chiffré.
Illustration : Avec client-to-client : Client 1 → Réseau (chiffré) → Interface eth0 serveur (chiffré) → Serveur OpenVPN (déchiffrement/rechiffrement)→ interface eth0 serveur (chiffré) → Réseau (chiffré) → Client 2
Sans client-to-client : Client 1 → Réseau (chiffré) → Interface eth0 serveur (chiffré) → Serveur OpenVPN (déchiffrement)→ tun → noyau → tun → Serveur OpenVPN (chiffrement) →interface eth0 serveur (chiffré) → Réseau (chiffré) → Client 2
Un autre avantage est visible dans notre configuration multi-instances (une UDP, l'autre TCP) : si deux clients, l'un connecté sur l'instance TCP et l'autre sur l'instance UDP, tentent de communiquer entre eux, avec « client-to-client », la première instance va recevoir le paquet, se dire que c'est du trafic à destination du VPN, voir qu'elle n'a aucun client pour cette IP et jeter le paquet (voir les iroute ci-dessus). Sans « client-to-client », le paquet sera remonté à la pile IP du noyau qui transférera le paquet à la bonne interface et à la bonne instance d'OpenVPN.
Nous allons définir les addresses IP associés à chaque utilisateur dans un fichier de configuration /etc/openvpn/users/user_login :
# Interco IPv4 : IP4="198.51.100.1" # Interco IPv6 : IP6="2001:DB8:2::1" # Préfixe délégué IPv6 : PREFIX="2001:DB8:1::/48"
Il s'agit en réalité d'un bout de script sh qui sera sourcé par un script maison (il ne s'agit donc pas d'une syntaxe standard d'OpenVPN mais d'une syntaxe maison).
Le script va utiliser ces variables pour générer de la configuration OpenVPN spécifique au client à la connection (évènement client-connect) :
# Interco IPv4 : ifconfig-push 80.67.188.1 80.67.188.1/25 # Interco IPv6 : ifconfig-ipv6-push 2001:913:0:2005::1/64 2001:913:0:2005:: # Préfixe délégué IPv6 : iroute-ipv6 2001:DB8:1::/48 # Annonce au client son préfixe délégué dans une variable d'environnement: push "setenv-safe DELEGATED_IPV6_PREFIX 2001:DB8:1::/48"
ainsi que pour ajouter des routes supplémentaires en sh :
# Add explicit routes for interconnection IPs in order to fix ambiguity: ip -4 route add 198.51.100.1/32 dev $dev protocol static ip -6 route add 2001:DB8:2::1 128 dev $dev protocol static # Add route for delegated prefix: ip -6 route add 2001:DB8:1::/48 via 2001:DB8:2::1 dev $dev protocol static
À la déconnexion du client (client-disconnect), les routes sont supprimées.
Configuration de l'utilisation du script :
script-security 2 client-connect /etc/openvpn/handler.sh client-disconnect /etc/openvpn/handler.sh
Retrouvez ce script « handler.sh » sur notre dépôt git.
Chaque instance va créer une socket de contrôle utilisable par root :
management /var/run/openvpn_tcp.socket unix management-client-user root
Cela offre quelques possibilités, la plus intéressante étant de pouvoir tuer une connexion en cours.
On peut y accéder par socat :
$ sudo socat STDIO /var/run/openvpn_tcp.socket >INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info
La législation française nous impose de garder des logs pendant 365 jours et pas un de plus. On veut donc faire des rotations quotidiennes gardées sur 356 jours des logs d'OpenVPN
OpenVPN ne sait pas réouvrir des fichiers de log (sans redémarrer l'instance) ce qui pose problème pour la rotation des logs.
On utilise donc le comportement (par défaut) qui consiste à envoyer les informations de log dans syslog :
# Logs syslog ovpn-udp # Send log to syslog using the "ovpn-udp" name verb 4 mute 10
Les instances OpenVPN lancées automatiquement par Debian (et assimilé) par /etc/init.d/openvpn sont de la forme :
/usr/sbin/openvpn --writepid /run/openvpn/foo.pid --daemon ovpn-foo --cd /etc/openvpn --config /etc/openvpn/foo.conf
Le paramètre –daemon ovpn-foo lance le processus en démon et définit le nom “procname” utilisé pour syslog. Du coup, il n'est pas possible de surcharger ce nom dans le fichier de configuration (“syslog ovpn-foo” est inutile).
On peut ensuite demander à rsyslog d'envoyer ces informations dans des fichiers spécifiques.
Pour rsyslog (/etc/rsyslog.d/openvpn.conf) :
# ovpn-udp for the UDP instance # ovpn-tcp for the TCP instance # ovpn-script for the script if $programname startswith 'ovpn-' then /var/log/openvpn.log
Le PID utilisé normalement est généré par le programme qui envoi les message et peut donc être forgé. Si vous avez un rsyslog récent (depuis 6.5, pas dans Debian Wheezy), je recommande aussi (par exemple dans /etc/rsyslog.conf) :
$SystemLogSocketAnnotate on
Ceci demande à rsyslog d'ajouter les UID, PID, GID *réels* des messages syslog locaux à la fin du message.
Il faut ensuite configurer logrotate pour conserver 365 fichiers de logs quotidiens :
/var/log/openvpn.log { rotate 365 daily delaycompress compress missingok create 0664 root utmp datetext postrotate invoke-rc.d rsyslog rotate > /dev/null endscript }
Notes :
mssfix keepalive 10 120 comp-lzo adaptive push "comp-lzo adaptive" max-clients 64
Éléments de solution :
L'idée est de rendre le service sur un maximum de ports. Dans la pratique nous avons ici conservé le port 2222 pour SSH (mais on peut s'en passer à condition d'allouer une IP supplémentaire au serveur ou d'utiliser l'addresse d'interco du VPN pour faire du SSH).
Voir nos jeux de règles dans notre dépôt git.
En l'état, ça ne fonctionne pas pour le flux qui vient du serveur lui même mais ce n'est pas trop grave.
Pour avoir ça en persistant, on peut utiliser le paquet iptables-persistent :
iptables-save > /etc/iptables/rules.v4 ip6tables-save > /etc/iptables/rules.v6
Dans le cas d'un réseau qui ferait de la DPI pour filtrer le trafic ou pour éviter que quelqu'un qui écoute le réseau découvre que l'on utilise OpenVPN, on peut envisager de le faire passer par un proxy HTTPS.
Il y aurait alors 2 niveau de chiffrement ce qui dégraderait les performances.
TODO, à faire si quelqu'un en exprime le besoin.
Il peut être intéressant qu'OpenVPN ne s'exécute pas avec les privilèges root en permanence pour augmenter un peu la sécurité (deuxième ligne de défense).
Il faut créer un utilisateur :
adduser --system --no-create-home --group openvpn
Il faut ajouter les directives suivantes dans la configuration d'OpenVPN :
user openvpn group openvpn persist-tun ; Ne pas fermer la TUN, ne pas relancer les scripts up/down lors d'un ping-restart persist-key ; Ne pas relire les clés lors d'un ping-restart (pb de réduction des privilèges)
Le script “handler” détecte automatiquement qu'il est lancé en tant que non-root et utilise alors sudo pour modifier la table de routage. Il faut donc indiquer à sudo que l'utilisateur OpenVPN a le droit de toucher à la table de routage (fichier /etc/sudoers.d/openvpn)
openvpn ALL=(root) NOPASSWD:/bin/ip route *, /bin/ip -6 route *
Une autre solution (voir discussion) consiste à faire sudo le handler.
Pas implémenté.
Quelqu'un est motivé pour faire marcher un chroot (il faut avoir accès à sudo, ip, sh, aux fichiers de configuration utilisateur …) ?
https://wiki.ldn-fai.net/wiki/Mise_en_place_d%27un_CA_avec_easyrsa Voir aussi https://wiki.arn-fai.net/benevoles:procedures:vpn#a_faire_en_cli_chaque_annee_renouveler_le_certificat_du_serveur
Pour renouveler le certificat du serveur
cp -a /etc/openvpn/crypto /root/crypto.old cd /etc/openvpn/easy-rsa . ./vars ./revoke-full server rm /etc/openvpn/easy-rsa/keys/server.* cp -f /etc/openvpn/easy-rsa/keys/crl.pem /etc/openvpn/crypto/crl.pem ./build-key-server server cp -f /etc/openvpn/easy-rsa/keys/server.{key,crt} /etc/openvpn/crypto/ rm -f /etc/openvpn/easy-rsa/keys/server.{key,csr} reboot
- MANAGED BY PUPPET - Template : openvpn/openvpn.erb - Interface ; listen a.b.c.d port 1194 proto tcp6-server dev tuntcp tun-ipv6 ; push "tun-ipv6" # We don't push it so the user can turn it on/off easily. - Mode mode server topology subnet push "topology subnet" - User user openvpn group openvpn persist-tun persist-key - TLS tls-server ca /etc/openvpn/keys/ca.crt remote-cert-tls client cert /etc/openvpn/keys/server.crt key /etc/openvpn/keys/server.key dh /etc/openvpn/keys/dh2048.pem crl-verify /etc/openvpn/keys/crl.pem - TODO, add support for crl-verify - TODO, fidn certificate in LDAP with tls-verify+tls-export-cert? - IPv4 ifconfig 80.67.188.126 255.255.255.128 push "route-gateway 80.67.188.126" ; push "redirect-gateway def1" - IPv6 ifconfig-ipv6 2001:913:0:2003::0/64 2001:913:0:2003::0 - DHCP emulation push "dhcp-option DNS 80.67.188.188" push "dhcp-option DNS 2001:913::8" - Misc mssfix keepalive 10 120 comp-lzo adaptive push "comp-lzo adaptive" max-clients 64 - Authentication, user password ; auth-user-pass ; auth-user-pass-verify auth-pam.pl via-file - Authentication, RADIUS ; plugin /usr/share/openvpn/plugin/lib/openvpn-auth-pam.so login - Scripts script-security 2 ; up /etc/openvpn/handler ; tls-verify /etc/openvpn/handler client-connect /etc/openvpn/handler ; route-up /etc/openvpn/handler client-disconnect /etc/openvpn/handler ; down /etc/openvpn/handler ; learn-address /etc/openvpn/handler ; auth-user-pass-verify /etc/openvpn/handler - Logs verb 4 mute 10 - This is already done /etc/openvpn/foo.conf: syslog ovpn-tcp - Open console with: socat STDIO /var/run/openvpn.foo.socket management /var/run/openvpn.tcp.socket unix management-client-user root
- MANAGED BY PUPPET - Template : openvpn/openvpn.erb - Interface ; listen a.b.c.d port 1194 proto udp6 dev tunudp tun-ipv6 ; push "tun-ipv6" # We don't push it so the user can turn it on/off easily. - Mode mode server topology subnet push "topology subnet" - User user openvpn group openvpn persist-tun persist-key - TLS tls-server ca /etc/openvpn/keys/ca.crt remote-cert-tls client cert /etc/openvpn/keys/server.crt key /etc/openvpn/keys/server.key dh /etc/openvpn/keys/dh2048.pem crl-verify /etc/openvpn/keys/crl.pem ;tls-auth /etc/openvpn/keys/ta.key 0 - TODO, add support for crl-verify - TODO, fidn certificate in LDAP with tls-verify+tls-export-cert? - IPv4 ifconfig 80.67.188.126 255.255.255.128 push "route-gateway 80.67.188.126" ; push "redirect-gateway def1" - IPv6 ifconfig-ipv6 2001:913:0:2003::0/64 2001:913:0:2003::0 - DHCP emulation push "dhcp-option DNS 80.67.188.188" push "dhcp-option DNS 2001:913::8" - Misc mssfix keepalive 10 120 comp-lzo adaptive push "comp-lzo adaptive" max-clients 64 - Authentication, user password ; auth-user-pass ; auth-user-pass-verify auth-pam.pl via-file - Authentication, RADIUS ; plugin /usr/share/openvpn/plugin/lib/openvpn-auth-pam.so login - Scripts script-security 2 ; up /etc/openvpn/handler ; tls-verify /etc/openvpn/handler client-connect /etc/openvpn/handler ; route-up /etc/openvpn/handler client-disconnect /etc/openvpn/handler ; down /etc/openvpn/handler ; learn-address /etc/openvpn/handler ; auth-user-pass-verify /etc/openvpn/handler - Logs verb 4 mute 10 - This is already done /etc/openvpn/foo.conf: syslog ovpn-udp - Open console with: socat STDIO /var/run/openvpn.foo.socket management /var/run/openvpn.udp.socket unix management-client-user root
Voir les fichiers « common.conf », « udp.conf » et « tcp.conf » sur notre dépôt git.
Dans cette partie, nous comparons/développons certaines solutions alternatives, qui n'ont pas été retenus dans notre configuration.
LDN a retenu l'option d'utiliser un subnet d'interco IPv6 (expliqué dans ce tuto) en plus du préfixe IPv6 délégué.
Avantages et inconvénients du subnet d'interco :
Avantages et inconvénients d'utiliser directement le bloc délégué à l'utilisateur :
Nous n'utilisons pas une AC tiers connue (Verisign, Comodo, CACert, StartCom …) pour OpenVPN mais notre propre AC interne (gérée avec easy-rsa pour le moment, mais d'autres solutions sont possibles).
On sait depuis longtemps que les grandes AC sont faillibles et 2011 nous l'a prouvé (DigiNotar, Comodo, et ceux qui n'ont rien dit (cf étude EFF)), concentrons-nous sur le reste.
Résumé (tiré de section « Important Note on the use of commercial certificate authorities (CAs) with OpenVPN » dans la doc OpenVPN pour la branche 1.X) :
OpenVPN's security model in SSL/TLS mode is oriented toward users who will <mark>generate their own root certificate, and hence be their own CA.</mark> ... This authentication procedure ... presents a problem if you wish to use the root certificate of a commercial CA such as Thawte. If, for example, you specified Thawte's root certificate ... any certificate signed by Thawte would now be able to authenticate with your OpenVPN peer.
Bref la conclusion est que ce n'est pas une bonne idée d'utiliser un AC tiers que ce soit pour les certificats serveur ou pour les certificats client (mais en particulier pour les certificats serveurs).
Certificats serveur : Sauf configuration spécifique du client, le client OpenVPN ne va pas vérifier que le nom du serveur dans le certificat correspond bien au serveur OpenVPN auquel on souhaite se connecter. Tout certificat serveur émise par l'AC sera donc accepté par le client OpenVPN. La sécurité du dispositif nécessite donc une configuration spécifique supplémentaire du client OpenVPN. Il est donc fortement préférable d'utiliser une AC spécifique à OpenVPN pour les certificats serveurs.
Certificats clients : Si une AC tiers est utilisée, les certificats client X.509 émis par cette autorité seront reconnus par notre serveur OpenVPN. Il faudrait donc ajouter du code spécifique pour vérifier qu'il s'agit bien d'un utilisateur de notre système (et pas d'un “homonyme”), probablement en se basant sur le distinguished name du certificat ou mapper les DN émis par l'AC sur des utilisateurs de notre système d'information. Dans la mesure où nous avons la main sur le côté serveur, il est envisageable d'utiliser une AC tierse pour les certificats clients. Cependant, dépendre d'une AC tierse pour émettre les certificats utilisateurs est très contraignant.
Du coup, quel est l'apport d'une signature du certificat du serveur OpenVPN par une AC connue ?
C'est possible, FDN le fait. J'ai moi-même testé, ça marche sans prendre la tête . Quels sont les inconvénients ?
On notera que, apparemment, quiconque dispose d'un certificat signé par CACert (ce que tout le monde peut avoir, il faut une adresse mail valide et un nom de domaine valide sur lequel on a le contrôle) peut donc tenter un MITM en se faisant passer pour le serveur. OpenVPN ne vérifie pas la cohérence entre le CN du certif et la directive « remote » (serveur VPN auquel on se connecte), ce qui nécessiterait d'avoir un certificat signé par CACert pour notre domaine (arn-fai/ldn-fai.net).
How-to :
openssl req -new -newkey rsa:2048 -out server.csr -nodes -keyout server.key
Attention : le CN doit être de la forme quelquechose.domaine.enregistre.sur.cacert.example
C'est moins sûr.
Il y a deux cas de figure :
>* De valider l'adresse mail de chaque abonné au VPN
* Que le certificat aura une validité de 6 mois (contre 2 ans avec easy-rsa) ce qui peut vite devenir ingérable avec une 10aine d'abonnés, pas tous inscrit à la même période.
* CN=CACert_WOT_User pour tous les certificats donc le script handler ne marche pas (sauf si on peut matcher l'user sur l'adressemail du certif)
* et surtout, ces certifs ne sont pas fait pour cet usage
Mais ça marche pour un certif :D
> ca.crt (CACert) — server.crt
+– sub-ca.crt — client.crtCela ne marche pas :
* Il faudrait que CACert n'enlève pas la propriété « CA:TRUE » de notre certificat, ce qui n'est pas le cas. Notre certificat intermédiaire n'étant pas un certificat d'AC, OpenVPN ne sait pas remonter la chaîne (ça se voit aussi avec un openssl verify).
* On n'échappe toujours pas au problème de la validité limitée à 6 mois. Et comme le sub-ca.crt est chaîné dans le certificat client, cela implique de refaire les certificats clients tous les 6 mois même si nous signons avec une durée plus longue.
C'est intégrable avec easy-rsa. Il suffit de générer une requête de certification, avec un CN valide (exemple : vpn-client-ca.domaine.enregistre.sur.cacert.example) et de la soumettre à CACert (comme pour le cas serveur, voir ci-dessus). En récupérant le certificat chez CACert, on le place dans easy-rsa/keys/ sous le nom « ca.crt », avec sa clé privée, « ca.key », avec un fichier vide « index.txt » et un fichier « serial » qui contient « 01 ». Ensuite, on génère un certificat client avec easy-rsa (voir procédure dans la section précédente).
On notera que, apparemment, quiconque dispose d'un certificat signé par CACert (ce que tout le monde peut avoir, il faut une adresse mail valide et un nom de domaine valide sur lequel on a le contrôle) peut donc être un client de notre VPN. Actuellement, l'initialisation foirera sur le script handler qui ne trouvera pas d'utilisateur associé au CN dans le dossier /etc/openvpn/users et la connexion s'arrêtera sur un AUTH_FAILED mais qui dit que ce comportement restera identique à l'avenir (cela dépend du comportement d'OpenVPN et du script handler → poids non négligeable).
Tutos les plus intéressants :
Nous avons choisi de ne pas utiliser de fichiers de configuration par client statiques (répertoire ccd) mais de générer ces configurations dynamiquement à partir de données de plus haut niveau.
Raisons :
La configuration classique utiliserait un client-disconnect:
#!/bin/sh # Add routes set -e [[|-z "$ifconfig_pool_remote_ip" ]] || ip route add $ifconfig_pool_remote_ip/32 dev $dev proto static [[|-z "$ifconfig_ipv6_pool_remote_ip" ]] || ip route add $ifconfig_ipv6_pool_remote_ip/128 dev $dev proto static
et un client-disconnect:
#!/bin/sh # Remove routes [[|-z "$ifconfig_pool_remote_ip" ]] || ip route del $ifconfig_pool_remote_ip/32 dev $dev proto static [[|-z "$ifconfig_ipv6_pool_remote_ip" ]] || ip route del $ifconfig_ipv6_pool_remote_ip/128 dev $dev proto static
Pour l'instant, nous n'avons pas mis en place de RADIUS pour le VPN et on s'en porte bien. Est-ce que ça a un intérêt ?
On peut aussi faire le sudo en appelant le handler plutôt que au niveau de ip route.
Configuration OpenVPN :
client-connect "sudo -E -u root /etc/openvpn/handler" client-disconnect "sudo -E -u root /etc/openvpn/handler"
Configuration /etc/sudoers.d/openvpn :
openvpn ALL=(root) NOPASSWD:SETENV:/etc/openvpn/handler
L'utilisateur openvpn peut prendre uniquement les droits de root (pas des autres utilisateurs du système), et il peut utiliser une seule commande sans saisir un mot de passe et pour laquelle il peut demander une conservation des variables d'environnement : /etc/openvpn/handler.
Avec cette approche, une compromission de l'utilisateur openvpn, pourrait potentiellement tenter de jouer sur certaines variables d'environnement (LC_PRELOAD) pour modifier le comportement de “ip route”. Il semble plus sécurisé de simplement permettre à l'utilisateur openvpn de modifier la table de routage.
Exemple de fichier ccd/my_user :
# Interco IPv4 ifconfig-push 80.67.188.1 80.67.188.1/25 # Interco IPv6 ifconfig-ipv6-push 2001:913:0:2005::1/64 2001:913:0:2005:: # Préfixe délégué iroute-ipv6 2001:DB8:1::/48 push "setenv-safe DELEGATED_IPV6_PREFIX 2001:DB8:1::/48" # Annonce au client
On active cette fonctionnalité par :
client-config-dir ccd # active le répertoire ccd ccd-exclusive # n'accepte que les utilisateurs ayant un fichier ccd
On utilise des scripts afin de router les paquets vers la bonne interface (résoudre l'ambiguïté des deux instances, une UDP, l'autre TCP) :
script-security 2 client-connect /etc/openvpn/handler client-disconnect /etc/openvpn/handler
Avec le fichier handler :
#!/bin/sh # MANAGED BY PUPPET # File: openvpn/handler if [[|"$(id -ru)" = "0" ]]; then SUDO ====== else SUDO=sudo fi # ##### Utility log() { local level level="$1" shift # You might want to disable logs: logger -t"ovpn-script[[$$]]" -pdaemon."$level" -- "$@" } # ##### Functions # Load user information from /etc/openvpn/users/$common_name get_user_info() { if ! echo "$common_name" | grep ^[[a-zA-Z]][[a-zA-Z0-9_-]]*$; then log notice "Bad common name $common_name" return 1 fi if ! . "/etc/openvpn/users/$common_name" ; then log notice "No configuration for user $common_name" return 1 fi } # Write user specific OpenvPN configuration to stdout create_conf() { if ! [[|-z "$IP4" ]]; then echo "ifconfig-push $IP4 $ifconfig_netmask" fi if ! [[|-z "$IP6" ]]; then echo "ifconfig-ipv6-push $IP6/64 $ifconfig_ipv6_local" fi if ! [[|-z "$PREFIX" ]]; then # Route the IPv6 delegated prefix: echo "iroute-ipv6 $PREFIX" # Set the OPENVPN_DELEGATED_IPV6_PREFIX in the client: echo "push \"setenv-safe DELEGATED_IPV6_PREFIX $PREFIX\"" fi } add_route() { $SUDO ip route replace "$@" } # Add the routes for the user in the kernel add_routes() { if ! [[|-z "$IP4" ]]; then log info "IPv4 $IP4 for $common_name" add_route $IP4/32 dev $dev protocol static fi if ! [[|-z "$IP6" ]]; then log info "IPv6 $IP6 for $common_name" add_route $IP6/128 dev $dev protocol static fi if ! [[|-z "$PREFIX" ]]; then log info "IPv6 delegated prefix $PREFIX for $common_name" add_route $PREFIX via $IP6 dev $dev protocol static fi } remove_routes() { if ! [[|-z "$IP4" ]]; then $SUDO ip route del $IP4/32 dev $dev protocol static fi if ! [[|-z "$IP6" ]]; then $SUDO ip route del $IP6/128 dev $dev protocol static fi if ! [[|-z "$PREFIX" ]]; then $SUDO ip route del $PREFIX via $IP6 dev $dev protocol static fi } set_routes() { if ! add_routes; then remove_routes return 1 fi } # ##### OpenVPN handlers client_connect() { conf="$1" get_user_info || exit 1 create_conf > "$conf" set_routes } client_disconnect() { get_user_info || exit 1 remove_routes } # ##### Dispatch case "$script_type" in client-connect) client_connect "$@" ;; client-disconnect) client_disconnect "$@" ;; esac
Problèmes :
Nous allons donc utiliser une autre solution afin d'éviter de maintenir une version patchée d'OpenVPN.