Chocoblog

Chocoblog

Billets sur l'informatique, les logiciels libres et retours d'utilisation sont au programme avec la possibilité de publier des billets de copains.

OpenVPN avec interface d'administration

EDIT : J'ai mis à jour le dépôt afin d'avoir une interface en bootstrap, que l'utilisateur puisse télécharger les fichiers de configuration directement depuis un formulaire et d'automatiser l'installation d'un OpenVPN avec l'interface d'administration via un script car le faire de façon manuelle peut en décourager plus d'un. Je détaille la nouvelle utilisation dans cet article : http://blog.cpy.re/installation-automatique-dopenvpn-avec-interface-dadministration/

Ce billet a été initialement publié l'année dernière sur http://blog.sandrocazzaniga.fr

En cette belle journée j'ai décidé de vous présenter en détail comment j'ai configuré mon OpenVPN afin de chiffrer mes échanges et surtout d'outrepasser le pare-feu de ma fac.
Je voulais aussi en faire profiter les collègues qui voulaient notamment jouer à LoL et autres bizarreries qui me dépassent :D. Il fallait donc que je puisse gérer des utilisateurs.

Je me suis donc tourné sur OpenVPN (what else ?) couplé à une identification SQL pour qu'il me soit facile d'ajouter/supprimer des utilisateurs. Il était par contre assez pénible d'ajouter les utilisateurs via PHPMyAdmin ou directement en CLI donc j'ai codé une petite interface WEB me permettant d'ajouter/supprimer facilement des utilisateurs. Je voulais aussi avoir accès aux historiques de connexion.

Aujourd'hui, ça donne quelque chose dans ce style :

Screenshot

Toutes les sources (interface web, conf serveur avec scripts et confs client GNU/Linux et windows) sont disponibles sur mon GitHub.

Tout ce qui suit a été testé sur une Debian Wheezy, mais n'hésitez pas à me signaler des erreurs/problèmes ou coquilles !

Vu qu'il ne sert à rien de réinventer la roue, les deux premières parties sont fortement inspirées de l'article de Nicolargo et du wiki de deimos. Enfin Cet article m'a aussi beaucoup aidé pour la configuration des scripts ainsi que pour la structure des différentes tables de la base de données. Merci à eux.

OpenVPN côté serveur

Commençons par tout simplement installer OpenVPN. Pas besoin de préciser si on veut la version serveur ou client, le même programme s'occupe de faire l'un et l'autre.

aptitude install openvpn

Dans un premier temps il va falloir créer la clé et le certificat. OpenVPN nous facilite la tâche en nous mettant à disposition des scripts.

Créons tout d'abord un dossier dédié :

sudo mkdir /etc/openvpn/easy-rsa/

On copie ensuite les scripts d'OpenVPN :

sudo cp -r /usr/share/doc/openvpn/examples/easy-rsa/2.0/* /etc/openvpn/easy-rsa/

Et on se donne la propriété du dossier :

sudo chown -R $USER /etc/openvpn/easy-rsa/

Avant d'exécuter les scripts, on doit d'abord configurer les variables de /etc/openvpn/easy-rsa/vars

export KEY_SIZE=1024
export CA_EXPIRE=36500
export KEY_EXPIRE=36500
export KEY_COUNTRY="FR"
export KEY_PROVINCE="France"
export KEY_CITY="Saint-Etienne"
export KEY_ORG="Chocobozzz Corporation"
export KEY_EMAIL="admin@domain.tld"

Les certificats expireront au bout de 10 ans et on choisit une clé de 1024 bits. Le reste des informations est relativement clair.

On génère ensuite la clé privée, publique ainsi que les certificats grâce à l'appel des scripts suivants (toujours dans le dossier /etc/openvpn/easy-rsa/) :

source vars
./clean-all
./build-dh
./pkitool --initca
./pkitool --server server
sudo openvpn --genkey --secret keys/ta.key

On copie ensuite nos clés/certificats dans le dossier d'OpenVPN :

sudo cp /etc/openvpn/easy-rsa/keys/{ca.crt,ta.key,server.crt,server.key,dh1024.pem} /etc/openvpn/

On va maintenant éditer la configuration d'OpenVPN côté serveur en modifiant le fichier /etc/openvpn/server.conf :

## GENERAL ##

# Server en tcp, port 443 en écoute en créant un tunnel
mode server
proto tcp
port 443
dev tun

## CLE, CERTIFICATS ET CONFIGURATION RÉSEAU ##
# Prouver l'identité 
ca ca.crt
# Clé publique du serveur
cert server.crt
# Clé privée du serveur
key server.key
# Clé partagée (utilisé pour le chiffrement symétrique, hashage...)
dh dh1024.pem
# Augmente la sécurité (DDOS, port flooding...)
# Deuxième paramètre à 0 sur le serveur et 1 sur le client)
tls-auth ta.key 0
# Choix de la méthode de cryptographie
cipher AES-256-CBC

# Reseau
# Sous réseau, le serveur prendra l'adresse 10.8.0.1 et le reste sera disponible pour les clients
server 10.8.0.0 255.255.255.0

# Redirection du trafic via internet
push "redirect-gateway def1"

# DNS alternatifs (FDN)
push "dhcp-option DNS 80.67.169.12" 
push "dhcp-option DNS 80.67.169.40"

# (OpenDNS)
# push "dhcp-option DNS 208.67.222.222" 
# push "dhcp-option DNS 208.67.220.220"

# (Google)
# push "dhcp-option DNS 8.8.8.8" 
# push "dhcp-option DNS 8.8.4.4"

# Ping toutes les 10 secondes et si après 120 secondes le client ne répond toujours pas alors on déconnecte
keepalive 10 120
# Regenerer la clé du canal toutes les 5h (implique la déconnexion du client)
reneg-sec 18000

## SECURITE ##

# On abaisse les privilèges du démon
user nobody
group nogroup

# Rend les clés persistantes au démarrage
# Donc, plus besoin de relire les clés cryptographiques (dont on aurait pas eu les droits car on a abaissé les privilèges du démon)
persist-key
# Ne pas fermer et rouvrir le périphérique TUN/TAP 
persist-tun
# Active la compression
comp-lzo

## LOG ##

# Quantité de log (choix entre 0 et 9)
# Choisir entre 3 et 4 pour une utilisation normale
verb 3
# Imprime au plus 20 messages de la même catégorie
mute 20
# Fichier où on liste les connexions des clients
status openvpn-status.log
# Fichier de log
log-append /var/log/openvpn.log
# Dossier de configuration des clients
client-config-dir ccd

## PASS ##

# Autoriser l'exécution de scripts externes en passant les mots de passe via les variables d'environnement
script-security 3 system
# Utiliser le pseudo d'authentification comme nom (au lieu du nom du certificat du client)
username-as-common-name
# Certificat du client non requis
client-cert-not-required
# Utiliser le script de connexion lorsqu'un client veut s'authentifier
auth-user-pass-verify scripts/login.sh via-env
# Nombre maximum de clients
max-clients 50
# Appeler ces scripts à la connexion et déconnexion du client
client-connect scripts/connect.sh
client-disconnect scripts/disconnect.sh

Cette configuration n'est en revanche pas encore exploitable à l'heure actuelle. Nous créerons la base de données et les scripts de connexion plus tard. Avant cela, il va d'abord nous falloir s'occuper du réseau.

Nous devons dire au système de router vers l'extérieur.

sudo sh -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'

Pour rendre l'option persistante même après un démarrage, il faut ajouter la ligne suivante dans le fichier /etc/sysctl.conf :

net.ipv4.ip_forward = 1

En enfin, nous configurons iptables (cf Nicolargo) :

sudo iptables -I FORWARD -i tun0 -j ACCEPT
sudo iptables -I FORWARD -o tun0 -j ACCEPT
sudo iptables -I OUTPUT -o tun0 -j ACCEPT

sudo iptables -A FORWARD -i tun0 -o eth0 -j ACCEPT
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -s 10.8.0.2/24 -o eth0 -j MASQUERADE

Scripts de l'identification SQL

Nous avons spécifié dans le fichier de configuration d'utiliser certains scripts. On va maintenant les créer. Créez d'abord un dossier dédié :

mkdir /etc/openvpn/scripts

Nous allons créer 4 scripts :

  • config.sh qui contiendra les informations de connexions à notre base de données
  • login.sh qui vérifiera si les identifiants rentrés par l'utilisateur sont corrects
  • connect.sh qui loguera la connexion de l'utilisateur
  • disconnect.sh qui modifiera la ligne de log de l'utilisateur afin de spécifier la date de fin de connexion, le fait qu'il n'est plus en ligne etc

config.sh

#!/bin/bash
# Serveur
HOST='localhost'
# Port (par défaut : 3306)
PORT='3306'
# Username
USER='vpn_user'
# Password
PASS='mdp'
# Nom de la base de données
DB='vpn'

Maintenant, le script lorsque l'utilisateur veut s'identifier : login.sh

#!/bin/bash
. /etc/openvpn/scripts/config.sh

# Authentication
user_id=$(mysql -h$HOST -P$PORT -u$USER -p$PASS $DB -sN -e "SELECT user_id FROM user WHERE user_id = '$username' AND user_pass = SHA1('$password') AND user_enable=1 AND (TO_DAYS(now()) >= TO_DAYS(user_start_date) OR user_start_date='0000-00-00') AND (TO_DAYS(now()) <= TO_DAYS(user_end_date) OR user_end_date='0000-00-00')")

# Vérification de l'utilisateur
[ "$user_id" != '' ] && [ "$user_id" = "$username" ] && echo "user : $username" && echo 'authentication ok.' && exit 0 || echo 'authentication failed.'; exit 1

Maintenant, lorsque l'utilisateur se connecte au VPN : connect.sh

#!/bin/bash
. /etc/openvpn/scripts/config.sh

# On insert les données dans la table de log
mysql -h$HOST -P$PORT -u$USER -p$PASS $DB -e "INSERT INTO log (log_id, user_id, log_trusted_ip, log_trusted_port, log_remote_ip, log_remote_port, log_start_time, log_end_time, log_received, log_send) VALUES(NULL, '$common_name','$trusted_ip', '$trusted_port','$ifconfig_pool_remote_ip', '$remote_port_1', now(),'0000-00-00 00:00:00', '$bytes_received', '$bytes_sent')"

# On spécifie que l'utilisateur est en ligne
mysql -h$HOST -P$PORT -u$USER -p$PASS $DB -e "UPDATE user SET user_online=1 WHERE user_id='$common_name'"

Et enfin lorsque l'utilisateur se déconnecte du VPN : disconnect.sh

#!/bin/bash
. /etc/openvpn/scripts/config.sh

# On précise que l'utilisateur n'est plus en ligne
mysql -h$HOST -P$PORT -u$USER -p$PASS $DB -e "UPDATE user SET user_online=0 WHERE user_id='$common_name'"

# On insert le datetime de déconnexion
mysql -h$HOST -P$PORT -u$USER -p$PASS $DB -e "UPDATE log SET log_end_time=now(), log_received='$bytes_received', log_send='$bytes_sent' WHERE log_trusted_ip='$trusted_ip' AND log_trusted_port='$trusted_port' AND user_id='$common_name' AND log_end_time='0000-00-00 00:00:00'"

OpenVPN côté client

Bien, maintenant nous allons créer la configuration du client :

client
dev tun
proto tcp-client
remote xxx.xxx.xxx.xxx 443
resolv-retry infinite
cipher AES-256-CBC
redirect-gateway

# Cles
# Prouver l'identité
ca ca.crt
tls-auth ta.key 1
key-direction 1
ns-cert-type server
auth-user-pass
auth-nocache

# Securite
nobind
persist-key
persist-tun
comp-lzo
verb 3

# DNS
script-security 2
up ./update-resolv.sh
down ./update-resolv.sh

# Proxy ?
# http-proxy cache.univ.fr 3128

Deux précisions : vous l'aurez compris, chaque client devra récupérer ca.crt et ta.key du serveur.
Sur GNU/Linux, la partie DNS est nécessaire (pas tout le temps, mais c'était le cas à la fac). En effet, il fallait modifier les DNS pour ne plus utiliser ceux par défaut, mais ceux spécifiés par le serveur (dans notre cas OpenDNS).

Le fichier update-resolv.sh est composé des lignes suivantes (cf la doc d'ArchLinux) :

#!/bin/bash                                                                   
#
# Parses DHCP options from openvpn to update resolv.conf
# To use set as 'up' and 'down' script in your openvpn *.conf:
# up /etc/openvpn/update-resolv-conf
# down /etc/openvpn/update-resolv-conf
#
# Used snippets of resolvconf script by Thomas Hood <jdthood@yahoo.co.uk>
# and Chris Hanson
# Licensed under the GNU GPL.  See /usr/share/common-licenses/GPL.
# 07/2013 colin@daedrum.net Fixed intet name
# 05/2006 chlauber@bnc.ch
#
# Example envs set from openvpn:
# foreign_option_1='dhcp-option DNS 193.43.27.132'
# foreign_option_2='dhcp-option DNS 193.43.27.133'
# foreign_option_3='dhcp-option DOMAIN be.bnc.ch'

#[ -x $(which resolvconf) ] || exit 0
[ -x /usr/bin/resolvconf ] || exit 0

case $script_type in

up)
   for optionname in ${!foreign_option_*} ; do
      option="${!optionname}"
      echo $option
      part1=$(echo "$option" | cut -d " " -f 1)
      if [ "$part1" == "dhcp-option" ] ; then
         part2=$(echo "$option" | cut -d " " -f 2)
         part3=$(echo "$option" | cut -d " " -f 3)
         if [ "$part2" == "DNS" ] ; then
            IF_DNS_NAMESERVERS="$IF_DNS_NAMESERVERS $part3"
         fi
         if [ "$part2" == "DOMAIN" ] ; then
            IF_DNS_SEARCH="$IF_DNS_SEARCH $part3"
         fi
      fi
   done
   R=""
   if [ "$IF_DNS_SEARCH" ] ; then
           R="${R}search $IF_DNS_SEARCH
"
   fi
   for NS in $IF_DNS_NAMESERVERS ; do
           R="${R}nameserver $NS
"
   done
   #echo -n "$R" | resolvconf -p -a "${dev}"
   echo -n "$R" | /usr/bin/resolvconf -a "${dev}.inet"
   ;;
down)
   resolvconf -d "${dev}.inet"
   ;;
esac

Création de l'espace d'administration

Super, le plus gros du travail est fait. On peut enfin créer notre base de données, que j'appelerai vpn. Il suffit ensuite d'importer dans votre base ce fichier. Il créera 3 tables : une pour les utilisateurs, une pour les logs et enfin une pour stocker la liste des administrateurs.
Commençons par créer une administrateur :

INSERT INTO admin (admin_id, admin_pass) VALUES ("superadmin", SHA1('monmdp'));

Bien, maintenant notre VPN peut fonctionner. Via PHPMyAdmin ou en CLI vous pouvez directement ajouter des utilisateurs, administrateurs ou bien simplement voir les logs des utilisateurs qui se sont connectés. Seulement, je trouvais ça assez pénible du coup j'ai créé une petite interface web permettant de facilement voir les logs, d'ajouter/supprimer des utilisateurs ou des administrateurs.

L'interface est faite en HTML5/CSS3 PHP JQuery. Je n'ai pas voulu trop me prendre la tête donc j'ai utilisé les plugins SlickGrid ainsi que de SlickGridEnhancementPager.

Bref, commençons par récupérer les fichiers soit en clonant le repo soit en téléchargeant le ZIP.
Déplacez vous dans le dossier. Commencez par supprimer le dossier sql qui contient le script permettant de créer les tables (ce que nous avons fait précédemment).
Vous pouvez aussi supprimer/déplacer le dossier openvpn-conf qui contient toutes les confs serveur (scripts compris) et clients.

Il faut ensuite copier le fichier de connexion à la base de données :

cp include/config.php.example include/config.php

Puis modifier son contenu pour rentrer vos identifiants.

Le tout devrait maintenant être opérationnel. Vous pouvez simplement vous connecter via la page d'index avec les identifiants de l'administrateur rentrés précédemment et vous devriez vous retrouver avec vos 3 tableaux comme montré via la première image de cet article.

Passons aux tests

On va maintenant tester notre VPN sous GNU/Linux (désolé pour les Windowsiens, mais la procédure reste la même).
Il va donc premièrement nous falloir créer l'utilisateur via l'interface web (ou via PHPMyAdmin) :

Screenshot

Le client doit récupérer les fichiers client.conf, ca.crt, ta.key et le script update-resolv.sh (à priori l'utilisation de ce dernier n'est pas nécessaire sous Windows, dans ce cas vous pouvez supprimer la partie DNS de la conf du client).

Il doit ensuite lancer la commande suivante en root ou en sudo :

openvpn client.conf

Si il utilise systemd (sinon il devrait :p) il peut utiliser OpenVPN comme service de la manière suivante (en root, bien évidemment) :

systemctl start openvpn@nom_conf.service

nom_conf représente... le nom de notre configuration. Dans notre cas :

systemctl start openvpn@client.service

Ou bien si on veut qu'OpenVPN se lance à chaque démarrage :

systemctl enable openvpn@client.service

Ensuite, il faudra rentrer ses identifiants :

Enter Auth Username: *******
Enter Auth Password: *********

Si OpenVPN est lancé en tant que service à chaque démarrage, il faudra passer à la configuration cliente un fichier comprenant vos identifiants.

Si le serveur refuse votre identification, étudiez le fichier de log d'OpenVPN sur le serveur, et n'hésitez pas à augmenter la valeur de l'option verb. Vérifiez dans la base SQL que l'utilisateur est bien renseigné avec le bon mot de passe. Vous pouvez aussi ajouter des echo dans les scripts afin de vérifier ce que retourne SQL. La sortie se fera dans le fichier de log d'OpenVPN.

Si l'identification est bonne, OpenVPN se lancera et finira par afficher les lignes suivantes :

Wed Mar 19 18:37:44 2014 /usr/bin/ip link set dev tun0 up mtu 1500
Wed Mar 19 18:37:44 2014 /usr/bin/ip addr add dev tun0 local 10.8.0.6 peer 10.8.0.5
Wed Mar 19 18:37:44 2014 ./update-resolv.conf tun0 1500 1560 10.8.0.6 10.8.0.5 init
dhcp-option DNS 208.67.222.222
dhcp-option DNS 208.67.220.220
Wed Mar 19 18:37:44 2014 /usr/bin/ip route add xxx.xxx.xxx.xxx/32 via 192.168.10.1
Wed Mar 19 18:37:44 2014 /usr/bin/ip route add 0.0.0.0/1 via 10.8.0.5
Wed Mar 19 18:37:44 2014 /usr/bin/ip route add 128.0.0.0/1 via 10.8.0.5
Wed Mar 19 18:37:44 2014 /usr/bin/ip route add 10.8.0.1/32 via 10.8.0.5
Wed Mar 19 18:37:44 2014 Initialization Sequence Completed

Allez ensuite sur un site donnant votre IP, comme celui-ci par exemple. Si tout fonctionne normalement, le site devrait se charger (c'est déjà pas mal) et donner l'adresse IP de votre serveur.

Sur l'interface d'administration, vous pourrez voir que test est connecté via le tableau "Users" et voir à l'heure où il s'est connecté grâce au tableau "Logs". Et voilà, vous pouvez maintenant monter votre business et faire payer l'accès VPN à vos amis :D

Enjoy :)