<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Docker on Zwindler's Reflection</title><link>https://blog.zwindler.fr/en/tags/docker/</link><description>Recent content in Docker on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Tue, 28 Apr 2026 10:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/en/tags/docker/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes UserNamespaces: the overhyped GA feature</title><link>https://blog.zwindler.fr/en/2026/04/28/kubernetes-usernamespaces-the-overhyped-ga-feature/</link><pubDate>Tue, 28 Apr 2026 10:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2026/04/28/kubernetes-usernamespaces-the-overhyped-ga-feature/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/04/usernamespaces.webp" alt="Featured image of post Kubernetes UserNamespaces: the overhyped GA feature" /&gt;&lt;h2 id="the-infographic-that-triggered-me"&gt;The infographic that triggered me
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Note: I stumbled upon this GenAI infographic and it was so bad I wrote a post about it. I didn&amp;rsquo;t generate this thing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Over the past few days, LinkedIn has been flooded with the same kind of infographic. Kubernetes 1.36 is out, and one of the most talked-about features is the GA release of &lt;strong&gt;UserNamespaces&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a topic I&amp;rsquo;ve been following since 2018 (talk &lt;a class="link" href="https://blog.zwindler.fr/2018/05/03/recap-du-premier-jour-de-kubecon-europe-2018/" target="_blank" rel="noopener"
&gt;The Route to rootless containers&lt;/a&gt; at KubeCon EU 2018), so I&amp;rsquo;m genuinely glad to see this long journey finally reach the finish line. That said, I&amp;rsquo;m appalled by the way it&amp;rsquo;s being marketed on LinkedIn, apparently by people who have no idea how it actually works — and frankly don&amp;rsquo;t care.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Kubernetes just made root safer. Just add &lt;code&gt;hostUsers: false&lt;/code&gt; to your Pod spec.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The visual: an all-powerful king &amp;ldquo;inside the container&amp;rdquo; and a helpless beggar &amp;ldquo;outside on the host&amp;rdquo;. The promise: &amp;ldquo;No Host Access. No Privilege Escalation. No Lateral Movement. No Node Takeover.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Catchy.&lt;/p&gt;
&lt;p&gt;But presenting it this way is genuinely dangerous, because it obscures entire areas of application and operational security. Selling &lt;code&gt;hostUsers: false&lt;/code&gt; as the universal fix for the &amp;ldquo;root in containers&amp;rdquo; problem is a dramatic oversimplification that will push teams to ignore the real security &lt;strong&gt;priorities&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="what-usernamespaces-actually-do"&gt;What UserNamespaces actually do
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;The threat model: container escape&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First, what are we actually talking about? A &lt;strong&gt;container escape&lt;/strong&gt; is when an attacker manages to break out of their container and directly access the host&amp;rsquo;s kernel or filesystem — completely bypassing the normal isolation mechanisms.&lt;/p&gt;
&lt;p&gt;This type of vulnerability is &lt;strong&gt;rare&lt;/strong&gt;, but real-world examples exist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2019-5736" target="_blank" rel="noopener"
&gt;CVE-2019-5736&lt;/a&gt;&lt;/strong&gt; (runc): write to &lt;code&gt;/proc/self/exe&lt;/code&gt; of the host process from inside the container&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2022-0492" target="_blank" rel="noopener"
&gt;CVE-2022-0492&lt;/a&gt;&lt;/strong&gt; (cgroups v1): escape via &lt;code&gt;unshare&lt;/code&gt; in certain configurations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2024-21626" target="_blank" rel="noopener"
&gt;CVE-2024-21626&lt;/a&gt;&lt;/strong&gt; (runc, &amp;ldquo;Leaky Vessels&amp;rdquo;): file descriptor leak to the host working directory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If there&amp;rsquo;s a vulnerability of this type on your node AND a process is compromised AND it runs as root in the container AND it doesn&amp;rsquo;t use UserNamespaces, the attacker gets &lt;strong&gt;root on the host&lt;/strong&gt; — &lt;strong&gt;game over&lt;/strong&gt;. Full access to every file on the node, every secret mounted by other pods, ability to install a rootkit or exfiltrate data from all tenants running on that node.&lt;/p&gt;
&lt;p&gt;It remains possible, but that&amp;rsquo;s a lot of &amp;ldquo;ifs&amp;rdquo;. Either way, this is exactly the scenario UserNamespaces address. They introduce &lt;strong&gt;UID mapping&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UID 0 inside the container is mapped to an unprivileged UID on the host (e.g. 100000, unique per pod)&lt;/li&gt;
&lt;li&gt;If an attacker successfully escapes the container via a kernel exploit, they land as &lt;strong&gt;&lt;code&gt;nobody&lt;/code&gt;&lt;/strong&gt; on the node — the escape succeeds, but the post-escape impact is dramatically reduced&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the &amp;ldquo;Breakouts Lose Impact&amp;rdquo; scenario from the infographic, and on that point, &lt;strong&gt;the infographic is right&lt;/strong&gt;. That&amp;rsquo;s the real value of the feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge case: multi-tenancy even with non-root containers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even without root containers, UserNamespaces provide something extra in a &lt;strong&gt;truly multi-tenant&lt;/strong&gt; context (multiple customers on the same cluster). Without UserNamespaces, if two pods from different customers both run with &lt;code&gt;runAsUser: 1000&lt;/code&gt;, they share the same UID 1000 on the node. If one escapes, the attacker can access files from the other pod with the same owner. UserNamespaces, by assigning a unique UID offset per pod, isolates UIDs between pods even when they use the same value inside the container.&lt;/p&gt;
&lt;p&gt;For internal clusters where you control all workloads, this scenario is theoretical. For a multi-tenant SaaS platform or a public build service, it&amp;rsquo;s a real line of defense.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical requirements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are a few prerequisites, but most up-to-date clusters should qualify.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux kernel ≥ 5.19&lt;/li&gt;
&lt;li&gt;Compatible runtime (containerd ≥ 1.7, CRI-O ≥ 1.25)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Idmapped mounts&lt;/em&gt; support for persistent volumes (XFS, ext4 — not NFS in all cases)&lt;/li&gt;
&lt;li&gt;Kubernetes ≥ 1.33 (Beta), ≥ 1.36 (GA)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-the-infographic-exaggerates-and-leaves-out"&gt;What the infographic exaggerates (and leaves out)
&lt;/h2&gt;&lt;p&gt;The infographic is right about one specific thing: UserNamespaces reduces the impact of a successful container escape. That&amp;rsquo;s real. The problem is it sells the feature as a universal solution to &amp;ldquo;root in containers&amp;rdquo; — and that&amp;rsquo;s just wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. UID isolation is not application privilege isolation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The infographic promises &amp;ldquo;No Lateral Movement&amp;rdquo;. That&amp;rsquo;s false — completely false.&lt;/p&gt;
&lt;p&gt;A root container with &lt;code&gt;hostUsers: false&lt;/code&gt; can still read the &lt;strong&gt;ServiceAccount Token&lt;/strong&gt; mounted at &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/code&gt;. If that token has broad RBAC permissions (which happens — we may cover this in a future post), the attacker can call the API Server, enumerate cluster resources, and move laterally — all without ever touching the host node.&lt;/p&gt;
&lt;p&gt;UID mapping protects the host. It does not protect the cluster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. A root container is still root inside the container&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Install Anything ✅&amp;rdquo; — it&amp;rsquo;s literally written in the infographic, presented as a feature 😖.&lt;/p&gt;
&lt;p&gt;In a root container (even with UserNS), an attacker who gains control can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;nmap&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;nc&lt;/code&gt; to scan the internal network&lt;/li&gt;
&lt;li&gt;Modify application files, binaries, configurations&lt;/li&gt;
&lt;li&gt;Read all files mounted as volumes&lt;/li&gt;
&lt;li&gt;Persist in the container across restarts if the filesystem is writable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UserNamespaces removes none of these attack vectors. The ability to install software is a fast track to lateral movement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. It&amp;rsquo;s not that easy, especially for storage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Enabling &lt;code&gt;hostUsers: false&lt;/code&gt; breaks existing storage in most cases.&lt;/p&gt;
&lt;p&gt;Container UID 0 is mapped to UID 100000+ on the host (each container has its own offset). If a persistent volume (NFS, EBS, Ceph RBD) is owned by UID 1000, the root container can&amp;rsquo;t read or write it. The result: counterintuitive &lt;code&gt;Permission Denied&lt;/code&gt; errors that are potentially hard to diagnose, since the application was likely never designed to be root yet have no access to its own files.&lt;/p&gt;
&lt;p&gt;The technical solution exists (&lt;em&gt;idmapped mounts&lt;/em&gt;), but it requires a recent kernel and a compatible filesystem. See the &lt;a class="link" href="https://www.kernel.org/doc/html/latest/filesystems/idmappings.html" target="_blank" rel="noopener"
&gt;official idmapped mounts documentation&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Same story, but for networking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hostUsers: false&lt;/code&gt; is incompatible with &lt;code&gt;hostNetwork: true&lt;/code&gt;. It&amp;rsquo;s a corner case, but it catches networking workloads (monitoring agents, CNI plugins, etc.).&lt;/p&gt;
&lt;p&gt;Note: that said, running containers with &lt;code&gt;hostNetwork&lt;/code&gt; is &lt;strong&gt;its own security problem&lt;/strong&gt;, so&amp;hellip;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="honest-comparison-userns-vs-the-real-alternatives"&gt;Honest comparison: UserNS vs the real alternatives
&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Attack vector&lt;/th&gt;
&lt;th style="text-align: center"&gt;UserNS (root inside)&lt;/th&gt;
&lt;th style="text-align: center"&gt;Non-root (UID 1000)&lt;/th&gt;
&lt;th style="text-align: center"&gt;Distroless / Scratch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Post-escape impact after successful container escape&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Nobody on host&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ UID 1000 on host&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ UID 1000 on host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;UID isolation between pods (multi-tenant)&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Unique offset per pod&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Shared UID on node&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Shared UID on node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Malware installation inside the container&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Trivial&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Near impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Write scope in ephemeral container FS&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Full filesystem&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ App directory only&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Near impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Lateral movement via SA Token&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ Potentially difficult&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Operational complexity&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Sometimes high&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Often near zero&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Often low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Compatibility with existing storage&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Sometimes problematic&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Standard&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Standard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Reading the table reveals the true nature of UserNamespaces: it excels on &lt;strong&gt;exactly two rows&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;post-escape impact&lt;/li&gt;
&lt;li&gt;UID isolation in multi-tenant environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On everything else, non-root + distroless does better, or just as well, without the operational complexity. And that &amp;ldquo;everything else&amp;rdquo; — write scope in ephemeral FS, malware installation, lateral movement via SA Token — represents the vast majority of real-world attack vectors, far more common than a container escape. We&amp;rsquo;ll come back to this in the &lt;a class="link" href="#where-to-invest-your-security-budget" &gt;Where to invest your security budget&lt;/a&gt; section.&lt;/p&gt;
&lt;h2 id="real-use-cases"&gt;Real use cases
&lt;/h2&gt;&lt;p&gt;It would be dishonest to dismiss the feature entirely. There are three scenarios where UserNamespaces aren&amp;rsquo;t a lazy option but a genuine technical necessity (with caveats).&lt;/p&gt;
&lt;h3 id="1-build-as-a-service-buildah-rootless-podman"&gt;1. Build-as-a-Service (Buildah, rootless Podman)
&lt;/h3&gt;&lt;p&gt;To build a Docker image, the build engine needs to perform &lt;code&gt;chown&lt;/code&gt;, &lt;code&gt;chmod&lt;/code&gt; and &lt;code&gt;mknod&lt;/code&gt;. These operations require &lt;code&gt;CAP_CHOWN&lt;/code&gt; and &lt;code&gt;CAP_FOWNER&lt;/code&gt;. Before UserNamespaces, the solution was to run the pod as &lt;code&gt;--privileged&lt;/code&gt; — an obvious open door to the host.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;hostUsers: false&lt;/code&gt;, the build engine believes it&amp;rsquo;s root for its own file manipulation, but it can&amp;rsquo;t touch the host. This is the only case where &amp;ldquo;root inside&amp;rdquo; is a technical constraint rather than technical debt.&lt;/p&gt;
&lt;p&gt;Note: &lt;a class="link" href="https://github.com/GoogleContainerTools/kaniko" target="_blank" rel="noopener"
&gt;Kaniko&lt;/a&gt;, long the go-to for in-cluster builds, has been archived since October 2025 and no longer receives security updates. Buildah or rootless Podman are the active alternatives.&lt;/p&gt;
&lt;p&gt;My take: it can be useful for shared CI/CD platforms (GitLab Runners, Tekton) that refuse privileged pods. But if isolation is critical (public platform, aggressive multi-tenancy), microVMs (Kata Containers, Firecracker) offer far stronger guarantees for an overhead that has become quite manageable.&lt;/p&gt;
&lt;h3 id="2-hostile-multi-tenancy-user-code-platforms"&gt;2. Hostile multi-tenancy (user code platforms)
&lt;/h3&gt;&lt;p&gt;If your business is running code provided by strangers (PaaS, online code editors, public CI/CD), you know upfront that users &lt;em&gt;will&lt;/em&gt; try to escalate their privileges. In this context, UserNS is an extra barrier against kernel 0-days.&lt;/p&gt;
&lt;p&gt;My take: honestly, if the environment is truly &lt;strong&gt;hostile&lt;/strong&gt;, UserNS alone isn&amp;rsquo;t enough. MicroVMs (Kata Containers, Firecracker) provide real hardware isolation and are the right choice here. UserNS can be a complement, not a substitute.&lt;/p&gt;
&lt;h3 id="3-hard-coded-legacy-postfix-dovecot-bind"&gt;3. Hard-coded legacy (Postfix, Dovecot, BIND)
&lt;/h3&gt;&lt;p&gt;Some old UNIX daemons start as root to open a privileged port (&amp;lt; 1024) or read sensitive config files, then drop privileges via &lt;code&gt;setuid()&lt;/code&gt;. This mechanism fails in a classic non-root container.&lt;/p&gt;
&lt;p&gt;UserNamespaces let these processes believe they can make their identity management syscalls, because they are root inside their namespace.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a concrete example written by a colleague (thanks Louis 😘):&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;Pod&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;postfix&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;hostUsers: false # UID mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root in container → nobody on host&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;securityContext&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;runAsNonRoot: false # allowed under PSS Restricted *only* because of hostUsers&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 class="nt"&gt;fsGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;103&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# postfix GID&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;containers&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;postfix&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;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postfix:latest&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;securityContext&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;runAsNonRoot&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 class="c"&gt;# same — cf. https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#integration-with-pod-security-admission-checks&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;allowPrivilegeEscalation&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 class="nt"&gt;readOnlyRootFilesystem&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;seccompProfile&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RuntimeDefault&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;capabilities&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;drop&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="s2"&gt;&amp;#34;ALL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# drop everything first&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;add&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;SETUID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# privilege drop via setuid() done by postfix itself at startup&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;SETGID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# same for groups&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;CHOWN &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# chown on mail queues at startup&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;FOWNER &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# file operations without being the owner&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;FSETID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# preserve setuid bit after write&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;DAC_OVERRIDE # MANDATORY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root in UserNS is not &amp;#34;real&amp;#34; root —&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="c"&gt;# DAC checks are not automatically bypassed&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;This manifest illustrates several important things.&lt;/p&gt;
&lt;p&gt;First, making a legacy application actually secure with UserNS is painful and requires compromises — especially around capabilities. This is a far cry from the &amp;ldquo;magic feature that secures root apps&amp;rdquo; the LinkedIn wanna-be influencers imply.&lt;/p&gt;
&lt;p&gt;Then there are some interesting surprises. Normally, the &lt;code&gt;Restricted&lt;/code&gt; Pod Security Standard forbids &lt;code&gt;runAsNonRoot: false&lt;/code&gt;. Kubernetes makes an exception when &lt;code&gt;hostUsers: false&lt;/code&gt; is present. This is documented &lt;a class="link" href="https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#integration-with-pod-security-admission-checks" target="_blank" rel="noopener"
&gt;here&lt;/a&gt;. Without UserNamespaces, this pod would be rejected by the admission controller.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also the &lt;strong&gt;&lt;code&gt;DAC_OVERRIDE&lt;/code&gt; capability&lt;/strong&gt;, which is counterintuitive. Root in a UserNS is not real root from the kernel&amp;rsquo;s perspective for DAC (Discretionary Access Control) checks. When Postfix runs &lt;code&gt;set-permissions&lt;/code&gt; to &lt;code&gt;chown&lt;/code&gt; its queues, the kernel still verifies permissions — and denies them if &lt;code&gt;DAC_OVERRIDE&lt;/code&gt; isn&amp;rsquo;t present. This is exactly the kind of operational surprise that stays invisible until the first production deployment.&lt;/p&gt;
&lt;p&gt;Worth noting: we were still able to keep &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; and &lt;code&gt;allowPrivilegeEscalation: false&lt;/code&gt; — legacy doesn&amp;rsquo;t justify throwing everything overboard.&lt;/p&gt;
&lt;p&gt;My take: this is the only use case where UserNS is genuinely acceptable. No untrusted third-party code, no hostile platform — just well-identified legacy with a &lt;strong&gt;migration plan&lt;/strong&gt;. The other two cases are &amp;ldquo;acceptable under conditions&amp;rdquo;; the legacy case is the cleanest of the three.&lt;/p&gt;
&lt;h2 id="some-counterarguments"&gt;Some counterarguments
&lt;/h2&gt;&lt;p&gt;I see you coming with objections, so let&amp;rsquo;s save everyone some time with a quick Q&amp;amp;A:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;It&amp;rsquo;s defense in depth.&amp;rdquo;&lt;/strong&gt;
True — but defense in depth assumes the foundational layers are already in place. If you haven&amp;rsquo;t migrated your images to non-root yet, investing energy in UserNS is putting the cart before the horse. And once you&amp;rsquo;re non-root, the marginal gain of UserNS is negligible compared to the complexity it introduces.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;We don&amp;rsquo;t control third-party images.&amp;rdquo;&lt;/strong&gt;
Somewhat weak, in my opinion: if a proprietary vendor&amp;rsquo;s black-box image is hardcoded to run as root, there&amp;rsquo;s a good chance it either genuinely needs it (as is the case for some proprietary security tooling) or it will break with UID mapping (see the storage problem above). UserNS is not a magic wand that makes any third-party image compatible and secure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;It&amp;rsquo;s a centralized safeguard against human error.&amp;rdquo;&lt;/strong&gt;
It&amp;rsquo;s just as easy to forget &lt;code&gt;hostUsers: false&lt;/code&gt; as it is to forget &lt;code&gt;runAsNonRoot: true&lt;/code&gt;. The real centralized solution is &lt;strong&gt;Pod Security Standards&lt;/strong&gt; or an Admission Controller (Kyverno, OPA) that outright rejects root pods. Simpler, more reliable, and it doesn&amp;rsquo;t break storage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;We need it for SOC2/PCI-DSS/&amp;hellip; compliance.&amp;rdquo;&lt;/strong&gt;
If your compliance requires strict tenant isolation, UserNS will likely be deemed insufficient by your auditors. VMs or microVMs remain the gold standard. Using UserNS for compliance means choosing the most complex tool to maintain for a result that remains debatable.&lt;/p&gt;
&lt;h2 id="where-to-invest-your-security-budget"&gt;Where to invest your security budget
&lt;/h2&gt;&lt;p&gt;Setting aside the marketing, here&amp;rsquo;s where effort actually pays off — from highest impact to most niche:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priority 1 — Non-root images + &lt;code&gt;nobody&lt;/code&gt; (UID 65534)&lt;/strong&gt;
Move images to non-root, ideally using the &lt;code&gt;nobody&lt;/code&gt; user (the least privileged on the system). If an application is compromised under &lt;code&gt;nobody&lt;/code&gt;, the attacker can do almost nothing, even on the container filesystem. Combine with &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; and &lt;code&gt;capabilities: drop: [&amp;quot;ALL&amp;quot;]&lt;/code&gt;.&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;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;securityContext&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;runAsNonRoot&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;runAsUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65534&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# nobody&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;seccompProfile&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RuntimeDefault&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;containers&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;app&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;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-app:distroless&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;securityContext&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;allowPrivilegeEscalation&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 class="nt"&gt;capabilities&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;drop&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="s2"&gt;&amp;#34;ALL&amp;#34;&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;readOnlyRootFilesystem&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;&lt;strong&gt;Priority 2 — Pod Security Standards (PSS) at &lt;code&gt;Baseline&lt;/code&gt; or &lt;code&gt;Restricted&lt;/code&gt;&lt;/strong&gt;
Block root and privileges without breaking anything at the infra level. It requires having done Priority 1 first, but it&amp;rsquo;s free, standard, and applies cluster-wide via a namespace label (with per-namespace overrides when needed). No more risk of forgetting. Already enabled by default on several Kubernetes distributions (Talos being one, but not the only one).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priority 3 — MicroVMs (Kata Containers, Firecracker)&lt;/strong&gt;
For truly untrusted workloads. Real hardware isolation, overhead now quite reasonable on recent generations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priority 4 — UserNamespaces&lt;/strong&gt;
When all else fails. Only for the legitimate cases identified above (builds, legacy, hostile multi-tenancy). This is genuinely the &lt;strong&gt;last&lt;/strong&gt; thing to do.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Kubernetes 1.36 UserNamespaces are the result of a project that officially took five years (KEP-127 dates back to 2021) and has been discussed since nearly the dawn of Kubernetes. For shared build platforms and multi-tenant SaaS running user-provided code, it&amp;rsquo;s a potentially useful building block — particularly to prevent one customer&amp;rsquo;s app from reading another&amp;rsquo;s in the event of a container escape without privilege escalation.&lt;/p&gt;
&lt;p&gt;For everything else — that is to say, 99% of production clusters — that&amp;rsquo;s not where container security starts. And that&amp;rsquo;s precisely the problem with this kind of infographic.&lt;/p&gt;
&lt;p&gt;LinkedIn infographics selling effortless security are dangerous: &amp;ldquo;keep your 800MB root image full of tools, just add &lt;code&gt;hostUsers: false&lt;/code&gt;, and you&amp;rsquo;re protected.&amp;rdquo; That&amp;rsquo;s exactly &lt;strong&gt;the wrong approach&lt;/strong&gt;. Real container security is built in the Dockerfile, not in the PodSpec.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&amp;rsquo;re enabling UserNamespaces to secure an application whose source code you own, you&amp;rsquo;ve probably missed a step in your secure software development lifecycle.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="references"&gt;References
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/127-user-namespaces/README.md" target="_blank" rel="noopener"
&gt;KEP-127: Support for User Namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/" target="_blank" rel="noopener"
&gt;Kubernetes docs — User Namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/concepts/security/pod-security-standards/" target="_blank" rel="noopener"
&gt;Pod Security Standards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2019-5736" target="_blank" rel="noopener"
&gt;CVE-2019-5736&lt;/a&gt; — runc: write to host&amp;rsquo;s &lt;code&gt;/proc/self/exe&lt;/code&gt; from inside the container&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2022-0492" target="_blank" rel="noopener"
&gt;CVE-2022-0492&lt;/a&gt; — cgroups v1: escape via &lt;code&gt;unshare&lt;/code&gt;, UserNS helps but &lt;code&gt;runAsNonRoot&lt;/code&gt; does too&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2024-21626" target="_blank" rel="noopener"
&gt;CVE-2024-21626&lt;/a&gt; — runc &amp;ldquo;Leaky Vessels&amp;rdquo;: file descriptor leak to host working directory&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/GoogleContainerTools/distroless" target="_blank" rel="noopener"
&gt;Distroless containers — GoogleContainerTools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Running Docker containers with Proxmox VE (and LXC)</title><link>https://blog.zwindler.fr/en/2022/11/07/running-docker-containers-with-proxmox-ve-and-lxc/</link><pubDate>Mon, 07 Nov 2022 06:30:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2022/11/07/running-docker-containers-with-proxmox-ve-and-lxc/</guid><description>&lt;img src="https://blog.zwindler.fr/2019/10/proxmox.webp" alt="Featured image of post Running Docker containers with Proxmox VE (and LXC)" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;I found several tutorials to install Docker (engine) &lt;strong&gt;inside&lt;/strong&gt; an LXC container under Proxmox VE to then run Docker containers inside it. The problem is that you have to log &lt;strong&gt;into&lt;/strong&gt; the LXC container before you can interact with your Docker containers.&lt;/p&gt;
&lt;p&gt;You can also install Docker directly on Proxmox VE but it&amp;rsquo;s not really recommended and you don&amp;rsquo;t have the ease of use of LXC with Proxmox GUI.&lt;/p&gt;
&lt;p&gt;And actually, it turns out you can run Docker images &lt;strong&gt;as the OS&lt;/strong&gt; of an LXC container and thus have Docker controlled by Proxmox. So we won&amp;rsquo;t have 100% identical behavior to if you had done a &lt;code&gt;docker run&lt;/code&gt; on your machine.&lt;/p&gt;
&lt;p&gt;However, we&amp;rsquo;ll gain in exchange a container totally identical to your other LXC containers in your cluster (with all associated functions) but launched with a Docker image.&lt;/p&gt;
&lt;p&gt;But first I suggest we go back a bit before showing you how&amp;hellip;&lt;/p&gt;
&lt;h2 id="context"&gt;Context
&lt;/h2&gt;&lt;p&gt;You know, I&amp;rsquo;ve been doing Proxmox VE for a while. Proxmox VE is a great turnkey and production-ready distribution for server virtualization (it&amp;rsquo;s even hyperconverged if you enable the Ceph part).&lt;/p&gt;
&lt;p&gt;There are lots of little things I like about Proxmox VE, and one of them is that we can create Linux containers with LXC rather than QEMU virtual machines. &lt;a class="link" href="https://blog.zwindler.fr/2022/10/22/proxmox-tips-tricks/" &gt;I wrote an article to talk about it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In use (except for specific kernel configuration), the LXC container behaves strictly identically to a VM in Proxmox, but for a fraction of the resources! We have an OS, we install applications on it, like a VM.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/11/nginx.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An nginx frontend for several applications. The complete LXC container only consumes a few MB&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Admittedly it&amp;rsquo;s much less isolated than a real VM (because it&amp;rsquo;s a container, we&amp;rsquo;re isolated from other processes, but we run on the hypervisor&amp;rsquo;s kernel directly).&lt;/p&gt;
&lt;p&gt;But with so few resource needs, I can host many small applications in different contexts with very small physical machines (Atom 4GB RAM at €6 per month at OneProviders) which would be impossible with a real VM.&lt;/p&gt;
&lt;h2 id="its-good-but-not-enough"&gt;It&amp;rsquo;s good, but not enough
&lt;/h2&gt;&lt;p&gt;However, LXC is not the most known/hyped containerization technology. For years on the forum, whenever people ask for Docker support in Proxmox VE, they get sent away. Not always very nicely either&amp;hellip;&lt;/p&gt;
&lt;p&gt;Yet, there are several reasons to want to run Docker containers on your Proxmox infrastructure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, Docker isn&amp;rsquo;t as much stateless and/or ephemeral workloads as we think. Many containerized applications would have their place on a virtualization cluster.&lt;/li&gt;
&lt;li&gt;Then because many software vendors have become lazy. Some no longer manage installation processes (or only on one OS) and just provide a Docker image that they maintain up to date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we have to settle for VMs in Proxmox to run Docker containers (see my point at the beginning).&lt;/p&gt;
&lt;h2 id="lxc-supports-oci-images"&gt;LXC supports OCI images
&lt;/h2&gt;&lt;p&gt;Plot twist. A few days ago, I discovered that LXC, Proxmox VE&amp;rsquo;s containerization engine, was compatible with the OCI format.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.buzzwrd.me/index.php/2021/03/10/creating-lxc-containers-from-docker-and-oci-images/" target="_blank" rel="noopener"
&gt;buzzwrd.me - Creating LXC containers from docker and OCI images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For those who don&amp;rsquo;t know OCI, it&amp;rsquo;s an organization aiming to create a standard to unify the way containers are stored.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Open Container Initiative is an open governance structure for the express purpose of creating open industry standards around container formats and runtimes.
&lt;a class="link" href="https://opencontainers.org/" target="_blank" rel="noopener"
&gt;opencontainers.org/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And Docker respects this format: we can therefore theoretically run from LXC containers whose base image is a Docker image.&lt;/p&gt;
&lt;h2 id="so-when-do-we-start"&gt;So, when do we start?
&lt;/h2&gt;&lt;p&gt;Disclaimer: what follows is tinkering. Nothing is supported and I don&amp;rsquo;t recommend it in production. There are also likely limitations (especially regarding storage).&lt;/p&gt;
&lt;p&gt;First, some dependencies are missing for LXC&amp;rsquo;s OCI function to be usable under Proxmox VE:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt install skopeo umoci jq
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, by default, LXC containers attach to a Linux bridge called &lt;code&gt;lxcbr0&lt;/code&gt;. However this bridge probably doesn&amp;rsquo;t exist on your Proxmox VE installation (not installed by default).&lt;/p&gt;
&lt;p&gt;In &lt;em&gt;quick and dirty&lt;/em&gt;, we can just replace the default name, although ideally we should find the flag in the CLI to change the bridge. Depending on how you configured the network in your Proxmox VE server, you&amp;rsquo;ll probably use &lt;code&gt;vmbr0&lt;/code&gt; or &lt;code&gt;vmbr1&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sed -i &amp;#39;s/lxcbr0/vmbr1/g&amp;#39; /etc/lxc/default.conf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now that we have everything, we can run our LXC containers from docker images. Here I&amp;rsquo;m running the official &lt;code&gt;alpine:latest&lt;/code&gt; image, but I could have taken any other:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lxc-create 500 -t oci -- --url docker://alpine:latest
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I created a container called &amp;ldquo;500&amp;rdquo; (because VMs and LXC containers in proxmox VE are called with numbers). The image is downloaded then the container created:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/11/lxc_docker_pull.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;We can now start it (with &lt;code&gt;lxc-execute&lt;/code&gt; we run the container and open a prompt directly inside it, but we also have &lt;code&gt;lxc-start&lt;/code&gt; and &lt;code&gt;lxc-attach&lt;/code&gt;)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lxc-execute 500
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/11/alpine.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;As is, Proxmox VE doesn&amp;rsquo;t know our container exists and it won&amp;rsquo;t appear in the UI. But it works ;-).&lt;/p&gt;
&lt;h2 id="bonus"&gt;Bonus
&lt;/h2&gt;&lt;p&gt;We can even cheat and make Proxmox believe it&amp;rsquo;s an LXC container it created itself by creating a &lt;code&gt;500.conf&lt;/code&gt; file, in the &lt;code&gt;/etc/pve/lxc/&lt;/code&gt; folder&lt;/p&gt;
&lt;p&gt;The only mandatory fields are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;arch (amd64 unless you&amp;rsquo;re on ARM)&lt;/li&gt;
&lt;li&gt;cores (the CPU limit to impose on the container)&lt;/li&gt;
&lt;li&gt;hostname&lt;/li&gt;
&lt;li&gt;memory (the RAM limit to impose on the container)&lt;/li&gt;
&lt;li&gt;ostype&lt;/li&gt;
&lt;li&gt;rootfs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/11/lxc_config.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Once the file is created, the container automatically appears in the host&amp;rsquo;s VM list. We can start it and it works :)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/11/docker_lxc_running.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;The container can be started, stopped, etc. All from the Proxmox VE GUI :)&lt;/p&gt;
&lt;p&gt;Have fun!&lt;/p&gt;
&lt;h2 id="source"&gt;Source
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://pve.proxmox.com/wiki/Linux_Container" target="_blank" rel="noopener"
&gt;Proxmox VE - Linux Container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://thehomelab.wiki/books/promox-ve/page/setup-and-install-docker-in-a-promox-7-lxc-conainer" target="_blank" rel="noopener"
&gt;Setup and Install Docker in a Promox 7 LXC Conainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.buzzwrd.me/index.php/2021/03/10/creating-lxc-containers-from-docker-and-oci-images/" target="_blank" rel="noopener"
&gt;buzzwrd.me - Creating LXC containers from docker and OCI images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Kubernetes Compliance Policies with Kyverno - part 2</title><link>https://blog.zwindler.fr/en/2022/09/05/kubernetes-compliance-policies-with-kyverno-part-2/</link><pubDate>Mon, 05 Sep 2022 06:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2022/09/05/kubernetes-compliance-policies-with-kyverno-part-2/</guid><description>&lt;img src="https://blog.zwindler.fr/2022/07/Kyverno_Horizontal.webp" alt="Featured image of post Kubernetes Compliance Policies with Kyverno - part 2" /&gt;&lt;h2 id="part-1-recap"&gt;Part 1 recap
&lt;/h2&gt;&lt;p&gt;In the &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;previous article&lt;/a&gt;, I introduced &lt;a class="link" href="https://kyverno.io/" target="_blank" rel="noopener"
&gt;Kyverno&lt;/a&gt;, a &amp;ldquo;cloud native&amp;rdquo; tool for managing compliance policies on Kubernetes.&lt;/p&gt;
&lt;p&gt;To illustrate the tool&amp;rsquo;s capabilities, I used an example: preventing two Ingresses from using the same entry URL. I had done the same exercise with OPA in an earlier article (&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) and its Kubernetes counterpart, Gatekeeper&lt;/a&gt;) and compared the two syntaxes (Kyverno vs rego) at the end.&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;I have a clear preference for Kyverno&amp;rsquo;s syntax, even though OPA has been around longer and isn&amp;rsquo;t limited to Kubernetes (Kyverno is Kube-only).&lt;/p&gt;
&lt;h2 id="going-a-bit-further"&gt;Going a bit further
&lt;/h2&gt;&lt;p&gt;Beyond my example, I mentioned that Kyverno provides a very large number of pre-written policies (191 at the time of writing).&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;By default, most available policies are set to &amp;ldquo;audit&amp;rdquo; mode initially. Don&amp;rsquo;t forget to switch them to &amp;ldquo;enforce&amp;rdquo; mode once your cluster is mature enough.&lt;/p&gt;
&lt;p&gt;Obviously, when you&amp;rsquo;re just getting started, you don&amp;rsquo;t really know where to begin. The simplest approach is to deploy the &lt;code&gt;kyverno/kyverno-policies&lt;/code&gt; Helm chart, which contains the Kyverno equivalent of the &amp;ldquo;Pod Security Standards&amp;rdquo; (PSPs being officially deprecated since 1.21 I think, and no longer usable in 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;Here&amp;rsquo;s what was deployed (the last one was already there from the previous article):&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;With that, we already have plenty to work with to educate our users (developers) on producing secure Kubernetes deployments.&lt;/p&gt;
&lt;h2 id="user-experience"&gt;User experience
&lt;/h2&gt;&lt;p&gt;One of the annoying things about both tools is that out of the box, they&amp;rsquo;re not very user friendly. Personally, running a &lt;code&gt;kubectl get events&lt;/code&gt; command (or &lt;code&gt;kubectl get events --field-selector=reason=PolicyViolation&lt;/code&gt; in Kyverno&amp;rsquo;s case) to figure out why a deployment failed doesn&amp;rsquo;t bother me much (I almost enjoy it).&lt;/p&gt;
&lt;p&gt;But for some people, it&amp;rsquo;s a bit off-putting, and I can understand that.&lt;/p&gt;
&lt;p&gt;My goal is to make infrastructure simpler for people whose job it isn&amp;rsquo;t, and imposing my way of working isn&amp;rsquo;t necessarily the best approach.&lt;/p&gt;
&lt;p&gt;Often, to help adopt a new tool or practice, the simplest thing is to provide a graphical interface.&lt;/p&gt;
&lt;p&gt;And it turns out Kyverno has a tool for that.&lt;/p&gt;
&lt;h2 id="kyverno-policy-reporter"&gt;Kyverno policy reporter
&lt;/h2&gt;&lt;p&gt;In reality, the tool wasn&amp;rsquo;t originally designed for that exact purpose. But you&amp;rsquo;ll see it&amp;rsquo;s actually even better.&lt;/p&gt;
&lt;p&gt;Historically, it&amp;rsquo;s called policy reporter because the developers needed a tool to easily and quickly export the &lt;em&gt;PolicyReports&lt;/em&gt; I mentioned in the previous article:&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;The policy reporter&amp;rsquo;s role is to export PolicyReports to several external sources for easier processing:&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;I&amp;rsquo;ll cover 2 of them: &lt;strong&gt;Slack&lt;/strong&gt; and &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;I&amp;rsquo;ll start with the UI since it&amp;rsquo;s the simplest to set up. You just need to deploy the Policy Reporter Helm chart, making sure to specify that you want the 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;We could obviously add an Ingress, but if you&amp;rsquo;re lazy like me during this demo, a simple port-forward will do.&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;The UI itself is fairly intuitive. It includes notably:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a Dashboard with a big global indicator showing how many policy violations you have&lt;/li&gt;
&lt;li&gt;a page with your &amp;ldquo;policy reports&amp;rdquo;, filterable by namespace or policy&lt;/li&gt;
&lt;li&gt;the list of your policies&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;You can immediately see the value of alerting administrators in Slack (or Teams&amp;hellip;) when a policy violation occurs.&lt;/p&gt;
&lt;p&gt;The configuration is fairly trivial — you just need to add a webhook in Slack and a small piece of configuration in 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;And we re-apply all this to our Helm release:&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: If you don&amp;rsquo;t know how to do this, you need to be admin of the Slack workspace, create an application at &lt;a class="link" href="https://api.slack.com/apps" target="_blank" rel="noopener"
&gt;https://api.slack.com/apps&lt;/a&gt;, enable &lt;code&gt;Incoming Webhooks&lt;/code&gt;, and create one with permissions on a 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="how-about-we-trigger-a-policy-violation-just-to-see"&gt;How about we trigger a policy violation, just to see?
&lt;/h2&gt;&lt;p&gt;To keep it simple, I went back to the example from the previous article. I switch my &lt;strong&gt;unique-ingress-host&lt;/strong&gt; policy back to &lt;strong&gt;audit&lt;/strong&gt; mode and recreate the problematic ingress:&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;Instantly, 2 failures show up in the Kyverno UI, along with messages in 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;Victory :)&lt;/p&gt;
&lt;h2 id="additional-resources"&gt;Additional Resources
&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;Catalog&amp;rdquo; of official Kyverno Policies&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>Kubernetes Compliance Policies with Kyverno</title><link>https://blog.zwindler.fr/en/2022/08/01/kubernetes-compliance-policies-with-kyverno/</link><pubDate>Mon, 01 Aug 2022 06:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/en/2022/08/01/kubernetes-compliance-policies-with-kyverno/</guid><description>&lt;img src="https://blog.zwindler.fr/2022/07/Kyverno_Horizontal.webp" alt="Featured image of post Kubernetes Compliance Policies with Kyverno" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;If the title of this article sounds familiar, it&amp;rsquo;s probably because I already used it 2 years ago when talking about another compliance policy management tool: &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) and its Kubernetes counterpart, Gatekeeper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And I won&amp;rsquo;t lie, I haven&amp;rsquo;t done much OPA since that 2020 article&amp;hellip;&lt;/p&gt;
&lt;p&gt;The first reason is that I changed companies right at that time and had other priorities at my new employer.&lt;/p&gt;
&lt;p&gt;The second reason, probably more accurate but perhaps less flattering, is that OPA is not exactly user friendly. Especially because of &lt;strong&gt;rego&lt;/strong&gt;, the compliance policy language.&lt;/p&gt;
&lt;p&gt;Since then, I discovered &lt;a class="link" href="https://kyverno.io/" target="_blank" rel="noopener"
&gt;Kyverno&lt;/a&gt;, a newcomer in the &amp;ldquo;cloud native&amp;rdquo; game (recently promoted to &amp;ldquo;CNCF sandbox&amp;rdquo; level) that offers compliance policies for Kubernetes only (unlike OPA + rego which are general-purpose), written in 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;Take that, &lt;strong&gt;rego&lt;/strong&gt; ;-)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This article will remain relatively simple, it&amp;rsquo;s an introduction. We&amp;rsquo;ll go further in subsequent articles (writing your own policy, the UI, etc).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not going to innovate much compared to the OPA article and will start from the same use case: preventing two developers from using the same URL for their web app deployed on Kubernetes (because we&amp;rsquo;ve seen it&amp;rsquo;s pretty bad when that happens).&lt;/p&gt;
&lt;p&gt;And you&amp;rsquo;ll see that the initial learning curve is MUUUCH more accessible with Kyverno than with OPA (even though it might annoy half of my Twitter followers that I&amp;rsquo;m once again talking about YAML).&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites
&lt;/h2&gt;&lt;p&gt;Same setup as for OPA / Gatekeeper, you need Kube 1.14 to use Kyverno. Version 1.14 wasn&amp;rsquo;t the latest back in 2020, it&amp;rsquo;s practically prehistoric by now ;-).&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;Obviously, you also need admin access to the cluster, because we&amp;rsquo;ll have to create &lt;em&gt;Validating Admission Webhooks&lt;/em&gt; and &lt;em&gt;CRDs&lt;/em&gt;. Again, I&amp;rsquo;ll refer you to the &lt;a class="link" href="https://blog.zwindler.fr/2020/07/20/vos-politiques-de-conformite-sur-kubernetes-avec-opa-et-gatekeeper/" &gt;previous article&lt;/a&gt; and the official documentation if this doesn&amp;rsquo;t ring a bell.&lt;/p&gt;
&lt;p&gt;In short, we&amp;rsquo;re going to insert ourselves into Kubernetes&amp;rsquo; deployment process to prevent people from doing things that don&amp;rsquo;t follow your best practices (with webhooks) and we&amp;rsquo;ll add extra APIs to extend Kubernetes (the CRDs), so we can describe what&amp;rsquo;s not allowed.&lt;/p&gt;
&lt;h2 id="deployment"&gt;Deployment
&lt;/h2&gt;&lt;p&gt;Installation-wise, we&amp;rsquo;re keeping it simple and efficient.&lt;/p&gt;
&lt;p&gt;Kyverno offers several Helm Charts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one for &lt;strong&gt;Kyverno&lt;/strong&gt; itself&lt;/li&gt;
&lt;li&gt;one for &lt;strong&gt;Policy Reporter&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;one containing a &amp;ldquo;pack&amp;rdquo; of default rules, configured as &amp;ldquo;non-blocking&amp;rdquo; (basically the equivalent of what you could do with Pod Security Policies)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For this article, I&amp;rsquo;m only interested in Kyverno itself. But as I mentioned above, we&amp;rsquo;ll discuss the other two in a future post.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by adding Kyverno itself:&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; you&amp;rsquo;ll notice the &lt;strong&gt;WARNING&lt;/strong&gt; during install. By default, Kyverno is deployed with a single replica. If it crashes, you can no longer deploy or modify anything on your cluster (it happened to me). The solution to recover is to delete Kyverno&amp;rsquo;s &lt;strong&gt;webhooks&lt;/strong&gt; to unblock the situation. And of course, give it more room (higher limits, especially for RAM) and definitely 3 replicas (or more)&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note 2:&lt;/strong&gt; It&amp;rsquo;s probably best to exclude certain namespaces (kube-system, kyverno, &amp;hellip;) from Kyverno&amp;rsquo;s scope to avoid nasty surprises. &lt;em&gt;Thibault Lengagne&lt;/em&gt;&amp;rsquo;s blogpost at Padok also discusses this (link at the bottom of the article).&lt;/p&gt;
&lt;h2 id="what-did-we-deploy"&gt;What did we deploy?
&lt;/h2&gt;&lt;p&gt;At first glance, not much. If you run &lt;code&gt;kubectl -n kyverno get all&lt;/code&gt;, you&amp;rsquo;ll find just a &lt;em&gt;Pod&lt;/em&gt; (+&lt;em&gt;ReplicaSet&lt;/em&gt; +&lt;em&gt;Deployment&lt;/em&gt;) and two &lt;em&gt;Services&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But behind the scenes, we&amp;rsquo;ve deployed much more than that ;)&lt;/p&gt;
&lt;p&gt;Looking for CRDs, you&amp;rsquo;ll see we have quite a few new objects at our disposal.&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;Similarly, we also have new 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;What&amp;rsquo;s it for? How does it work?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To simplify, we&amp;rsquo;ll describe what we don&amp;rsquo;t want to allow on our cluster using the &lt;em&gt;ClusterPolicy&lt;/em&gt; CRD.&lt;/p&gt;
&lt;p&gt;Based on the rules we write, the &lt;strong&gt;webhooks&lt;/strong&gt; will insert themselves into Kubernetes&amp;rsquo; normal resource deployment process to either allow or reject them.&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="ingress-with-the-same-url-example"&gt;Ingress with the same URL example
&lt;/h2&gt;&lt;p&gt;Enough theory, the easiest way is to show you. As I mentioned at the beginning of the article, to compare the two products, I&amp;rsquo;m going to start from the same use case.&lt;/p&gt;
&lt;p&gt;In my Kubernetes clusters, I want to prevent two applications from creating an &lt;em&gt;Ingress&lt;/em&gt; with the same URL, because in my case, that&amp;rsquo;s not allowed. It&amp;rsquo;s a sign of a copy/paste error from one Helm Chart to another (or any other human error).&lt;/p&gt;
&lt;p&gt;The consequence of this error is a conflict in routing incoming HTTP requests, and you end up with &lt;em&gt;some kind of janky round robin&lt;/em&gt; where half the requests hit the wrong backend. In production, that&amp;rsquo;s a bad look, and you risk ending up with customers complaining on Twitter that the application is down ;-).&lt;/p&gt;
&lt;p&gt;Just as OPA provides a community policy to address this problem, &lt;a class="link" href="https://kyverno.io/policies/other/unique-ingress-paths/unique-ingress-paths/" target="_blank" rel="noopener"
&gt;Kyverno has the same&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll look in more detail in the next article at how these rules are built. For now, we&amp;rsquo;ll just deploy this rule in &amp;ldquo;audit&amp;rdquo; mode and see if it works.&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;If you have a Kubernetes cluster at hand, I suggest you apply these two &lt;em&gt;Ingresses&lt;/em&gt;, configured to receive traffic from the same 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;Normally, nothing happens. Kyverno doesn&amp;rsquo;t prevent you from creating these two &lt;em&gt;Ingresses&lt;/em&gt; since we&amp;rsquo;re in &amp;ldquo;validationFailureAction: audit&amp;rdquo; mode (even if the backends don&amp;rsquo;t actually exist behind them, that doesn&amp;rsquo;t matter).&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;However, if you check the &lt;strong&gt;Events&lt;/strong&gt;, you&amp;rsquo;ll find &lt;strong&gt;PolicyViolation&lt;/strong&gt; entries:&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;The fact that we have 2 &lt;em&gt;Ingresses&lt;/em&gt; pointing to the same URL is properly detected and shows up in Kubernetes &lt;em&gt;Events&lt;/em&gt;. Nice :)&lt;/p&gt;
&lt;p&gt;In a much more verbose format, you can also retrieve &lt;em&gt;PolicyReports&lt;/em&gt;, which say the same thing in an even less readable way.&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;Let&amp;rsquo;s clean up for what&amp;rsquo;s next:&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="the-law-is-me"&gt;&amp;ldquo;The law is me&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;Imagine you&amp;rsquo;ve now thoroughly tested your policies, your developers no longer generate manifests that don&amp;rsquo;t follow your best practices (e.g., no root containers, mandatory labels present, correct limits/requests, etc). Your cluster is clean!&lt;/p&gt;
&lt;p&gt;You can switch your compliance policies to &amp;ldquo;enforcing&amp;rdquo; mode. Unlike &amp;ldquo;audit&amp;rdquo; mode, this mode is blocking, and manifests that don&amp;rsquo;t comply with the policies will be rejected by 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;Yes, I&amp;rsquo;m using &amp;ldquo;patch&amp;rdquo; instead of &amp;ldquo;edit&amp;rdquo; to show off ;-)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And let&amp;rsquo;s test again:&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;AND BAM!&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;In this basic (but genuinely useful) example, we can just as easily prevent users from creating &lt;em&gt;Ingresses&lt;/em&gt; with the same URL as we could with OPA.&lt;/p&gt;
&lt;p&gt;So one might ask &amp;ldquo;why prefer Kyverno?&amp;rdquo;. Beyond some other nice features we&amp;rsquo;ll explore in future posts, my answer fits in a single 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;My (very personal) take is that &lt;strong&gt;rego&lt;/strong&gt; is unreadable and verbose, and therefore unmaintainable. Again, this is personal, but I&amp;rsquo;ve never managed to get into it.&lt;/p&gt;
&lt;p&gt;Without going as far as idealizing Kyverno&amp;rsquo;s language*, I find it is clearly &lt;strong&gt;much simpler&lt;/strong&gt; to write, but more importantly, to read. If you ever need to modify/write/maintain your own policies, this will be very handy.&lt;/p&gt;
&lt;p&gt;(*Yes, there is a language, especially for pattern matching&amp;hellip; and as an annoying point, it&amp;rsquo;s &lt;strong&gt;not&lt;/strong&gt; jsonpath like &lt;code&gt;kubectl&lt;/code&gt;, but &lt;a class="link" href="https://kyverno.io/docs/writing-policies/variables/" target="_blank" rel="noopener"
&gt;JMESPath&lt;/a&gt;, similar but not identical&amp;hellip;)&lt;/p&gt;
&lt;h2 id="additional-resources"&gt;Additional Resources
&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;Catalog&amp;rdquo; of official Kyverno Policies&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: Securing your Kubernetes environments&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>Kubernetes Compliance Policies with OPA and Gatekeeper</title><link>https://blog.zwindler.fr/en/2020/07/20/kubernetes-compliance-policies-with-opa-and-gatekeeper/</link><pubDate>Mon, 20 Jul 2020 06:35:00 +0000</pubDate><guid>https://blog.zwindler.fr/en/2020/07/20/kubernetes-compliance-policies-with-opa-and-gatekeeper/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/05/gatekeeper.webp" alt="Featured image of post Kubernetes Compliance Policies with OPA and Gatekeeper" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;One of the problems I encountered while managing Kubernetes clusters shared across multiple teams was that the manifests being deployed were not always perfectly controlled.&lt;/p&gt;
&lt;p&gt;In some cases, manifests were copy/pasted from other teams, sometimes causing small surprises (no label indicating the team responsible for the app, incorrect Ingress information&amp;hellip;). One of the solutions we found was to maintain an up-to-date, as-generic-as-possible Helm Chart, with only values for developers to fill in. This Chart is jointly maintained by Ops and tech leads.&lt;/p&gt;
&lt;p&gt;However, maintaining this template is complex, and some applications diverge too much from the general framework to comply, even by forking and modifying parts of it. And some issues like verifying images or registries running on the platform were not addressed by this solution.&lt;/p&gt;
&lt;h2 id="open-policy-agent--gatekeeper"&gt;Open Policy Agent / Gatekeeper
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/07/opa.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a class="link" href="https://www.thingiverse.com/thing:1236133" target="_blank" rel="noopener"
&gt;https://www.thingiverse.com/thing:1236133&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rather than trying at all costs to unify the way applications are deployed in the cluster (and having to manage exceptions), another approach is to add a tool that would verify, at deployment time, that what&amp;rsquo;s being deployed follows the company&amp;rsquo;s best practices in terms of security and configuration.&lt;/p&gt;
&lt;p&gt;As you may have guessed, the tool for this is Open Policy Agent (or OPA). It&amp;rsquo;s an open-source generic policy engine (CNCF project at the Incubating stage).&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s a generic engine, there&amp;rsquo;s also another project, &lt;a class="link" href="https://github.com/open-policy-agent/gatekeeper" target="_blank" rel="noopener"
&gt;Gatekeeper&lt;/a&gt;, which handles the interaction between OPA and Kubernetes.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;To use Gatekeeper, you should have a minimum Kubernetes version of 1.14, which adds webhook timeouts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you&amp;rsquo;re on an earlier version, things get complicated. There&amp;rsquo;s a bug, fixed in 1.14, that could crash your OPA/Gatekeeper setup. For this reason, if you&amp;rsquo;re not on 1.14 (and can&amp;rsquo;t update your platform), you&amp;rsquo;ll need to skip Gatekeeper (using K8s Policy Controller for instance). That&amp;rsquo;s a bit trickier and outside the scope of this tutorial.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/05/k8spolicycontroller.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &amp;ldquo;V2&amp;rdquo;, without Gatekeeper, with Kube-Mgmt and OPA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Of course, you also need admin access on the cluster. Gatekeeper offers a simple way to test your permissions by trying to grant yourself admin rights.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/05/inception.gif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;wait for it&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl create clusterrolebinding cluster-admin-binding \
--clusterrole cluster-admin \
--user [your user]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quick tip: if you don&amp;rsquo;t know the name of the access account you&amp;rsquo;re currently using, you can run this command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl config view --template=&amp;#39;{{ range .contexts }}{{ if eq .name &amp;#34;&amp;#39;$(kubectl config current-context)&amp;#39;&amp;#34; }}Current user: {{ .context.user }}{{ end }}{{ end }}&amp;#39;
Current user: clusterUser_zwindlerk8s_rg_zwindlerk8s
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="installation"&gt;Installation
&lt;/h2&gt;&lt;p&gt;I set up a small AKS cluster in version 1.14 for the occasion and deployed the prepackaged version of Gatekeeper.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tada! And just like that, Gatekeeper is installed on your cluster.
Note: The official documentation provides steps to &lt;a class="link" href="https://github.com/open-policy-agent/gatekeeper#deploying-head-using-make" target="_blank" rel="noopener"
&gt;build your own image&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="whats-in-my-cluster"&gt;What&amp;rsquo;s in my cluster?
&lt;/h2&gt;&lt;p&gt;Taking a closer look at what the manifest does, here&amp;rsquo;s what you deploy when running the above command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a Namespace &lt;code&gt;gatekeeper-system&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a ServiceAccount &lt;code&gt;gatekeeper-admin&lt;/code&gt;, associated with a Role and a ClusterRole &lt;code&gt;gatekeeper-manager-role&lt;/code&gt; via (respectively) a RoleBinding and ClusterRoleBinding &lt;code&gt;gatekeeper-manager-rolebinding&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a Deployment &lt;code&gt;gatekeeper-controller-manager&lt;/code&gt; and &lt;code&gt;gatekeeper-audit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a Service &lt;code&gt;gatekeeper-webhook-service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a Secret &lt;code&gt;gatekeeper-webhook-server-cert&lt;/code&gt; containing a certificate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing exceptional so far. Where it gets interesting is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;two CRDs (Custom Resource Definitions): &lt;code&gt;configs.config.gatekeeper.sh&lt;/code&gt; and &lt;code&gt;constrainttemplates.templates.gatekeeper.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a validating webhook configuration &lt;code&gt;gatekeeper-validating-webhook-configuration&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="validating-webhook"&gt;Validating Webhook
&lt;/h2&gt;&lt;p&gt;The validation webhook first. The first time I heard about validating webhooks was during the talk &lt;a class="link" href="https://www.youtube.com/watch?v=likHm-KHGWQ" target="_blank" rel="noopener"
&gt;101 Ways to &amp;ldquo;Break and Recover&amp;rdquo; Kubernetes Cluster&lt;/a&gt; at Kubecon 2018.&lt;/p&gt;
&lt;p&gt;The problems these Oath (formerly Yahoo) employees were facing were identical to mine. Some teams, when pulling (a.k.a. copy/pasting) manifests from other teams, forgot to change the Ingress URLs. This resulted in user requests being distributed randomly between 2 completely different services.&lt;/p&gt;
&lt;p&gt;The solution proposed at that conference was to use Kubernetes&amp;rsquo; Validating/Mutating Admission Webhooks (roughly stable since 1.11).&lt;/p&gt;
&lt;p&gt;Back in 2018, when digging into the topic, I found very little documentation about it beyond the official docs (I&amp;rsquo;ve found more since). Another issue: Admission webhooks require developing your own Controller, which wasn&amp;rsquo;t very accessible (see links at the end of the article).&lt;/p&gt;
&lt;p&gt;Fortunately, Gatekeeper and OPA will handle that for us.&lt;/p&gt;
&lt;h2 id="custom-resource-definition"&gt;Custom Resource Definition
&lt;/h2&gt;&lt;p&gt;For those unfamiliar with CRDs, they are simply extensions of the Kubernetes API. The big advantage of CRDs is that they provide a way for third-party vendors to add their own logic &lt;em&gt;inside&lt;/em&gt; Kubernetes.&lt;/p&gt;
&lt;p&gt;I already talked about CRDs in my article on &lt;a class="link" href="https://blog.zwindler.fr/2019/09/10/du-ceph-dans-mon-kubernetes/" &gt;Rook&lt;/a&gt; which allows automated management of Ceph clusters (and more!) via the CephCluster CRD. All common administration tasks are integrated into Kubernetes (even though Kube has no native knowledge of them), managed by a Controller included in Rook, and configured via CRDs.&lt;/p&gt;
&lt;p&gt;Gatekeeper will add 3 components to our Kubernetes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a Config object&lt;/li&gt;
&lt;li&gt;a ConstraintTemplate object&lt;/li&gt;
&lt;li&gt;a Constraint object&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="config"&gt;Config
&lt;/h2&gt;&lt;p&gt;The principle of Gatekeeper, as shown in the following diagram, is that it hooks into your Kubernetes API server and &amp;ldquo;synchronizes&amp;rdquo; events for new object creation, looking for objects that don&amp;rsquo;t comply with your compliance policies.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/05/gatekeeper_v3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Source: &lt;a class="link" href="https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/" target="_blank" rel="noopener"
&gt;kubernetes.io/blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By default, if you don&amp;rsquo;t configure it, Gatekeeper doesn&amp;rsquo;t listen to events on any Kubernetes objects&amp;hellip; so it will do&amp;hellip; absolutely nothing!&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s fix that right away by telling Gatekeeper to listen to all events on Ingresses:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cat gatekeeper/demo/basic/sync.yaml
apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
name: config
namespace: &amp;#34;gatekeeper-system&amp;#34;
spec:
sync:
syncOnly:
- group: &amp;#34;extensions&amp;#34;
version: &amp;#34;v1beta1&amp;#34;
kind: &amp;#34;Ingress&amp;#34;
- group: &amp;#34;networking.k8s.io&amp;#34;
version: &amp;#34;v1beta1&amp;#34;
kind: &amp;#34;Ingress&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="constrainttemplate"&gt;ConstraintTemplate
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Before you can define a constraint, you must first define a ConstraintTemplate, which describes both the Rego that enforces the constraint and the schema of the constraint.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The idea here is that before you start creating constraints on the cluster, you need to create constraint templates. Think of these templates as the object that links the function (the OPA rego code) on one side with parameters on the other.&lt;/p&gt;
&lt;p&gt;This extra step allows us to potentially reuse the same template for several different constraints.&lt;/p&gt;
&lt;h2 id="your-first-template"&gt;Your first template
&lt;/h2&gt;&lt;p&gt;For the first time, we can use the sample templates provided on the OPA/Gatekeeper GitHub, which enforces the presence of a label on a given Kube object type, since it&amp;rsquo;s simpler (&lt;a class="link" href="https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/basic/templates/k8srequiredlabels_template.yaml" target="_blank" rel="noopener"
&gt;k8srequiredlabels_template.yaml&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;However, since the beginning of this article, I&amp;rsquo;ve been talking about preventing teams from using the same FQDN in Ingresses for different services. Because if that were to happen, as a reminder, we&amp;rsquo;d end up with some kind of janky load balancer redirecting half of user requests to one application and the other half to another for the same URL.&lt;/p&gt;
&lt;p&gt;And it just so happens that this &lt;a class="link" href="https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/agilebank/dryrun/k8suniqueingresshost_template.yaml" target="_blank" rel="noopener"
&gt;template&lt;/a&gt; will help us ensure that!&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8suniqueingresshost
spec:
crd:
spec:
names:
kind: K8sUniqueIngressHost
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8suniqueingresshost
identical(obj, review) {
obj.metadata.namespace == review.object.metadata.namespace
obj.metadata.name == review.object.metadata.name
}
violation[{&amp;#34;msg&amp;#34;: msg}] {
input.review.kind.kind == &amp;#34;Ingress&amp;#34;
re_match(&amp;#34;^(extensions|networking.k8s.io)$&amp;#34;, input.review.kind.group)
host := input.review.object.spec.rules[_].host
other := data.inventory.namespace[ns][otherapiversion][&amp;#34;Ingress&amp;#34;][name]
re_match(&amp;#34;^(extensions|networking.k8s.io)/.+$&amp;#34;, otherapiversion)
other.spec.rules[_].host == host
not identical(other, input.review)
msg := sprintf(&amp;#34;ingress host conflicts with an existing ingress &amp;lt;%v&amp;gt;&amp;#34;, [host])
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At first glance, it&amp;rsquo;s pretty dense. But then again, I didn&amp;rsquo;t pick the simplest example either, and if you re-watch the video &lt;a class="link" href="https://www.youtube.com/watch?v=Yup1FUc2Qn0&amp;amp;feature=youtu.be" target="_blank" rel="noopener"
&gt;Kubecon 2019 | Intro: Open Policy Agent - Rita Zhang, Microsoft &amp;amp; Max Smythe, Google&lt;/a&gt;, it&amp;rsquo;s actually fairly easy to understand.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/agilebank/dryrun/k8suniqueingresshost_template.yaml
constrainttemplate.templates.gatekeeper.sh/k8suniqueingresshost created
kubectl get constrainttemplate
NAME AGE
k8suniqueingresshost 23m
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="and-a-constraint"&gt;And a Constraint
&lt;/h2&gt;&lt;p&gt;Now that we have our template, we can simply instantiate it with the variables we&amp;rsquo;re interested in.&lt;/p&gt;
&lt;p&gt;Sticking with the demo example, we ensure that across the entire cluster, no URL is used twice for two different Ingresses by instantiating this constraint:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sUniqueIngressHost
metadata:
name: unique-ingress-host
spec:
enforcementAction: dryrun
match:
kinds:
- apiGroups: [&amp;#34;extensions&amp;#34;, &amp;#34;networking.k8s.io&amp;#34;]
kinds: [&amp;#34;Ingress&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;m going to slightly modify this Constraint because, as you can see, it defines an enforcementAction: &lt;code&gt;dryrun&lt;/code&gt;, which has no immediate effect (it only logs an error for future audit). It&amp;rsquo;s a great feature for getting up to speed with OPA, but for my demo it&amp;rsquo;s less fun&amp;hellip;&lt;/p&gt;
&lt;p&gt;So I apply the file and change the enforcementAction:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/agilebank/dryrun/unique-ingress-host.yaml
kubectl patch K8sUniqueIngressHost.constraints.gatekeeper.sh unique-ingress-host -p &amp;#39;{&amp;#34;spec&amp;#34;:{&amp;#34;enforcementAction&amp;#34;:&amp;#34;deny&amp;#34;}}&amp;#39; --type=merge
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="lets-try-it-out"&gt;Let&amp;rsquo;s try it out
&lt;/h2&gt;&lt;p&gt;At this point, we should have everything. Gatekeeper listens to the API server for all events on Ingress objects (via the Config). We have a ConstraintTemplate with &lt;code&gt;rego&lt;/code&gt; code that checks we don&amp;rsquo;t already have an identical URL in existing Ingresses, and a Constraint that defines what to do (dryrun) and on which objects (Ingresses).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s run the demo :D&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Create the namespace that will contain the demo objects
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/agilebank/bad_resources/namespace.yaml
namespace/production created
# Create an Ingress with a URL that doesn&amp;#39;t exist yet
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/agilebank/dryrun/existing_resources/example.yaml
ingress.extensions/ingress-host created
# Create a second Ingress with the SAME URL
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/agilebank/dryrun/bad_resource/duplicate_ing.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And bam!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[denied by unique-ingress-host] ingress host conflicts with an existing ingress &amp;lt;example-host.example.org&amp;gt;&lt;/p&gt;
&lt;/blockquote&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;As I explained in the introduction, adding compliance policies and constraints to a Kubernetes cluster was not trivial.&lt;/p&gt;
&lt;p&gt;Without going as far as saying it&amp;rsquo;s become easy, OPA and Gatekeeper make things simpler (no need to develop and then host your own microservice for each Admission/Validation Webhook). You still need to learn a new language (rego) to start doing cool things, but even with the default templates, there&amp;rsquo;s already plenty to work with.&lt;/p&gt;
&lt;p&gt;Another really interesting point with OPA is the ability to log and audit all compliance errors using &lt;code&gt;dryrun&lt;/code&gt; mode instead of &lt;code&gt;deny&lt;/code&gt;, without blocking.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get K8sUniqueIngressHost.constraints.gatekeeper.sh unique-ingress-host -o yaml
[...]
- enforcementAction: dryrun
kind: Ingress
message: ingress host conflicts with an existing ingress &amp;lt;example-host.example.org&amp;gt;
name: ingress-host2
namespace: default
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/" target="_blank" rel="noopener"
&gt;Kubernetes.io | OPA Gatekeeper: Policy and Governance for Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/watch?v=Yup1FUc2Qn0&amp;amp;feature=youtu.be" target="_blank" rel="noopener"
&gt;Kubecon 2019 | Intro: Open Policy Agent - Rita Zhang, Microsoft &amp;amp; Max Smythe, Google&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;A Guide to Kubernetes Admission Controllers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.container-solutions.com/some-admission-webhook-basics" target="_blank" rel="noopener"
&gt;Blog container-solutions | Some Admission Webhook Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://medium.com/ibm-cloud/diving-into-kubernetes-mutatingadmissionwebhook-6ef3c5695f74" target="_blank" rel="noopener"
&gt;IBM Cloud | Diving into Kubernetes MutatingAdmissionWebhook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Should we have containers ?</title><link>https://blog.zwindler.fr/en/2016/08/25/should-we-have-containers/</link><pubDate>Thu, 25 Aug 2016 16:00:21 +0000</pubDate><guid>https://blog.zwindler.fr/en/2016/08/25/should-we-have-containers/</guid><description>&lt;img src="https://blog.zwindler.fr/2016/08/Fullvirt_containers.webp" alt="Featured image of post Should we have containers ?" /&gt;&lt;h2 id="a-more-complete-answer-to-why-do-we-have-containers-by-fntlnz"&gt;A more complete answer to « Why do we have containers » by fntlnz
&lt;/h2&gt;&lt;p&gt;2 days ago, I stumbled apon &lt;em&gt;fntlnz&lt;/em&gt; article &lt;a class="link" href="https://web.archive.org/web/20161026190150/http://blog.fntlnz.wtf/post/why-containers/" target="_blank" rel="noopener"
&gt;Why do we have containers. (dead link, using Internet Archive)&lt;/a&gt; The article had been reposted by Docker official Twitter account and the first thing that struck me was the displayed image : a schema comparing full virtualization, para virtualization and linux containers.&lt;/p&gt;
&lt;p&gt;And of course, I say this because I didn’t entirely agree with it ;-). But then I read the article and I found many good points.&lt;/p&gt;
&lt;p&gt;So I decided to write « Should we have containers ? » as an answer to develop my remarks a little.&lt;/p&gt;
&lt;h2 id="full-virtualization-vs-paravirtualization-vs-os-level-virtualization"&gt;Full virtualization vs paravirtualization vs OS level virtualization
&lt;/h2&gt;&lt;p&gt;First, I’ll go with the schema. I know the point was to provide a really simple schema to help understand the differences between virtualization types but I don’t think it should be presented like that.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2016/08/Virtualization.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Hypervisor and host OS are both displayed in the full/para. I find this be misleading. Of course there is a Host OS and a virtualization engine on top of it. But most of the time, either the OS is really thin (ESXi fits in 8GB SD cards and runs nothing but virtualization) or the virt engine is a part of the kernel (KVM, Xen) just like LXC is a part of the Linux kernel.&lt;/p&gt;
&lt;p&gt;Also, the bins/libs are only displayed on OS level virt. This is interesting to put in the « host level virtualization schema » but it should be added on full and para as well because this is one of the advantages host level virt. has over full/para.&lt;br&gt;
Even if your VMs are all identicals, bins and libs have to be present (copied) in each VMs. On contrary, in OS Level virt, you CAN share identicals bins/libs between containers, thus reducing the storage footprint of the virtualized environment.&lt;/p&gt;
&lt;p&gt;That’s how I would have presented it&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2016/08/Fullvirt_containers.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;You can see that OS level virtualization and Hypervisors are not so different on this simplified view. The differences appear more clearly when you add x86 Security Rings (0-&amp;gt;3). The « overhead » of full virtualization comes from the calls that guest OSes do to hypervisor pilots and the translation of CPU instructions.&lt;/p&gt;
&lt;p&gt;On a side note, I put Docker on the left to show that it’s basically an administration suite that helps you run LXC containers&lt;/p&gt;
&lt;h2 id="the-rest-of-the-analysis"&gt;The rest of the analysis
&lt;/h2&gt;&lt;h3 id="security"&gt;Security
&lt;/h3&gt;&lt;p&gt;When talking about containers, the security point is often raised.&lt;/p&gt;
&lt;p&gt;VMs are really hermetics. Taking control of the hypervisor after hacking a VM should be MUCH harder than taking control of the host OS if you are inside a container with whom the kernel is shared.&lt;/p&gt;
&lt;p&gt;But I agree with &lt;strong&gt;fntlnz&lt;/strong&gt;: having to patch only one kernel for all your virtual environments instead of having to patch ALL kernels of your virtual machines is actually a good point.&lt;/p&gt;
&lt;p&gt;I encourage you to read &lt;a class="link" href="https://web.archive.org/web/20200925215559/https://2015.rmll.info/IMG/pdf/containers_docker_and_security__state_of_the_union.pdf" target="_blank" rel="noopener"
&gt;this (dead link, using Internet Archive)&lt;/a&gt; from Jérôme Petazzoni if you’d like a good presentation on the subject.&lt;/p&gt;
&lt;h3 id="performance"&gt;Performance
&lt;/h3&gt;&lt;p&gt;A point that’s not really developed in &lt;em&gt;fntlnz&lt;/em&gt; article is performance, but I read it/ear it &lt;strong&gt;all the time&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;General belief is that VMs are slow. That was true in the « early days » of x86 virtualization, because of the limitations of the x86 architectures which didn’t allow efficient OS virtualisation. Overhead for CPU instructions was known to be of about 15% (which is enormous). Thus, running processes directly on the OS are believed to be more efficent.&lt;/p&gt;
&lt;p&gt;But nowadays, modern hypervisors on modern hardware nearly generates no overhead at all over CPU and RAM consumption. You could argue that guest OSes consume ressources while containers don’t. But let’s be honest: with the 32 GB RAM tomcat instances I regularly see, don’t say to me that a Linux guest OS CPU&amp;amp;RAM consumption is significative.&lt;/p&gt;
&lt;p&gt;The real difference is the on the storage consumption. First, for N VMs, you save yourself N*xGB of guest OS files. If your Linux templates are good, you don’t consume that much (a few GB at most) but this can add up when we are talking about thousands of instances. [Edit jan. 2020]Not so true today if you host your own hardware and have so deduplicated storage[/Edit]&lt;/p&gt;
&lt;p&gt;From there, you still have some margin for gains, mostly through &lt;strong&gt;Copy on write and union filesystems&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You make a modification to an official tomcat image to add you configuration files ? Only the delta will be stored.&lt;/li&gt;
&lt;li&gt;You launch thousands of containers from this image ? You only consume the container image once.&lt;/li&gt;
&lt;li&gt;The real data (user data, business data) is of course the same for both architectures&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="so-should-i-use-containers-"&gt;So, should I use containers ?
&lt;/h2&gt;&lt;p&gt;The problem here is that’s not the real question. &lt;strong&gt;Do you need containers ?&lt;/strong&gt; Here’s &lt;em&gt;fntlnz&lt;/em&gt; point of view.**&lt;br&gt;
**&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But if at some extent your motivation consist in &lt;strong&gt;decreasing costs related to full virtualization overheads&lt;/strong&gt; while allowing &lt;strong&gt;developers to ship, develop and test code faster&lt;/strong&gt; we already found two.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fntlnz&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I won’t tell you not to use containers or Docker. Truth be told, we use it in dev and production. Docker is awesome.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yes, Docker reduces overhead of full virtualization. Storage mostly, but it highly depends on your workload. For example if you have the need for millions of really tiny instances, then Guest OS overhead is significative  in full virtualization.&lt;/li&gt;
&lt;li&gt;Yes, Docker allows developers to ship, develop and test code faster. You still have to change your way to develop yours app but Docker and DevOps is the new thing. Better get on with it ;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But you shouldn’t use Docker just because of that. Docker containers need you to change your philosophy toward applications and administration.&lt;/p&gt;
&lt;h3 id="shared-kernel--same-kernel-"&gt;Shared kernel = same kernel~~ ~~
&lt;/h3&gt;&lt;p&gt;You need to understand that you’ll share a kernel. For a long time that ment a 100% Linux only which could be problematic in some cases. I just realized that Windows containers exists in Windows 2016 tech preview. And Solaris is coming.&lt;/p&gt;
&lt;p&gt;But independantly of platforms, you still you share a kernel. If you want Linux containers, you’ll need Linux Docker nodes. If you need Windows containers, you’ll have to have Windows Docker nodes.&lt;/p&gt;
&lt;h3 id="non-persistantcloud-ready-instances"&gt;Non-persistant/cloud-ready instances
&lt;/h3&gt;&lt;p&gt;Docker containers are by design non-persistant and by best practice single-process. This isn’t mandatory of course, but Docker works better that way.&lt;/p&gt;
&lt;p&gt;The whole idea is to help you make « cloud-ready » &amp;amp; atomic applications that don’t store anything in the container itself (persistant storage is presented to the container but outside it). This way, you can scale out really quickly: you just start up a few more instances if you need more firepower.&lt;/p&gt;
&lt;p&gt;This also modifies the way you do systems/applications administration. A container/application is spinning out of control ? Don’t try to repair it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kill it&lt;/li&gt;
&lt;li&gt;Launch a new one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fact that persistant storage is handled outside the containers also helps you to deal with lifecycle management.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update your application inside the docker image&lt;/li&gt;
&lt;li&gt;Shutdown the old container&lt;/li&gt;
&lt;li&gt;Start the new container with the new image, plugged to the persistant storage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All that requires of course applications that have been designed this way! This is a massive change in philosophy and old monolithic apps on a Docker platform won’t work as well as microservices will.&lt;/p&gt;
&lt;h3 id="system-administration"&gt;System administration
&lt;/h3&gt;&lt;p&gt;I don’t say solutions don’t exist because they do.&lt;/p&gt;
&lt;p&gt;But starting up and shutting down containers on the fly will give headaches to your « old school Nagios » monitoring administrator. What could be done easily in the physical/VM world (installing an agent in the template), each of those having an IP adress to query for health checks is a lot easier than checking on a single host a highly volatile number of isolated processes.&lt;/p&gt;
&lt;p&gt;You’ll probably have to adapt and use tools that are designed to handle such cases.&lt;/p&gt;
&lt;p&gt;Backup administrator may find it confusing at first to make application consistant data backups when applications are running in an variable number of containers.&lt;/p&gt;
&lt;p&gt;And so on&amp;hellip;&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final thoughts
&lt;/h2&gt;&lt;p&gt;Yes, you should definitely &lt;em&gt;look into&lt;/em&gt; containers. They are mature, stable, a great new way to speed up development and contribute to save money on hardware.&lt;/p&gt;
&lt;p&gt;But be aware that VMs and containers don’t answer all usecases; and that you won’t migrate all your applications overnight!&lt;/p&gt;</description></item></channel></rss>