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.

Faire cohabiter OpenVPN et HTTPS sur le port 443 en IPV4 et IPV6 avec SSLH

Il se peut que vous vouliez vous monter un VPN sur votre serveur. Le principal soucis c'est que dans certains réseaux (entreprises, écoles...) quasi tous les ports sont fermés à l'exceptions des ports web (80/443). Vous décidez donc de mettre votre VPN en écoute sur le port 443 et tout fonctionne parfaitement.

Puis un jour vous voulez ajouter TLS à votre site internet. Et là c'est le drame. Dilemme (lemme) : vous gardez le VPN ou la sécurité de votre site ? Que nenni mon bon monsieur, il est tout à fait possible de garder les deux !

Première solution : port-share d'OpenVPN

OpenVPN sait différencier les paquets qui lui sont destinés, et les autres. Il peut donc faire office de proxy et rerouter les paquets sur un autre port.

C'est ce que permet la directive port-share :

port-share 127.0.0.1 1443

De ce fait si vous avez un serveur web qui écoute en HTTPS sur le port 1443, un visiteur se connectant sur votre site internet en HTTPS (donc sur le port 443) y accédera sans se rendre compte que le trafic est passé par OpenVPN.

De l'autre côté, si un utilisateur de votre VPN se connecte à votre serveur alors OpenVPN saura que ces paquets lui sont destinés et ne les transmettra donc pas à votre serveur web. Vous partagez donc un port pour deux services différents. Elle est pas belle la vie ? :)

Tout n'est pas rose

En effet, il y a plusieurs défauts à cette directive. La principale c'est que si votre OpenVPN plante alors votre serveur web n'est plus accessible via HTTPS. Autant dire que vous n'avez pas intérêt à changer votre configuration OpenVPN sans bien tester ensuite :)

La deuxième est que vous n'aurez plus l'IP du visiteur dans les logs de votre serveur web. Vous aurez simplement "127.0.0.1". En effet, vu que les paquets sont proxifiés par votre OpenVPN qui est sur le même serveur ils possèdent maintenant votre IP locale. Ça peut gêner si vous êtes habitué à faire des stats sur vos visiteurs à l'aide de vos logs.

Enfin, je ne suis pas arrivé à faire fonctionner OpenVPN avec le port-share sur IPV6. Ce bug est peut-être lié ?

Présentation de SSLH

Pour remédier à chacun de ces soucis on peut utiliser un autre programme nous permettant de partager le port 443 entre notre OpenVPN et notre serveur Web (voire plus comme XMPP, SSH...) : SSLH.

Il peut fonctionner avec IPV6 et proxifier les paquets de manière transparente afin de garder l'IP d'origine de ces derniers. Enfin, si votre OpenVPN plante ça n'aura aucune incidence sur votre serveur web :)

Pour l'installer sur une Debian :

# apt-get install sslh

Vous avez le choix entre deux modes : le premier fonctionne dans un seul thread pour toutes les connexions ce qui ajoute peu d'overhead notamment au niveau de l'empreinte mémoire et l'autre crée un processus pour chaque requête. À vous de voir quelle méthode vous convient le plus.

IPV4

La configuration se passe dans le fichier /etc/default/sslh :

RUN=yes
STARTTIME=2
DAEMON=/usr/sbin/sslh
DAEMON_OPTS="--transparent --timeout 5 --user sslh --listen xxx.xxx.xxx.xxx:443 --ssl xxx.xxx.xxx.xxx:1443 --openvpn xxx.xxx.xxx.xxx:2443 --pidfile /var/run/sslh/sslh.pid"

On ajoute l'option --transparent qui nous permet de garder l'adresse IP d'origine, le port sur lequel on écoute ainsi que des ports sur lesquels écoutent OpenVPN et notre serveur Web en SSL. Attention, xxx.xxx.xxx.xxx doit être remplacé par votre vraie adresse IPV4 (et non 127.0.0.1) pour que le mode transparent fonctionne correctement.
Il faut donc qu'OpenVPN et que votre serveur Web écoutent non pas sur le port 443 mais sur les ports que vous avez définis dans ce fichier de configuration.

Il ne nous reste qu'à paramétrer notre pare-feu et notre nouvelle route :

# iptables -t mangle -N SSLH
# iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 1443 --jump SSLH
# iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 2443 --jump SSLH
# iptables -t mangle -A SSLH --jump MARK --set-mark 0x1
# iptables -t mangle -A SSLH --jump ACCEPT
# ip rule add fwmark 0x1 lookup 100
# ip route add local 0.0.0.0/0 dev lo table 100

Et enfin on lance notre service pour tester qu'on peut bien accéder à notre OpenVPN/serveur web en SSL via le port 443 :

# systemctl start sslh

IPV6

Vous pouvez sauter cette partie si vous voulez rester qu'en IPV4.

Je n'ai pas trouvé moyen de faire fonctionner un seul démon SSLH en mode transparent pour faire le boulot en IPV4 et en IPV6 en même temps.

J'ai donc créé un nouveau service spécialement dédié pour l'IPV6 :

# cp /lib/systemd/system/sslh.service /etc/systemd/system/sslh-ipv6.service
# cp /etc/default/sslh /etc/default/sshl-ipv6

Je sais que c'est pas tip top (oui je suis un peu has been) mais je n'ai pas trouvé une manière plus propre. Si vous avez des idées, n'hésitez pas ;)

On modifie le service pour adapter le nom du fichier de configuration :

[Unit]
Description=SSL/SSH multiplexer
After=network.target

[Service]
EnvironmentFile=/etc/default/sslh-ipv6
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
KillMode=process

[Install]
WantedBy=multi-user.target

Il nous suffit donc maintenant de paramétrer /etc/default/sslh-ipv6 :

RUN=yes
DAEMON=/usr/sbin/sslh
DAEMON_OPTS="--transparent --timeout 5 --user sslh --listen xxxx:xxxx:xxxx:xxxx::1:443 --ssl xxxx:xxxx:xxxx:xxxx::1:1443 --openvpn xxxx:xxxx:xxxx:xxxx::1:2443 --pidfile /var/run/sslh/sslh.pid"

Encore une fois il vous faut bien spécifier votre adresse IPV6 complète à la place de xxxx:xxxx:xxxx:xxxx. Vos services OpenVPN et serveur web doivent bien entendu écouter en IPV6 sur cette interface/port.

Comme en IPV4 on configure le pare-feu et les routes :

# ip6tables -t mangle -N SSLH
# ip6tables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 1443 --jump SSLH
# ip6tables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 2443 --jump SSLH
# ip6tables -t mangle -A SSLH --jump MARK --set-mark 0x1
# ip6tables -t mangle -A SSLH --jump ACCEPT
# ip -6 rule add fwmark 0x1 lookup 100
# ip -6 route add local ::/0 dev lo table 100

On finit par lancer notre service :

# systemctl start sslh-ipv6

Et on peut directement tester :)

Attention au reboot

Il faut bien penser à sauvegarder nos règles iptables/ip6tables à l'aide d'un iptables-persistent par exemple pour qu'au reboot on ne perde pas toutes nos règles.

En ce qui concerne les routes on peut l'ajouter à notre fichier /etc/network/interfaces par exemple :

iface eth0 inet dhcp
    post-up ip rule add fwmark 0x1 lookup 100
    post-up ip route add local 0.0.0.0/0 dev lo table 100
    pre-down ip rule delete fwmark 0x1 lookup 100
    pre-down ip route delete local 0.0.0.0/0 dev lo table 100

Et si vous avez IPV6 :

iface eth0 inet6 static
    post-up ip -6 rule add fwmark 0x1 lookup 100
    post-up ip -6 route add local ::/0 dev lo table 100
    pre-down ip -6 rule delete fwmark 0x1 lookup 100
    pre-down ip -6 route delete local ::/0 dev lo table 100
Enfin on active les services SSLH au démarrage :
# systemctl enable sslh
# systemctl enable sslh-ipv6

On reboot, on teste que nos services fonctionnent bien et le tour est joué :)