Lorsqu’un script client-connect se termine avec un code non nul, OpenVPN rejette le client, et les administrateurs supposent souvent que le script client-disconnect correspondant range tout. Il ne s’exécute souvent jamais, car le traitement du disconnect dépend de ce que l’étape connect a réussi à achever. Traiter le rejet et le retour arrière comme un seul mécanisme, c’est ce qui transforme une connexion ratée en règle de pare-feu fuitée ou en enregistrement orphelin. Ce sont deux obligations distinctes, et une seule est automatique. Lisez le chemin de sortie propre au hook avant de blâmer le serveur de laisser un état derrière lui.

Une sortie non nulle rejette le client ; elle n’annule pas votre travail

Le code de sortie de client-connect est une décision d’admission. Zéro signifie admettre, tout le reste signifie démonter l’instance avant qu’elle ne soit pleinement établie. Rien dans ce contrat n’inspecte ce que le script a déjà fait au monde extérieur. Si le script a ajouté une règle iptables, écrit une ligne ou incrémenté un compteur, ces changements survivent au rejet.

La même asymétrie s’applique aux plugins, où plusieurs fonctions connect peuvent s’exécuter pour une seule instance. Un échec ultérieur n’annule pas rétroactivement un succès antérieur, et la fonction antérieure n’a aucun moyen d’apprendre qu’une fonction sœur a échoué.

Le rejet est automatique, le nettoyage ne l’est pas

Le manuel d’OpenVPN 2.6 lie le traitement de client-disconnect à la réussite du traitement connect antérieur. Une exception documentée couvre les chaînes en cascade : si une étape connect a déjà renvoyé un succès, les étapes disconnect correspondantes s’exécutent tout de même lors du démontage de l’instance, même si une étape connect ultérieure a échoué. Cette exception est étroite et dépend du nombre de callbacks configurés ; c’est pourquoi tester un seul script shell ne vous dit rien d’une chaîne.

Cartographiez la configuration réelle avant de concevoir autour d’elle : quel script, quel plugin et dans quel ordre ils s’exécutent. Le tableau ci-dessous énumère ce qu’une installation à moitié terminée laisse derrière elle.

What the hook did before failingWhat the client seesWhat stays behind
Created a firewall entryA rejection, with no tunnelA rule matching an address nobody owns
Wrote a state rowA rejection, with no tunnelA record of a session that never started
Allocated a tunnel addressA rejection, with no tunnelAn address marked busy until something releases it

Chaque ligne se termine de la même façon pour le client et différemment pour l’hôte. Le client ne peut pas savoir laquelle s’est produite, donc le hook doit défaire ce qu’il a fait avant de renvoyer le statut d’échec.

Compensez à l’intérieur du script qui a échoué

Validez tout ce que vous pouvez avant de toucher à l’état partagé : champs du certificat, disponibilité de l’adresse, groupe auquel appartient le client. Le rejet le moins coûteux est celui qui survient avant toute allocation. Une fois que l’installation couvre plusieurs opérations externes, tenez un registre de celles qui sont terminées et défaites-les dans l’ordre inverse avant de renvoyer le statut d’échec.

Un cas concret : le script crée une entrée de pare-feu par client, puis échoue à écrire la ligne d’état qui la décrit. La branche qui échoue doit supprimer l’entrée qu’elle vient de créer, puis sortir avec un code non nul. Attendre un événement disconnect qui ne sera peut-être jamais planifié, c’est ainsi qu’une règle survit à la session pour laquelle elle était prévue.

Rendez la compensation idempotente et attribuée

Le nettoyage doit être sûr à exécuter deux fois et précis sur ce qu’il peut supprimer. Un événement tardif d’une tentative antérieure ne doit pas supprimer l’allocation d’une session plus récente simplement parce que les deux partagent un nom commun ou une adresse de tunnel recyclée. Enregistrez le handle ou l’identifiant d’opération précis avec la ressource, et supprimez par cette clé plutôt que par le nom. Les transactions de base de données n’aident que pour les changements propres à la base ; une règle de pare-feu hors de cette frontière exige une étape de compensation explicite.

Partez du principe que le processus peut mourir entre la modification et le nettoyage, et ajoutez un réconciliateur qui compare l’état externe à la liste des clients admis selon un calendrier.

Testez les trois chemins qui diffèrent vraiment

Exercez trois cas : un rejet avant toute allocation, un après une allocation partielle, et une session admise normale qui se termine plus tard. Ces trois produisent des lignes de journal différentes et des résidus différents. Vérifiez l’état externe après chacun, pas seulement le message d’échec du client, car l’échec du client est le résultat attendu dans deux des trois.

Journalisez l’étape du hook, le statut de sortie et ce qui a été alloué ou libéré, sans les identifiants. Un journal de hook qui nomme la ressource créée est ce qui rend une règle fuitée repérable six mois plus tard.

Articles similaires