<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Politique on Zwindler's Reflection</title><link>https://blog.zwindler.fr/en/tags/politique/</link><description>Recent content in Politique on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Mon, 05 Sep 2022 06:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/en/tags/politique/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>