Un script qui démarre OpenVPN et ne se connecte jamais n'est généralement pas un problème réseau : le client attend une invite sans terminal pour la dessiner. OpenVPN dispose d'exactement deux moyens pris en charge de répondre à cette invite sans humain, un fichier d'identifiants derrière auth-user-pass et le socket de gestion, et ils ne sont pas interchangeables. Le fichier coûte une directive et répond à une invite ; le socket répond à toutes les invites et offre à tout ce qui peut ouvrir un port loopback un canal de contrôle non authentifié. Lequel convient à votre déploiement dépend de l'invite qui bloque réellement et du secret que vous pouvez vous permettre de laisser au repos.

Nommez l'invite avant de choisir un mécanisme

Quatre invites se ressemblent sur un terminal. La paire de compte vient de la configuration PAM ou auth-user-pass-verify du serveur et y répond auth-user-pass. La phrase secrète d'une clé privée appartient au fichier de clé et se lit via --askpass ou se saisit à la main. Un paquet PKCS#12 demande son propre mot de passe. L'authentification proxy utilise le fichier auth que prend --http-proxy, pas les identifiants du compte.

Quand OpenVPN tourne comme enfant de systemd, PHP ou Node, il n'y a pas de terminal, donc chacune de ces invites bloque en silence. Le processus reste là, le journal s'arrête après la ligne de chiffrement et l'unité finit par expirer ; ce silence explique pourquoi les gens ajoutent une boucle de nouvelle tentative ou un sleep au lieu de regarder quelle invite est en attente. Seul le socket de gestion peut répondre aux quatre. Le fichier d'identifiants répond à la première et à rien d'autre.

auth-user-pass : un fichier, une invite

Le fichier contient le nom d'utilisateur à la première ligne et le mot de passe à la deuxième, et rien d'autre : pas de guillemets, pas de noms de clés, pas de commentaire. Réglez le mode 600 et faites du propriétaire le compte qui exécute openvpn, ce qui pour un service est souvent un utilisateur dédié plutôt que votre session. Un fichier appartenant à root sous /root fait la différence entre une commande qui marche dans votre shell et une unité qui échoue au démarrage.

Deux détails transforment un fichier correct en invite. Un éditeur qui enregistre en CRLF laisse un retour chariot collé au mot de passe, si bien que le serveur refuse un mot de passe qui paraît correct dans le fichier ; vérifiez avec cat -A ou od -c plutôt qu'à l'œil. Et un auth-user-pass sans argument dans le profil signifie « demander à l'utilisateur » par conception, ce qu'une seconde directive sur la ligne de commande peut ne pas annuler, car le profil est analysé là où il se trouve.

L'ordre des options est le troisième piège, et il dépend de la version. Certaines compilations d'OpenVPN 2.4 ignorent --auth-user-pass lorsqu'il précède --config, si bien que les deux mêmes options dans l'autre ordre se connectent. Placez la directive dans le profil, ou après --config sur la ligne de commande, et grep le profil pour chaque occurrence avant de changer autre chose.

auth-nocache appartient à ce mécanisme mais n'apporte rien. Il limite la durée pendant laquelle le client garde l'identifiant en mémoire, ce qui aide après un plantage et pas du tout quand le fichier manque. Une reconnexion après un délai de session côté serveur relit le fichier, si bien qu'un identifiant qui fonctionnait au démarrage peut encore expirer au milieu de la journée.

Le socket de gestion : toutes les invites, plus de surface

Démarrez le client avec un écouteur et déclarez explicitement que les invites seront rapportées dessus, avec les directives documentées dans le manuel d'OpenVPN 2.6 :

management 127.0.0.1 3030
management-query-passwords

Depuis bash, gardez la connexion sur un descripteur de fichier libre et répondez par nom d'invite plutôt que par position, car une reconnexion peut demander dans un autre ordre :

exec 3<>/dev/tcp/127.0.0.1/3030
while read -r -u 3 line; do
  case "$line" in
    *"Need 'Auth' username/password"*)
      echo 'username "Auth" alice' >&3
      echo 'password "Auth" secret' >&3 ;;
    *"Need 'Private Key' password"*)
      echo 'password "Private Key" secret' >&3 ;;
  esac
done

La frontière est le transport. Le protocole de gestion est en clair sur TCP en loopback, donc l'écouteur ne doit pas être publié sur une adresse LAN ou de tunnel pour qu'un contrôleur distant l'atteigne ; un contrôleur distant signifie un transfert SSH, pas une liaison plus large. Le démon communautaire n'accepte aussi qu'un seul client de gestion à la fois, si bien qu'une GUI ou un moniteur ne peut pas partager le socket avec votre script. La seconde connexion est refusée, pas mise en file, et la panne ressemble à un script devenu sourd.

Ce socket est aussi un canal de contrôle. Sans mot de passe de gestion, tout ce qui peut se connecter à 127.0.0.1:3030 peut lire l'état, tuer le tunnel ou répondre lui-même à une invite. Le mot de passe de gestion et le fichier d'identifiants sont de la même classe de secret, et tous deux méritent le même soin.

Lequel convient au déploiement

Choisissez le fichier d'identifiants quand la seule invite est la paire de compte, que le client tourne comme service avec un propriétaire stable et que vous pouvez garder un fichier 600 hors des sauvegardes que vous ne contrôlez pas. Choisissez le socket de gestion quand une phrase secrète de clé ou un mot de passe PKCS#12 entre en jeu, quand le wrapper supervise déjà le processus, ou quand vous devez observer l'état et les invites en déboguant une reconnexion.

Deux conditions font bouger la décision. Si le wrapper est /bin/sh, une unité systemd qui appelle le binaire directement, ou un conteneur minimal, la colle de socket réservée à bash n'est pas disponible et le fichier est la réponse la moins chère. Si le secret tourne souvent, aucun fichier n'est un bon logement : interrogez un trousseau depuis un petit wrapper et laissez l'unité appeler le wrapper, ce qui garde la rotation au même endroit et le mot de passe hors de l'historique du shell.

La substitution de processus se situe entre les deux et n'appartient à aucun. La ligne unique ci-dessous fonctionne parce que bash l'étend avant qu'openvpn démarre, non parce que le client comprend la syntaxe :

openvpn --config client.ovpn --auth-user-pass <(printf "%s
%s
" "$USER" "$PASS")

Elle casse sous /bin/sh, dans une unité qui contourne le shell et dans des images sans bash ni /dev/fd. Gardez-la pour les tests interactifs, jamais comme le contrat dont dépend un service.

Protégez le secret, puis vérifiez depuis le journal

Les deux mécanismes laissent un secret lisible par le processus OpenVPN. Ne mettez jamais le mot de passe sur la ligne de commande ni dans le fichier d'unité, où ps et systemctl le montrent à chaque utilisateur local ; passez le chemin du fichier, pas la valeur qu'il contient. Là où des certificats peuvent remplacer la paire de compte, cela supprime la catégorie d'invite au lieu de la cacher.

Une exécution correcte montre la séquence d'invites se terminer et la session atteindre « Initialization Sequence Completed ». Si le client s'arrête à la même invite, la correspondance a échoué : imprimez les lignes exactes envoyées par le socket, vérifiez les guillemets et le nom de l'invite, et corrigez le motif au lieu d'ajouter un sleep. Un tunnel qui annonce un succès et ne transporte rien est un problème de routage ou de push, pas d'identifiants.

Testez ensuite la reconnexion, pas seulement le premier démarrage. Coupez la session côté serveur ou attendez son délai d'expiration ; le client redemande, et c'est là qu'un fichier qui semblait correct au démarrage montre s'il est encore lisible par le compte qui se reconnecte.

Articles similaires