OpenVPN avec authentification et autorisation avancées

L'article décrit la configuration d'OpenVPN avec des fonctionnalités supplémentaires:

  • Certificats de jeton pour l'authentification principale (Rutoken comme exemple)
  • Backend LDAP pour l'authentification secondaire (en utilisant ActiveDirectory comme exemple)
  • filtrage des ressources internes disponibles pour les utilisateurs (via iptables)

Il décrit également comment configurer les clients pour Linux, Windows et MacOS.

Configuration du serveur


Installer OpenVPN


Prenez le script Nyr / openvpn-install , exécutez à partir de la racine.

git clone https://github.com/Nyr/openvpn-install.git cd openvpn-install 

Le processus de démarrage posera quelques questions.

  • protocole udp
  • port 1194
  • Serveurs DNS - locaux
  • IP externe - adresse de passerelle sur Internet via laquelle le serveur VPN sera disponible

Il existe également une version sécurisée du script d'origine - github.com/Angristan/OpenVPN-install . Il a plus de paramètres de cryptage et explique pourquoi.

Gestion des utilisateurs


Ajout
Si les jetons ne sont pas utilisés, l'utilisateur est ajouté via le même script. Le script génère essentiellement une configuration ovpn personnalisée et y insère un certificat signé par le certificat racine.

Si des jetons sont utilisés (voir la section sur les jetons ci-dessous), le certificat est écrit manuellement en fonction de la demande de certificat générée sur le jeton. La configuration utilisateur doit être effectuée manuellement à partir du modèle existant (à partir du même modèle à partir duquel le script de configuration est généré). Le modèle est ici /etc/openvpn/client-common.txt . Il n'est pas inclus dans le package openvpn et est généré par le script pendant le processus de configuration.

Effacer
La suppression d'utilisateurs se fait via le même script d'installation. Le certificat est ajouté à la CRL , la nouvelle CRL est envoyée au serveur VPN. Le serveur considère tous les certificats qui sont en CRL non valides et refuse d'accepter.

Comment révoquer un certificat manuellement:

 cd /etc/openvpn/easyrsa #   ./easyrsa revoke $CLIENT #   crl ./easyrsa gen-crl #   crl rm -rf /etc/openvpn/crl.pem #    cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem # openvpn    crl,       nobody chown nobody:nobody /etc/openvpn/crl.pem 

Filtrage des hôtes disponibles pour les clients


Les clients doivent être limités par les hôtes auxquels ils peuvent accéder sur le réseau lorsqu'ils se connectent à openvpn.

Manuellement

L'idée est d'attraper les paquets même sur l'interface tun0 , dans laquelle ils proviennent des clients et de les filtrer avant d'entrer dans le NAT. Après NAT, il n'y aura aucune raison de les filtrer - ils auront tous une adresse IP de serveur openvpn sur le réseau interne. Avant d'entrer dans le NAT, les paquets pour chaque utilisateur ont leur propre adresse IP unique (pour la correspondance des adresses IP et des utilisateurs, voir le fichier /etc/openvpn/ipp.txt ).

Les paquets qui transitent par le système (ne proviennent pas directement de celui-ci et ne sont pas entrants, c'est-à-dire en fait acheminés par le système) sont traités par la table FORWARD. Les tables dans iptables sont traitées de haut en bas, si aucune des règles du tableau n'a conduit à une décision sur le sort du paquet, alors la règle par défaut est déclenchée.

Préparation de la table FORWARD:

 #   iptables -F FORWARD #     FORWARD -    iptables -P FORWARD DROP #     iptables -I FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT 

Exemples de règles pour un client spécifique. Étant donné que la règle par défaut pour la table est DROP, il ne reste qu'à autoriser ces paires hôte + port où vous le pouvez. Autorisez l'accès au port sur l'hôte + envoyez un ping à l'hôte lui-même:

 iptables -I FORWARD -s 10.8.0.3 -i tun0 -d 10.0.2.3 -p tcp --dport 443 -j ACCEPT iptables -I FORWARD -s 10.8.0.3 -i tun0 -d 10.0.2.3 -p icmp --icmp-type echo-request -j ACCEPT 

Dans l'exemple ci-dessus, l'hôte 10.8.0.3 est autorisé à accéder au port 443 de l'hôte 10.0.2.3.

Comment fermer l'accès:

 #         iptables -L FORWARD --line-numbers #     iptables -D FORWARD { } 

Ensuite, vous devez trouver toutes les règles pour un client particulier et les supprimer.

Pendant le débogage, il est pratique de voir quelles règles fonctionnent. Chaque règle a un compteur de paquets traités.

 #  ,      watch iptables -nvL FORWARD #     iptables -Z FORWARD 

Automatiquement

Un serveur openvpn a la possibilité d'exécuter des scripts pour certaines actions. En particulier, lors de la connexion et de la déconnexion de clients. Les scripts peuvent être écrits sur n'importe quoi si seulement ils sont exécutables. À l'intérieur du script, les variables d'environnement transmettent toutes sortes de paramètres à la connexion actuelle. Nous nous intéressons aux variables:

  • common_name (nom du propriétaire du certificat; ce qui conduit dans le champ du nom commun lors de la création du certificat)
  • ifconfig_pool_remote_ip (adresse IP client sur tun0)
  • script_type (quel événement s'est produit - se connecter ou se déconnecter).

Vous avez besoin des privilèges root pour gérer iptables. Openvpn après la connexion réinitialise les droits sur personne et exécute des scripts à partir de celui-ci. Il est mauvais de ne permettre à personne de faire quelque chose sous sudo, et il vaut mieux ne pas utiliser d'astérisque dans les règles, mais vous devez en quelque sorte permettre à l'utilisateur de contrôler iptables.

 # /etc/sudoers.d/50_openvpn # #    nobody ALL = NOPASSWD: /sbin/iptables -A FORWARD* #     nobody ALL = NOPASSWD: /sbin/iptables -L FORWARD* #    nobody ALL = NOPASSWD: /sbin/iptables -D FORWARD* 

Dans la configuration du serveur, vous devez ajouter l'autorisation d'exécuter des fichiers tiers et activer deux hooks responsables de la connexion et de la déconnexion de l'utilisateur.

 script-security 2 client-connect /etc/openvpn/bin/hosts.rb client-disconnect /etc/openvpn/bin/hosts.rb 

Le script lui-même, qui lit les configurations et applique les règles pour iptables. Le script fonctionne selon les mêmes principes que ceux décrits dans la section précédente.

/openvpn/bin/hosts.rb
 #!/usr/bin/ruby # -*- coding: utf-8 -*- require 'pp' def log(string) puts 'hosts.rb: ' + string end def parse_config_file(name) config_path = "hosts/#{name}" unless File.exist?(config_path) puts "There is no specific configuration for #{name}." p name exit 0 end config_source = IO.read(config_path).split("\n") config = config_source.inject([]) do |result,line| ip, port, protocol = line.split(/\s+/) result << { ip: ip, port: port, protocol: protocol || 'tcp' } end end def get_config(name) user_config = parse_config_file(name) if user_config everybody_config = parse_config_file('everybody') end everybody_config + user_config end def apply_rule(rule) command = "sudo iptables #{rule}" log(command) system(command) end def remove_rule(number) command = "sudo iptables -D FORWARD #{number}" log(command) system(command) end def allow_target(source_ip, options) #         . apply_rule("-A FORWARD -s #{source_ip} -i tun0 -d #{options[:ip]} -p #{options[:protocol]} --dport #{options[:port]} -j ACCEPT") #       apply_rule("-A FORWARD -s #{source_ip} -i tun0 -d #{options[:ip]} -p icmp --icmp-type echo-request -j ACCEPT") end def clear_targets(source_ip) #      FORWARD,  source_ip. rules_exist = true while rules_exist table = `sudo iptables -L FORWARD --line-number`.split("\n") the_line = table.find do |line| fields = line.split(/\s+/) ip = fields[4] ip == source_ip end if the_line number = the_line.split(/\s+/)[0] remove_rule(number) else rules_exist = false end end end ################################################################################ script_type = ENV['script_type'] log(script_type) name = ENV['common_name'] source_ip = ENV['ifconfig_pool_remote_ip'] case script_type when 'client-connect' config = get_config(name) config.each{|target| allow_target(source_ip, target)} when 'client-disconnect' clear_targets(source_ip) else puts "Unknown script type #{script_type}." end 

Les règles sont stockées dans des fichiers correspondant au nom commun des certificats dans le dossier /etc/openvpn/hosts . Ils spécifient exactement quelles adresses IP sont disponibles pour un client particulier. Séparateur - un nombre arbitraire d'espaces. L'adresse IP, le port et le protocole (tcp ou udp) sont écrits via le séparateur.

 10.0.0.24 53 udp 10.0.0.25 53 udp 10.0.2.3 443 tcp 

Par conséquent, la structure suivante doit /etc/openvpn dans le dossier /etc/openvpn

├── bin
│ └── hosts.rb
├── hôtes
│ ├── user1
│ ├── user2
│ └── tout le monde
├── server.conf
└── ...

User1 et user2 sont des fichiers dans le format ci-dessus. Ils décrivent les hôtes auxquels l'utilisateur avec le nom commun correspondant a accès.

Il existe un autre fichier supplémentaire pour everybody le everybody , il contient des règles qui s'appliquent à tous les clients, à condition qu'il existe un fichier de configuration distinct pour ces clients. Autrement dit, si l'utilisateur a une liste d'hôtes où il peut aller, alors cette liste et les hôtes répertoriés dans everybody appliqués. Sinon, everybody pas applicable. Par exemple, il est pratique de placer un serveur DNS dans ce fichier.

Journalisation

Le script d'installation comprend uniquement la journalisation des connexions actuelles (paramètre d' status) . Pour qu'un journal régulier apparaisse, vous devez ajouter une ligne à la configuration du serveur ( /etc/openvpn/server.conf ):
 log-append /var/log/openvpn.log 


LDAP

Il existe un plugin openvpn-auth-ldap qui vous permet d'authentifier à nouveau un utilisateur via LDAP.

Livrer le colis:

 sudo yum install openvpn-auth-ldap 

Ajoutez à server.conf:

 plugin /usr/lib64/openvpn/plugin/lib/openvpn-auth-ldap.so "/etc/openvpn/ldap.conf" 

Créez une configuration pour ldap dans /etc/openvpn/ldap.conf :
 <LDAP> URL ldaps://{LDAP_DOMAIN_HERE} Timeout 15 TLSEnable no FollowReferrals yes BindDN "BIND_DN_HERE" Password "BIND_PASSWORD_HERE" </LDAP> <Authorization> BaseDN "{BASE_DN_HERE}" SearchFilter "(&(sAMAccountName=%u)(objectClass=organizationalPerson)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" RequireGroup false </Authorization> 

Ajoutez la ligne à la configuration ovpn personnalisée:

 auth-user-pass 

Ainsi, l'utilisateur sera d'abord demandé le nom d'utilisateur et le mot de passe du domaine, puis le code PIN du jeton. Si l'une de ces étapes échoue, la connexion ne sera pas établie.

La description des options de ldap.conf se trouve dans le référentiel de plugins . Il prend en charge l'authentification par appartenance à un groupe, mais je ne l'ai pas testé.

La vitesse


La plus grande augmentation de vitesse donne l'inclusion du mode udp. Ceci est conseillé dans tous les manuels. Le fait est que cela n'a aucun sens de démarrer une connexion client TCP dans un canal TCP. Un tcp chez le client suffit pour effectuer la livraison correcte des paquets. Si des paquets sont perdus dans le canal udp, la connexion TCP client contrôlera l'ajustement de la livraison.

La vitesse augmentera au moins car il n'est pas nécessaire d'attendre la confirmation de la livraison de chaque paquet dans le canal. Il y a un deuxième problème avec TCP - un paquet TCP client ne correspond probablement pas à un paquet du canal VPN. MTU est le même, mais les en-têtes doivent être ajoutés au package client. En conséquence, vous devez envoyer deux paquets dans le canal vpn par paquet utilisateur.

TCP est logique à utiliser lorsqu'il est impossible d'une autre manière. Par exemple, lorsque vpn fonctionne via le canal ssh.

Exemple de configuration complète d'un serveur


exemple-serveur.conf
 port 1194 proto tcp dev tun sndbuf 0 rcvbuf 0 ca ca.crt cert server.crt key server.key dh dh.pem tls-auth ta.key 0 topology subnet server 10.8.0.0 255.255.255.0 ifconfig-pool-persist ipp.txt push "redirect-gateway def1 bypass-dhcp" push "dhcp-option DNS 10.0.0.25" push "dhcp-option DNS 10.0.0.24" keepalive 10 120 cipher AES-256-CBC comp-lzo user nobody group nobody persist-key persist-tun status openvpn-status.log verb 3 crl-verify crl.pem log-append /var/log/openvpn.log script-security 2 client-connect /etc/openvpn/bin/hosts.rb client-disconnect /etc/openvpn/bin/hosts.rb 


Configuration du jeton


Bibliothèque PKCS # 11


Pour travailler avec des jetons, vous avez besoin d'une bibliothèque spéciale. La bibliothèque est nécessaire à la fois pour créer des paires de clés et pour la connexion réelle. Téléchargez pour toutes les plateformes par le lien .

Partout où librtpkcs11ecp.so se trouvera plus tard - c'est la bibliothèque même qui doit être téléchargée et placée quelque part dans un endroit pratique.

Création d'un certificat sur un jeton


Générez une paire de clés sur le jeton. Le paramètre id ici est le numéro de série de l'emplacement sur le jeton où la paire de clés s'adapte.

 pkcs11-tool --module /usr/lib64/librtpkcs11ecp.so --keypairgen --key-type rsa:2048 -l --id 01 

Faites une demande de certificat pour la clé publique. Lors de la création d'une demande de certificat, la durée de vie du certificat et le nom commun sont définis, ce qui est utilisé pour filtrer les adresses IP disponibles au sein du réseau. Le nom commun doit correspondre à la connexion dans ActiveDirectory pour qu'il n'y ait aucune confusion.

 openssl openssl> engine -t dynamic -pre SO_PATH:/usr/lib64/openssl/engines/pkcs11.so -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:/usr/lib64/librtpkcs11ecp.so openssl> req -engine pkcs11 -new -key slot_0-id_01 -keyform engine -out /home/john/good.req 

La demande reçue doit être déplacée vers le /etc/openvpn/easy-rsa/pki/reqs/ . L'extension du fichier doit être req .
Conversion d'une demande en certificat:

 cd /etc/openvpn/easy-rsa/ ./easyrsa sign-req client good 

Après cela, un certificat du même nom mais avec l'extension crt apparaîtra dans le /etc/openvpn/easy-rsa/pki/issued/ .

Avant l'enregistrement, le certificat doit être converti en DER:

 openssl x509 -in /home/user/user-cert.pem -out /home/user/user-cert.crt -outform DER 

Écrire un certificat pour un jeton:

 pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -l -y cert -w /home/user/user-cert.crt --id 45 --label TEST 

Il est rédigé sur la base de l'article «Utilisation de Rutoken EDS avec OpenSSL (RSA)» .

Utilisation du jeton pour l'authentification


Recherchez l'ID du certificat à présenter au serveur:

 $ openvpn --show-pkcs11-ids /usr/lib64/librtpkcs11ecp.so The following objects are available for use. Each object shown below may be used as parameter to --pkcs11-id option please remember to use single quote mark. Certificate DN: /CN=User1 Serial: 490B82C4000000000075 Serialized id: aaaa/bbb/41545F5349474E415455524581D2A1A1B23C4AA4CB17FAF7A4600 

Nous sommes intéressés par l'id sérialisé ici.

Options à saisir dans la configuration ovpn pour que les jetons récupèrent:

 pkcs11-providers /usr/lib64/librtpkcs11ecp.so pkcs11-id 'aaaa/bbb/41545F5349474E415455524581D2A1A1B23C4AA4CB17FAF7A4600' 

L'option pkcs11-id doit être placée entre guillemets simples.

Ce manuel a du sens sur toutes les plateformes. Vous devez spécifier le chemin d'accès à la bibliothèque et l'ID de certificat sur le jeton. La bibliothèque peut être appelée un peu différemment, soit .dll , pas .so , mais le sens est le même.

Dans ce cas, vous devez supprimer les sections cert et key du fichier ovpn, car le certificat et la clé privée seront extraits du jeton.

La configuration entièrement client (pour Windows) ressemble à ceci:

client.ovpn
client
dev tun
proto tcp
sndbuf 0
rcvbuf 0
remote 78.47.37.247 22222
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-CBC
comp-lzo
setenv opt block-outside-dns
key-direction 1
verb 3

pkcs11-providers "c://Windows//System32//rtPKCS11ECP.dll"
pkcs11-id 'Aktiv\x20Co\x2E/Rutoken\x20ECP/342b871d/Rutoken/01'

-----BEGIN CERTIFICATE-----
{CERT_HERE}
-----END CERTIFICATE-----


<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
{KEY_HERE}
-----END OpenVPN Static key V1-----
</tls-auth>


Écrit sur la base de «Comment ajouter une authentification à double facteur à une configuration OpenVPN à l'aide de cartes à puce côté client» .

Configuration client


Linux


Openvpn a un bogue qui empêche l'utilisateur d'entrer le code PIN à partir du jeton si le package est construit avec la prise en charge de systemd. Depuis que systemd a été partout ces derniers temps, tous les packages qui sont déjà disponibles dans les référentiels sont compilés avec son support. Les clients sous Linux doivent collecter le package par eux-mêmes. Voici un exemple de configuration qui a fonctionné pour moi sur Arch Linux:

 ./configure \ --prefix=/usr \ --sbindir=/usr/bin \ --enable-iproute2 \ --enable-pkcs11 \ --enable-plugins \ --enable-x509-alt-username 

Vous pouvez vérifier que openvpn est construit avec ou sans systemd en utilisant la commande suivante:

 openvpn --version | grep --color enable_systemd 

Mas os


Sous Mac OS, il n'y a qu'un seul client gratuit - Tunnelblink .

Il ne sait pas comment saisir un code PIN à partir d'un jeton de gui. Le bug est décrit par exemple ici - https://groups.google.com/forum/#!topic/tunnelblick-discuss/f_Rp_2nV-x8 Contourné en lançant openvpn à partir de la console. Ce n'est pas surprenant, étant donné que le client officiel pour Windows ne le sait pas non plus.

Sous Mac OS (contrairement à Windows), des scripts supplémentaires sont également nécessaires pour configurer le réseau. Si vous exécutez simplement openvpn à partir de la console, DNS ne fonctionnera pas (peut-être autre chose, seul DNS apparaîtra).

TunnelBlick possède ces scripts de configuration réseau, ils n'ont besoin d'être appelés que lors de l'établissement et de la déconnexion de la connexion. Ce que vous devez ajouter à la configuration ovpn:

 script-security 2 up "/Applications/Tunnelblick.app/Contents/Resources/client.up.tunnelblick.sh -9 -d -f -m -w -ptADGNWradsgnw" down "/Applications/Tunnelblick.app/Contents/Resources/client.down.tunnelblick.sh -9 -d -f -m -w -ptADGNWradsgnw" 

Un exemple de script pour lancer une connexion openvpn, qui peut être placé sur le bureau et piqué avec la souris:

 #!/bin/bash tunnelblick=/Applications/Tunnelblick.app/Contents/Resources/openvpn/openvpn-2.4.2-openssl-1.0.2k sudo $tunnelblick/openvpn --config $tunnelblick/user.ovpn 

Windows


Sous les fenêtres, tout semble fonctionner. Le client officiel ne sait pas saisir le code PIN depuis le token, il parvient à ouvrir manuellement vpn depuis la console.

La chose la plus importante est de tout faire sous l'administrateur. Exécutez le programme d'installation du client en tant qu'administrateur. Lancez un terminal dans lequel openvpn démarre également avec les droits d'administrateur, sinon il ne pourra pas contrôler l'interface réseau.

Sous Windows, le chemin d'accès à la bibliothèque pour travailler avec des jetons doit être enregistré via des doubles barres obliques. Cela s'applique à la fois à la configuration ovpn et à l' --show-pkcs11-ids sur la ligne de commande.

 pkcs11-providers "c://Windows//System32//rtPKCS11ECP.dll" pkcs11-id 'Aktiv\x20Co\x2E/Rutoken\x20ECP/342b871d/Rutoken/01' 

Source: https://habr.com/ru/post/fr433250/


All Articles