<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sécurité on Zwindler's Reflection</title><link>https://blog.zwindler.fr/tags/s%C3%A9curit%C3%A9/</link><description>Recent content in Sécurité on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr-fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Sun, 15 Mar 2026 18:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/tags/s%C3%A9curit%C3%A9/index.xml" rel="self" type="application/rss+xml"/><item><title>Flannel et NetworkPolicies : comment ajouter le support avec Cilium en CNI chaining</title><link>https://blog.zwindler.fr/2026/03/15/flannel-networkpolicies-cilium-cni-chaining/</link><pubDate>Sun, 15 Mar 2026 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/03/15/flannel-networkpolicies-cilium-cni-chaining/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/03/flannel-cilium-chaining.webp" alt="Featured image of post Flannel et NetworkPolicies : comment ajouter le support avec Cilium en CNI chaining" /&gt;&lt;h2 id="flannel-flannel-flannel"&gt;Flannel, flannel, flannel&amp;hellip;
&lt;/h2&gt;&lt;p&gt;Flannel est un CNI simple et populaire 😢.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est le CNI par défaut de k3s, celui que la moitié des tutos kubeadm utilisent, et on le retrouve aussi dans pas mal d&amp;rsquo;offres managées.&lt;/p&gt;
&lt;p&gt;OK, il est simple, il route les paquets entre les pods, il supporte VXLAN et WireGuard, il se configure en 2 minutes. Que demander de plus ?&lt;/p&gt;
&lt;p&gt;Ben justement. Il y a un truc que flannel ne fait &lt;strong&gt;pas&lt;/strong&gt; : les NetworkPolicies. (Et c&amp;rsquo;est très grave).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Flannel is focused on networking. For network policy, other projects such as Calico can be used.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;[Edit]&lt;/strong&gt; Contrairement à ce que j&amp;rsquo;écris ici, flannel supporte en fait les NetworkPolicies depuis au moins 2 ans, via l&amp;rsquo;implémentation de référence &lt;a class="link" href="https://github.com/kubernetes-sigs/kube-network-policies" target="_blank" rel="noopener"
&gt;kube-network-policies&lt;/a&gt; du projet Kubernetes. L&amp;rsquo;option n&amp;rsquo;est pas mise en avant dans le README et ce n&amp;rsquo;est probablement pas plus recommandé en production, mais elle existe. Voir la &lt;a class="link" href="https://github.com/flannel-io/flannel/blob/master/Documentation/netpol.md" target="_blank" rel="noopener"
&gt;documentation officielle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et le piège, c&amp;rsquo;est que si vous n&amp;rsquo;avez pas lu cette petite ligne dans le &lt;strong&gt;README.md&lt;/strong&gt;, rien ne vous le dit explicitement.&lt;/p&gt;
&lt;p&gt;Vous pouvez parfaitement créer des objets &lt;code&gt;NetworkPolicy&lt;/code&gt; dans votre cluster, &lt;code&gt;kubectl apply&lt;/code&gt; ne bronchera pas, &lt;code&gt;kubectl get netpol&lt;/code&gt; vous les listera gentiment. Sauf que&amp;hellip; elles ne sont pas enforced. Le trafic passe quand même. Votre deny-all ne deny rien du tout.&lt;/p&gt;
&lt;h2 id="on-vérifie-pour-être-bien-sûr"&gt;On vérifie pour être bien sûr
&lt;/h2&gt;&lt;p&gt;Avant de résoudre quoi que ce soit, vérifions que le problème existe. En partant du prérequis qu&amp;rsquo;on a un cluster Kubernetes qui a flannel comme CNI et que tout est fonctionnel, on va déployer deux pods dans deux namespaces différents : un client (curl) et un serveur (nginx).&lt;/p&gt;
&lt;p&gt;On vérifie que le client peut joindre le serveur :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; -n netpol-test-a client -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -s --max-time &lt;span class="m"&gt;5&lt;/span&gt; http://server.netpol-test-b.svc.cluster.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On obtient la page d&amp;rsquo;accueil nginx. Jusque-là, tout est normal. Maintenant, on applique une NetworkPolicy &lt;em&gt;deny-all&lt;/em&gt; sur le namespace du serveur :&lt;/p&gt;
&lt;div class="highlight"&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="c"&gt;# 01-deny-all-ingress.yaml&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;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;networking.k8s.io/v1&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;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;NetworkPolicy&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;metadata&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;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;deny-all-ingress&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;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;netpol-test-b&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;spec&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;podSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;policyTypes&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;Ingress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&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;kubectl apply -f 01-deny-all-ingress.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et on re-teste :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; -n netpol-test-a client -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -s --max-time &lt;span class="m"&gt;5&lt;/span&gt; http://server.netpol-test-b.svc.cluster.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Résultat : la page nginx s&amp;rsquo;affiche toujours.&lt;/strong&gt; La NetworkPolicy est bien créée (&lt;code&gt;kubectl get netpol -n netpol-test-b&lt;/code&gt; la montre), mais elle n&amp;rsquo;est pas enforced. Le trafic passe comme si de rien n&amp;rsquo;était.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est le comportement attendu avec flannel (par défaut). Flannel ne fait que du routage L3 (overlay VXLAN ou WireGuard). Il n&amp;rsquo;implémente pas de contrôleur NetworkPolicy. Les objets existent dans etcd, mais personne ne les traduit en règles de filtrage.&lt;/p&gt;
&lt;p&gt;On nettoie la policy avant de passer à la suite :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl delete -f 01-deny-all-ingress.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="les-alternatives-pour-ajouter-le-support"&gt;Les alternatives pour ajouter le support
&lt;/h2&gt;&lt;p&gt;Pendant longtemps j&amp;rsquo;ai pensé que c&amp;rsquo;était une fatalité. Je pense toujours que n&amp;rsquo;est pas un bon choix pour n&amp;rsquo;importe quelle production.&lt;/p&gt;
&lt;p&gt;MAIS récemment, j&amp;rsquo;ai découvert qu&amp;rsquo;il était possible de chaîner les CNI au sein d&amp;rsquo;un même cluster, et ainsi d&amp;rsquo;avoir un CNI qui gère la majorité des tâches (ici Flannel) et un autre qui se charge d&amp;rsquo;autres tâches, comme par exemple enforcer des Netpols.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est d&amp;rsquo;ailleurs le principe de Canal, que je connaissais de nom mais que je n&amp;rsquo;avais jamais exploré. En fait, c&amp;rsquo;est ni plus ni moins qu&amp;rsquo;un manifeste qui déploie Flannel comme CNI principal avec Calico pour l&amp;rsquo;enforcing des NetOK fine, it&amp;rsquo;s truepols !&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Canal was the name of Tigera and CoreOS’s project to integrate Calico and flannel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note : &lt;a class="link" href="https://github.com/projectcalico/canal?tab=readme-ov-file" target="_blank" rel="noopener"
&gt;le projet GitHub a été archivé en octobre 2025&lt;/a&gt; mais en théorie ça devrait encore fonctionner, si on trouve la doc correcte (pas trouvé, les liens sont KO, mais j&amp;rsquo;ai pas vraiment cherché non plus).&lt;/p&gt;
&lt;p&gt;Vous avez donc compris le principe, on va ajouter un composant qui va &lt;strong&gt;watch&lt;/strong&gt; les objets NetworkPolicy et les traduire en règles de filtrage effectives (iptables, eBPF, nftables&amp;hellip;), &lt;strong&gt;sans toucher au flannel existant&lt;/strong&gt;. C&amp;rsquo;est ce qu&amp;rsquo;on appelle le &amp;ldquo;CNI chaining&amp;rdquo; ou le mode &amp;ldquo;policy-only&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Il existe plusieurs solutions :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Calico (Canal)&lt;/strong&gt;, Historiquement, la combinaison flannel + Calico s&amp;rsquo;appelle &amp;ldquo;Canal&amp;rdquo;, dont je viens de parler. &lt;strong&gt;Mais&lt;/strong&gt; le manifeste Canal officiel embarque son propre flannel dans le même DaemonSet que calico-node. Si votre flannel est déjà installé et géré (par vous, par un opérateur, par un provider&amp;hellip;), vous ne voulez probablement pas le remplacer. Et l&amp;rsquo;opérateur Tigera (la méthode Helm &amp;ldquo;officielle&amp;rdquo;) ne supporte pas non plus le déploiement en mode policy-only sur un flannel existant. Bref, c&amp;rsquo;est faisable mais ça nécessite un peu d&amp;rsquo;effort. Flemme.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;kube-router&lt;/strong&gt;, kube-router peut fonctionner en mode firewall-only (&lt;code&gt;--run-firewall=true&lt;/code&gt;) et n&amp;rsquo;a besoin que d&amp;rsquo;iptables/ipset. C&amp;rsquo;est d&amp;rsquo;ailleurs ce que k3s utilise par défaut pour les NetworkPolicies. C&amp;rsquo;est la solution la plus légère (a priori ~50 Mo de RAM par node). Vérifiez que votre kernel dispose du module &lt;code&gt;ip_set&lt;/code&gt;, sinon ça ne fonctionnera pas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cilium en mode CNI chaining&lt;/strong&gt;, C&amp;rsquo;est la solution que j&amp;rsquo;ai retenue et qu&amp;rsquo;on va détailler. Cilium s&amp;rsquo;attache aux interfaces veth créées par flannel et ajoute ses programmes eBPF pour le policy enforcement. Pas de dépendance à iptables ou ipset, et en bonus on récupère Hubble pour l&amp;rsquo;observabilité réseau.&lt;/p&gt;
&lt;h2 id="installer-cilium-en-mode-cni-chaining"&gt;Installer Cilium en mode CNI chaining
&lt;/h2&gt;&lt;h3 id="prérequis"&gt;Prérequis
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Un cluster Kubernetes fonctionnel avec flannel&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm&lt;/code&gt; v3+&lt;/li&gt;
&lt;li&gt;Un kernel &amp;gt;= 4.19 (idéalement &amp;gt;= 5.10 pour toutes les features eBPF)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="récupérer-la-configuration-cni-de-flannel"&gt;Récupérer la configuration CNI de flannel
&lt;/h3&gt;&lt;p&gt;Cilium en mode chaining doit connaître la configuration CNI existante. On va la récupérer depuis un node :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl debug node/&lt;span class="k"&gt;$(&lt;/span&gt;kubectl get nodes -o &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.items[0].metadata.name}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -it --image&lt;span class="o"&gt;=&lt;/span&gt;busybox -- cat /host/etc/cni/net.d/10-flannel.conflist
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sur mon cluster, ça donne :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&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="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cbr0&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="nt"&gt;&amp;#34;cniVersion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.3.1&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="nt"&gt;&amp;#34;plugins&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="p"&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;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;flannel&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="nt"&gt;&amp;#34;delegate&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="nt"&gt;&amp;#34;hairpinMode&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="nt"&gt;&amp;#34;isDefaultGateway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;portmap&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="nt"&gt;&amp;#34;capabilities&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="nt"&gt;&amp;#34;portMappings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;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;/div&gt;&lt;p&gt;&lt;strong&gt;Notez le champ &lt;code&gt;name&lt;/code&gt;&lt;/strong&gt; (ici &lt;code&gt;cbr0&lt;/code&gt;). On en aura besoin.&lt;/p&gt;
&lt;h3 id="créer-le-configmap-de-chaining"&gt;Créer le ConfigMap de chaining
&lt;/h3&gt;&lt;p&gt;On va créer un ConfigMap qui reprend la config flannel et y ajoute le plugin &lt;code&gt;cilium-cni&lt;/code&gt; en chaining :&lt;/p&gt;
&lt;div class="highlight"&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;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&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;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ConfigMap&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;metadata&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;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;cni-configuration&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;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-system&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;data&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;cni-config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|-&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;name&amp;#34;: &amp;#34;cbr0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;cniVersion&amp;#34;: &amp;#34;0.3.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;plugins&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;type&amp;#34;: &amp;#34;flannel&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;delegate&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;hairpinMode&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;isDefaultGateway&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;type&amp;#34;: &amp;#34;portmap&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;capabilities&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;portMappings&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;type&amp;#34;: &amp;#34;cilium-cni&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;chaining-mode&amp;#34;: &amp;#34;generic-veth&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Attention&lt;/strong&gt; : le champ &lt;code&gt;name&lt;/code&gt; doit correspondre à celui de votre &lt;em&gt;conflist&lt;/em&gt; flannel. Si le vôtre s&amp;rsquo;appelle &lt;code&gt;cni0&lt;/code&gt; ou autre chose, adaptez.&lt;/p&gt;
&lt;p&gt;Avant d&amp;rsquo;appliquer, vérifiez aussi que votre CNI utilise bien des interfaces &lt;strong&gt;veth&lt;/strong&gt; (c&amp;rsquo;est le cas par défaut avec flannel, mais mieux vaut s&amp;rsquo;en assurer). Depuis un node :&lt;/p&gt;
&lt;div class="highlight"&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;ip -d link &lt;span class="p"&gt;|&lt;/span&gt; grep veth
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Vous devriez voir des interfaces de type &lt;code&gt;veth&lt;/code&gt; correspondant à vos pods, par exemple :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;103: lxcb3901b7f9c02@if102: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; ...
veth addrgenmode eui64 numtxqueues 1 numrxqueues 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si c&amp;rsquo;est bien le cas, le mode &lt;code&gt;generic-veth&lt;/code&gt; de Cilium fonctionnera. On applique :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl apply -f cilium-cni-configmap.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="installer-cilium-via-helm"&gt;Installer Cilium via Helm
&lt;/h3&gt;&lt;div class="highlight"&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;helm repo add cilium https://helm.cilium.io/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Voici les values pour le mode chaining :&lt;/p&gt;
&lt;div class="highlight"&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="c"&gt;# cilium-values.yaml&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;cni&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;chainingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;generic-veth&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;customConf&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="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;configMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cni-configuration&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;install&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="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;routingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;native&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;enableIPv4Masquerade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;enableIPv6Masquerade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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="nt"&gt;hubble&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;enabled&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="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relay&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;enabled&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="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ui&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;enabled&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Les points importants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cni.chainingMode: generic-veth&lt;/code&gt;, c&amp;rsquo;est le mode chaining, Cilium s&amp;rsquo;attache aux interfaces veth existantes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cni.customConf: true&lt;/code&gt; + &lt;code&gt;cni.configMap&lt;/code&gt;, on fournit notre propre config CNI&lt;/li&gt;
&lt;li&gt;&lt;code&gt;routingMode: native&lt;/code&gt;, flannel gère le routage, pas Cilium&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enableIPv4Masquerade: false&lt;/code&gt;, flannel gère le masquerading&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hubble.enabled: true&lt;/code&gt;, l&amp;rsquo;observabilité réseau, c&amp;rsquo;est le gros bonus de Cilium&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&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;helm install cilium cilium/cilium --version 1.19.1 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace kube-system &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -f cilium-values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On attend que tout soit prêt :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl rollout status daemonset/cilium -n kube-system --timeout&lt;span class="o"&gt;=&lt;/span&gt;120s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="vérification"&gt;Vérification
&lt;/h3&gt;&lt;div class="highlight"&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;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; -n kube-system ds/cilium -- cilium status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ce qui nous intéresse :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Kubernetes: Ok 1.35 (v1.35.0) [linux/amd64]
CNI Chaining: generic-veth
Cilium: Ok 1.19.1
Hubble: Ok
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;La ligne &lt;code&gt;CNI Chaining: generic-veth&lt;/code&gt; confirme que Cilium fonctionne en mode &lt;em&gt;chaining&lt;/em&gt; et ne remplace pas flannel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt; : les pods qui existaient avant l&amp;rsquo;installation de Cilium ne sont pas automatiquement gérés par Cilium. Il faut les redémarrer pour que Cilium attache ses programmes eBPF. Pensez à faire un &lt;code&gt;kubectl rollout restart&lt;/code&gt; de vos workloads de test (ou à les recréer).&lt;/p&gt;
&lt;h2 id="tester-les-networkpolicies"&gt;Tester les NetworkPolicies
&lt;/h2&gt;&lt;p&gt;C&amp;rsquo;est le moment de vérité. On re-applique notre deny-all :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl apply -f 01-deny-all-ingress.yaml
&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;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; -n netpol-test-a client -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -s --max-time &lt;span class="m"&gt;5&lt;/span&gt; http://server.netpol-test-b.svc.cluster.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Résultat : timeout&lt;/strong&gt; ! Cette fois, la NetworkPolicy est bien enforced. Le trafic est bloqué.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai enchaîné avec les autres scénarios classiques de NetworkPolicy, et tout fonctionne :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Allow ingress sélectif par namespace&lt;/strong&gt;, en ajoutant une policy qui autorise le trafic depuis &lt;code&gt;netpol-test-a&lt;/code&gt; uniquement, le curl passe depuis ce namespace mais reste bloqué depuis &lt;code&gt;default&lt;/code&gt;. L&amp;rsquo;isolation par namespace fonctionne.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deny-all egress&lt;/strong&gt;, en bloquant tout le trafic sortant du client, même la résolution DNS est bloquée (timeout immédiat).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Allow egress sélectif&lt;/strong&gt;, en autorisant uniquement le DNS (port 53) et le serveur (port 80 dans le namespace &lt;code&gt;netpol-test-b&lt;/code&gt;), le curl vers le serveur passe mais &lt;code&gt;curl http://example.com&lt;/code&gt; reste bloqué.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, ingress, egress, sélectif par namespace, tout marche comme attendu.&lt;/p&gt;
&lt;h2 id="bonus--hubble-lobservabilité-réseau"&gt;Bonus : Hubble, l&amp;rsquo;observabilité réseau
&lt;/h2&gt;&lt;p&gt;C&amp;rsquo;est pour moi le vrai atout de Cilium par rapport aux alternatives. Hubble permet de voir en temps réel les flux réseau et les verdicts de policy :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; -n kube-system ds/cilium -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hubble observe --namespace netpol-test-b --last &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;Mar 15 13:20:42.287: netpol-test-a/client:40066 (ID:9745) -&amp;gt;
netpol-test-b/server:80 (ID:22271)
policy-verdict:none ALLOWED (TCP Flags: SYN)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On voit le pod source, le pod destination, le port, l&amp;rsquo;identité Cilium, et le verdict de policy. Quand vous debuggez une NetworkPolicy qui ne se comporte pas comme prévu, c&amp;rsquo;est vraiment pratique.&lt;/p&gt;
&lt;h2 id="combien-ça-coûte-en-ressources-"&gt;Combien ça coûte en ressources ?
&lt;/h2&gt;&lt;p&gt;Sur mon cluster (3 nodes), voici ce que Cilium consomme juste après installation :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Composant&lt;/th&gt;
&lt;th&gt;Par node&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cilium agent&lt;/td&gt;
&lt;td&gt;oui (DaemonSet)&lt;/td&gt;
&lt;td&gt;~160 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cilium-envoy&lt;/td&gt;
&lt;td&gt;oui (DaemonSet)&lt;/td&gt;
&lt;td&gt;~22 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cilium-operator&lt;/td&gt;
&lt;td&gt;non (2 replicas)&lt;/td&gt;
&lt;td&gt;~42 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hubble-relay&lt;/td&gt;
&lt;td&gt;non (1 replica)&lt;/td&gt;
&lt;td&gt;~16 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hubble-ui&lt;/td&gt;
&lt;td&gt;non (1 replica)&lt;/td&gt;
&lt;td&gt;~21 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Soit environ &lt;strong&gt;180 Mo par node&lt;/strong&gt; pour l&amp;rsquo;agent + envoy. J&amp;rsquo;ai pas de point de comparaison par rapport à Calico ou kube-router, mais ça me semble acceptable, et le fait de pouvoir avoir une vision complète sur la totalité des flux avec Hubble justifie largement le surcoût (à mon avis).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Si jamais vous n&amp;rsquo;avez pas le choix et que vous devez composer avec flannel, et que vous voulez boucher le trou béant de sécurité que représente l&amp;rsquo;absence d&amp;rsquo;enforcement des Netpols, sachez donc qu&amp;rsquo;il est maintenant possible :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;d&amp;rsquo;activer l&amp;rsquo;ajout de l&amp;rsquo;implémentation officielle (même si je ne l&amp;rsquo;ai pas testée, ça doit marcher)&lt;/li&gt;
&lt;li&gt;de chaîner un autre CNI pour le faire&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;À défaut de l&amp;rsquo;avoir en CNI pour tout (tout bon cluster Kubernetes a Cilium comme CNI), Cilium en mode CNI chaining (generic-veth) est une solution plutôt sympa pour combler ce manque. Il ne touche pas au flannel existant, il s&amp;rsquo;y greffe. Et en bonus, vous récupérez Hubble pour l&amp;rsquo;observabilité réseau, ce qui est franchement appréciable.&lt;/p&gt;
&lt;p&gt;Have fun :)&lt;/p&gt;</description></item><item><title>De F à A+ sur HTTP Observatory : sécuriser les headers de mon blog Hugo</title><link>https://blog.zwindler.fr/2026/02/20/securite-headers-http-observatory-hugo/</link><pubDate>Sat, 21 Feb 2026 10:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/02/20/securite-headers-http-observatory-hugo/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/02/observatory-a-plus.webp" alt="Featured image of post De F à A+ sur HTTP Observatory : sécuriser les headers de mon blog Hugo" /&gt;&lt;p&gt;Après &lt;a class="link" href="https://blog.zwindler.fr/2026/02/19/optimisation-webperf-avif-precompression/" &gt;l&amp;rsquo;optimisation des performances du blog&lt;/a&gt; (AVIF, pré-compression), j&amp;rsquo;étais super content de moi. 🥳&lt;/p&gt;
&lt;p&gt;Mais c&amp;rsquo;était sans compter sur Antoine Caron, qui est venu (à très juste titre) me chatouiller sur un autre aspect que j&amp;rsquo;avais ignoré, le rapport Mozilla Observatory&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/02/antoine-mozilla-observatory.avif"
loading="lazy"
alt="https://bsky.app/profile/slashgear.dev/post/3mfc62ckw3s2g"
&gt;&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai donc lancé un scan &lt;a class="link" href="https://developer.mozilla.org/en-US/observatory" target="_blank" rel="noopener"
&gt;Mozilla HTTP Observatory&lt;/a&gt; sur mon blog et le résultat était : &lt;strong&gt;F&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Cool, je peux pas faire pire.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/02/observatory-initial.avif"
loading="lazy"
alt="Le scan initial : F avec 0/100"
&gt;&lt;/p&gt;
&lt;h2 id="le-déclic"&gt;Le déclic
&lt;/h2&gt;&lt;p&gt;En relisant &lt;a class="link" href="https://codeka.io/2026/02/20/optimiser-les-perfs-et-la-s%C3%A9curit%C3%A9-dun-site-hugo/" target="_blank" rel="noopener"
&gt;l&amp;rsquo;article de Julien sur codeka.io&lt;/a&gt;, qui a aussi parlé avec Antoine, a aussi un blog statique avec Hugo, MAIS qui lui avait fait le taf, j&amp;rsquo;ai réalisé que c&amp;rsquo;était à la fois &lt;strong&gt;important et peut-être pas si compliqué&lt;/strong&gt; à corriger.&lt;/p&gt;
&lt;p&gt;Julien y détaille les headers de sécurité à ajouter sur un site Hugo, avec des exemples pour Caddy. Moi je suis sur nginx, mais le principe est le même.&lt;/p&gt;
&lt;p&gt;Note : si ça vous intéresse, les liens des épisodes précédents sur l&amp;rsquo;infra du blog sont ici :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2019/12/24/ca-bouge-pas-mal-sur-le-blog/" &gt;Ça bouge pas mal sur le blog !&lt;/a&gt; (2019) - De 5s à 1s en virant Wordpress pour Hugo&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/01/15/ca-bouge-encore-sur-le-blog/" &gt;Ça bouge encore sur le blog&lt;/a&gt; (2025) - Nettoyage massif, retour auto-hébergé&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2023/08/01/automatiser-hugo-sans-github-action/" &gt;Automatiser son site Hugo sans Github Action ou Vercel&lt;/a&gt; - Le setup nginx + webhook actuel&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2026/02/19/optimisation-webperf-avif-precompression/" &gt;Optimisation webperf : AVIF et pré-compression&lt;/a&gt; - Le round précédent (focus sur les images)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ce-que-teste-http-observatory"&gt;Ce que teste HTTP Observatory
&lt;/h2&gt;&lt;p&gt;HTTP Observatory vérifie une série de headers HTTP que votre serveur devrait envoyer pour protéger vos visiteurs. Les principaux :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strict-Transport-Security (HSTS)&lt;/strong&gt; : force le navigateur à toujours utiliser HTTPS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content-Security-Policy (CSP)&lt;/strong&gt; : contrôle quelles ressources le navigateur peut charger (scripts, styles, images, iframes&amp;hellip;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Content-Type-Options&lt;/strong&gt; : empêche le navigateur de &amp;ldquo;deviner&amp;rdquo; le type MIME des fichiers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Referrer-Policy&lt;/strong&gt; : contrôle les informations de provenance envoyées aux sites tiers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Frame-Options&lt;/strong&gt; / &lt;strong&gt;frame-ancestors&lt;/strong&gt; : empêche l&amp;rsquo;inclusion de votre site dans une iframe (clickjacking)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sur un blog statique, on pourrait se dire que ce n&amp;rsquo;est pas critique. Pas de base de données, pas de sessions utilisateur, pas de formulaires sensibles. Mais ces headers protègent quand même contre des attaques réelles :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un &lt;strong&gt;script injecté&lt;/strong&gt; (XSS) pourrait rediriger vos lecteurs vers un site malveillant&lt;/li&gt;
&lt;li&gt;Sans HSTS, un attaquant sur un Wi-Fi public pourrait intercepter la connexion initiale en HTTP&lt;/li&gt;
&lt;li&gt;Sans &lt;code&gt;frame-ancestors&lt;/code&gt;, quelqu&amp;rsquo;un pourrait inclure le blog dans une iframe piégée&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, même pour un blog statique, ça peut valoir le coup de faire l&amp;rsquo;effort, a minima pour s&amp;rsquo;habituer aux bonnes pratiques.&lt;/p&gt;
&lt;h2 id="étape-1--les-headers-faciles"&gt;Étape 1 : les headers &amp;ldquo;faciles&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Le piège nginx : &lt;code&gt;add_header&lt;/code&gt; et l&amp;rsquo;héritage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;La première chose, c&amp;rsquo;est que je suis tombé dans un piège classique avec mon nginx. Je l&amp;rsquo;ignorais, mais les directives &lt;code&gt;add_header&lt;/code&gt; dans un bloc &lt;code&gt;location&lt;/code&gt; &lt;strong&gt;écrasent&lt;/strong&gt; (et ne complètent pas) celles du bloc &lt;code&gt;server&lt;/code&gt; parent.&lt;/p&gt;
&lt;p&gt;Ça veut dire que si vous avez un &lt;code&gt;add_header Cache-Control&lt;/code&gt; dans une &lt;code&gt;location&lt;/code&gt; (c&amp;rsquo;était mon cas), tous vos headers de sécurité définis au niveau &lt;code&gt;server&lt;/code&gt; &lt;strong&gt;disparaissent&lt;/strong&gt; pour cette location. J&amp;rsquo;ai pété un plomb pendant quelques minutes à reload / restart en boucle sans comprendre.&lt;/p&gt;
&lt;p&gt;La solution : créer un fichier snippet et l&amp;rsquo;inclure partout.&lt;/p&gt;
&lt;div class="highlight"&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;cat &amp;gt; /etc/nginx/snippets/security-headers.conf &lt;span class="s"&gt;&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;add_header Strict-Transport-Security &amp;#34;max-age=63072000; includeSubDomains; preload&amp;#34; always;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;add_header X-Content-Type-Options &amp;#34;nosniff&amp;#34; always;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;add_header Referrer-Policy &amp;#34;strict-origin-when-cross-origin&amp;#34; always;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;add_header Content-Security-Policy &amp;#34;...&amp;#34; always;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puis dans la config nginx :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&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="c1"&gt;# Headers de sécurité au niveau server
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/snippets/security-headers.conf&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Cache des assets statiques
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="s"&gt;\.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg|webp|avif)&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="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;1y&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="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;public,&lt;/span&gt; &lt;span class="s"&gt;immutable&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="c1"&gt;# OBLIGATOIRE : ré-inclure les headers ici !
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/snippets/security-headers.conf&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="s"&gt;\.html&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="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;1h&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="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;public,&lt;/span&gt; &lt;span class="s"&gt;must-revalidate&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="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/snippets/security-headers.conf&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;HSTS, X-Content-Type-Options, Referrer-Policy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ces trois-là sont les plus simples à ajouter et &amp;ldquo;ne cassent rien&amp;rdquo; :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HSTS&lt;/strong&gt; avec &lt;code&gt;max-age=63072000&lt;/code&gt; (2 ans) et &lt;code&gt;preload&lt;/code&gt; permet à terme (pas encore fait) de s&amp;rsquo;inscrire sur la &lt;a class="link" href="https://hstspreload.org/" target="_blank" rel="noopener"
&gt;HSTS preload list&lt;/a&gt;. Le navigateur refusera même la toute première connexion HTTP initiale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;nosniff&lt;/strong&gt; empêche les navigateurs d&amp;rsquo;interpréter un fichier texte comme du JavaScript.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;strict-origin-when-cross-origin&lt;/strong&gt; envoie l&amp;rsquo;URL complète en referrer pour les requêtes same-origin, mais seulement l&amp;rsquo;origine (domaine) pour les requêtes cross-origin.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="étape-2--content-security-policy-la-vraie-difficulté"&gt;Étape 2 : Content-Security-Policy (la vraie difficulté)
&lt;/h2&gt;&lt;p&gt;La CSP est un header puissant et a priori complexe. C&amp;rsquo;est lui qui rapporte le plus de points sur Observatory, mais c&amp;rsquo;est aussi lui qui peut tout casser si on va trop vite.&lt;/p&gt;
&lt;p&gt;Le principe : vous déclarez au navigateur &lt;strong&gt;exactement&lt;/strong&gt; quelles ressources votre site a le droit de charger (scripts, styles, images, iframes, fonts&amp;hellip;) et depuis quelles origines. Tout le reste est bloqué. C&amp;rsquo;est la meilleure défense contre le XSS, parce que même si un attaquant arrive à injecter un &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; dans votre HTML, le navigateur refusera de l&amp;rsquo;exécuter s&amp;rsquo;il n&amp;rsquo;est pas dans la liste autorisée.&lt;/p&gt;
&lt;p&gt;Le revers de la médaille : si vous oubliez une ressource légitime dans votre CSP, elle sera bloquée et votre site casse silencieusement : un script qui ne charge plus, une font qui disparaît, une iframe vide. Il faut donc faire un inventaire exhaustif &lt;em&gt;avant&lt;/em&gt; de verrouiller.&lt;/p&gt;
&lt;p&gt;Je me suis fait épauler par un LLM (Opus 4.6) pour les modifs à réaliser et on a fait plusieurs passes jusqu&amp;rsquo;à ce que tout fonctionne.&lt;/p&gt;
&lt;h3 id="inventaire-des-ressources-externes"&gt;Inventaire des ressources externes
&lt;/h3&gt;&lt;p&gt;Avant d&amp;rsquo;écrire la CSP, il faut faire l&amp;rsquo;inventaire de &lt;strong&gt;tout&lt;/strong&gt; ce que le site charge. Voilà une petite synthèse de mon pote &lt;em&gt;Claudio&lt;/em&gt; :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Ressource&lt;/th&gt;
&lt;th&gt;Origine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Script&lt;/td&gt;
&lt;td&gt;GoatCounter analytics&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://gc.zgo.at/count.js&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script&lt;/td&gt;
&lt;td&gt;Umami analytics&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://cloud.umami.is/script.js&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connect&lt;/td&gt;
&lt;td&gt;GoatCounter API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://blogzwindler.goatcounter.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connect&lt;/td&gt;
&lt;td&gt;Umami API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://api-gateway.umami.dev&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script&lt;/td&gt;
&lt;td&gt;De mon thème &amp;lsquo;stack&amp;rsquo; - vibrant.js (couleurs d&amp;rsquo;images)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://cdn.jsdelivr.net&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script&lt;/td&gt;
&lt;td&gt;De mon thème &amp;lsquo;stack&amp;rsquo; - PhotoSwipe (lightbox images)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://cdn.jsdelivr.net&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Style&lt;/td&gt;
&lt;td&gt;De mon thème &amp;lsquo;stack&amp;rsquo; - PhotoSwipe CSS (lightbox)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://cdn.jsdelivr.net&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Style&lt;/td&gt;
&lt;td&gt;De mon thème &amp;lsquo;stack&amp;rsquo; - CSS du thème&lt;/td&gt;
&lt;td&gt;local (&lt;code&gt;'self'&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Font&lt;/td&gt;
&lt;td&gt;Luciole&lt;/td&gt;
&lt;td&gt;locale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frame&lt;/td&gt;
&lt;td&gt;Widget Deezer (inclu dans un seul article 😖)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://widget.deezer.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;data: URIs (SVGs inline)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data:&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form&lt;/td&gt;
&lt;td&gt;Mailchimp (abonnement newsletter)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://zwindler.us15.list-manage.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="le-problème-des-scripts-danalytics-externes"&gt;Le problème des scripts d&amp;rsquo;analytics externes
&lt;/h3&gt;&lt;p&gt;Avec une CSP comme &lt;code&gt;script-src 'self' https://gc.zgo.at https://cloud.umami.is&lt;/code&gt;, je devais soit :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Faire confiance à ces CDN&lt;/strong&gt; -&amp;gt; si le CDN est compromis, le script malveillant s&amp;rsquo;exécute sur mon blog. Nope.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ajouter des hashes SRI&lt;/strong&gt; (Subresource Integrity). Problème : ces scripts peuvent changer côté serveur sans prévenir, ce qui casserait le hash.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;J&amp;rsquo;ai choisi une troisième voie : &lt;strong&gt;télécharger les scripts à chaque build&lt;/strong&gt; et les servir localement.&lt;/p&gt;
&lt;div class="highlight"&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;&lt;span class="c1"&gt;# Dans mon petit script blog_refresh.sh, avant le hugo --minify&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p static/js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sf --max-time &lt;span class="m"&gt;10&lt;/span&gt; https://gc.zgo.at/count.js -o static/js/count.js &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;WARN: failed to download GoatCounter script&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sf --max-time &lt;span class="m"&gt;10&lt;/span&gt; https://cloud.umami.is/script.js -o static/js/umami.js &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;WARN: failed to download Umami script&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et dans le template Hugo :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;data-goatcounter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://blogzwindler.goatcounter.com/count&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/js/count.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/js/umami.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-website-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;3d8f0ea9-...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Est-ce que c&amp;rsquo;est &amp;ldquo;tricher&amp;rdquo; ?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Une fois que j&amp;rsquo;avais fait la modif, je me suis demandé : est-ce qu&amp;rsquo;héberger les scripts localement à chaque build ne contourne pas l&amp;rsquo;esprit des vérifications d&amp;rsquo;Observatory ? Sur ce point précis j&amp;rsquo;ai un petit doute, n&amp;rsquo;étant pas expert sur ces points là. J&amp;rsquo;ai demandé à un LLM qui m&amp;rsquo;a dit que &lt;em&gt;non&lt;/em&gt;, mais je veux bien un avis tiers.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, ça semble être l&amp;rsquo;approche recommandée par &lt;a class="link" href="https://www.goatcounter.com/help/csp" target="_blank" rel="noopener"
&gt;la documentation GoatCounter&lt;/a&gt; pour les sites avec une CSP stricte.&lt;/p&gt;
&lt;h3 id="supprimer-unsafe-inline-de-script-src"&gt;Supprimer &lt;code&gt;unsafe-inline&lt;/code&gt; de &lt;code&gt;script-src&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;Observatory pénalise fortement &lt;code&gt;'unsafe-inline'&lt;/code&gt; dans &lt;code&gt;script-src&lt;/code&gt;. Et c&amp;rsquo;est normal, car c&amp;rsquo;est la porte ouverte au XSS : n&amp;rsquo;importe quel script injecté dans le HTML s&amp;rsquo;exécute.&lt;/p&gt;
&lt;p&gt;Le problème : mon thème Hugo (&lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
&gt;hugo-theme-stack&lt;/a&gt;) générait &lt;strong&gt;4 scripts inline&lt;/strong&gt; :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Color scheme init&lt;/strong&gt; — lit le localStorage pour appliquer le thème clair/sombre avant le premier rendu&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Color scheme detection&lt;/strong&gt; — détecte la préférence système (&lt;code&gt;prefers-color-scheme: dark&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Language switcher&lt;/strong&gt; — le sélecteur de langue &lt;code&gt;&amp;lt;select onchange=&amp;quot;window.location.href=this.selectedOptions[0].value&amp;quot;&amp;gt;&lt;/code&gt; dans la sidebar, qui est un attribut d&amp;rsquo;événement inline (&lt;code&gt;script-src-attr&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Font loader&lt;/strong&gt; — charge la CSS de &lt;a class="link" href="https://luciole-vision.com/" target="_blank" rel="noopener"
&gt;la font Luciole&lt;/a&gt; dynamiquement&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Luciole: A typeface for visual impairment&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pour chacun, j&amp;rsquo;ai extrait le code dans un fichier &lt;code&gt;.js&lt;/code&gt; externe dans &lt;code&gt;static/js/&lt;/code&gt; et remplacé le script inline par une balise &lt;code&gt;&amp;lt;script src=&amp;quot;...&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Par exemple, pour le color scheme, le thème avait dans &lt;code&gt;layouts/partials/head/colorScheme.html&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;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 class="kd"&gt;function&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="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colorSchemeKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;StackColorScheme&amp;#39;&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colorSchemeKey&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="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colorSchemeKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;auto&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="cm"&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;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Que j&amp;rsquo;ai remplacé par un &lt;a class="link" href="https://gohugo.io/templates/lookup-order/" target="_blank" rel="noopener"
&gt;override de template Hugo&lt;/a&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{- /* Override: external JS file instead of inline (CSP compliance) */ -}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/js/color-scheme.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Avec le contenu correspondant dans &lt;code&gt;static/js/color-scheme.js&lt;/code&gt;. Hugo permet de surcharger n&amp;rsquo;importe quel template du thème en plaçant un fichier au même chemin dans le dossier &lt;code&gt;layouts/&lt;/code&gt; du projet.&lt;/p&gt;
&lt;p&gt;Pour le &lt;strong&gt;language switcher&lt;/strong&gt;, même approche : j&amp;rsquo;ai surchargé &lt;code&gt;layouts/partials/sidebar/left.html&lt;/code&gt; pour retirer l&amp;rsquo;attribut &lt;code&gt;onchange&lt;/code&gt; du &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; et ajouté un &lt;code&gt;id=&amp;quot;language-select&amp;quot;&lt;/code&gt;, puis créé un fichier &lt;code&gt;static/js/language-switcher.js&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;select&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;language-select&amp;#39;&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;select&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="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;change&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedOptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&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;/div&gt;&lt;p&gt;&lt;strong&gt;Note importante :&lt;/strong&gt; il m&amp;rsquo;a fallu plusieurs allers-retours avec la console du navigateur (F12 -&amp;gt; Console) avant de trouver la totalité des violations CSP. Chaque correction en révélait de nouvelles, il fallait bien regarder tous les différents types de pages sur le blog (pas juste la page d&amp;rsquo;accueil et les posts), et certaines erreurs affichées dans la console venaient en fait d&amp;rsquo;&lt;strong&gt;extensions navigateur&lt;/strong&gt; (typiquement les gestionnaires de mots de passe comme Bitwarden, dont le &lt;code&gt;bootstrap-autofill-overlay.js&lt;/code&gt; génère des violations &lt;code&gt;style-src-elem&lt;/code&gt;) et non du site lui-même. Il faut donc bien distinguer les erreurs du site de celles injectées par les extensions.&lt;/p&gt;
&lt;h3 id="supprimer-unsafe-inline-de-style-src-la-suite"&gt;Supprimer &lt;code&gt;unsafe-inline&lt;/code&gt; de &lt;code&gt;style-src&lt;/code&gt;, la suite
&lt;/h3&gt;&lt;p&gt;Même logique pour les styles. J&amp;rsquo;avais quatre sources d&amp;rsquo;inline CSS :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Un bloc &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;&lt;/strong&gt; dans &lt;code&gt;custom.html&lt;/code&gt; — les CSS custom properties pour ma font Luciole&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Des attributs &lt;code&gt;style=&amp;quot;&amp;quot;&lt;/code&gt;&lt;/strong&gt; sur les badges de catégories — &lt;code&gt;background-color: #2a9d8f; color: #fff;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un attribut &lt;code&gt;style=&amp;quot;enable-background:...&amp;quot;&lt;/code&gt;&lt;/strong&gt; sur un SVG (export Adobe Illustrator)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Des attributs &lt;code&gt;style=&amp;quot;&amp;quot;&lt;/code&gt;&lt;/strong&gt; dans le formulaire Mailchimp du footer — &lt;code&gt;display:none&lt;/code&gt; et un positionnement off-screen pour le honeypot anti-spam&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Le &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; bloc&lt;/strong&gt; : déplacé dans &lt;code&gt;assets/scss/custom.scss&lt;/code&gt;, le fichier de personnalisation SCSS prévu par le thème.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-scss" data-lang="scss"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/* Font family CSS custom properties */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;:root&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="na"&gt;--article-font-family&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Luciole&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="na"&gt;--base-font-family&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Luciole&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="cm"&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;/div&gt;&lt;p&gt;&lt;strong&gt;Les badges de catégories&lt;/strong&gt; : toutes mes catégories utilisent les mêmes couleurs (#2a9d8f / #fff), définies dans le front matter de chaque &lt;code&gt;_index.md&lt;/code&gt;. Le thème les injectait en &lt;code&gt;style=&amp;quot;&amp;quot;&lt;/code&gt; via le template &lt;code&gt;article/components/details.html&lt;/code&gt;. J&amp;rsquo;ai surchargé ce template pour retirer le &lt;code&gt;style&lt;/code&gt; inline et ajouté une règle CSS dans &lt;code&gt;custom.scss&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-scss" data-lang="scss"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nc"&gt;.article-category&lt;/span&gt; &lt;span class="nt"&gt;a&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="na"&gt;background-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#2a9d8f&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="na"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#fff&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Le SVG&lt;/strong&gt; : j&amp;rsquo;avais un SVG que j&amp;rsquo;avais ajouté à la main (un petit Robot mignon pour mon &amp;ldquo;&lt;a class="link" href="https://blog.zwindler.fr/ai-manifesto/" target="_blank" rel="noopener"
&gt;AI Manifesto&lt;/a&gt;&amp;rdquo;) avec &lt;code&gt;enable-background&lt;/code&gt;, qui est un attribut SVG 1.1 déprécié. Je l&amp;rsquo;ai simplement supprimé du fichier SVG source, sans impact visuel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Le formulaire Mailchimp&lt;/strong&gt; : le code HTML Mailchimp utilise &lt;code&gt;style=&amp;quot;display:none&amp;quot;&lt;/code&gt; pour masquer les divs de feedback et &lt;code&gt;style=&amp;quot;position: absolute; left: -5000px;&amp;quot;&lt;/code&gt; pour le champ honeypot (pour les robots qui s&amp;rsquo;inscrivent à ma newsletter 🤔). J&amp;rsquo;ai remplacé tout ça par des classes CSS dans &lt;code&gt;custom.scss&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-scss" data-lang="scss"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;#mce-error-response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;#mce-success-response&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="na"&gt;display&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="ni"&gt;none&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nc"&gt;.mce-honeypot&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="na"&gt;position&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="ni"&gt;absolute&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="na"&gt;left&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="kt"&gt;px&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="la-csp-finale"&gt;La CSP finale
&lt;/h3&gt;&lt;p&gt;Après tous ces changements, plus aucun script ni style inline sur le site. La CSP peut donc être stricte :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;default-src &amp;#39;none&amp;#39;;
script-src &amp;#39;self&amp;#39; https://cdn.jsdelivr.net;
style-src &amp;#39;self&amp;#39; https://cdn.jsdelivr.net;
img-src &amp;#39;self&amp;#39; data:;
connect-src &amp;#39;self&amp;#39; https://blogzwindler.goatcounter.com https://api-gateway.umami.dev https://cdn.jsdelivr.net;
font-src &amp;#39;self&amp;#39;;
frame-src https://widget.deezer.com;
frame-ancestors &amp;#39;none&amp;#39;;
base-uri &amp;#39;self&amp;#39;;
form-action &amp;#39;self&amp;#39; https://zwindler.us15.list-manage.com;
manifest-src &amp;#39;self&amp;#39;;
media-src &amp;#39;self&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Points notables :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;default-src 'none'&lt;/code&gt;&lt;/strong&gt; : par défaut, rien n&amp;rsquo;est autorisé. Chaque type de ressource doit être explicitement listé. C&amp;rsquo;est la politique la plus restrictive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pas de &lt;code&gt;unsafe-inline&lt;/code&gt;&lt;/strong&gt; nulle part : ni dans &lt;code&gt;script-src&lt;/code&gt;, ni dans &lt;code&gt;style-src&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;frame-ancestors 'none'&lt;/code&gt;&lt;/strong&gt; remplace &lt;code&gt;X-Frame-Options: DENY&lt;/code&gt; (plus moderne)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cdn.jsdelivr.net&lt;/code&gt;&lt;/strong&gt; dans &lt;code&gt;script-src&lt;/code&gt;, &lt;code&gt;style-src&lt;/code&gt; ET &lt;code&gt;connect-src&lt;/code&gt; : le thème charge &lt;code&gt;vibrant.js&lt;/code&gt; (JS avec SRI), les CSS de PhotoSwipe (lightbox d&amp;rsquo;images) et les source maps associées depuis ce CDN&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;form-action&lt;/code&gt;&lt;/strong&gt; autorise Mailchimp pour le formulaire d&amp;rsquo;abonnement dans le footer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Au final, de nombreux morceaux du thème de mon blog ont du être réécrit / overridés. Je suis un peu gêné d&amp;rsquo;avoir du en arriver là, mais c&amp;rsquo;était le prix à payer pour une note maximale 😂.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/02/rework-theme.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="nettoyage-bonus--mailchimp"&gt;Nettoyage bonus : Mailchimp
&lt;/h2&gt;&lt;p&gt;En faisant l&amp;rsquo;inventaire des ressources externes, j&amp;rsquo;ai découvert un vieux widget Mailchimp dans la sidebar qui chargeait du CSS et du JavaScript depuis &lt;code&gt;chimpstatic.com&lt;/code&gt;. Je ne l&amp;rsquo;utilisais plus : supprimé. Le formulaire d&amp;rsquo;abonnement dans le footer des articles a été gardé, mais nettoyé de ses &lt;code&gt;style=&amp;quot;&amp;quot;&lt;/code&gt; inline (voir plus haut).&lt;/p&gt;
&lt;h2 id="résultat-final"&gt;Résultat final
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/02/observatory-a-plus.avif"
loading="lazy"
alt="A&amp;#43; sur HTTP Observatory"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;De F (0/100) à A+ (125/100).&lt;/strong&gt; Tous les tests sont verts.&lt;/p&gt;
&lt;p&gt;Un grand merci à Antoine pour sa patience avec les débutants naïfs comme moi 😂 et ses super talks !&lt;/p&gt;</description></item><item><title>Touraine Tech 2026 - Récap du jour 2</title><link>https://blog.zwindler.fr/2026/02/13/recap-touraine-tech-2026-jour2/</link><pubDate>Fri, 13 Feb 2026 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/02/13/recap-touraine-tech-2026-jour2/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/02/tnt26-entree2.webp" alt="Featured image of post Touraine Tech 2026 - Récap du jour 2" /&gt;&lt;h2 id="jour-2---cest-reparti-"&gt;Jour 2 - C&amp;rsquo;est reparti !
&lt;/h2&gt;&lt;p&gt;Me voici dans le train du retour et je prend un peu de temps pour mettre en forme mes notes de la journée. C&amp;rsquo;est parti pour le récap du jour 2 de Touraine Tech 2026 !&lt;/p&gt;
&lt;p&gt;Après une soirée speakers très sympa dans un bar à jeux, je me suis levé un poil trop tard et j&amp;rsquo;ai loupé la keynote&amp;hellip; Dommage 😅.&lt;/p&gt;
&lt;h2 id="lorsquun-simple-bug-réseau-vous-emmène-dans-les-entrailles-de-linux"&gt;Lorsqu&amp;rsquo;un simple bug réseau vous emmène dans les entrailles de Linux
&lt;/h2&gt;&lt;p&gt;Florian Forestier nous a raconté une histoire de debugging &lt;strong&gt;épique&lt;/strong&gt;.&lt;/p&gt;
&lt;img src="https://blog.zwindler.fr/2026/02/tnt26-kernel.avif"
alt="Talk debugging kernel Linux"
loading="lazy"
decoding="async"&gt;
&lt;p&gt;Le contexte : une équipe de 3 devs en mode &amp;ldquo;You build it, you run it&amp;rdquo;, avec une stack Golang/Svelte. Tout va bien, quand soudain arrive un manager qui dit &amp;ldquo;ça marche PAS&amp;rdquo;. La PKI est en panne, sur du hardware custom&amp;hellip; provenant de matériel reconditionné tchèque (pour la modique somme de 200k€ tout de même).&lt;/p&gt;
&lt;p&gt;Le matériel incriminé : des DL380 Gen 10 avec des cartes QLogic FastlinQ 45000. Il y en a 8, et aucune ne fonctionne.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;enquête commence :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les cartes sont visibles dans le BIOS ✓&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lspci&lt;/code&gt; les détecte ✓&lt;/li&gt;
&lt;li&gt;Mais &lt;code&gt;ip link&lt;/code&gt; ne les voit pas ✗&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dmesg&lt;/code&gt; affiche des erreurs sur les drivers &lt;code&gt;qed&lt;/code&gt;/&lt;code&gt;qede&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Testé avec Ubuntu, CentOS, Alpine Linux -&amp;gt; KO partout. Impossible d&amp;rsquo;avoir des drivers pour des kernels récents compatibles avec Proxmox.&lt;/p&gt;
&lt;p&gt;Après une semaine d&amp;rsquo;échecs collectifs, Florian a une &amp;ldquo;idée à la con&amp;rdquo; : installer une vieille Ubuntu 18.04. Moi ça me fait rire, parce que j&amp;rsquo;aurais commencé par là ! Quand on a du vieux matériel, on teste avec un OS contemporain du matériel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bingo !&lt;/strong&gt; Ça fonctionne.&lt;/p&gt;
&lt;p&gt;À partir de là, investigation par dichotomie : Debian 12 avec le kernel 4.15 de Ubuntu -&amp;gt; OK. Bug présent en 4.17, absent en 4.16, puis rechercher le commit dans les modifications sur les pilotes qed/qede.&lt;/p&gt;
&lt;p&gt;La solution : un code &amp;ldquo;non supporté&amp;rdquo; pas pris en compte dans une fonction ajoutée en 4.16. &lt;strong&gt;3 lignes de fix.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour reverser le correctif dans le kernel Linux, il a fallu écrire sur la bonne mailing-list, avec ses 8 pages de règles. 3 semaines entre l&amp;rsquo;envoi du mail et le merge (habituellement 1-2 semaines).&lt;/p&gt;
&lt;p&gt;Un beau REX de debugging bas niveau ! J&amp;rsquo;étais vraiment content de l&amp;rsquo;avoir vu :).&lt;/p&gt;
&lt;h2 id="lampoule-qui-en-savait-trop--voyage-au-cœur-de-la-sécurité-des-objets-connectés"&gt;L&amp;rsquo;ampoule qui en savait trop : voyage au cœur de la sécurité des objets connectés
&lt;/h2&gt;&lt;p&gt;Paul Pinault nous a plongé dans le monde de la sécurité IoT.&lt;/p&gt;
&lt;img src="https://blog.zwindler.fr/2026/02/tnt26-iot.avif"
alt="Talk sécurité IoT"
loading="lazy"
decoding="async"&gt;
&lt;p&gt;Quelques chiffres pour poser le contexte : &lt;strong&gt;300 millions d&amp;rsquo;ampoules connectées&lt;/strong&gt; vendues chaque année, dont 60% en WiFi (vs Zigbee/ZWave). Et côté réglementation, ça bouge :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RED&lt;/strong&gt; (août 2025) : garantie sécurité cyber EN18031, mises à jour possibles et sécurisées, communications chiffrées, données personnelles nettoyées, plus de mots de passe par défaut&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cyber Resilience Act&lt;/strong&gt; (2026-2027) : SBOM obligatoire, MAJ pendant 10 ans minimum&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Paul a donc disséqué (à la DREMMEL 🤣) une ampoule connectée équipée d&amp;rsquo;un ESP32-U4WD, avec ses 2 oscillateurs RTC/horloge et son eFuse, pour voir s&amp;rsquo;il pouvait la compromettre.&lt;/p&gt;
&lt;p&gt;Les vecteurs d&amp;rsquo;attaque analysés :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Port série et JTAG&lt;/strong&gt; : désactivés post-usine&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pairing WiFi&lt;/strong&gt; : pas RED-compliant (HTTP car pas d&amp;rsquo;Internet au moment du pairing = pas de certificat, WiFi ouvert)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BLE Pin&lt;/strong&gt; : pas possible sur une ampoule&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BLE pairing&lt;/strong&gt; : la voie privilégiée&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Point rigolo : pour que les télécommandes soient CRA/RED compliant, l&amp;rsquo;ampoule doit aussi devenir une gateway BLE vers Internet (sinon, pas de mise à jour).&lt;/p&gt;
&lt;h2 id="au-secours--mes-images-pourrissent-mes-perfs"&gt;Au secours ! Mes images pourrissent mes perfs
&lt;/h2&gt;&lt;p&gt;Antoine Caron et Mathieu Mure nous ont parlé d&amp;rsquo;un sujet crucial pour les performances web : les images.&lt;/p&gt;
&lt;img src="https://blog.zwindler.fr/2026/02/tnt26-images.avif"
alt="Talk optimisation images web"
loading="lazy"
decoding="async"&gt;
&lt;p&gt;Un peu d&amp;rsquo;histoire&amp;hellip; En &lt;strong&gt;1992&lt;/strong&gt;, la toute première image est affichée sur le web : &lt;a class="link" href="https://www.letemps.ch/culture/musiques/y-trente-ans-web-souvrait-musique-geneve" target="_blank" rel="noopener"
&gt;les Cernettes (du CERN, évidemment)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Aujourd&amp;rsquo;hui, 99.9% des sites web ont au moins 1 image, et 68% des pages mobiles ont leur LCP (Largest Contentful Paint) lié à une image.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Le royaume des formats&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Morts/vieillissants : BMP, GIF, JPEG, PNG&lt;/li&gt;
&lt;li&gt;Modernes : WebP, AVIF, SVG, JPEG XL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Après une chouette explication de comment ça fonctionne &amp;ldquo;en théorie&amp;rdquo;, Antoine et Mathieu nous on montré qu&amp;rsquo;à compression égale, les formats récents prennent moins de place, mais surtout, affichent des artefacts visuels bien moindres dans les hauts niveaux de compression (notamment AVIF).&lt;/p&gt;
&lt;p&gt;Les bonnes pratiques présentées :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Taille adaptative&lt;/strong&gt; avec &lt;code&gt;srcset&lt;/code&gt; pour laisser le navigateur choisir la meilleure taille&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Balises &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;&lt;/strong&gt; avec plusieurs &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; pour gérer différents formats&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;fetchpriority=&amp;quot;high&amp;quot;&lt;/code&gt;&lt;/strong&gt; au-dessus de la ligne de flottaison&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt;&lt;/strong&gt; en dessous&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sharp&lt;/strong&gt; en JS pour le traitement côté serveur&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git LFS&lt;/strong&gt; pour éviter de commit des fichiers lourds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache de build&lt;/strong&gt; pour éviter de reconstruire chaque image à chaque CI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, j&amp;rsquo;ai du boulot sur ce blog (je le savais&amp;hellip;).&lt;/p&gt;
&lt;h2 id="makers-de-père-en-fils"&gt;Makers de Père en Fils
&lt;/h2&gt;&lt;p&gt;Matthias et Sylvain Gougouzian nous ont présenté un talk bonbon sur leurs projets makers en famille.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/02/tnt26-makers.avif"
alt="Talk Makers de Père en Fils"
loading="lazy"
decoding="async"&gt;
Un duo pétillant, pour ne pas dire explosif 😂 ! Matthias (le fils) est super cash (pour rester poli), c&amp;rsquo;était très rigolo à voir.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;idée de Sylvain : initier ses enfants à l&amp;rsquo;électronique, comme nous à l&amp;rsquo;époque en cours de techno. Objectif : faire 1 à 2 projets par an ensemble.&lt;/p&gt;
&lt;p&gt;Après découverte de &lt;strong&gt;Scratch&lt;/strong&gt; (apprendre le code par blocs), &lt;strong&gt;Pyrates&lt;/strong&gt; (approfondir avec Python), &lt;strong&gt;Micro:Bit&lt;/strong&gt; (code visuel avec accéléromètres et boutons), &lt;strong&gt;TinkerCAD&lt;/strong&gt; (conception 3D de projets physiques), Sylvain s&amp;rsquo;est dit que c&amp;rsquo;était le moment de passer à un vrai projet DE ZERO. Recréer un équivalent &lt;a class="link" href="https://nabaztag.com/" target="_blank" rel="noopener"
&gt;du Nabaztag&lt;/a&gt; !&lt;/p&gt;
&lt;p&gt;Ils ont utilisé la méthode &lt;strong&gt;MoSCoW&lt;/strong&gt; pour prioriser :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Must have&lt;/strong&gt; : Météo (LEDs), Moteurs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Should have&lt;/strong&gt; : Écran, GreenIT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Could have&lt;/strong&gt; : Micro, HP, Carte SD&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Won&amp;rsquo;t have&lt;/strong&gt; : Bourse, Traffic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Choix techniques : ESP32/Raspberry Pico avec MicroPython.&lt;/p&gt;
&lt;p&gt;Le moment touchant du talk : apprendre à se dire les choses. Matthias n&amp;rsquo;osait plus parler à son père quand une tâche était trop grosse, par peur de décevoir. C&amp;rsquo;est un point qu&amp;rsquo;en tant que parent, on peut trop facilement oublier&amp;hellip;&lt;/p&gt;
&lt;p&gt;Une fois les problèmes de communication réglé, ils ont pu découper en étapes : PoC d&amp;rsquo;abord (NeoLED, écran, servomoteur, bouton, carte microSD), puis passage au MVP avec la forme physique sur TinkerCAD.&lt;/p&gt;
&lt;p&gt;Un bel exemple de transmission intergénérationnelle.&lt;/p&gt;
&lt;h2 id="another-world-une-belle-leçon-darchitecture-logicielle"&gt;Another World, une belle leçon d&amp;rsquo;architecture logicielle
&lt;/h2&gt;&lt;p&gt;Olivier Poncet nous a fait découvrir les entrailles d&amp;rsquo;un jeu cultissime (bon, moi je le connaissais pas 🫣) : Another World.&lt;/p&gt;
&lt;img src="https://blog.zwindler.fr/2026/02/tnt26-anotherworld.avif"
alt="Talk Another World"
loading="lazy"
decoding="async"&gt;
&lt;p&gt;Un talk que je voulais voir depuis longtemps, mais comme je savais que je le verrai à TNT26, j&amp;rsquo;avais décidé de ne pas regarder le replay !&lt;/p&gt;
&lt;p&gt;Another World, c&amp;rsquo;est un jeu vidéo d&amp;rsquo;aventure/action cinématique sorti en 1991, fait par &lt;strong&gt;une seule personne&lt;/strong&gt; (sauf la musique) : Eric Chahi, pionnier du jeu vidéo français des années 80-90.&lt;/p&gt;
&lt;p&gt;Les specs impressionnantes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exécutable de &lt;strong&gt;24 Ko&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Assets de 1.2 Mo (images et sons)&lt;/li&gt;
&lt;li&gt;Un moteur graphique polygonal 2D (pour les animations fluides et séquences cinématiques)&lt;/li&gt;
&lt;li&gt;Un moteur sonore 4 pistes PCM&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Une machine virtuelle&lt;/strong&gt; pour tout gérer !&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le matériel de l&amp;rsquo;époque : Amiga 500 avec 1 Mo de RAM et un HDD de 20 Mo, augmenté d&amp;rsquo;un Genlock (caméscope/magnétoscope) et d&amp;rsquo;un magnétophone. Eric Chahi a créé ses propres outils en GFA Basic. Il a utilisé la &lt;strong&gt;rotoscopie&lt;/strong&gt; : capturer une image vidéo, détourer les assets pour créer les animations. Toutes les peintures et graphismes sont faits à la main.&lt;/p&gt;
&lt;p&gt;Les données sont compressées avec l&amp;rsquo;algo &lt;strong&gt;ByteKiller&lt;/strong&gt;, répandu sur Amiga, économe en RAM.&lt;/p&gt;
&lt;p&gt;La VM est impressionnante agit comme une vrai machine : &lt;strong&gt;64 threads&lt;/strong&gt;, &lt;strong&gt;256 registres&lt;/strong&gt;, &lt;strong&gt;29 instructions / 200+ opcodes&lt;/strong&gt;. Olivier est rentré dans les détails de l&amp;rsquo;implémentation des opcodes et de leur conception.&lt;/p&gt;
&lt;p&gt;Le moteur vidéo utilise des framebuffers avec 4 pages (2 backgrounds + 2 fenêtres principales, 2 actives et 2 inactives). Les polygones sont hiérarchisés dans des &amp;ldquo;bounding boxes&amp;rdquo; pour gérer l&amp;rsquo;affichage et les collisions. Les backgrounds, trop complexes pour être calculés en temps réel, sont pré-dessinés et mis en &amp;ldquo;cache&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Difficile de tout retranscrire, Olivier nous a montré 1001 autres annecdotes sur les optimisations et autres astuces malines dans ce jeu. Olivier n&amp;rsquo;avait plus de temps pour nous montrer sa réimplémentation du jeu en C++ (qui fonctionne dans un navigateur avec WASM, si je ne dis pas de bêtises).&lt;/p&gt;
&lt;p&gt;Une vraie leçon d&amp;rsquo;ingénierie avec les contraintes de l&amp;rsquo;époque !&lt;/p&gt;
&lt;h2 id="communauté-et-remerciements"&gt;Communauté et remerciements
&lt;/h2&gt;&lt;p&gt;Comme toujours, le meilleur d&amp;rsquo;une conférence reste les rencontres.&lt;/p&gt;
&lt;p&gt;Je retiens surtout les longues (très longues) discussions avec Horacio et Yannick de Clever Cloud. J&amp;rsquo;ai aussi enfin pris le temps de papoter avec Estelle, Geoffrey, Julien (x3, décidément 🙃), Valentin, Stéphane (x2), Aurélie, Florian, Jérôme, et sûrement plein d&amp;rsquo;autres que j&amp;rsquo;ai oubliés dans le &amp;ldquo;blur&amp;rdquo; de la conférence.&lt;/p&gt;
&lt;img src="https://blog.zwindler.fr/2026/02/tnt26-foues.avif"
alt="Fouées tourangelles partagées lors de la conférence"
loading="lazy"
decoding="async"&gt;
&lt;p&gt;Un grand merci à l&amp;rsquo;équipe d&amp;rsquo;organisation de Touraine Tech pour cette édition 2026 réussie. Le passage sur 2 jours et le changement de lieu ont été de bonnes décisions. À l&amp;rsquo;année prochaine !&lt;/p&gt;</description></item><item><title>Retrouvez-moi dans le podcast Hack'n Speak (0x33)</title><link>https://blog.zwindler.fr/2026/01/30/hacknspeak-podcast/</link><pubDate>Fri, 30 Jan 2026 08:00:00 +0100</pubDate><guid>https://blog.zwindler.fr/2026/01/30/hacknspeak-podcast/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/01/hacknspeak.webp" alt="Featured image of post Retrouvez-moi dans le podcast Hack'n Speak (0x33)" /&gt;&lt;h2 id="hackn-speak"&gt;Hack&amp;rsquo;n Speak
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai eu le plaisir d&amp;rsquo;être invité par Martial Puygrenier (aka &lt;a class="link" href="https://x.com/mpgn_x64" target="_blank" rel="noopener"
&gt;mpgn&lt;/a&gt;) dans son podcast Hack&amp;rsquo;n Speak !&lt;/p&gt;
&lt;p&gt;Pour la première fois dans ce podcast orienté sécurité, on plonge dans &lt;strong&gt;Kubernetes&lt;/strong&gt;, évidemment sous l&amp;rsquo;angle de la sécurité.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;y partage mon retour d&amp;rsquo;expérience après bientôt 10 ans dans le grand bain de Kubernetes : bonnes pratiques, pièges à éviter et vision terrain.&lt;/p&gt;
&lt;p&gt;On termine l&amp;rsquo;épisode en parlant de ce blog, de mon titre de &lt;a class="link" href="https://blog.zwindler.fr/2026/01/18/rex-kubestronaut/" &gt;Kubestronaut&lt;/a&gt; et de mon &lt;a class="link" href="https://50ndk.zwindler.fr/" target="_blank" rel="noopener"
&gt;livre&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="0x33--du-kubernetes-de-la-sécurité-une-anecdote"&gt;0x33 – Du Kubernetes, de la sécurité, une anecdote
&lt;/h2&gt;&lt;p&gt;En attendant que la moulinette de MAJ des postcasts côté deezer rattrape le retard (coucou les anciens collègues 🫣), l&amp;rsquo;épisode est disponible sur Spotify :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://open.spotify.com/show/2lwA1WLVqnYvnlc7WkV3yU" target="_blank" rel="noopener"
&gt;Écouter l&amp;rsquo;épisode sur Spotify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[EDIT] Voilà la version DEEZER&lt;/p&gt;
&lt;p&gt;-&lt;a class="link" href="https://link.deezer.com/s/32kqMFeXqiXpamTjD4myv" target="_blank" rel="noopener"
&gt;Écouter l&amp;rsquo;épisode sur DEEZER&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="les-liens-mentionnés-dans-lépisode"&gt;Les liens mentionnés dans l&amp;rsquo;épisode
&lt;/h2&gt;&lt;p&gt;Et pour vous faciliter la tâche si vous les cherchez, voici les ressources dont je parle dans l&amp;rsquo;épisode :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.datadoghq.com/blog/engineering/2023-03-08-deep-dive-into-platform-level-impact/" target="_blank" rel="noopener"
&gt;Le post mortem de Datadog&lt;/a&gt; - une plongée fascinante dans un incident majeur&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/watch?v=RbqHDpoaqR8" target="_blank" rel="noopener"
&gt;Ciel ! Mon Kubernetes mine des bitcoins !&lt;/a&gt; - mon talk sur la sécurité Kubernetes&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://50ndk.zwindler.fr/" target="_blank" rel="noopener"
&gt;Mon livre : 50 solutions pour les postes de dev et les clusters de production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="merci-martial-"&gt;Merci Martial !
&lt;/h2&gt;&lt;p&gt;Un grand merci à Martial pour l&amp;rsquo;invitation et l&amp;rsquo;échange. C&amp;rsquo;était vraiment cool de pouvoir parler de Kubernetes sous l&amp;rsquo;angle sécu dans un podcast dédié au sujet.&lt;/p&gt;
&lt;p&gt;Bonne écoute à toutes et à tous !&lt;/p&gt;</description></item><item><title>Vos politiques de conformité sur Kubernetes avec Kyverno - part 2</title><link>https://blog.zwindler.fr/2022/09/05/vos-politiques-de-conformite-sur-kubernetes-avec-kyverno-part2/</link><pubDate>Mon, 05 Sep 2022 06:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2022/09/05/vos-politiques-de-conformite-sur-kubernetes-avec-kyverno-part2/</guid><description>&lt;img src="https://blog.zwindler.fr/2022/07/Kyverno_Horizontal.webp" alt="Featured image of post Vos politiques de conformité sur Kubernetes avec Kyverno - part 2" /&gt;&lt;h2 id="récap-de-la-partie-1"&gt;Récap&amp;rsquo; de la partie 1
&lt;/h2&gt;&lt;p&gt;Dans le &lt;a class="link" href="https://blog.zwindler.fr/2022/08/01/vos-politiques-de-conformite-sur-kubernetes-avec-kyverno" target="_blank" rel="noopener"
&gt;précédent article&lt;/a&gt;, j&amp;rsquo;ai introduit &lt;a class="link" href="https://kyverno.io/" target="_blank" rel="noopener"
&gt;Kyverno&lt;/a&gt;, un outil &amp;ldquo;cloud native&amp;rdquo; pour gérer des politiques de conformité sur Kubernetes.&lt;/p&gt;
&lt;p&gt;Pour illustrer les capacités de l&amp;rsquo;outil, j&amp;rsquo;avais pris un exemple : empêcher deux Ingress d&amp;rsquo;utiliser la même URL d&amp;rsquo;entrée. J&amp;rsquo;avais fait le même exercice avec OPA dans un article précédent (&lt;a class="link" href="https://blog.zwindler.fr/2020/07/20/vos-politiques-de-conformite-sur-kubernetes-avec-opa-et-gatekeeper/" &gt;Open Policy Agent (OPA) et de son pendant dans Kubernetes, Gatekeeper&lt;/a&gt;) et j&amp;rsquo;avais comparé à la fin les deux syntaxes (Kyverno vs rego).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/07/rego_vs_kyverno.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai une nette préférence pour la syntaxe de Kyverno, même si OPA est là depuis plus longtemps et n&amp;rsquo;est pas limité à Kubernetes (Kyverno c&amp;rsquo;est uniquement pour Kube).&lt;/p&gt;
&lt;h2 id="aller-un-peu-plus-loin"&gt;Aller un peu plus loin
&lt;/h2&gt;&lt;p&gt;Au-delà de mon exemple, j&amp;rsquo;ai indiqué que Kyverno mettait à disposition un très grand nombre de politiques déjà écrites pour vous (191 au moment où j&amp;rsquo;écris l&amp;rsquo;article).&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://kyverno.io/policies/" target="_blank" rel="noopener"
&gt;kyverno.io/policies&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Par défaut, la plupart des politiques disponibles sont positionnées en mode &amp;ldquo;audit&amp;rdquo; dans un premier temps. N&amp;rsquo;oubliez pas de la passer en mode &amp;ldquo;enforce&amp;rdquo; quand vous serez assez mature sur votre cluster.&lt;/p&gt;
&lt;p&gt;Évidemment, quand on débute on ne sait pas trop par quoi commencer. Le plus simple dans ce cas-là est de déployer la chart Helm &lt;code&gt;kyverno/kyverno-policies&lt;/code&gt;, qui contient l&amp;rsquo;équivalent des Kyverno des &amp;ldquo;Pod Security Standards&amp;rdquo; (les PSP étant officiellement obsolètes depuis la 1.21 je crois, et plus utilisables en 1.25).&lt;/p&gt;
&lt;div class="highlight"&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;helm install kyverno-policies kyverno/kyverno-policies -n kyverno
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Thank you &lt;span class="k"&gt;for&lt;/span&gt; installing kyverno-policies v2.5.2 😀
&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;We have installed the &lt;span class="s2"&gt;&amp;#34;baseline&amp;#34;&lt;/span&gt; profile of Pod Security Standards and &lt;span class="nb"&gt;set&lt;/span&gt; them in audit mode.
&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;Visit https://kyverno.io/policies/ to find more sample policies.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Voilà ce que ça nous a déployé (la dernière étant déjà là à cause de l&amp;rsquo;article précédent)&lt;/p&gt;
&lt;div class="highlight"&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;kubectl get clusterpolicy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME BACKGROUND ACTION READY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;disallow-capabilities &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;disallow-host-namespaces &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;disallow-host-path &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;disallow-host-ports &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;disallow-host-process &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;disallow-privileged-containers &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;disallow-proc-mount &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;disallow-selinux &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;restrict-apparmor-profiles &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;restrict-seccomp &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;restrict-sysctls &lt;span class="nb"&gt;true&lt;/span&gt; audit &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unique-ingress-host &lt;span class="nb"&gt;false&lt;/span&gt; enforce &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Avec ça, on va déjà avoir pas mal de pain sur la planche pour éduquer nos utilisateurs (les développeurs) à produire des déploiements Kubernetes sécurisés.&lt;/p&gt;
&lt;h2 id="user-experience"&gt;User experience
&lt;/h2&gt;&lt;p&gt;Un des points ennuyeux de ces deux outils, c&amp;rsquo;est que de base, ils ne sont pas très user friendly. Moi, faire une commande &lt;code&gt;kubectl get events&lt;/code&gt; (voire &lt;code&gt;kubectl get events --field-selector=reason=PolicyViolation&lt;/code&gt; dans le cas de Kyverno) pour savoir pourquoi un déploiement a échoué, ça ne m&amp;rsquo;ennuie pas trop (ça m&amp;rsquo;amuse, presque).&lt;/p&gt;
&lt;p&gt;Mais pour certaines personnes, c&amp;rsquo;est un peu rebutant et je peux l&amp;rsquo;entendre.&lt;/p&gt;
&lt;p&gt;Mon but c&amp;rsquo;est de rendre l&amp;rsquo;infrastructure plus simple à des gens dont qui ce n&amp;rsquo;est pas le métier et imposer ma façon de travailler n&amp;rsquo;est pas forcément la meilleure méthode.&lt;/p&gt;
&lt;p&gt;Souvent, pour aider à adopter un nouvel outil / usage, le plus simple c&amp;rsquo;est de lui fournir une interface graphique.&lt;/p&gt;
&lt;p&gt;Et ça tombe bien, Kyverno dispose d&amp;rsquo;un outil pour faire ça.&lt;/p&gt;
&lt;h2 id="kyverno-policy-reporter"&gt;Kyverno policy reporter
&lt;/h2&gt;&lt;p&gt;En réalité, l&amp;rsquo;outil ne sert pas tout à fait à ça, à la base. Mais vous allez voir que c&amp;rsquo;est même encore mieux.&lt;/p&gt;
&lt;p&gt;Historiquement, l&amp;rsquo;outil s&amp;rsquo;appelle policy reporter, car les développeurs avaient besoin d&amp;rsquo;un outil permettant d&amp;rsquo;exporter facilement et rapidement les &lt;em&gt;PolicyReports&lt;/em&gt; dont je parle dans l&amp;rsquo;article précédent :&lt;/p&gt;
&lt;div class="highlight"&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="go"&gt;kubectl describe policyreport polr-ns-default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Name: polr-ns-default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Namespace: default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Labels: managed-by=kyverno
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Annotations: &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;API Version: wgpolicyk8s.io/v1alpha2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Kind: PolicyReport
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Results:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Category: Sample
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Message: The Ingress host name must be unique.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Policy: unique-ingress-host
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; API Version: networking.k8s.io/v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Kind: Ingress
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Name: ingress2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Summary:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Error: 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Fail: 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Pass: 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Skip: 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Warn: 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le policy reporter a donc pour rôle d&amp;rsquo;exporter les PolicyReports vers plusieurs sources externes pour les traiter plus facilement :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Grafana Loki&lt;/li&gt;
&lt;li&gt;Elasticsearch&lt;/li&gt;
&lt;li&gt;Slack&lt;/li&gt;
&lt;li&gt;Discord&lt;/li&gt;
&lt;li&gt;MS Teams&lt;/li&gt;
&lt;li&gt;Policy Reporter UI&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je vais vous en parler de 2 : &lt;strong&gt;slack&lt;/strong&gt; et &lt;strong&gt;Policy Reporter UI&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="policy-reporter-ui"&gt;Policy Reporter UI
&lt;/h2&gt;&lt;p&gt;Je commence par l&amp;rsquo;UI, car c&amp;rsquo;est le plus simple à configurer. Il suffit juste de déployer la Chart helm de Policy Reporter en oubliant pas de spécifier qu&amp;rsquo;on veut l&amp;rsquo;UI :&lt;/p&gt;
&lt;div class="highlight"&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;helm repo add policy-reporter https://kyverno.github.io/policy-reporter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm install -n kyverno policy-reporter policy-reporter/policy-reporter --set kyvernoPlugin.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; --set ui.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; --set ui.plugins.kyverno&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&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;kubectl -n kyverno get pods
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS RESTARTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kyverno-5f8bfd6fc5-xp6bm 1/1 Running &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;59m ago&lt;span class="o"&gt;)&lt;/span&gt; 33d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;policy-reporter-74cc9bf4b9-9ggwh 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 18s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;policy-reporter-kyverno-plugin-6b48c4bc5f-5rwfb 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 18s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;policy-reporter-ui-5c9449d58-f9fwf 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 18s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On peut évidemment ajouter un Ingress mais si on est fénéant comme moi dans cette démo un simple port-forward suffit 🙃.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl port-forward service/policy-reporter-ui 8082:8080 -n kyverno
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;L&amp;rsquo;UI en elle-même est assez intuitive. On a notamment :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;un Dashboard avec un gros indicateur global pour savoir combien on a de violation de politiques&lt;/li&gt;
&lt;li&gt;une page avec nos &amp;ldquo;policy reports&amp;rdquo;, dans lequel on peut filtrer par namespace ou par politique&lt;/li&gt;
&lt;li&gt;la liste de nos politiques&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/09/kyverno-policy-reporter-ui.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/09/kyverno-policy-reporter-ui-2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="slack"&gt;Slack
&lt;/h2&gt;&lt;p&gt;On voit tout de suite l&amp;rsquo;intérêt d&amp;rsquo;alerter les administrateurs dans Slack (ou Teams 😭) si jamais une violation de la politique arrive.&lt;/p&gt;
&lt;p&gt;On voit que la configuration est assez triviale, on a juste à ajouter un webhook dans Slack et un petit bout de configuration dans Kyverno&lt;/p&gt;
&lt;div class="highlight"&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;kyvernoPlugin&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;enabled&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;ui&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;enabled&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="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;plugins&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;kyverno&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;target&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;slack&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;webhook&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://hooks.slack.com/services/T0xxxxxxxx&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;minimumPriority&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;medium&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;skipExistingOnStartup&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="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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="l"&gt;kyverno&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et on re-applique tout ça à notre release helm&lt;/p&gt;
&lt;div class="highlight"&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;helm upgrade -n kyverno policy-reporter policy-reporter/policy-reporter -f kyverno-values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note: Si vous ne savez pas comment faire, il faut être admin du workspace Slack, créer une application dans &lt;a class="link" href="https://api.slack.com/apps" target="_blank" rel="noopener"
&gt;https://api.slack.com/apps&lt;/a&gt;, activer les &lt;code&gt;Incoming Webhooks&lt;/code&gt; et en créer un en lui donnant les droits sur un channel.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/09/slack-create-app.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/09/slack-create-app-2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/09/slack-app-webhooks.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/09/slack-app-webhooks-2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="et-si-on-faisait-une-violation-de-politique-pour-voir-"&gt;Et si on faisait une violation de politique, pour voir ?
&lt;/h2&gt;&lt;p&gt;Pour faire simple, je suis reparti de l&amp;rsquo;exemple de l&amp;rsquo;article précédent. Je repasse ma politique &lt;strong&gt;unique-ingress-host&lt;/strong&gt; en mode &lt;strong&gt;audit&lt;/strong&gt; et je recréé l&amp;rsquo;ingress problématique :&lt;/p&gt;
&lt;div class="highlight"&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;kubectl edit clusterpolicy unique-ingress-host
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f ingress2.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Instantanément, j&amp;rsquo;ai bien 2 échecs qui apparaissent dans l&amp;rsquo;UI de Kyverno, ainsi que des messages dans Slack&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/09/kyverno-fail.avif"
loading="lazy"
&gt;
&lt;img src="https://blog.zwindler.fr/2022/09/kyverno-slack.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Victoire :)&lt;/p&gt;
&lt;h2 id="ressources-complémentaires"&gt;Ressources complémentaires
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kyverno.io/policies/" target="_blank" rel="noopener"
&gt;&amp;ldquo;Catalogue&amp;rdquo; des Politiques officielles de Kyverno&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://dev.to/aurelievache/understanding-kubernetes-part-44-tools-kyverno-52mc" target="_blank" rel="noopener"
&gt;Aurélie Vache - Understanding Kubernetes: part 44 – Tools - Kyverno &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Vos politiques de conformité sur Kubernetes avec Kyverno</title><link>https://blog.zwindler.fr/2022/08/01/vos-politiques-de-conformite-sur-kubernetes-avec-kyverno/</link><pubDate>Mon, 01 Aug 2022 06:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2022/08/01/vos-politiques-de-conformite-sur-kubernetes-avec-kyverno/</guid><description>&lt;img src="https://blog.zwindler.fr/2022/07/Kyverno_Horizontal.webp" alt="Featured image of post Vos politiques de conformité sur Kubernetes avec Kyverno" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Si le titre de cet article vous dit quelque chose, c&amp;rsquo;est peut-être parce que je l&amp;rsquo;ai déjà utilisé il y a 2 ans tout pile pour parler d&amp;rsquo;un autre logiciel de gestion de politiques de conformité : &lt;a class="link" href="https://blog.zwindler.fr/2020/07/20/vos-politiques-de-conformite-sur-kubernetes-avec-opa-et-gatekeeper/" &gt;Open Policy Agent (OPA) et de son pendant dans Kubernetes, Gatekeeper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et je ne vais pas mentir, je n&amp;rsquo;ai pas beaucoup refait de l&amp;rsquo;OPA depuis cet article de 2020&amp;hellip;&lt;/p&gt;
&lt;p&gt;La première raison est que j&amp;rsquo;ai changé d&amp;rsquo;entreprise pile à ce moment-là et que j&amp;rsquo;avais d&amp;rsquo;autres priorités dans la nouvelle entreprise qui m&amp;rsquo;emploie.&lt;/p&gt;
&lt;p&gt;La seconde, certainement plus vraie mais peut être aussi moins avouable, c&amp;rsquo;est qu&amp;rsquo;OPA c&amp;rsquo;est quand même pas super user friendly. Notamment à cause du &lt;strong&gt;rego&lt;/strong&gt;, le langage des politiques de conformités.&lt;/p&gt;
&lt;p&gt;Depuis, j&amp;rsquo;ai découvert &lt;a class="link" href="https://kyverno.io/" target="_blank" rel="noopener"
&gt;Kyverno&lt;/a&gt;, un petit nouveau dans le &amp;ldquo;cloud native&amp;rdquo; game (promu au niveau de &amp;ldquo;sandbox CNCF&amp;rdquo; depuis peu) et qui propose des politiques de conformité pour Kubernetes uniquement (contrairement à OPA + rego qui sont généralistes), écrites en YAML.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With Kyverno, policies are managed as Kubernetes resources and no new language is required to write policies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Prends toi ça, &lt;strong&gt;rego&lt;/strong&gt; ;-)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note :&lt;/strong&gt; Cet article va rester relativement simple, c&amp;rsquo;est une introduction. On ira plus loin dans les articles suivants (coder sa propre politique, l&amp;rsquo;UI, etc).&lt;/p&gt;
&lt;p&gt;Je ne vais pas beaucoup innover par rapport à l&amp;rsquo;article sur OPA et repartir du même cas d&amp;rsquo;usage : interdire à deux développeurs d&amp;rsquo;utiliser la même URL pour leur app web déployée dans Kubernetes (car on a vu que c&amp;rsquo;était assez nul quand ça arrive).&lt;/p&gt;
&lt;p&gt;Et vous allez voir que la première marche d&amp;rsquo;apprentissage est BEAAAAUCOUP plus accessible avec Kyverno que OPA (même si ça va faire râler la moitié de mon Twitter que je parle encore de YAML 🤣).&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Même topo que pour OPA / Gatekeeper, il faut la 1.14 de Kube pour utiliser Kyverno. La 1.14 était déjà pas la plus récente en 2020, c&amp;rsquo;est carrément la préhistoire maintenant ;-).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your Kubernetes cluster version must be above v1.14 which adds webhook timeouts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bien évidemment, il faut également avoir les droits d’accès admin sur le cluster, car on va devoir créer des &lt;em&gt;Validating Admission Webhooks&lt;/em&gt; et des &lt;em&gt;CRDs&lt;/em&gt;. Là encore, je vous renvoie &lt;a class="link" href="https://blog.zwindler.fr/2020/07/20/vos-politiques-de-conformite-sur-kubernetes-avec-opa-et-gatekeeper/" &gt;au précédent article&lt;/a&gt; et à la doc officielle si ça ne vous parle pas.&lt;/p&gt;
&lt;p&gt;Pour faire bref, on va s&amp;rsquo;insérer dans le processus de déploiement de Kubernetes pour interdire aux gens de faire des choses qui ne respectent pas vos bonnes pratiques (avec les webhooks) et on va ajouter des APIs supplémentaires pour étendre Kubernetes (les CRDs), de manière à pouvoir décrire qu&amp;rsquo;est ce qui n&amp;rsquo;est pas autorisé.&lt;/p&gt;
&lt;h2 id="déploiement"&gt;Déploiement
&lt;/h2&gt;&lt;p&gt;Niveau installation, on reste sur du simple et efficace.&lt;/p&gt;
&lt;p&gt;Kyverno propose plusieurs Charts helm :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;une pour &lt;strong&gt;Kyverno&lt;/strong&gt; même&lt;/li&gt;
&lt;li&gt;une pour &lt;strong&gt;Policy Reporter&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;une contenant un &amp;ldquo;pack&amp;rdquo; de règles par défaut, configurées comme &amp;ldquo;non bloquantes&amp;rdquo; (en vrai, l&amp;rsquo;équivalent de ce qu&amp;rsquo;on pouvait faire avec les Pods Security policies)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans le cadre de cet article, je ne m&amp;rsquo;intéresse qu&amp;rsquo;à Kyverno même. Mais comme je l&amp;rsquo;ai dis plus haut, on reparlera des 2 suivantes dans un prochain numéro.&lt;/p&gt;
&lt;p&gt;On commence donc par ajouter Kyverno lui-même :&lt;/p&gt;
&lt;div class="highlight"&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; helm repo add kyverno https://kyverno.github.io/kyverno/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; helm install kyverno kyverno/kyverno --namespace kyverno --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; NAME: kyverno
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; LAST DEPLOYED: Mon Jul 25 21:55:11 2022
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; NAMESPACE: kyverno
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; STATUS: deployed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; REVISION: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; NOTES:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Chart version: v2.5.2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Kyverno version: v1.7.2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Thank you for installing kyverno! Your release is named kyverno.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ⚠️ WARNING: Setting replicas count below 3 means Kyverno is not running in high availability mode.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 💡 Note: There is a trade-off when deciding which approach to take regarding Namespace exclusions. Please see the documentation at https://kyverno.io/docs/installation/#security-vs-operability to understand the risks.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Note :&lt;/strong&gt; vous remarquerez le &lt;strong&gt;WARNING&lt;/strong&gt; à l&amp;rsquo;install. Par défaut, Kyverno est déployé avec un seul replica. S&amp;rsquo;il crashe, vous ne pouvez plus rien déployer ou modifier sur votre cluster (ça m&amp;rsquo;est arrivé). La solution pour s&amp;rsquo;en sortir est de supprimer les &lt;strong&gt;webhooks&lt;/strong&gt; de Kyverno pour débloquer la situation. Et bien sûr, de lui donner plus d&amp;rsquo;air (de plus grandes limites, surtout niveau RAM) et surtout 3 réplicas (ou plus)&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note 2 :&lt;/strong&gt; Il est probablement préférable d&amp;rsquo;exclure certains namespaces (kube-system, kyverno, &amp;hellip;) du scope de Kyverno pour éviter de mauvaises surprises. Le blogpost &lt;em&gt;Thibault Lengagne&lt;/em&gt; de chez Padok en parle aussi (lien en bas de l&amp;rsquo;article).&lt;/p&gt;
&lt;h2 id="quest-ce-quon-a-déployé-"&gt;Qu&amp;rsquo;est-ce qu&amp;rsquo;on a déployé ?
&lt;/h2&gt;&lt;p&gt;A première vue, pas grande chose. Si on fait un &lt;code&gt;kubectl -n kyverno get all&lt;/code&gt;, on trouve juste un &lt;em&gt;Pod&lt;/em&gt; (+&lt;em&gt;ReplicaSet&lt;/em&gt; +&lt;em&gt;Deplopyment&lt;/em&gt;) et deux &lt;em&gt;Services&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Mais en coulisse, on a déployé bien plus que ça ;)&lt;/p&gt;
&lt;p&gt;Si on cherche des CRDs, on voit qu&amp;rsquo;on a pas mal de nouveaux objets à notre disposition.&lt;/p&gt;
&lt;div class="highlight"&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; kubectl api-resources --api-group&lt;span class="o"&gt;=&lt;/span&gt;kyverno.io
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; NAME SHORTNAMES APIVERSION NAMESPACED KIND
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; clusterpolicies cpol kyverno.io/v1 false ClusterPolicy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; clusterreportchangerequests crcr kyverno.io/v1alpha2 false ClusterReportChangeRequest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; generaterequests gr kyverno.io/v1 true GenerateRequest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; policies pol kyverno.io/v1 true Policy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; reportchangerequests rcr kyverno.io/v1alpha2 true ReportChangeRequest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; updaterequests ur kyverno.io/v1beta1 true UpdateRequest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;De même, on a également des nouveaux webhooks&lt;/p&gt;
&lt;div class="highlight"&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; kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; NAME WEBHOOKS AGE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; kyverno-policy-validating-webhook-cfg 1 8m48s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; kyverno-resource-validating-webhook-cfg 2 8m48s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; NAME WEBHOOKS AGE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; kyverno-policy-mutating-webhook-cfg 1 11m
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; kyverno-resource-mutating-webhook-cfg 2 11m
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; kyverno-verify-mutating-webhook-cfg 1 11m
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;A quoi ça sert ? Comment ça marche ?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pour simplifier, on va décrire ce qu&amp;rsquo;on ne veut pas autoriser dans notre cluster à l&amp;rsquo;aide de la CRD &lt;em&gt;ClusterPolicy&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;En fonction des règles qu&amp;rsquo;on aura écrites dedans, les &lt;strong&gt;webhooks&lt;/strong&gt; vont s&amp;rsquo;insérer dans le processus normal de déploiement de ressources de Kubernetes pour les autoriser ou pas.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/07/admission-controller-phases.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a class="link" href="https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/" target="_blank" rel="noopener"
&gt;Kubernetes Blog - A Guide to Kubernetes Admission Controllers&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="exemple-des-ingress-avec-la-même-url"&gt;Exemple des Ingress avec la même URL
&lt;/h2&gt;&lt;p&gt;Assez de théorie, le plus simple c&amp;rsquo;est que je vous montre. Je vous l&amp;rsquo;ai dit au début de l&amp;rsquo;article, de manière à comparer les deux produits, je vais repartir du même cas d&amp;rsquo;usage.&lt;/p&gt;
&lt;p&gt;Dans mes clusters Kubernetes, je veux interdire à deux applications de créer un &lt;em&gt;Ingress&lt;/em&gt; avec la même URL, car dans mon cas personnel, ce n&amp;rsquo;est pas autorisé. C&amp;rsquo;est le signe d&amp;rsquo;une erreur de copié-collé d&amp;rsquo;une Chart Helm vers une autre (ou tout autre erreur humaine).&lt;/p&gt;
&lt;p&gt;La conséquence de cette erreur est un conflit dans le routage des requêtes HTTP entrantes et on se retrouve avec &lt;em&gt;un genre de round robin chelou&lt;/em&gt; où la moitié des requêtes tapent le mauvais backend. En prod, ça la fout mal, et on risque de se retrouver avec des clients qui râlent sur Twitter que l&amp;rsquo;application est down ;-).&lt;/p&gt;
&lt;p&gt;Au même titre qu&amp;rsquo;OPA propose de régler ce problème dans les politiques mises à disposition par la communauté, &lt;a class="link" href="https://kyverno.io/policies/other/unique-ingress-paths/unique-ingress-paths/" target="_blank" rel="noopener"
&gt;il existe la même avec Kyverno&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On verra plus en détails dans l&amp;rsquo;article suivant comment sont construites ces règles. Dans un premier temps, on va juste déployer cette règle en mode &amp;ldquo;audit&amp;rdquo; et voir si ça fonctionne.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;spec:
validationFailureAction: audit&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&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; kubectl apply -f https://raw.githubusercontent.com/kyverno/policies/main/other/unique-ingress-paths/unique-ingress-paths.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; clusterpolicy.kyverno.io/unique-ingress-host created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si vous avez un cluster kubernetes sous la main, je vous propose donc d&amp;rsquo;appliquer ces deux &lt;em&gt;Ingress&lt;/em&gt;, configurés pour récupérer du trafic depuis la même URL (toto.example.org) :&lt;/p&gt;
&lt;div class="highlight"&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; &lt;span class="k"&gt;for&lt;/span&gt; counter in &lt;span class="m"&gt;1&lt;/span&gt; 2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;cat &amp;gt; ingress${counter}.yaml &amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;apiVersion: networking.k8s.io/v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;kind: Ingress
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; name: ingress${counter}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; rules:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; - host: toto.example.org
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; http:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; paths:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; - path: /
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; pathType: Prefix
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; backend:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; service:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; name: service${counter}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; port:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; number: 4200
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; kubectl apply -f ingress1.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ingress.networking.k8s.io/ingress1 created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; kubectl apply -f ingress2.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ingress.networking.k8s.io/ingress2 created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Normalement, rien ne se passe. Kyverno ne vous empêche pas de créer ces deux &lt;em&gt;Ingress&lt;/em&gt; puisqu&amp;rsquo;on est en mode &amp;ldquo;validationFailureAction: audit&amp;rdquo; (même si les backends n&amp;rsquo;existent pas derrière, ça n&amp;rsquo;a pas d&amp;rsquo;importance).&lt;/p&gt;
&lt;div class="highlight"&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; kubectl get ingress
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; NAME CLASS HOSTS ADDRESS PORTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ingress1 &amp;lt;none&amp;gt; toto.example.org 80 90s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ingress2 &amp;lt;none&amp;gt; toto.example.org 80 87s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;En revanche, en allant lire les &lt;strong&gt;Events&lt;/strong&gt;, vous allez trouver des &lt;strong&gt;PolicyViolation&lt;/strong&gt; :&lt;/p&gt;
&lt;div class="highlight"&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; kubectl get events --field-selector&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PolicyViolation
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;LAST SEEN TYPE REASON OBJECT MESSAGE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;118s Warning PolicyViolation ingress/ingress1 policy unique-ingress-host/check-single-host fail: The Ingress host name must be unique.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;118s Warning PolicyViolation ingress/ingress1 policy unique-ingress-host/check-single-host fail: The Ingress host name must be unique.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;118s Warning PolicyViolation clusterpolicy/unique-ingress-host Ingress default/ingress1: [check-single-host] fail
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;115s Warning PolicyViolation clusterpolicy/unique-ingress-host Ingress default/ingress2: [check-single-host] fail
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le fait qu&amp;rsquo;on ait 2 &lt;em&gt;Ingress&lt;/em&gt; pointant sur la même URL est bien détecté et est remonté dans les &lt;em&gt;Events&lt;/em&gt; Kubernetes. Chouette :)&lt;/p&gt;
&lt;p&gt;En beaucoup plus verbeux, on pourra également récupérer les &lt;em&gt;PolicyReports&lt;/em&gt;, qui disent la même chose en (encore ?) moins lisible.&lt;/p&gt;
&lt;div class="highlight"&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="go"&gt;kubectl describe policyreport polr-ns-default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Name: polr-ns-default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Namespace: default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Labels: managed-by=kyverno
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Annotations: &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;API Version: wgpolicyk8s.io/v1alpha2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Kind: PolicyReport
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Results:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Category: Sample
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Message: The Ingress host name must be unique.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Policy: unique-ingress-host
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; API Version: networking.k8s.io/v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Kind: Ingress
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Name: ingress2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Summary:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Error: 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Fail: 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Pass: 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Skip: 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Warn: 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On nettoie pour la suite :&lt;/p&gt;
&lt;div class="highlight"&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; kubectl delete ingress ingress1 ingress2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="la-loi-cest-moi"&gt;&amp;ldquo;La loi, c&amp;rsquo;est moi&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;Imaginons que vous avez maintenant bien testé vos politiques, que vos devs ne génèrent plus de manifests ne correspondant pas à vos bonnes pratiques (ex. pas de containers en root, des labels obligatoires présents, limits/requests correctes, etc). Votre cluster est propre !&lt;/p&gt;
&lt;p&gt;Vous pouvez passer vos politiques de conformités en mode &amp;ldquo;enforcing&amp;rdquo;. Contrairement au mode &amp;ldquo;audit&amp;rdquo;, ce mode est bloquant et les manifests qui ne respectent pas les politiques en questions seront refusés par Kubernetes.&lt;/p&gt;
&lt;div class="highlight"&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; kubectl patch clusterpolicies.kyverno.io unique-ingress-host --patch &lt;span class="s1"&gt;&amp;#39;{&amp;#34;spec&amp;#34;:{&amp;#34;validationFailureAction&amp;#34;:&amp;#34;enforce&amp;#34;}}&amp;#39;&lt;/span&gt; --type merge
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Oui, je fais un &amp;ldquo;patch&amp;rdquo; plutôt qu&amp;rsquo;un &amp;ldquo;edit&amp;rdquo; pour me la péter ;-)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Et on teste à nouveau :&lt;/p&gt;
&lt;div class="highlight"&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; kubectl apply -f ingress1.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ingress.networking.k8s.io/ingress1 created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So far, so good&lt;/p&gt;
&lt;div class="highlight"&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; kubectl apply -f ingress2.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Error from server: error when creating &amp;#34;ingress2.yaml&amp;#34;: admission webhook &amp;#34;validate.kyverno.svc-fail&amp;#34; denied the request:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;resource Ingress/default/ingress2.yaml was blocked due to the following policies
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;unique-ingress-host:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; check-single-host: The Ingress host name must be unique.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ET PAF 💥🍫!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/05/chocapic.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Dans cet exemple basique (mais réellement utile), on arrive aussi facilement qu&amp;rsquo;avec OPA à empêcher les utilisateurs de créer des &lt;em&gt;Ingress&lt;/em&gt; ayant la même URL.&lt;/p&gt;
&lt;p&gt;On peut alors se demander &amp;ldquo;pourquoi préférer Kyverno ?&amp;rdquo;. Au-delà d&amp;rsquo;autres trucs sympas que nous verrons dans les billets suivants, ma réponse va tenir en une seule image.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/07/rego_vs_kyverno.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Mon point de vue (très personnel), c&amp;rsquo;est que le &lt;strong&gt;rego&lt;/strong&gt;, c&amp;rsquo;est illisible et verbeux, et donc pas maintenable. Là encore, c&amp;rsquo;est personnel, mais je n&amp;rsquo;ai jamais réussi à m&amp;rsquo;y mettre.&lt;/p&gt;
&lt;p&gt;Sans aller jusqu&amp;rsquo;à idéaliser le langage de Kyverno*, je trouve que c&amp;rsquo;est quand même clairement &lt;strong&gt;beaucoup plus simple&lt;/strong&gt; à écrire, mais surtout, à lire. Dans le cas où vous devriez modifier/écrire/maintenir vos propres politiques, cela sera très utile.&lt;/p&gt;
&lt;p&gt;(*Oui, il y a un langage, notamment pour le pattern matching&amp;hellip; et point irritant, ce n&amp;rsquo;est &lt;strong&gt;pas&lt;/strong&gt; jsonpath comme pour &lt;code&gt;kubectl&lt;/code&gt;, mais &lt;a class="link" href="https://kyverno.io/docs/writing-policies/variables/" target="_blank" rel="noopener"
&gt;JMESPath&lt;/a&gt;, similaire mais pas identique&amp;hellip;)&lt;/p&gt;
&lt;h2 id="ressources-complémentaires"&gt;Ressources complémentaires
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kyverno.io/policies/" target="_blank" rel="noopener"
&gt;&amp;ldquo;Catalogue&amp;rdquo; des Politiques officielles de Kyverno&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.padok.fr/blog/securite-kubernetes-kyverno" target="_blank" rel="noopener"
&gt;Blog de Padok - Kyverno : Sécuriser vos environnements Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://dev.to/aurelievache/understanding-kubernetes-part-44-tools-kyverno-52mc" target="_blank" rel="noopener"
&gt;Aurélie Vache - Understanding Kubernetes: part 44 – Tools - Kyverno &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/" target="_blank" rel="noopener"
&gt;Kubernetes Blog - A Guide to Kubernetes Admission Controllers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Retrouvez-moi à Devoxx France 2022</title><link>https://blog.zwindler.fr/2022/04/18/retrouvez-moi-a-devoxx-2022/</link><pubDate>Mon, 18 Apr 2022 12:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2022/04/18/retrouvez-moi-a-devoxx-2022/</guid><description>&lt;img src="https://blog.zwindler.fr/2022/04/devoxx.webp" alt="Featured image of post Retrouvez-moi à Devoxx France 2022" /&gt;&lt;h2 id="jai-10-ans-"&gt;J&amp;rsquo;ai 10 ans 🎶
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://www.devoxx.fr/" target="_blank" rel="noopener"
&gt;Devoxx France&lt;/a&gt; revient après deux ans de Covid (chaotique pour tout le monde) en présentiel total. Et ce n&amp;rsquo;est pas n&amp;rsquo;importe quelle édition de Devoxx, c&amp;rsquo;est l&amp;rsquo;année des 10 ans !&lt;/p&gt;
&lt;p&gt;Pour ceux qui ne sont pas familiers de cette conférence, Devoxx c&amp;rsquo;est :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Conférence pour développeurs organisée par Quantixx au Palais des Congrès, à Paris. 60 exposants pendant 3 jours. 3000 visiteurs. 260 orateurs, 220 présentations, ateliers et plénières.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Et cette fois-ci, je serai présent sur les 3 jours, en particulier le jeudi puisque je donnerai mon talk &amp;ldquo;Ciel, mon Kubernetes mine des bitcoins&amp;rdquo; (&lt;a class="link" href="https://blog.zwindler.fr/talks/2022-ciel-kubernetes-mine-bitcoins/index.html" target="_blank" rel="noopener"
&gt;slides ici, comme d&amp;rsquo;habitude&lt;/a&gt;). &lt;strong&gt;Je donnerai la conférence le jeudi à 14:30 en salle 243&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;en parle dans mon billet sur &lt;a class="link" href="https://blog.zwindler.fr/2022/03/30/combien-temps-pour-preparer-conf/" target="_blank" rel="noopener"
&gt;le temps que je mets à préparer une conférence&lt;/a&gt;, être sélectionné à des conférences, c&amp;rsquo;est compliqué, mais à Devoxx encore plus. Il y a beaucoup de speakers expérimentés et de nombreux talks passionnants en simultanée.&lt;/p&gt;
&lt;p&gt;Je suis donc particulièrement content de faire partie des heureux élus :). Une superbe occasion de revoir (ou rencontrer IRL) des copains des Internets et des anciens collègues (va même falloir faire une liste je crois 😅).&lt;/p&gt;
&lt;h2 id="comment-on-fait-pour-venir-"&gt;Comment on fait pour venir ?
&lt;/h2&gt;&lt;p&gt;Evidemment, il est trop tard pour prendre un billet pour les conférences (ça part en quelques heures), mais cette année il est possible d&amp;rsquo;accéder au salon et aux stands d&amp;rsquo;exposants gratuitement, et même d&amp;rsquo;accéder à certaines conférences, le jeudi soir.&lt;/p&gt;
&lt;p&gt;Seule condition, avoir un billet (gratuit du coup je le répète) pour avoir accès aux stands ou aux BOF (événement terminé).&lt;/p&gt;
&lt;p&gt;Dans tous les cas, que vous ayez un billet conférence, speaker ou &amp;ldquo;salon&amp;rdquo;, n&amp;rsquo;hésitez pas à me pinger sur &lt;a class="link" href="https://bsky.app/profile/zwindler.fr" target="_blank" rel="noopener"
&gt;Bluesky&lt;/a&gt;, Mastodon (@zwindler@framapiaf.org) ou &lt;a class="link" href="https://www.linkedin.com/in/denis-germain/" target="_blank" rel="noopener"
&gt;LinkedIn&lt;/a&gt; si vous voulez me faire un coucou 👋, ça sera avec grand plaisir !&lt;/p&gt;
&lt;h2 id="le-pitch-de-mon-talk"&gt;Le pitch de mon talk
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;A tort ou à raison, Kubernetes s’est imposé, ces 4 dernières années, comme un des nouveaux standards dans la gestion des architectures microservices modernes.&lt;/p&gt;
&lt;p&gt;Cependant cet outil, riche plus que complexe, amène son lot de pièges dans lesquels les débutants ne manqueront pas de tomber… Et ce n’est pas l’apparente facilité apportée par les offres Kubernetes as a Service qui va arranger les choses !&lt;/p&gt;
&lt;p&gt;Après un rapide tour de l’actualité sécurité autour de Kubernetes, je vous donnerai les clés pour éviter de vous faire voler (trop facilement) vos cycles CPU.&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>Open Source Experience 2021 – Récap’ du 2ème jour</title><link>https://blog.zwindler.fr/2021/11/12/open-source-experience-2021-recap-jour-2/</link><pubDate>Fri, 12 Nov 2021 07:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2021/11/12/open-source-experience-2021-recap-jour-2/</guid><description>&lt;img src="https://blog.zwindler.fr/2021/11/village.webp" alt="Featured image of post Open Source Experience 2021 – Récap’ du 2ème jour" /&gt;&lt;h2 id="deuxième-jour-à-osxp"&gt;Deuxième jour à OSXP
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://blog.zwindler.fr/2021/11/10/open-source-experience-2021-recap" &gt;Je vous parlais plus tôt dans la semaine de ma première journée à OSXP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il est donc temps d&amp;rsquo;enchaîner avec la seconde ;-)&lt;/p&gt;
&lt;h2 id="comment-contribuer-à-lopen-source-ou-pas-sans-être-éditeur-de-solutions-open-source"&gt;Comment contribuer à l’Open Source (ou pas) sans être éditeur de solutions Open Source
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai commencé la journée par un talk de Rémi VERCHERE, qui a fait un retour sur quasiment 20 ans d&amp;rsquo;expérience d&amp;rsquo;interaction, en entreprise, avec l&amp;rsquo;open source.&lt;/p&gt;
&lt;p&gt;Ce que je retiens de son intervention, c&amp;rsquo;est surtout que les entreprises ont pris conscience au fur et à mesure de l&amp;rsquo;intérêt de l&amp;rsquo;open source, pour &lt;strong&gt;elles&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Quand il travaillait dans le domaine de l&amp;rsquo;embarqué en début de carrière, l&amp;rsquo;open source était vu comme un simple moyen de mise en conformité (mise à disposition du code modifié). Au fur et à mesure qu&amp;rsquo;il a changé d&amp;rsquo;employeurs, il a pu à la fois influencer (demander à l&amp;rsquo;entreprise de contribuer financièrement, accepter de laisser les employé en inter-contrat contribuer, &amp;hellip;) et contribuer plus ouvertement.&lt;/p&gt;
&lt;p&gt;Aujourd&amp;rsquo;hui, dans son entreprise actuelle, c&amp;rsquo;est marqué noir sur blanc dans son contrat de travail qu&amp;rsquo;il peut (doit?) contribuer aux logiciels libres et à l&amp;rsquo;open source.&lt;/p&gt;
&lt;h2 id="créer-à-partir-de-zéro-un-chabot-opérationnel-en-20-minutes-avec-tock"&gt;Créer à partir de zéro un chabot opérationnel en 20 minutes avec Tock
&lt;/h2&gt;&lt;p&gt;Dans cette session, François NOLLEN nous a fait une démo live de la création d&amp;rsquo;un chatbot avec &lt;strong&gt;tock&lt;/strong&gt;, totalement offline et en quelques minutes.&lt;/p&gt;
&lt;p&gt;Si l&amp;rsquo;exemple montré en démo était assez basique, faute de temps, François nous a quand même montré tout un tas de piste d&amp;rsquo;amélioration pour le chatbot. C&amp;rsquo;était assez impressionnant de simplicité (intégrations multiples avec des providers tiers genre slack, twitter, etc) et de possibilités (apprentissage).&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://doc.tock.ai/fr/" target="_blank" rel="noopener"
&gt;doc.tock.ai&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="sexion-dasso-quiz"&gt;SEXION d’ASSO (quiz)
&lt;/h2&gt;&lt;p&gt;Entre midi et deux, le tissu associatif du logiciel libre français a réalisé un quiz rigolo sur les assoss' elles mêmes. C&amp;rsquo;était très bon enfant et amusant de voir que ces gens sont liés depuis très longtemps.&lt;/p&gt;
&lt;p&gt;Et surtout super intro en live de KPTN !&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/11/kptn.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="cloud--enseignements-tirés-de-mes-nos-échecs"&gt;Cloud : enseignements tirés de mes (nos) échecs
&lt;/h2&gt;&lt;p&gt;Jonathan LE LOUS, &lt;em&gt;Field CTO&lt;/em&gt; dans une grande ESN française, a commencé sa session par rappeler 2 choses en anglais :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I fail a lot&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;To be a master, you have to fail more often than the novice has tried&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/11/mistakes.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Grosso modo, dans son métier de consultant, beaucoup de paris qui ont été fait sur l&amp;rsquo;avenir se sont retrouvés être faux (la fin de VMware, l&amp;rsquo;ère du tout dans le cloud, l&amp;rsquo;hégémonie d&amp;rsquo;openstack, rien ne s&amp;rsquo;est réalisé).&lt;/p&gt;
&lt;p&gt;Ce qu&amp;rsquo;il explique ensuite, c&amp;rsquo;est qu&amp;rsquo;à chaque fois qu&amp;rsquo;il a &amp;ldquo;échoué&amp;rdquo; (terme à prendre avec des pincettes), c&amp;rsquo;est parce que lui et son équipe étaient trop enthousiastes. Ils pensaient pouvoir gérer tout une nouvelle plateforme, un nouveau produit (eg. Openstack) avec une équipe de 5 personnes.&lt;/p&gt;
&lt;p&gt;Sauf qu&amp;rsquo;il faut plus de 5 personnes et surtout, il faut d&amp;rsquo;abord casser les silos entre les devs (produit) et les ops, notamment via la communication et la collaboration.&lt;/p&gt;
&lt;p&gt;Sans jamais le citer, je pense qu&amp;rsquo;il voulait parler des principes de devops mais je n&amp;rsquo;ai pas insisté ;-).&lt;/p&gt;
&lt;h2 id="lopen-source--contributeur--essentiel-au-greenit"&gt;L’Open Source, « contributeur » essentiel au GreenIT
&lt;/h2&gt;&lt;p&gt;Table ronde avec :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Agnès CRÉPET - directrice Software Longevity &amp;amp; IT - Fairphone&lt;/li&gt;
&lt;li&gt;Cyrille CHAUSSON - Rédacteur en chef - IT for Business&lt;/li&gt;
&lt;li&gt;Richard HANNA - chargé de mission interministérielle Green Tech - Dinum&lt;/li&gt;
&lt;li&gt;Tristan NITOT - Fondateur - Octet Vert&lt;/li&gt;
&lt;li&gt;Véronique TORNER - Co-fondatrice et Co-Présidente - Alter way&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/11/greenit.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Le maitre mot de la table ronde était de rappeler que le plus gros pour de GES dans le numérique (et les GES ne sont pas le seul problème) proviennent de la fabrication du terminal et de très loin.&lt;/p&gt;
&lt;p&gt;Il est donc important de faire durer les terminaux le plus longtemps possible, notamment en lutant contre l&amp;rsquo;obsolescence programmée logicielle (aussi bien applicatives que OS).&lt;/p&gt;
&lt;p&gt;Les speakers ont mis le doigt sur les éditeurs de logiciels qui refusent de continuer à maintenir les vieux terminaux&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;La loi de moore a créé une industrie de feignasses. On a des processeurs 100 fois plus puissants mais on ne fait pas 100 fois mieux pour autant !
&amp;ndash; Tristan NITOT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Les intervenants ont également parlé de &lt;em&gt;Planet tech care&lt;/em&gt; : une association dédiée à la sensibilisation et acculturation des acteurs, notamment l&amp;rsquo;entreprise, notamment via la mise à disposition d&amp;rsquo;outils open source.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai un peu décroché quand ça a commencé à parler de &lt;strong&gt;The Shift Project&lt;/strong&gt;&amp;hellip;&lt;/p&gt;
&lt;h2 id="comment-accueillir-plus-de-femmes-dans-lécosystème-libre-éclairage-scientifique--pistes-concrètes"&gt;Comment accueillir plus de femmes dans l’écosystème libre? Éclairage scientifique &amp;amp; pistes concrètes
&lt;/h2&gt;&lt;p&gt;Lancelot PECQUET, maitre de conférence à Poitier et également auteur d&amp;rsquo;une enquête sur l&amp;rsquo;état de l&amp;rsquo;open source en France, nous a présenté la place des femmes dans l&amp;rsquo;open source, à partir de données scientifiques, psychologiques et éthiques.&lt;/p&gt;
&lt;p&gt;Cette étude est partie d&amp;rsquo;un constat : il y a proportionnellement moins de dirigeantes dans les entreprises numériques &lt;strong&gt;du libre&lt;/strong&gt; que dans la moyenne des entreprises numériques / startup.&lt;/p&gt;
&lt;p&gt;La première question qui vient à l&amp;rsquo;esprit est &amp;ldquo;pourquoi ?&amp;rdquo;. La seconde, la grande question étant &amp;ldquo;Comment faire un numérique plus inclusif ?&amp;rdquo;, et plus particulièrement dans le libre ?&lt;/p&gt;
&lt;p&gt;Car les enjeux sont multiples : éthiques, sociétaux, économiques (peut on vraiment se priver de 50% des talents ?).&lt;/p&gt;
&lt;p&gt;En étudiant ce problème par le scope de code reviews, les chercheurs ont été capables de déceler des biais &lt;strong&gt;dans les deux sens&lt;/strong&gt;. Dans tous les cas, connaître le genre d&amp;rsquo;un/d&amp;rsquo;une commiteur/trice affecte négativement la chance que la PR soit acceptée.
Cependant, &lt;em&gt;en fonction des cas&lt;/em&gt; (contributeur/trice interne ou pas au projet), les codes reviews étaient négativement OU positivement affectées (PR rejetée) si la contributrice est une femme.&lt;/p&gt;
&lt;p&gt;On connaît également l&amp;rsquo;impact plus fort du syndrome de l&amp;rsquo;imposteur chez les femmes.&lt;/p&gt;
&lt;p&gt;Les moyens connus pour combattre ces biais et ces stéréotypes consistent à mettre en avant des femmes qui réussissent dans la tech, des &amp;ldquo;role model&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Bien sûr, ce n&amp;rsquo;est évidemment pas si simple. Il est nécessaire que les femmes puissent s&amp;rsquo;identifier aux roles models (et donc d&amp;rsquo;augmenter la diversité dans les role models) pour éviter l&amp;rsquo;effet de contraste, qui est contre-productif :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;ces femmes sont des &lt;em&gt;wonderwomen&lt;/em&gt;, je ne me reconnais pas en elle donc je n&amp;rsquo;y arriverai pas&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bref, il reste beaucoup de travail.&lt;/p&gt;
&lt;h2 id="exposants-et-village-associatif"&gt;Exposants et village associatif
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai eu un discussion un peu trop courte mais intéressante avec quelqu&amp;rsquo;un de chez Prestashop (j&amp;rsquo;ai malheureusement oublié son nom désolé :-(&amp;hellip;) sur les services de streaming en ligne et surtout sur le fait qu&amp;rsquo;ils utilisent ou non des codecs libres. Une vraie réflexion&amp;hellip;&lt;/p&gt;
&lt;p&gt;Je me suis fait &amp;ldquo;pinger&amp;rdquo; sur Twitter par Jean Couteau, que j&amp;rsquo;avais connu &lt;a class="link" href="https://blog.zwindler.fr/2020/08/12/rrll-2020-le-logiciel-libre-a-t-il-de-beaux-jours-devant-lui/" &gt;lors des RRLL avec Alliance Libre en 2020&lt;/a&gt;. Je suis donc allé le voir sur le stand de &lt;a class="link" href="https://www.libre-entreprise.org/" target="_blank" rel="noopener"
&gt;réseau Libre-Entreprise&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Le réseau Libre-entreprise regroupe des entreprises à taille humaine ayant des spécialités proches ou complémentaires dans le domaine du logiciel libre. Toutes partagent les mêmes valeurs et modes de fonctionnement, basés sur la démocratie d&amp;rsquo;entreprise, la transparence et la compétence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C&amp;rsquo;était une conversation vraiment très riche, j&amp;rsquo;ai pu apprendre plein de choses sur les SCOP, mais aussi d&amp;rsquo;autres modèles d&amp;rsquo;entreprises comme celles à participation ouvrière. On a parlé entreprise horizontale, transparence, salaire unique.&lt;/p&gt;
&lt;p&gt;On a également échangé sur les leviers pour participer au libre en entreprise (mécénat, participation à des projets, hackatons, &amp;hellip;). De quoi donner quelques idées :-).&lt;/p&gt;
&lt;p&gt;Côté SIDO, j&amp;rsquo;ai rencontré la société Smile qui a présenté une petite maquette d&amp;rsquo;industrie 4.0 avec des machines connectées, des capteurs connectés, des serveurs open source, &amp;hellip; et réalité augmentée pour aider le technicien qui remplace les pièces détectées comme usées.&lt;/p&gt;
&lt;p&gt;Ca m&amp;rsquo;a fait sourire car c&amp;rsquo;est des choses que j&amp;rsquo;ai déjà vu (depuis longtemps) dans un contexte technique précédent ;-).&lt;/p&gt;
&lt;p&gt;Globalement, j&amp;rsquo;ai profité de ce dernier jour pour l&amp;rsquo;imprégner de la bonne ambiance générale dans le village associatif, avec des gens ouverts et respectueux, à 1000 lieues de tous les clichés qui peuvent encore tourner&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/11/village.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;A refaire !&lt;/p&gt;</description></item></channel></rss>