Aller au contenu

Blog ·

Une sonde qu’on ne peut convaincre de rien

Superviser depuis l’extérieur du réseau d’un client suppose des machines à nous. Voici comment la plateforme empêche l’une d’elles de faire autre chose.

Par Provibr

Vérifier qu’une machine cliente est joignable n’a de sens que depuis un endroit où cette machine ne se trouve pas. Un agent placé à l’intérieur du réseau peut vous dire que le service écoute ; il ne peut pas vous dire que la route qui y mène est cassée. On installe donc des machines à soi sur l’internet public et on les laisse établir la connexion.

C’est une fonctionnalité et une vulnérabilité dans le même souffle. Une sonde, c’est du matériel qui nous appartient, dans un datacenter que nous partageons avec des inconnus, et qui garde une connexion de contrôle ouverte vers notre plateforme. Si quoi que ce soit sur cette connexion peut la persuader de faire autre chose que mesurer, elle devient le point d’appui le plus utile du produit.

Un seul binaire, et c’est le jeton qui dit qui vous êtes

Une sonde n’est pas un programme distinct. C’est l’agent que nos clients font tourner, démarré avec probe enroll ou probe run au lieu de run, sur le même moteur de supervision et le même RPC de session. Une compilation réduite serait incapable d’exécuter la moindre commande d’hyperviseur, ce qui semble plus sûr, mais cela signifie une deuxième implémentation des reprises, de la temporisation, de la rotation des certificats et du moteur de contrôles, et la deuxième reçoit un peu moins d’attention chaque mois. Nous avons gardé un seul binaire et déplacé la frontière à un endroit vérifiable.

La manière évidente de laisser un seul binaire s’enrôler comme deux choses différentes, c’est alors un champ dans la demande d’enrôlement qui dit laquelle. C’est la mauvaise manière, et il vaut la peine de dire crûment pourquoi : un client qui déclare sa propre nature déclare ses propres privilèges, et toute vérification ultérieure repose sur une chaîne de caractères fournie par la chose même que l’on vérifie.

Ce champ n’existe donc pas. Il y a deux tables de jetons : enrollment_tokens, avec une clé étrangère non nulle vers agents, et monitoring_probe_enrollment_tokens, avec une clé étrangère vers monitoring_probes. Un jeton existe dans exactement l’une des deux, si bien que ce qui s’enrôle découle de quelque chose que nous avons émis plutôt que de quelque chose que l’appelant a affirmé. Les deux échecs renvoient le refus identique, car des messages différents diraient à un attaquant quelle sorte de jeton il a trouvée.

Le certificat renvoyé porte un nom commun de la forme probe:<id> et, comme nom alternatif de sujet, un URI sans organisation dedans, là où celui d’un agent nomme le client. C’est destiné à l’opérateur qui lit un certificat, et ce n’est explicitement pas la frontière : un nom commun est une chaîne écrite par une autorité de certification. La frontière, c’est qu’une empreinte se résout dans agents ou dans monitoring_probes, jamais dans les deux, parce que ce sont deux tables.

Un verdict pour chaque message, pas une liste des seuls autorisés

Le protocole de session, ce sont deux messages d’enveloppe, chacun portant l’une parmi un ensemble de variantes. Une liste d’autorisation nommant seulement ce qu’une sonde a le droit d’envoyer reste verte pour toujours, y compris le jour où quelqu’un ajoute un cinquième type de message, et ce jour-là il existe un type de message que rien ne mentionne et que rien ne refuse donc. Les tables de la passerelle nomment par conséquent chaque champ des deux enveloppes avec un verdict explicite : vingt-deux entrées en amont, dont trois vrais messages plus un numéro de séquence sont acceptés, et quatorze en aval, dont une vraie instruction plus trois accusés de réception de session le sont. Un test lit le TypeScript généré et échoue si un champ n’a pas de verdict, et échoue aussi dans l’autre sens quand un nom de la table n’existe plus dans le protocole, car une règle qui ne contraint rien se lit exactement comme de la couverture. Ajouter un message de session coûte désormais une ligne, et cette ligne est une décision qui porte le nom de quelqu’un sur le commit.

Vers le haut, une sonde peut envoyer trois choses : un message de bonjour, un battement de cœur et un résultat de contrôle. Le refus qui mérite une explication, ce sont les résultats de commande : la répartition associe un résultat à une commande par identifiant plutôt que par expéditeur, donc une sonde capable d’en envoyer un pourrait clore une commande appartenant à n’importe quel agent du parc. Vers le bas, dix entrées sont refusées, et quatre d’entre elles sont la raison d’être de toute cette conception :

MessageCe qu’il transporteraitPourquoi une sonde ne doit jamais le voir
Envoi d’identifiantsLes accès panneau et hyperviseur d’un clientUne sonde n’a ni coffre ni intégration ; il n’existe aucun état où cela ait un sens
Oubli d’identifiantsL’instruction d’en effacer unLa même raison, en sens inverse
Directive de démarrage réseauCe qu’il faut servir à une machine qui démarre depuis le réseauUne sonde ne provisionne rien
Ouverture d’automatisationUn bac à sable qui exécute des scripts du clientUne sonde n’exécute aucun code du client

Le registre où une sonde ne figure pas

Le tableau ci-dessus est la moitié démontrable de la promesse. La moitié porteuse, c’est qu’il n’existe pas de route. La répartition des commandes trouve sa destination par identifiant d’agent ; les sondes vivent dans leur propre registre, indexé par identifiant de sonde, et n’ont aucun identifiant d’agent. Aucune recherche ne peut renvoyer une session de sonde au code qui envoie un envoi d’identifiants, même si quelqu’un ajoutait une nouvelle sorte d’instruction en oubliant complètement la table.

L’unique fonction qui écrit vers une sonde vérifie le verdict à la source plutôt qu’à la réception, car un message qui serait passé arriverait sur une machine à nous faisant tourner le même binaire qu’un agent client, laquelle pourrait tout simplement l’exécuter. Elle effectue aussi un contrôle que la table ne sait pas exprimer : command est accepté, mais une demande de commande est elle-même un choix parmi vingt-quatre variantes, dont toutes sauf trois sont une famille d’instructions de fournisseur, si bien que sans cela « une sonde peut recevoir des commandes » voudrait discrètement dire « une sonde peut recevoir une commande d’hyperviseur ». La charge utile doit être exactement un champ, et ce champ doit être le contrôle de supervision. Le test parcourt chaque champ de charge utile dans le code généré et affirme que tous sauf un sont refusés.

Choisir une sonde, puis basculer vers la suivante

Chaque localisation peut accueillir plus d’une sonde, il faut donc bien que quelque chose décide. Un hachage modulo le nombre de sondes est faux le jour où ce nombre change : presque toutes les règles se déplacent d’un coup, et des graphiques de latence partout dans le produit sautent le même jour sans raison visible. Le hachage rendez-vous ne déplace que ce qui était rattaché à la sonde partie. La clé, c’est la révision de la règle et la localisation. La révision est immuable, donc une règle que le client modifie peut légitimement atterrir ailleurs, tandis que le choix à l’intérieur d’une même révision ne dérive jamais. Le créneau est délibérément absent de la clé : avec le numéro de tick dedans, des mesures consécutives atterriraient sur des sondes différentes, et un écart entre deux points pourrait tout aussi bien être un écart entre deux machines. La comparabilité dans le temps vaut plus cher que la répartition de la charge.

Une sonde peut vous faire défaut de deux manières, constatées à des moments différents : elle peut avoir disparu au moment de l’envoi, ou elle peut accepter la commande et ne jamais répondre, ce qui n’apparaît qu’une fois écoulé le délai propre au contrôle. Les deux produisent une tentative de plus à l’intérieur du même tick, et jamais un second tick. Un basculement n’est pas une seconde mesure, et s’il l’était, une localisation malchanceuse doublerait silencieusement le nombre d’échantillons. Le plafond est de deux tentatives physiques, parce qu’ensemble elles doivent tenir dans l’échéance du tick.

Quand une localisation n’a aucune sonde en bonne santé, le créneau est tout de même réclamé et la source est enregistrée comme indisponible. Jamais comme en panne : l’absence d’une localisation à nous ne dit rien de la machine du client, et appeler cela une panne serait une fausse alerte produite par notre propre infrastructure.

Trois endroits qui refusent une adresse privée

Une règle de supervision, c’est un champ où quelqu’un nomme une destination et où une machine à nous s’y connecte chaque minute. Sans limites, c’est un moteur de falsification de requêtes avec une adresse de facturation dessus. La cible est une allocation d’adresse appartenant au serveur du client lui-même plutôt qu’un texte libre, ce qui retire l’essentiel du problème ; le reste est une liste de plages qu’une sonde hébergée ne doit pas toucher, en plus de celles qui sont fermées partout :

PlagePourquoi elle est fermée
10/8, 172.16/12, 192.168/16Espace privé. Sur une sonde hébergée, c’est le datacenter, pas le client
100.64/10NAT de grande échelle, là où les fournisseurs accrochent répartiteurs de charge et chemins d’administration
192.0.0/24, 192.0.2/24, 198.18/15, 198.51.100/24, 203.0.113/24, 192.88.99/24Affectations de protocole, évaluation de performances, documentation, un anycast de relais abandonné. Aucune de ces plages ne peut être une machine cliente, donc tout ce qui répond est plus proche que la cible
fc00::/7, 2001:db8::/32La première et la troisième ligne ci-dessus, en IPv6 : espace local unique et espace de documentation

Les formes IPv4 mappées sont d’abord déballées, sans quoi chaque règle ci-dessus a une écriture qui passe à côté.

Le contrôle tourne à trois endroits et les trois sont nécessaires. La plateforme refuse avant l’envoi, si bien qu’une mauvaise cible n’atteint jamais une machine capable de l’exécuter. La sonde refuse à l’exécution, parce que c’est le seul endroit où l’on sait vers quoi un nom s’est réellement résolu, et cela peut différer d’une fois sur l’autre. Et la plateforme relit l’adresse rapportée quand le résultat arrive, ce qui transforme une sonde ayant mesuré ailleurs en réponse invalide plutôt qu’en point de mesure. Une destination privée est une erreur de configuration et jamais un échec : rien n’a été mesuré, il n’y a donc rien à dire sur la cible.

Ce que nous retiendrions

  1. Ne laissez jamais le client nommer sa propre nature. Déduisez-la de quelque chose que vous avez émis, et gardez les deux refus identiques pour que l’erreur ne serve pas d’oracle.
  2. Énumérez tout le protocole, pas la partie autorisée. Laissez un test lire le code généré et exiger un verdict par champ, dans les deux sens, pour qu’un nouveau type de message soit un test rouge plutôt qu’un trou.
  3. Placez la vraie frontière là où on ne peut pas l’oublier. Un registre séparé sans identifiant d’agent signifie qu’il n’y a pas de route, quoi que dise la table.
  4. Vérifiez la charge utile autant que l’enveloppe. « Les commandes sont autorisées » n’est pas la même affirmation que « cette commande est autorisée ».
  5. Gardez l’horloge hors de la clé d’affectation, et faites d’un basculement une tentative supplémentaire plutôt qu’un échantillon supplémentaire, pour qu’un changement dans les données ne puisse jamais être pris pour un changement d’instrument.

Pour être clair : la supervision hébergée est derrière un indicateur de déploiement désactivé par défaut, il n’y a aucune couche de droits en dessous, et rien de tout cela n’est achetable aujourd’hui. Ce qui n’est pas prouvé mérite d’être nommé aussi. La sous-commande de sonde n’a jamais tourné de bout en bout, parce que l’un de ses composants réseau ne compile pas sur la machine où ceci a été écrit ; les politiques de localisation n’ont été exercées que dans leur forme la plus simple ; IPv6 n’a été mesuré nulle part. La liaison elle-même a été éprouvée pour de vrai, de l’enrôlement jusqu’à une session puis un contrôle qui est revenu, et un message interdit sur cette connexion a été refusé.

Issu du travail de supervision chez Provibr. Pourquoi un nom d’hôte doit être prouvé avant de pouvoir servir de cible est dans prouver la propriété d’un nom d’hôte, et pourquoi un incident ne se ferme pas du simple fait que les mesures ont cessé est dans un incident qui ne se ferme jamais sur un silence.

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.