Aller au contenu

Blog ·

Pourquoi ne pas rediriger un port vers votre panneau

Un agent qui appelle vers l’extérieur demande deux règles sortantes et aucun port entrant. Ce que cela apporte, ce que cela coûte, et ce que cela déplace.

Par Provibr

Si une plateforme de gestion doit piloter vos hyperviseurs, quelque chose doit franchir la frontière entre leur réseau et le vôtre. Il n’y a que deux directions possibles, et ce choix détermine presque tout le reste du modèle de sécurité.

La direction traditionnelle est entrante. La plateforme détient les identifiants de votre cluster Proxmox, de votre machine DirectAdmin et de votre panneau de jeu, et se connecte à chacun d’eux par internet. Cela suppose une API qui répond à internet, une règle de pare-feu ou un VPN, et un plan de contrôle joignable par quiconque sait le trouver.

L’autre direction est sortante. Vous exécutez un petit agent à l’intérieur de votre propre réseau. Il ouvre une connexion vers la plateforme et la maintient ouverte, les commandes descendent par cette connexion et les résultats y remontent. Rien de votre côté n’écoute.

Ce texte parle de ce que la seconde option coûte et de ce qu’elle apporte, du point de vue de ceux qui en ont construit une.

La modification du pare-feu tient en deux règles sortantes

Toute l’exigence réseau d’un agent sortant tient dans un tableau :

DirectionRègleQuand
SortantTCP 50051 vers la passerelleUne fois, à l’enrôlement
SortantTCP 50052 vers le même hôteEn continu, c’est la session
EntrantAucune

Deux ports plutôt qu’un, c’est une séparation délibérée. L’enrôlement a lieu avant que l’agent ne possède un certificat client, donc ce port ne peut offrir qu’un TLS côté serveur. Le port de session, lui, exige un TLS mutuel et rejette tout ce qui se présente sans certificat client valide. Un seul écouteur pour les deux voudrait dire un écouteur où le certificat client est facultatif et où la décision d’autorisation est conditionnelle. Deux écouteurs rendent la frontière structurelle : sur 50052, il n’existe aucun chemin de code par lequel un pair non authentifié franchit la poignée de main.

Si votre filtrage sortant est strict, ces deux règles constituent la totalité de la demande. Il n’y a aucune règle entrante à écrire, aucune entrée NAT, aucun concentrateur VPN, et rien à renouveler quand votre adresse publique change.

Ce que « sortant » veut dire quand le réseau est hostile

Une connexion TCP sortante à travers du NAT n’est pas gratuite : l’état qui la fait fonctionner vit dans la table de quelqu’un d’autre, et cette table a un délai d’expiration. Un NAT d’opérateur, un pare-feu d’entreprise et un répartiteur de charge cloud oublient tous discrètement une connexion inactive, et le mode de défaillance est le pire qui soit, car les deux extrémités croient encore que la socket va bien.

Deux mécanismes répondent à cela, et il vous faut les deux.

Le premier est le keepalive HTTP/2, à intervalles de 30 secondes avec un délai d’expiration de 10 secondes. C’est la couche qui bat les délais des NAT et des répartiteurs de charge, et la seule qui remarque une connexion TCP morte silencieusement sous le flux.

Le second est un battement de cœur applicatif toutes les 20 secondes, avec un chien de garde qui met fin à la session si aucun accusé de réception n’arrive dans les 45 secondes. Le keepalive prouve que la socket est vivante ; le battement de cœur prouve que l’autre extrémité traite encore quelque chose.

Quand la session prend effectivement fin, l’agent se reconnecte avec un délai exponentiel et une gigue complète : l’attente est tirée uniformément entre zéro et le plafond courant, lequel double d’une seconde jusqu’à soixante. La gigue n’est pas un ornement. Sans elle, tous les agents de tous les clients se reconnectent au même instant après un redémarrage de la passerelle, et votre reprise se transforme en ruée que vous vous êtes infligée vous-même.

Un détail qui n’apparaît qu’en production : le compteur de délai ne se réinitialise qu’après une minute complète de session saine. Sans cette règle, un agent dont la connexion est à moitié cassée se connecte, meurt, remet son compteur à une seconde, et martèle la passerelle une fois par seconde pour toujours.

L’identité : un certificat, pas un mot de passe

Un modèle entrant authentifie la plateforme auprès de votre panneau, en général avec une clé d’API que vous collez dans un formulaire. Un modèle sortant doit authentifier l’agent auprès de la plateforme, et il peut le faire avec mieux qu’un secret partagé.

L’agent s’enrôle une seule fois avec un jeton à usage unique : 36 octets aléatoires, 48 caractères base64url, stocké côté serveur uniquement sous forme d’empreinte SHA-256 et valide 72 heures. Il est consommé à l’intérieur d’une transaction qui revérifie les conditions sur lesquelles il a été sélectionné, car c’est entre la lecture et l’écriture qu’un enrôlement en double se faufile.

Ce qui revient est un certificat client. À partir de là, la session est mutuellement authentifiée, et la passerelle épingle le pair exact : elle prend l’empreinte SHA-256 du certificat présenté et cherche l’agent par cette empreinte, avant même que l’agent n’ait dit qui il pense être. L’identité est fixée par le certificat ; le premier message du flux ne peut que la confirmer.

La rotation suit l’horloge de l’agent lui-même : les certificats vivent 90 jours et le renouvellement commence à mi-parcours, au jour 45. Cela laisse 45 jours de tentatives avant que quoi que ce soit ne soit en danger, et le panneau n’avertit un humain qu’à 21 jours restants, si bien que le renouvellement doit avoir échoué pendant trois semaines avant que quiconque soit dérangé. Chaque rotation génère une nouvelle paire de clés : renouveler sur la même clé prolonge la vie de quelque chose qui a peut-être déjà fuité.

La révocation mérite sa propre phrase, car la version honnête est moins élégante que le schéma. Il n’y a ni CRL ni OCSP. Révoquer un agent est une écriture en base de données, et une session connectée est confrontée à celle-ci une fois par minute. Cette vérification échoue fermée, avec un budget de trois échecs consécutifs, si bien qu’un hoquet de la base de données ne peut pas maintenir en vie un agent révoqué en silence, et le pire cas est borné et calculable : soixante secondes fois trois.

Exactement une fois, sur une liaison qui va tomber

Dès lors que vous acceptez que le lien soit un flux de longue durée sur un chemin peu fiable, la sémantique de livraison cesse d’être une préoccupation théorique. La plateforme relivrera une commande après une reconnexion, car de son côté la différence entre « l’agent ne l’a pas reçue » et « le résultat n’est pas revenu » est invisible.

L’agent se souvient donc. Les commandes terminées sont conservées sur disque avec leurs résultats, indexées par identifiant de commande, dans un cache LRU borné. La répétition d’une commande terminée renvoie le résultat en cache et n’exécute rien. La répétition d’une commande encore en cours est ignorée, car la réponse est déjà en chemin.

Sur disque, précisément, et c’est tout l’enjeu : un cache en mémoire est vide exactement au moment où la plateforme repropose tout, c’est-à-dire juste après un redémarrage.

La direction inverse suit la même logique. Les événements partent par une file de sortie séquencée ; à la reconnexion, la plateforme annonce le numéro de séquence qu’elle détient et l’agent rejoue à partir de là. La file est bornée, et quand quelque chose en tombe, l’agent le dit à la connexion suivante, en déclarant la plus basse séquence qu’il détient encore moins un, pour que la plateforme cesse d’attendre des événements qui n’existent plus.

Il y a une fissure honnête dans cette promesse, et mieux vaut l’énoncer que la cacher : si l’écriture sur disque de l’enregistrement de déduplication échoue, le résultat est tout de même envoyé. L’alternative reviendrait à jeter le résultat d’une commande terminée pour protéger une garantie, et au pire une relivraison ultérieure l’exécutera une seconde fois.

Les identifiants restent de votre côté

C’est la partie qui change la forme d’une compromission.

Dans le modèle entrant, la plateforme stocke des identifiants qui donnent le contrôle de votre infrastructure, et ce coffre est le trophée. Dans le modèle sortant, les identifiants de vos hyperviseurs et de vos panneaux vivent dans un coffre chiffré sur votre propre hôte, poussés une seule fois au moment où vous ajoutez une intégration.

Notre coffre utilise XChaCha20-Poly1305 avec une clé dérivée par HKDF-SHA256 depuis un fichier de clé local et l’identifiant machine, l’identifiant de l’agent étant mélangé dans les informations de dérivation. Une conséquence qui mérite d’être explicitée : copier vault.enc sur une autre machine vous donne un fichier qui ne se déchiffrera pas, car la machine à laquelle il était lié fait partie de la clé.

Et la limite, que nous avons écrite dans le code juste à côté de l’implémentation, est que cela ne protège pas contre un accès root sur l’hôte. Ce n’est pas nécessaire. Le client possède l’hôte et possède les identifiants. Le coffre protège contre un fichier qui fuite, pas contre son propriétaire.

La piste d’audit obéit à la même règle. Chaque commande est une ligne qui n’est jamais supprimée définitivement, et la charge utile d’une poussée d’identifiants est stockée expurgée. Une table d’audit qui contient les secrets qu’elle audite est une deuxième copie du problème.

Quatre choses que cela ne résout pas

Un texte d’architecture qui n’énumère que des avantages est du marketing. Voici ce qui se déplace au lieu de disparaître.

Du logiciel sur votre hôte. L’échange est réel : vous n’exposez plus de port, et en contrepartie vous exécutez un binaire qu’il faut installer, mettre à jour et à qui il faut faire confiance. Cela déplace la question de « qui peut atteindre mon panneau » vers « qui signe le code que j’exécute », ce qui est une meilleure question mais pas une question gratuite.

Des privilèges étendus. Un agent qui gère un hyperviseur détient des identifiants qui gèrent un hyperviseur. La direction de la connexion ne change rien à ce que ces identifiants permettent de faire, seulement à l’endroit où ils vivent et à qui d’autre peut les atteindre.

Le sortant reste une sortie. Une connexion qui quitte votre réseau peut transporter des données hors de celui-ci. La destination est fixe, le transport est mutuellement authentifié et le protocole est étroit, mais « deux règles sortantes » est une affirmation plus modeste que « rien ne sort ».

Les réponses en temps réel. Celle-ci se manifeste immédiatement dans l’API. Puisque la plateforme ne peut pas appeler l’agent, une écriture ne peut pas répondre par son résultat : la bonne réponse à « créez cette machine » est un accusé de réception et de quoi interroger la suite. Les lectures ne sont pas concernées, puisqu’elles viennent de la base de données de la plateforme. Quiconque conçoit une API par-dessus un agent sortant doit accepter cette asymétrie au lieu de la maquiller.

Il existe aussi exactement un cas où l’agent se met bel et bien à l’écoute, et prétendre le contraire serait malhonnête. Installer une machine physique par le réseau suppose de répondre au DHCP et de servir des fichiers, ce qu’une socket sortante ne permet pas. L’agent ouvre donc ces services, sur une seule interface nommée, uniquement pour des machines ayant une tâche en cours, et uniquement tant qu’il y a du travail. « Activé » signifie « autorisé ici », pas « à l’écoute en ce moment ».

Quand l’entrant reste la bonne réponse

Si vous exploitez un seul hyperviseur et que vous le gérez vous-même, rien de tout ceci ne s’applique : un agent n’apporte rien de plus qu’une règle de pare-feu et un mot de passe solide.

L’argument se durcit avec la distance et avec le nombre. Plusieurs sites, des réseaux qui ne vous appartiennent pas, des adresses qui changent, des clients dont vous ne pouvez pas modifier le pare-feu, et un plan de contrôle que vous préféreriez ne pas voir du tout sur l’internet public. À ce stade, l’agent n’est pas un composant supplémentaire ; c’est le composant qui vous permet de supprimer tous les autres.

C’est ainsi que fonctionne Provibr : une connexion sortante par agent, aucun port entrant, et des identifiants qui ne quittent jamais le réseau du client. La documentation de l’agent couvre l’installation, le dimensionnement et la révocation, le guide de démarrage déroule la première connexion de bout en bout, et l’API documente la forme « accuser puis interroger » qu’impose la conception sortante.

Commencez avec un hôte et un agent

Créez un compte, installez l’agent avec une seule commande et connectez votre premier hôte Proxmox. La période d’essai dure 14 jours et ne demande aucune coordonnée de paiement.