<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ansible on Mook’s Lab Chronicles</title><link>https://blog.homeblack.fr/tags/ansible/</link><description>Recent content in Ansible on Mook’s Lab Chronicles</description><generator>Hugo -- gohugo.io</generator><language>fr-FR</language><lastBuildDate>Sat, 19 Sep 2026 16:06:02 +0200</lastBuildDate><atom:link href="https://blog.homeblack.fr/tags/ansible/index.xml" rel="self" type="application/rss+xml"/><item><title>Mon bastion Warpgate : des clés SSH au simple alias</title><link>https://blog.homeblack.fr/p/bastion-warpgate-cles-ssh/</link><pubDate>Sat, 19 Sep 2026 16:06:02 +0200</pubDate><guid>https://blog.homeblack.fr/p/bastion-warpgate-cles-ssh/</guid><description>&lt;img src="https://blog.homeblack.fr/p/bastion-warpgate-cles-ssh/cover.png" alt="Featured image of post Mon bastion Warpgate : des clés SSH au simple alias" /&gt;&lt;p&gt;Dans mon terminal, accéder à un serveur tient en une seule ligne :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh dmz-nextcloud-01
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Pas d’adresse IP à chercher, pas de serveur de rebond à saisir, aucune clé personnelle à copier sur la cible. Pourtant, sous la surface, une chaîne complète s&amp;rsquo;active : OpenSSH, Traefik, mon bastion Warpgate, Authentik et un fichier de configuration alimenté par WarpgateSH.&lt;/p&gt;
&lt;p&gt;Dans &lt;a class="link" href="https://blog.homeblack.fr/p/warpgatesh-synchroniser-acces-ssh-warpgate/" &gt;le billet consacré à WarpgateSH&lt;/a&gt;, j’expliquais comment un script lourd à maintenir avait donné naissance à un outil dédié. Il me restait à présenter l&amp;rsquo;autre pan du système : le bastion lui-même, la gestion des clés SSH et son intégration dans mon système d’information — à l’échelle d’un homelab.&lt;/p&gt;
&lt;p&gt;Je vais remonter cette commande étape par étape, depuis mon terminal jusqu’au serveur. Pour en reproduire le principe, je partirais simplement d’une VM et d’une machine de test.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Les exemples s’appuient sur mon infrastructure et le code de WarpgateSH au 19 septembre 2026 ; la version de Warpgate vérifiée le 10 septembre était la 0.27.5. Les valeurs &lt;code&gt;example.net&lt;/code&gt;, &lt;code&gt;alice&lt;/code&gt; et &lt;code&gt;srv-demo&lt;/code&gt; sont fictives et réservées aux démonstrations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="un-bastion-pour-quoi-faire-"&gt;Un bastion, pour quoi faire ?
&lt;/h2&gt;&lt;p&gt;Un bastion sert de point de passage contrôlé vers les ressources à administrer. Au lieu d’autoriser chaque poste à joindre directement chaque serveur, je centralise les flux sur un service unique. Il contrôle les identités, applique les droits et conserve des journaux d’accès.&lt;/p&gt;
&lt;p&gt;Warpgate remplit cette fonction chez moi. Je lui déclare les &lt;strong&gt;cibles&lt;/strong&gt; (les serveurs autorisés) et les &lt;strong&gt;rôles&lt;/strong&gt; (qui a le droit d&amp;rsquo;y accéder), tout en conservant mon client SSH habituel.&lt;/p&gt;
&lt;p&gt;À la différence d&amp;rsquo;un rebond classique via &lt;code&gt;ProxyJump&lt;/code&gt;, la connexion SSH directe depuis mon poste &lt;strong&gt;s’arrête au bastion&lt;/strong&gt;. Warpgate ouvre ensuite sa propre session vers la machine cible avec ses propres identifiants. Je n&amp;rsquo;ai donc jamais besoin d’y transférer mon agent SSH local.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.homeblack.fr/p/bastion-warpgate-cles-ssh/parcours-ssh.png"
width="1600"
height="850"
srcset="https://blog.homeblack.fr/p/bastion-warpgate-cles-ssh/parcours-ssh_hu_9e0bd21345f903d8.png 480w, https://blog.homeblack.fr/p/bastion-warpgate-cles-ssh/parcours-ssh_hu_e7022d0448aa93b1.png 1024w"
loading="lazy"
alt="Deux connexions distinctes : le poste s’authentifie auprès de Warpgate, puis Warpgate utilise sa clé pour joindre le serveur. Authentik valide l’identité humaine."
class="gallery-image"
data-flex-grow="188"
data-flex-basis="451px"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Vue logique : le réseau et le proxy de transport sont volontairement omis pour faire ressortir les deux authentifications.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Cette étanchéité constitue le point clé du montage : &lt;strong&gt;mon identité sur le bastion et le compte Linux de la cible sont deux entités totalement distinctes.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="clé-privée-clé-publique--qui-garde-quoi-"&gt;Clé privée, clé publique : qui garde quoi ?
&lt;/h2&gt;&lt;p&gt;Une paire de clés SSH repose sur deux éléments liés mathématiquement :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;la &lt;strong&gt;clé privée&lt;/strong&gt;, gardée secrète par le client qui prouve son identité ;&lt;/li&gt;
&lt;li&gt;la &lt;strong&gt;clé publique&lt;/strong&gt;, déposée sur le serveur qui vérifie cette identité.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lors de la connexion, le client signe le défi d&amp;rsquo;authentification avec sa clé privée. Le serveur valide cette signature via la clé publique correspondante, sans que la clé privée ne transite sur le réseau.&lt;/p&gt;
&lt;p&gt;Sur un serveur OpenSSH standard, les clés autorisées sont listées dans &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; au niveau du compte utilisateur Linux (&lt;code&gt;alice&lt;/code&gt;, &lt;code&gt;root&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;p&gt;Pour illustrer la génération manuelle, je prends cette commande :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_bastion -C &lt;span class="s2"&gt;&amp;#34;poste-perso&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Cette commande produit deux fichiers (décrits dans le &lt;a class="link" href="https://man.openbsd.org/ssh-keygen" target="_blank" rel="noopener"
&gt;manuel &lt;code&gt;ssh-keygen&lt;/code&gt;&lt;/a&gt;) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/.ssh/id_ed25519_bastion ← Fichier privé local
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/.ssh/id_ed25519_bastion.pub ← Clé publique à déployer côté serveur
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="chez-moi-les-clés-passent-par-lagent-bitwarden"&gt;Chez moi, les clés passent par l’agent Bitwarden
&lt;/h3&gt;&lt;p&gt;Je ne définis pas de mot de passe directement sur le fichier de clé : je délègue sa protection au coffre fort de Bitwarden et son utilisation à son &lt;strong&gt;agent SSH&lt;/strong&gt;. OpenSSH sollicite l’agent pour signer l’authentification sans lire de fichier privé sur mon disque. Le &lt;a class="link" href="https://bitwarden.com/help/ssh-agent/" target="_blank" rel="noopener"
&gt;guide SSH Bitwarden&lt;/a&gt; détaille sa mise en œuvre.&lt;/p&gt;
&lt;p&gt;Dans mon inventaire Ansible, la connexion pointe vers le socket de l&amp;rsquo;agent :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-o IdentityAgent=~/.bitwarden-ssh-agent.sock
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Une précision importante : stocker une clé non chiffrée dans Bitwarden protège sa copie dans le coffre, mais ne sécurise pas le fichier initial resté sur le disque s&amp;rsquo;il n&amp;rsquo;a pas été supprimé.&lt;/p&gt;
&lt;p&gt;Pour accéder au bastion via SSH, j’enregistre &lt;strong&gt;ma clé publique dans mon profil Warpgate&lt;/strong&gt;. Mon agent Bitwarden produit la signature ; le bastion la vérifie avec ma clé publique. Une fois authentifié, Warpgate initie la connexion vers la cible finale en utilisant &lt;strong&gt;sa propre clé privée&lt;/strong&gt;. J’autorise donc la clé publique du bastion dans le fichier &lt;code&gt;authorized_keys&lt;/code&gt; du serveur, et non ma clé personnelle.&lt;/p&gt;
&lt;p&gt;Ce flux par clé sert principalement aux comptes de service. Pour mes sessions d&amp;rsquo;administration courantes, je valide mon identité dans le navigateur via Authentik.&lt;/p&gt;
&lt;h3 id="ed25519-rsa-ecdsa--trois-algorithmes-distincts"&gt;Ed25519, RSA, ECDSA : trois algorithmes distincts
&lt;/h3&gt;&lt;p&gt;Ces appellations désignent des algorithmes utilisés pour les signatures d’authentification SSH :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Ce que j’en retiens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ed25519&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;C’est mon choix pour les systèmes récents et le type de clé déployé par mon socle Ansible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RSA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Je le réserverais aux besoins de rétrocompatibilité, avec des signatures SHA-2 comme l’expliquent les &lt;a class="link" href="https://www.openssh.com/txt/release-8.8" target="_blank" rel="noopener"
&gt;notes OpenSSH 8.8&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ECDSA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Variante basée sur des courbes elliptiques standardisées, disponible nativement dans OpenSSH.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ed25519-sk&lt;/code&gt; / &lt;code&gt;ecdsa-sk&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Déclinaisons exploitant une clé de sécurité matérielle (FIDO2/YubiKey).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Pour une nouvelle installation, j’écarte les algorithmes obsolètes comme DSA.&lt;/p&gt;
&lt;h3 id="et-la-clé-du-serveur-"&gt;Et la clé du serveur ?
&lt;/h3&gt;&lt;p&gt;SSH contrôle également l&amp;rsquo;authenticité de l&amp;rsquo;hôte distant pour éviter les interceptions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;authorized_keys&lt;/code&gt; contrôle &lt;strong&gt;qui peut entrer&lt;/strong&gt; ;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;known_hosts&lt;/code&gt; enregistre &lt;strong&gt;les serveurs de confiance&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le poste client valide l&amp;rsquo;empreinte du bastion, puis le bastion valide celle du serveur cible. À la première connexion, je recommande de comparer l’empreinte proposée avec celle relevée depuis la console de la VM. Pour afficher cette dernière sur la cible, j’utiliserais cette commande :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;Note d&amp;rsquo;usage : ne pas comparer cette empreinte revient à accepter aveuglément l&amp;rsquo;identité du serveur. C&amp;rsquo;est un point d&amp;rsquo;amélioration que je prévois d&amp;rsquo;automatiser entièrement dans mon lab.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="déploiement-dans-le-homelab"&gt;Déploiement dans le homelab
&lt;/h2&gt;&lt;p&gt;Mon bastion tourne sur une VM Proxmox dédiée (&lt;code&gt;dmz-bastion-01&lt;/code&gt;), provisionnée via Terraform (2 vCPU, 2 Gio de RAM, 30 Gio de stockage).&lt;/p&gt;
&lt;p&gt;Ansible déploie Docker et configure l&amp;rsquo;environnement. Voici un extrait de mon fichier Docker Compose :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;warpgate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;warpgate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;2222:2222&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8888:8888&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./data:/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le port &lt;code&gt;2222&lt;/code&gt; reçoit les connexions SSH et le port &lt;code&gt;8888&lt;/code&gt; héberge l&amp;rsquo;interface d&amp;rsquo;administration HTTPS. Le dossier &lt;code&gt;./data&lt;/code&gt; conserve l&amp;rsquo;état persistant de l&amp;rsquo;application.&lt;/p&gt;
&lt;h2 id="traefik-en-frontal--un-nom-deux-parcours"&gt;Traefik en frontal : un nom, deux parcours
&lt;/h2&gt;&lt;p&gt;Traefik écoute sur &lt;code&gt;bastion.int.homeblack.fr&lt;/code&gt; et redirige les requêtes vers le bastion :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Portail HTTPS (SSO)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Navigateur → bastion.int.homeblack.fr:443
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Traefik → Warpgate:8888 (HTTPS)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Session SSH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;OpenSSH → bastion.int.homeblack.fr:2222
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Traefik (Relais TCP) → Warpgate:2222
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Serveur cible:22
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Traefik achemine le trafic vers Warpgate ; Warpgate gère les accès aux cibles.&lt;/strong&gt; Traefik ignore tout des machines finales et se contente de relayer les flux.&lt;/p&gt;
&lt;h3 id="reverse-proxy-https-pour-le-portail"&gt;Reverse proxy HTTPS pour le portail
&lt;/h3&gt;&lt;p&gt;Le portail web passe par un reverse proxy standard. L&amp;rsquo;inventaire Ansible définit le routage :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bastion&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bastion.int.homeblack.fr&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;upstream_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://10.60.0.17:8888&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les liens d’approbation et le retour Authentik passent par le navigateur, via l’accès HTTPS du portail.&lt;/p&gt;
&lt;h3 id="relais-tcp-dédié-pour-le-flux-ssh"&gt;Relais TCP dédié pour le flux SSH
&lt;/h3&gt;&lt;p&gt;SSH ne traitant pas le protocole HTTP, Traefik utilise un &lt;strong&gt;routeur TCP&lt;/strong&gt;. La configuration statique déclare d&amp;rsquo;abord le point d&amp;rsquo;entrée :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;warpgate-ssh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;:2222&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La configuration dynamique (chargée via le provider &lt;code&gt;file&lt;/code&gt;) définit ensuite le routage :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;routers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;warpgate-ssh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;warpgate-ssh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;HostSNI(`*`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;warpgate-ssh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;warpgate-ssh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;loadBalancer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;proxyProtocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.60.0.17:2222&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La règle &lt;code&gt;HostSNI(`*`)&lt;/code&gt; accepte tous les flux TCP bruts sans interception TLS (détails dans la &lt;a class="link" href="https://doc.traefik.io/traefik/reference/routing-configuration/tcp/routing/rules-and-priority/" target="_blank" rel="noopener"
&gt;doc Traefik TCP&lt;/a&gt;). Le chiffrement SSH reste géré de bout en bout entre le client et Warpgate.&lt;/p&gt;
&lt;h3 id="conservation-des-ip-sources-avec-proxy-protocol"&gt;Conservation des IP sources avec PROXY Protocol
&lt;/h3&gt;&lt;p&gt;Lorsqu&amp;rsquo;un proxy transfère une connexion, le serveur final ne voit que l&amp;rsquo;IP du proxy.&lt;/p&gt;
&lt;p&gt;L’activation du &lt;strong&gt;PROXY protocol v2&lt;/strong&gt; permet à Traefik de transmettre l’IP d’origine du client dans un en-tête ajouté au début du flux TCP. Côté Warpgate (&lt;code&gt;ansible/inventory/group_vars/bastion.yml&lt;/code&gt;) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;warpgate_ssh_external_host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bastion.int.homeblack.fr&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;warpgate_ssh_external_port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2222&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;warpgate_ssh_proxy_protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;warpgate_ssh_allowed_sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;10.40.0.28/32&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# IP de Traefik autorisée&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="segmentation-réseau"&gt;Segmentation réseau
&lt;/h3&gt;&lt;p&gt;Dans mon architecture, Traefik réside sur le réseau &lt;code&gt;TRUST&lt;/code&gt; et Warpgate sur le réseau &lt;code&gt;DMZ&lt;/code&gt;. Le flux traverse le pare-feu inter-vlan. Pour une nouvelle installation, je privilégierais un reverse proxy dans le même segment réseau que le service, afin d’éviter ce passage entre zones.&lt;/p&gt;
&lt;p&gt;J’intègre aussi le filtrage des accès directs depuis les postes à la mise en place du bastion, avec l’objectif de faire passer les sessions d’administration par Warpgate.&lt;/p&gt;
&lt;h2 id="comment-les-serveurs-valident-le-bastion"&gt;Comment les serveurs valident le bastion
&lt;/h2&gt;&lt;p&gt;Une fois authentifié sur Warpgate, ce dernier initie la connexion SSH vers le serveur cible. La cible doit donc connaître la clé publique du bastion.&lt;/p&gt;
&lt;p&gt;Mon rôle Ansible &lt;code&gt;ssh-keys&lt;/code&gt; déploie automatiquement la clé publique du bastion (&lt;code&gt;warpgate-target&lt;/code&gt;) dans le fichier &lt;code&gt;authorized_keys&lt;/code&gt; du compte système distant :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy authorized_keys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.posix.authorized_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ ssh_user }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;present&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ item }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ ssh_public_keys }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Côté Warpgate, les paramètres de la cible (IP, port, utilisateur Linux) sont injectés via Terraform.&lt;/p&gt;
&lt;p&gt;Pour me connecter à Nextcloud, la commande sous-jacente générée équivaut à :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -p &lt;span class="m"&gt;2222&lt;/span&gt; -l &lt;span class="s1"&gt;&amp;#39;gregory.narcin:dmz-nextcloud-01&amp;#39;&lt;/span&gt; bastion.int.homeblack.fr
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le paramètre &lt;code&gt;-l&lt;/code&gt; transmet l&amp;rsquo;instruction : « Connecte l&amp;rsquo;utilisateur Warpgate &lt;code&gt;gregory.narcin&lt;/code&gt; à la cible &lt;code&gt;dmz-nextcloud-01&lt;/code&gt; ». Après validation des droits, Warpgate ouvre la session SSH vers la VM avec son propre compte d&amp;rsquo;administration.&lt;/p&gt;
&lt;h2 id="sso-avec-authentik-et-accès-automatisés"&gt;SSO avec Authentik et accès automatisés
&lt;/h2&gt;&lt;p&gt;J’utilise &lt;a class="link" href="https://blog.homeblack.fr/p/authentik-sso-homelab/" &gt;Authentik&lt;/a&gt; comme fournisseur d’identité via &lt;strong&gt;OIDC&lt;/strong&gt;, OpenID Connect, pour l’authentification unique (&lt;strong&gt;SSO&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;Pour une connexion humaine, l’option &lt;strong&gt;In-browser auth&lt;/strong&gt; affiche une URL et un code de validation dans le terminal. J’ouvre le lien, compare le code affiché et approuve la demande après authentification auprès d’Authentik. OpenSSH attend cette validation via la méthode &lt;code&gt;keyboard-interactive&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour raccorder Warpgate à Authentik, je configure une application cliente et son URL de redirection, par exemple &lt;code&gt;https://bastion.example.net/@warpgate/api/sso/return&lt;/code&gt;. Je sélectionne ensuite &lt;em&gt;In-browser auth&lt;/em&gt; dans la politique d’authentification SSH de mon compte, comme l’explique le &lt;a class="link" href="https://warpgate.null.page/sso/" target="_blank" rel="noopener"
&gt;guide SSO Warpgate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Je gère la correspondance des rôles avec Ansible :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;warpgate_oidc_roles_claim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;groups&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;warpgate_oidc_role_mappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;warpgate-operators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ops&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les appartenances aux groupes Authentik sont ainsi converties en rôles d’accès dans Warpgate.&lt;/p&gt;
&lt;p&gt;Pour mes automatisations Ansible et mes pipelines CI/CD, je garde des comptes de service avec des clés SSH dédiées, comme &lt;code&gt;svc-ansible&lt;/code&gt; : je ne veux pas qu’un traitement attende une validation dans mon navigateur.&lt;/p&gt;
&lt;h2 id="déclaration-des-cibles-avec-terraform"&gt;Déclaration des cibles avec Terraform
&lt;/h2&gt;&lt;p&gt;Je déclare mes cibles Warpgate avec Terraform pour éviter de les ajouter manuellement dans l’interface :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;warpgate_target&amp;#34; &amp;#34;ssh&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ssh_targets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;ssh_options&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ip_address&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ssh_username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;root&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; allow_insecure_algos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public_key_auth&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Mon parcours de création suit cet ordre : &lt;strong&gt;VM Terraform → Cible Warpgate Terraform → Socle Ansible → Synchronisation du client&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;J’exclus le bastion lui-même de cette liste et conserve un accès d’urgence indépendant pour pouvoir intervenir si Warpgate tombe en panne.&lt;/p&gt;
&lt;h2 id="automatisation-des-alias-avec-warpgatesh"&gt;Automatisation des alias avec WarpgateSH
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://github.com/M0okz/warpgatesh" target="_blank" rel="noopener"
&gt;WarpgateSH&lt;/a&gt; interroge l’API de Warpgate pour générer automatiquement la configuration SSH locale et les alias associés.&lt;/p&gt;
&lt;p&gt;Après avoir installé la CLI (&lt;a class="link" href="https://github.com/M0okz/warpgatesh/blob/main/docs/getting-started.md" target="_blank" rel="noopener"
&gt;guide de démarrage&lt;/a&gt;), je configure un profil ; voici les commandes avec les valeurs de démonstration :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warpgatesh profile add lab https://bastion.example.net
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warpgatesh profile default lab
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warpgatesh profile ssh-auth lab in-browser
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warpgatesh agent install
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warpgatesh sync
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warpgatesh ls
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;L&amp;rsquo;outil ajoute un appel d&amp;rsquo;inclusion dans &lt;code&gt;~/.ssh/config&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Include ~/.ssh/warpgatesh/config
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le fichier généré (&lt;code&gt;~/.ssh/warpgatesh/config&lt;/code&gt;) contient les entrées de ce type :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Profile lab
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host srv-demo srv-demo.lab
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HostName &amp;#34;bastion.example.net&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Port 2222
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User &amp;#34;alice:srv-demo&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; UserKnownHostsFile ~/.ssh/warpgatesh/known_hosts/lab
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; StrictHostKeyChecking yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PreferredAuthentications keyboard-interactive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; KbdInteractiveAuthentication yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PasswordAuthentication no
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PubkeyAuthentication no
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Explication des directives :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Host&lt;/code&gt; : définit l&amp;rsquo;alias court et l&amp;rsquo;alias qualifié.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HostName&lt;/code&gt; : pointe vers le bastion.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;User&lt;/code&gt; : passe le sélecteur &lt;code&gt;utilisateur:cible&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PreferredAuthentications&lt;/code&gt; : impose l&amp;rsquo;authentification interactive pour déclencher la validation SSO par navigateur.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je peux ensuite utiliser la commande raccourcie :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh srv-demo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="diagnostic-et-retours-dexpérience"&gt;Diagnostic et retours d&amp;rsquo;expérience
&lt;/h2&gt;&lt;p&gt;J’ai rencontré des incohérences entre mes alias SSH : certains forçaient le mode &lt;code&gt;keyboard-interactive&lt;/code&gt;, tandis que d’autres autorisaient aussi les clés publiques.&lt;/p&gt;
&lt;p&gt;Pour comprendre ce qu’OpenSSH utilise réellement, je consulte sa configuration effective avec &lt;code&gt;ssh -G&lt;/code&gt;, sans ouvrir de connexion :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -G srv-demo &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;/^(hostname|port|user|preferredauthentications|pubkeyauthentication) /&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Je peux ainsi repérer les conflits avec d’éventuelles directives &lt;code&gt;Host *&lt;/code&gt; globales.&lt;/p&gt;
&lt;p&gt;Pour valider un montage similaire, je vérifierais quatre points :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;L’accès aux cibles autorisées.&lt;/li&gt;
&lt;li&gt;Le refus d’accès à une cible non attribuée.&lt;/li&gt;
&lt;li&gt;Le compte Linux obtenu à l’arrivée, avec &lt;code&gt;whoami&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;La présence de la session dans les traces d’audit de Warpgate.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="bilan"&gt;Bilan
&lt;/h2&gt;&lt;p&gt;Avec ce montage, je centralise mes accès SSH sans intervenir sur chaque serveur à l’arrivée ou au départ d’un utilisateur. Je garde mes accès automatisés séparés grâce aux comptes de service dédiés.&lt;/p&gt;
&lt;p&gt;En contrepartie, mon bastion devient un composant critique de l’infrastructure. Je dois sauvegarder régulièrement sa clé privée d’administration et ses données d’audit, et en restreindre strictement les accès. C’est bien ce qui se passe en coulisses, et même un peu plus… mais si je raconte tout ici, ce billet va finir avec un sommaire en trois volumes.&lt;/p&gt;
&lt;p&gt;Selon moi, le plus simple est d’avancer par étapes : je commencerais par valider le flux SSH sur une seule cible, puis j’intégrerais le SSO OIDC avant d’automatiser la gestion des cibles et des alias sur mon poste.&lt;/p&gt;</description></item><item><title>Authentik, la porte d’entrée de mon homelab — et le ménage qui lui manquait</title><link>https://blog.homeblack.fr/p/authentik-sso-homelab/</link><pubDate>Fri, 31 Jul 2026 15:06:16 +0200</pubDate><guid>https://blog.homeblack.fr/p/authentik-sso-homelab/</guid><description>&lt;img src="https://blog.homeblack.fr/p/authentik-sso-homelab/cover.png" alt="Featured image of post Authentik, la porte d’entrée de mon homelab — et le ménage qui lui manquait" /&gt;&lt;p&gt;Je voulais gagner quelques millisecondes sur mes connexions. En ouvrant la VM d’Authentik, j’ai surtout trouvé un petit musée de son histoire : un conteneur Redis devenu inutile, deux fichiers d’environnement presque identiques et des images Docker accumulées.&lt;/p&gt;
&lt;p&gt;Comme le service tournait bien, j’avais laissé cette sédimentation vivre tranquillement. Pas idéal pour celui qui garde la porte de presque tout mon homelab. Cette maintenance était donc l’occasion de faire le ménage, mais aussi de présenter enfin Authentik sur ce blog.&lt;/p&gt;
&lt;h2 id="bien-plus-quune-jolie-page-de-connexion"&gt;Bien plus qu’une jolie page de connexion
&lt;/h2&gt;&lt;p&gt;SSO signifie &lt;em&gt;Single Sign-On&lt;/em&gt;, ou authentification unique. Le raccourci habituel consiste à dire : « un seul compte pour toutes mes applications ». C’est vrai, mais ce n’est pas le point le plus important.&lt;/p&gt;
&lt;p&gt;Une application raccordée à Authentik ne reçoit pas mon mot de passe. Elle délègue l’authentification à un fournisseur d’identité — un &lt;em&gt;Identity Provider&lt;/em&gt; ou IdP — puis fait confiance au jeton ou à l’assertion qu’il lui retourne. Je peux donc centraliser la double authentification, les groupes, les règles d’accès et le cycle de vie des comptes sans recopier la même logique dans chaque service.&lt;/p&gt;
&lt;p&gt;Au 31 juillet 2026, mon instance contient 21 applications. La majorité utilise OpenID Connect (OIDC), notamment GitLab, Grafana, Warpgate, Technitium et PatchMon. Wazuh passe par SAML. Pour OpenVSCode, qui n’a pas le même raccordement natif, nginx demande directement à l’outpost Proxy d’Authentik si la requête peut continuer.&lt;/p&gt;
&lt;p&gt;Cette souplesse est l’une des raisons pour lesquelles Authentik s’est imposé chez moi. Je privilégie OIDC lorsqu’il est disponible, SAML lorsque l’application l’impose et le &lt;a class="link" href="https://docs.goauthentik.io/add-secure-apps/providers/proxy/create-proxy-provider/" target="_blank" rel="noopener"
&gt;provider Proxy&lt;/a&gt; pour les services qui ne savent rien faire de tout cela.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GitLab ─────────────── OIDC ──────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Grafana ────────────── OIDC ──────┤
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Warpgate ───────────── OIDC ──────┤
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Wazuh ──────────────── SAML ──────┼──&amp;gt; Authentik ──&amp;gt; identité, MFA, groupes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;OpenVSCode ── nginx / forward auth ┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le confort est évident : une seule session ouvre plusieurs outils et je n’administre plus une collection de comptes locaux oubliés. Le revers l’est tout autant : Authentik est devenu une dépendance critique. Une panne n’arrête pas forcément les sessions déjà ouvertes, mais elle peut bloquer toute nouvelle connexion. Une compromission aurait, elle, un rayon d’action particulièrement généreux.&lt;/p&gt;
&lt;p&gt;Je le traite donc comme une brique de sécurité, pas comme un tableau de bord de plus.&lt;/p&gt;
&lt;h2 id="pourquoi-authentik-plutôt-que-keycloak-"&gt;Pourquoi Authentik plutôt que Keycloak ?
&lt;/h2&gt;&lt;p&gt;J’ai longtemps hésité avec Keycloak. Il avait pour lui l’ancienneté, une documentation abondante et un écosystème déjà installé. &lt;a class="link" href="https://api.github.com/repos/keycloak/keycloak" target="_blank" rel="noopener"
&gt;Son dépôt public remonte à 2013&lt;/a&gt;, alors que &lt;a class="link" href="https://goauthentik.io/blog/2023-11-1-happy-birthday-to-us/" target="_blank" rel="noopener"
&gt;le premier commit d’Authentik date de 2018&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Choisir Authentik revenait donc à miser sur le plus jeune. Son interface et ses flows me parlaient davantage, son proxy intégré convenait bien au mélange d’applications modernes et de vieux services de mon homelab, et je me sentais plus à l’aise avec un cœur Python/Django qu’avec une JVM.&lt;/p&gt;
&lt;p&gt;Ce n’est pas un verdict contre Java : Keycloak fonctionne sur Quarkus et propose un &lt;a class="link" href="https://www.keycloak.org/server/configuration" target="_blank" rel="noopener"
&gt;build optimisé&lt;/a&gt;. Authentik n’est pas spécialement léger non plus avec son serveur, son worker, PostgreSQL et ses éventuels outposts. Mon choix était surtout pratique : lequel aurais-je envie de comprendre et de dépanner pendant plusieurs années ? Pour moi, la réponse était Authentik.&lt;/p&gt;
&lt;h2 id="un-projet-qui-a-mûri"&gt;Un projet qui a mûri
&lt;/h2&gt;&lt;p&gt;Les premières versions que j’ai suivies demandaient davantage de prudence : migrations sensibles, interface mouvante et notes de version à lire religieusement. Depuis la création d’Authentik Security en 2022, le projet a changé d’échelle et adopté un modèle &lt;em&gt;open core&lt;/em&gt;, avec les protocoles essentiels toujours disponibles dans &lt;a class="link" href="https://goauthentik.io/pricing/" target="_blank" rel="noopener"
&gt;l’édition open source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Au 31 juillet 2026, &lt;a class="link" href="https://api.github.com/repos/goauthentik/authentik" target="_blank" rel="noopener"
&gt;le dépôt GitHub&lt;/a&gt; affichait 22 563 étoiles et une activité le jour même. La &lt;a class="link" href="https://github.com/goauthentik/authentik/releases/tag/version/2026.5.6" target="_blank" rel="noopener"
&gt;version 2026.5.6&lt;/a&gt; datait du 22 juillet. Ces chiffres ne prouvent pas la qualité du logiciel, mais ils confirment un développement suivi. Après plusieurs années de mises à jour, c’est surtout cette continuité qui me rassure.&lt;/p&gt;
&lt;h2 id="redis-était-parti-mon-compose-ne-le-savait-pas"&gt;Redis était parti, mon Compose ne le savait pas
&lt;/h2&gt;&lt;p&gt;Authentik a déplacé ses tâches vers PostgreSQL en 2025.8, puis le cache, les sessions Proxy et les WebSockets en 2025.10. Redis pouvait disparaître. Mon ancien Compose continuait pourtant à lancer ce quatrième conteneur, presque inactif mais toujours présent dans les dépendances et les variables d’environnement.&lt;/p&gt;
&lt;p&gt;L’architecture actuelle repose sur trois rôles : PostgreSQL conserve les données, le serveur traite les requêtes et le worker s’occupe des tâches asynchrones. Les &lt;a class="link" href="https://docs.goauthentik.io/releases/2025.10" target="_blank" rel="noopener"
&gt;notes de version 2025.10&lt;/a&gt; préviennent toutefois que PostgreSQL reçoit environ 50 % de connexions supplémentaires. J’ai supprimé une dépendance, pas sa charge de travail.&lt;/p&gt;
&lt;p&gt;Après le ménage, la liste des services est devenue très lisible :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker compose -f /opt/authentik/docker-compose.yml config --services
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;postgresql
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;server
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;worker
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;J’en ai profité pour fusionner &lt;code&gt;.env&lt;/code&gt; et &lt;code&gt;authentik.env&lt;/code&gt;, retirer les variables Redis, ajouter le montage &lt;code&gt;/data&lt;/code&gt; et porter la mémoire partagée du serveur et du worker à 512 Mio. Avant le déploiement Ansible, j’ai vérifié la sauvegarde Proxmox Backup Server et la lisibilité d’un dump PostgreSQL. Le second passage du playbook n’a produit aucun changement.&lt;/p&gt;
&lt;h2 id="les-logs-avaient-aussi-besoin-dune-limite"&gt;Les logs avaient aussi besoin d’une limite
&lt;/h2&gt;&lt;p&gt;La VM ne dispose que d’un disque de 20 Gio. Avant l’intervention, 13 Gio étaient occupés et le stack produisait environ 19 000 lignes de logs par jour sans aucune rotation.&lt;/p&gt;
&lt;p&gt;J’ai conservé le pilote &lt;code&gt;json-file&lt;/code&gt;. Vector consomme l’API de logs Docker et Wazuh lit les fichiers JSON ; changer de pilote aurait donc cassé silencieusement une partie de l’observabilité. En revanche, chaque conteneur possède maintenant une rotation bornée :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;json-file&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;20m&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max-file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;5&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Après inventaire des images actives et nettoyage des seules images inutilisées, l’occupation est passée de 13 Gio à 4,6 Gio : environ 8,4 Gio récupérés, sans toucher aux volumes.&lt;/p&gt;
&lt;h2 id="le-vrai-goulot-nétait-pas-docker"&gt;Le vrai goulot n’était pas Docker
&lt;/h2&gt;&lt;p&gt;J’étais venu chercher quelques millisecondes dans le bridge Docker. Il n’en ajoutait que 0,1 à 0,2, tandis que la route publique répondait autour de 152 ms. Passer en mode &lt;code&gt;host&lt;/code&gt; aurait surtout échangé de l’isolation contre un gain invisible.&lt;/p&gt;
&lt;p&gt;La piste utile se trouvait un étage plus bas. La VM tournait sur &lt;code&gt;pve-fuji&lt;/code&gt;, un nœud Intel déjà chargé, avec parfois 5 à 8 % de temps CPU volé. Je l’ai migrée à chaud vers &lt;code&gt;pve-forum&lt;/code&gt;, un nœud AMD plus disponible. La latence a immédiatement chuté… puis la VM a perdu le réseau avant de se figer. À cet instant, mon petit benchmark venait de se transformer en exercice de reprise.&lt;/p&gt;
&lt;p&gt;Après retour sur &lt;code&gt;pve-fuji&lt;/code&gt; et redémarrage forcé, PostgreSQL et les trois conteneurs sont repartis sans perte. La bonne méthode était une migration à froid : arrêt propre par Proxmox HA, déplacement de la VM éteinte, puis redémarrage sur &lt;code&gt;pve-forum&lt;/code&gt;. L’interruption a duré environ 2 min 40.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Étape&lt;/th&gt;
&lt;th style="text-align: right"&gt;Route publique moyenne observée&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;état initial sur &lt;code&gt;pve-fuji&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;151,6 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compose modernisé, retour sur &lt;code&gt;pve-fuji&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;105,8 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;migration à froid vers &lt;code&gt;pve-forum&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;68,9 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ces mesures appartiennent à mon environnement, pas à un benchmark universel d’Authentik. Elles montrent surtout que le placement de la VM pesait bien plus que le réseau Docker — rien de très surprenant dans mon homelab bricolé de toutes pièces :D&lt;/p&gt;
&lt;h2 id="une-première-porte-ouverte"&gt;Une première porte ouverte
&lt;/h2&gt;&lt;p&gt;Ce billet n’avait pas vocation à devenir un guide d’Authentik. Mon blog vient de démarrer et je n’avais encore jamais présenté le service qui protège pourtant 21 de mes applications. Cette maintenance était simplement la bonne occasion de lui ouvrir la porte.&lt;/p&gt;
&lt;p&gt;Je reviendrai dans un autre article sur mon organisation autour d’Authentik : les flows, les groupes, la double authentification, le raccordement des applications et les cas où j’utilise le proxy plutôt qu’OIDC ou SAML. Pour cette fois, je voulais simplement raconter comment un conteneur Redis devenu inutile m’a donné le prétexte de présenter le gardien de mon homelab.&lt;/p&gt;</description></item><item><title>Trop d’alertes, plus aucune confiance : comment j’ai repris en main Checkmk</title><link>https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/</link><pubDate>Wed, 29 Jul 2026 16:00:00 +0200</pubDate><guid>https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/</guid><description>&lt;img src="https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/cover.png" alt="Featured image of post Trop d’alertes, plus aucune confiance : comment j’ai repris en main Checkmk" /&gt;&lt;p&gt;Une interface réseau disparaît à la fin d’un job. La mémoire du runner grimpe pendant un build. Une sonde mail dépasse son délai une seule fois. Un disque rappelle toutes les quelques minutes qu’il est toujours plein.&lt;/p&gt;
&lt;p&gt;Pris séparément, chacun de ces événements mérite d’être observé. Transformés avec la même urgence en notifications, ils finissent surtout par m’apprendre à ne plus les regarder.&lt;/p&gt;
&lt;p&gt;C’est le piège classique de la supervision : au début, on veut tout voir. Puis on ajoute des contrôles, des machines et des seuils jusqu’au moment où le système fonctionne techniquement, mais où le signal utile se retrouve noyé dans le bruit.&lt;/p&gt;
&lt;p&gt;J’ai donc repris mes règles Checkmk une par une. Mon objectif n’était pas d’obtenir un joli écran entièrement vert. Je voulais qu’une notification signifie de nouveau : « il faut regarder maintenant ».&lt;/p&gt;
&lt;h2 id="le-bruit-na-pas-une-cause-unique"&gt;Le bruit n’a pas une cause unique
&lt;/h2&gt;&lt;p&gt;La mauvaise solution aurait été de relever tous les seuils ou de désactiver les services pénibles. C’est tentant, rapide et particulièrement efficace pour fabriquer une fausse tranquillité.&lt;/p&gt;
&lt;p&gt;À la place, j’ai classé chaque alerte dans l’une de ces familles :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Exemple dans mon homelab&lt;/th&gt;
&lt;th&gt;Réponse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;état attendu&lt;/td&gt;
&lt;td&gt;interface TAP supprimée avec une VM éphémère&lt;/td&gt;
&lt;td&gt;ignorer précisément ce service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;incident transitoire&lt;/td&gt;
&lt;td&gt;pic de mémoire pendant un job CI&lt;/td&gt;
&lt;td&gt;demander une seconde confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;problème réel mais répétitif&lt;/td&gt;
&lt;td&gt;disque toujours en alerte&lt;/td&gt;
&lt;td&gt;conserver l’état, espacer les notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seuil mal adapté&lt;/td&gt;
&lt;td&gt;résolution DNS correcte mais un peu variable&lt;/td&gt;
&lt;td&gt;recalibrer avec une mesure cohérente&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Cette distinction paraît évidente après coup. Dans la pratique, elle oblige à répondre à une question parfois inconfortable : qu’est-ce qui justifie réellement de m’interrompre ?&lt;/p&gt;
&lt;h2 id="les-interfaces-tap--du-bruit-parfaitement-normal"&gt;Les interfaces TAP : du bruit parfaitement normal
&lt;/h2&gt;&lt;p&gt;Mes nœuds Proxmox créent des interfaces TAP pour connecter les interfaces réseau virtuelles des VM. Leur nom suit une forme comme :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tap&amp;lt;vmid&amp;gt;i&amp;lt;nic&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ces interfaces vivent aussi longtemps que la VM ou la charge CI correspondante. Lorsqu’une machine temporaire s’arrête, son interface disparaît. Checkmk peut alors interpréter cette disparition comme un changement d’état à signaler.&lt;/p&gt;
&lt;p&gt;Le problème ne venait donc pas d’une panne réseau. Le cycle de vie normal d’une ressource éphémère était traité comme celui d’un port physique.&lt;/p&gt;
&lt;p&gt;J’ai commencé par faire découvrir les interfaces Proxmox avec leur description Linux stable plutôt qu’avec un numéro générique du type &lt;code&gt;Interface 12&lt;/code&gt;. Cela évite qu’un bridge, un VLAN ou une nouvelle interface récupère le service d’un ancien périphérique.&lt;/p&gt;
&lt;p&gt;J’ai ensuite ajouté une règle très ciblée :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ruleset&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ignored_services&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Homeblack - ignore ephemeral Proxmox TAP interfaces&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;conditions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;host_name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;match_on&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pve-dell&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pve-forum&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pve-fuji&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;operator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;one_of&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;service_description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;match_on&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Interface tap[0-9]+i[0-9]+$&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;operator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;one_of&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Je n’ignore pas « les interfaces de Proxmox ». Je retire uniquement les services qui correspondent au motif des TAP éphémères. Les interfaces physiques, les bridges, les VLANs et les interfaces des machines invitées restent surveillés.&lt;/p&gt;
&lt;p&gt;C’est une nuance importante : une bonne exception décrit un comportement attendu, pas une catégorie suffisamment large pour faire disparaître le problème.&lt;/p&gt;
&lt;h2 id="un-pic-nest-pas-encore-une-panne"&gt;Un pic n’est pas encore une panne
&lt;/h2&gt;&lt;p&gt;Le runner GitLab présente un autre cas. Pendant un build, sa consommation mémoire peut monter brutalement pendant un cycle de contrôle, puis revenir à la normale.&lt;/p&gt;
&lt;p&gt;Ce pic reste intéressant. S’il dure, je veux le savoir. S’il ne concerne qu’une mesure isolée, je ne veux pas être interrompu.&lt;/p&gt;
&lt;p&gt;Checkmk distingue justement les états &lt;em&gt;soft&lt;/em&gt; et &lt;em&gt;hard&lt;/em&gt;. Un premier échec peut rester transitoire ; il ne devient un problème confirmé qu’après le nombre de tentatives défini.&lt;/p&gt;
&lt;p&gt;Pour le service &lt;code&gt;Memory&lt;/code&gt; de &lt;code&gt;trust-runner-01&lt;/code&gt;, j’ai choisi deux contrôles consécutifs :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ruleset&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;extra_service_conf:max_check_attempts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Homeblack - retry transient runner memory pressure&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;conditions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;host_name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;match_on&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;trust-runner-01&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;operator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;one_of&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;service_description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;match_on&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Memory$&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;operator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;one_of&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Un pic d’un cycle reste visible dans l’historique, mais ne déclenche pas de notification immédiate. Si la pression persiste au contrôle suivant, l’état devient &lt;em&gt;hard&lt;/em&gt; et l’alerte suit son chemin normal. Avec la fréquence actuelle des contrôles, cela représente environ une minute de confirmation.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/cover.png"
width="2209"
height="1355"
srcset="https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/cover_hu_55525788465104df.png 480w, https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/cover_hu_7a059729b680908e.png 1024w"
loading="lazy"
alt="Le service Memory du runner dans Checkmk, avec ses pics de consommation visibles"
class="gallery-image"
data-flex-grow="163"
data-flex-basis="391px"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Les pics courts restent visibles dans le graphe sans transformer automatiquement chaque mesure isolée en notification.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;J’ai appliqué le même raisonnement aux contrôles SMTP entrant et IMAP de ma plateforme mail. Un timeout isolé mérite une nouvelle tentative ; deux échecs consécutifs commencent à ressembler à un incident.&lt;/p&gt;
&lt;p&gt;En revanche, je n’ai pas ajouté un délai arbitraire de plusieurs heures au test de livraison de bout en bout. Cette sonde possède déjà sa propre notion du temps : avertissement après 120 secondes et état critique après 300 secondes. Ajouter quatre heures de silence par-dessus revenait à rendre le contrôle presque inutile.&lt;/p&gt;
&lt;h2 id="espacer-une-notification-sans-effacer-le-problème"&gt;Espacer une notification sans effacer le problème
&lt;/h2&gt;&lt;p&gt;Les disques posent une question différente. Un filesystem plein, une erreur SMART ou une latence d’entrée-sortie ne disparaissent pas parce que j’ai déjà reçu l’alerte.&lt;/p&gt;
&lt;p&gt;Mais recevoir continuellement le même message ne rend pas la réparation plus rapide.&lt;/p&gt;
&lt;p&gt;J’ai conservé les services &lt;code&gt;SMART&lt;/code&gt;, &lt;code&gt;Filesystem&lt;/code&gt; et &lt;code&gt;Disk IO&lt;/code&gt; visibles dans Checkmk avec leurs états normaux. C’est uniquement la fréquence de leurs notifications sortantes qui est limitée :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DISK_NOTIFICATION_COOLDOWN_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DISK_SERVICE_PREFIXES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SMART &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Filesystem &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Disk IO &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le mécanisme garde une réservation par couple hôte/service. Une première notification de problème passe, puis les répétitions sont bloquées pendant 24 heures glissantes. Si le problème reste actif, Checkmk envoie un rappel quotidien.&lt;/p&gt;
&lt;p&gt;Les retours à l’état normal restent présents dans l’historique Checkmk, mais ne génèrent pas de nouvelle notification pour ces trois familles de services. Je préfère ouvrir l’interface et retrouver toute la chronologie plutôt que d’être interrompu à chaque oscillation autour d’un seuil disque.&lt;/p&gt;
&lt;p&gt;Il y a un détail auquel je tenais : si l’envoi de la notification échoue, la réservation est libérée. Le système pourra donc retenter plus tard au lieu de considérer comme livré un message qui n’est jamais arrivé.&lt;/p&gt;
&lt;p&gt;Enfin, cette limitation ne concerne ni les hôtes, ni les autres services. Une machine inaccessible ou un contrôle métier en échec ne profite pas discrètement du même silence.&lt;/p&gt;
&lt;h2 id="un-seuil-doit-représenter-un-risque"&gt;Un seuil doit représenter un risque
&lt;/h2&gt;&lt;p&gt;La supervision DNS de Technitium m’a fourni un bon exemple de seuil techniquement valide, mais opérationnellement trop sensible.&lt;/p&gt;
&lt;p&gt;Je contrôle deux choses depuis le serveur Checkmk :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;une résolution DNS autoritative en UDP, avec vérification de la réponse ;&lt;/li&gt;
&lt;li&gt;l’ouverture du transport DNS sur le port TCP 53.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Au départ, les deux contrôles avertissaient au-dessus de 500 millisecondes. J’ai conservé ce seuil pour l’ouverture TCP, mais porté l’avertissement UDP à une seconde. L’état critique reste fixé à 1,5 seconde et le timeout à trois secondes.&lt;/p&gt;
&lt;p&gt;Pourquoi séparer les deux ? Une connexion TCP sur le réseau interne doit rester très rapide. Une requête DNS complète traverse davantage d’étapes et peut subir une variation ponctuelle sans que le service soit dégradé.&lt;/p&gt;
&lt;p&gt;Le 29 juillet 2026, ma vérification en lecture seule remontait :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th style="text-align: right"&gt;État observé&lt;/th&gt;
&lt;th style="text-align: right"&gt;Temps de réponse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;résolution DNS Technitium en UDP&lt;/td&gt;
&lt;td style="text-align: right"&gt;OK&lt;/td&gt;
&lt;td style="text-align: right"&gt;65 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transport DNS Technitium en TCP&lt;/td&gt;
&lt;td style="text-align: right"&gt;OK&lt;/td&gt;
&lt;td style="text-align: right"&gt;7 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ce sont des mesures prises à un instant donné, pas une promesse de performance. Elles confirment surtout que les contrôles interrogent réellement le service et exposent leur métrique après la modification.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/checkmk-technitium-dns.png"
width="2209"
height="1355"
srcset="https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/checkmk-technitium-dns_hu_43c66d4b85749bcc.png 480w, https://blog.homeblack.fr/p/reduire-bruit-alertes-checkmk/checkmk-technitium-dns_hu_4d9627a493fa113b.png 1024w"
loading="lazy"
alt="La sonde DNS UDP de Technitium dans Checkmk, avec ses seuils d’avertissement et critique"
class="gallery-image"
data-flex-grow="163"
data-flex-basis="391px"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;La mesure UDP reste très en dessous du seuil d’avertissement fixé à une seconde ; le seuil critique apparaît à 1,5 seconde.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="ce-que-je-refuse-de-faire-taire"&gt;Ce que je refuse de faire taire
&lt;/h2&gt;&lt;p&gt;Réduire le bruit n’a d’intérêt que si les vrais problèmes restent bruyants.&lt;/p&gt;
&lt;p&gt;Les avertissements critiques NVMe et les erreurs d’intégrité continuent donc d’alerter dès leur première occurrence. Les filesystems restent surveillés avec leurs seuils normaux. Lorsque le disque du runner manque réellement de place, la réponse attendue est un nettoyage ou une augmentation du volume, pas un seuil plus généreux.&lt;/p&gt;
&lt;p&gt;Même logique pour les TAP : j’ignore les interfaces éphémères, mais pas les uplinks. Pour la mémoire du runner, j’ajoute une confirmation, mais je ne supprime pas le contrôle. Pour le DNS, je détends seulement le seuil UDP ; le contrôle TCP conserve sa valeur plus stricte.&lt;/p&gt;
&lt;p&gt;Je me sers désormais de cette règle simple :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Si l’alerte représente un problème mesuré, je la garde. Si elle décrit un comportement attendu, je corrige son modèle.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="des-règles-versionnées-plutôt-que-des-clics-oubliés"&gt;Des règles versionnées plutôt que des clics oubliés
&lt;/h2&gt;&lt;p&gt;Tous ces réglages sont décrits dans le dépôt d’infrastructure et appliqués par un helper qui utilise l’API REST de Checkmk. Cela me permet de relire une exception, de comprendre pourquoi elle existe et de la tester comme le reste du code.&lt;/p&gt;
&lt;p&gt;Pour appliquer les règles de calibration :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 ansible/scripts/checkmk-rest-api.py calibrate-rules
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Je rejoue ensuite la commande sans activation :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 ansible/scripts/checkmk-rest-api.py calibrate-rules --no-activate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La seconde exécution doit annoncer toutes les règles gérées comme inchangées. Pour les modifications de découverte, notamment les interfaces Proxmox, je relance aussi explicitement la découverte des services concernés :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 ansible/scripts/checkmk-rest-api.py discover &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --host pve-dell &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --host pve-forum &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --host pve-fuji
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les tests automatisés vérifient également la portée des règles : motif exact des TAP, deux tentatives pour la mémoire du runner et intervalle de 1 440 minutes pour les problèmes disque persistants.&lt;/p&gt;
&lt;p&gt;Enfin, je contrôle l’état effectif avec Livestatus, directement sur le serveur Checkmk. Lors de la dernière vérification, le service &lt;code&gt;Memory&lt;/code&gt; du runner était bien configuré avec deux tentatives maximales. Les deux boucles mail et les contrôles DNS étaient en état &lt;code&gt;OK&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cette dernière étape est indispensable. Une configuration correcte dans Git ne prouve pas encore que Checkmk l’a activée ni qu’elle s’applique au bon service.&lt;/p&gt;
&lt;h2 id="ce-que-je-retiens"&gt;Ce que je retiens
&lt;/h2&gt;&lt;p&gt;Je pensais au départ qu’une bonne supervision devait tout remonter le plus vite possible. Je la vois maintenant comme un système de décisions : que faut-il conserver dans l’historique, que faut-il confirmer et qu’est-ce qui mérite réellement une interruption ?&lt;/p&gt;
&lt;p&gt;Les quatre leviers ne sont pas interchangeables :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ignorer précisément une ressource éphémère ;&lt;/li&gt;
&lt;li&gt;confirmer un état transitoire avec une nouvelle tentative ;&lt;/li&gt;
&lt;li&gt;espacer le rappel d’un problème déjà connu ;&lt;/li&gt;
&lt;li&gt;adapter un seuil à la mesure réellement effectuée.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je garde volontairement le canal de réception en dehors de cet article. Je raconterai prochainement comment j’achemine ces alertes, où je les reçois et comment je décide lesquelles ont réellement le droit de m’interrompre.&lt;/p&gt;
&lt;p&gt;Depuis cette reprise, l’écran vert m’intéresse moins. Ce qui compte, c’est que la prochaine notification inhabituelle récupère immédiatement mon attention — et qu’elle la mérite.&lt;/p&gt;</description></item></channel></rss>