Table des matières

VPN ARN

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 :

Résumé de la configuration

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.

Configuration de base

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.

Transport du VPN

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

Type de VPN

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

Topologie

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

Configuration TLS

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

Toute personne qui écoute sur le réseau saura donc que l'utilisateur utilise OpenVPN car lors de la phase d'initialisation, OpenVPN a 4 octets qui lui sont spécifiques (source : présentation « Let's tunnel the planet » à PSES 2012).

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.

CRL

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.

Configuration IP

Configuration réseau

Résumé :

  1. activer le routage (IPv4 et IPv6) sur le serveur OpenVPN ;
  2. router la plages d'interco IPv4, la plage d'interco IPv6 et la place de préfixe délégués IPv6 vers le serveur OpenVPN ;
  3. ajout de routes blackholes la plage des préfixes IPv6 délégués pour éviter les aller retours de paquet. Le support des blackhole en IPv6 n'est pas aussi complet qu'en IPv4 (ou n'a pas la même syntaxe). Voir, par exemple : iproute: adding route blackholes doesn't work for IPv6. Il faut préciser une interface. Par exemple :
ip -6 route add blackhole 2001:DB8::/48 dev dummy0

Configuration OpenVPN

# 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

Notion de route interne (iroute)

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é).

Communication entre clients

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 :

  1. la première instance reçoit le paquet depuis le tunnel ;
  2. elle constate que le trafic est à destination du VPN ;
  3. elle consulte donc ses « iroute » mais ne trouve pas de « iroute » pour ce paquet (puisque le client est connecté à l'autre instance) ;
  4. elle jette donc le paquet.

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.

Gestion des utilisateurs

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.

Interface de gestion

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

Log

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

Configuration 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).

Configuration dans syslog

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.

Configuration dans logrotate

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 :

Configuration diverse

mssfix
keepalive 10 120
comp-lzo adaptive
push "comp-lzo adaptive"
max-clients 64

Configuration du client

Voir Configuration VPN ARN

Contournement des filtrages réseau

Éléments de solution :

PAT

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

Proxy HTTPs

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.

Renforcer la sécurité

Faire tourner en non-root

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.

Chroot

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 …) ?

Gestion du CA

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

TL;DR Configuration LDN

Instance TCP

  -  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

Instance UDP

  -  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

Fichiers de conf au final (ARN)

Voir les fichiers « common.conf », « udp.conf » et « tcp.conf » sur notre dépôt git.

Discussion, remarques et choix

Dans cette partie, nous comparons/développons certaines solutions alternatives, qui n'ont pas été retenus dans notre configuration.

Utiliser un subnet d'interco ou pas en IPv6 ?

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 :

Utiliser une AC perso ou une AC (re)connue/tiers ?

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.

Ce que sait et ne sais pas faire OpenVPN avec les certificats

Du coup, quel est l'apport d'une signature du certificat du serveur OpenVPN par une AC connue ?

Utiliser CACert pour signer le certificat du serveur?

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

Utiliser CACert pour signer les certifs clients ?

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

Cela 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).

Références

section « Important Note on the use of commercial certificate authorities (CAs) with OpenVPN » dans la doc OpenVPN pour la branche 1.X

Tutos les plus intéressants :

Pourquoi pas de fichier de configuration client standard (ccd/user) ?

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

Utilisation de RADIUS ?

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 ?

"sudo handler" plutôt que "sudo ip route"

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.

Approche ccd/

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.