<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Kubernetes on Zwindler's Reflection</title><link>https://blog.zwindler.fr/en/categories/kubernetes/</link><description>Recent content in Kubernetes on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Tue, 19 May 2026 12:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/en/categories/kubernetes/index.xml" rel="self" type="application/rss+xml"/><item><title>nodes/proxy GET: One Kubernetes permission too many</title><link>https://blog.zwindler.fr/en/2026/05/19/nodes/proxy-get-one-kubernetes-permission-too-many/</link><pubDate>Tue, 19 May 2026 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2026/05/19/nodes/proxy-get-one-kubernetes-permission-too-many/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/05/node_proxy_RCE.webp" alt="Featured image of post nodes/proxy GET: One Kubernetes permission too many" /&gt;&lt;h2 id="tldr"&gt;TL;DR
&lt;/h2&gt;&lt;p&gt;This is a bit old now, but I still wanted to share a quick write-up on the topic.&lt;/p&gt;
&lt;p&gt;Back in January, a cybersecurity researcher reported a Kubernetes flaw that generated quite a buzz. It had been a while since we had a Kube vulnerability that got people talking this much, at least from Denis&amp;rsquo;s memory (yes, I talk about myself in the third person).&lt;/p&gt;
&lt;p&gt;Honestly, it&amp;rsquo;s pretty wild: the &lt;code&gt;nodes/proxy GET&lt;/code&gt; RBAC permission allows any &lt;strong&gt;ServiceAccount&lt;/strong&gt; to execute code inside &lt;strong&gt;any Pod in the cluster&lt;/strong&gt;, without leaving a single trace in the audit logs. That&amp;rsquo;s unfortunate, especially when you have ServiceAccounts named &lt;code&gt;rook-ceph-system&lt;/code&gt; that also happen to have read access to all Secrets in the cluster.&lt;/p&gt;
&lt;p&gt;This article details the issue, how to check if you are vulnerable, the fixes to apply, and the preventive measures you can put in place if you can&amp;rsquo;t patch right away.&lt;/p&gt;
&lt;h2 id="the-problem-websocket--kubelet--un-audited-exec"&gt;The problem: WebSocket + Kubelet = un-audited exec
&lt;/h2&gt;&lt;p&gt;The vulnerability was documented by Graham Helton in &lt;a class="link" href="https://grahamhelton.com/blog/nodes-proxy-rce" target="_blank" rel="noopener"
&gt;this article&lt;/a&gt;. Here is how it works.&lt;/p&gt;
&lt;p&gt;The Kubernetes API exposes a &lt;code&gt;nodes/proxy&lt;/code&gt; subresource that proxies HTTP requests to each node&amp;rsquo;s Kubelet. The Kubelet itself exposes an API on port 10250, specifically the &lt;code&gt;/exec&lt;/code&gt; endpoint which allows executing commands inside a container.&lt;/p&gt;
&lt;p&gt;The issue comes from how the Kubelet handles authorizations for WebSocket connections:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;kubectl exec&lt;/code&gt; uses a WebSocket connection, whose handshake is an HTTP &lt;code&gt;GET&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The Kubelet maps this initial &lt;code&gt;GET&lt;/code&gt; to the &lt;code&gt;get&lt;/code&gt; RBAC verb&lt;/li&gt;
&lt;li&gt;It checks &lt;code&gt;nodes/proxy GET&lt;/code&gt;, then authorizes the operation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No secondary check&lt;/strong&gt; is performed for the &lt;code&gt;CREATE&lt;/code&gt; verb normally required for &lt;code&gt;/exec&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Result: any ServiceAccount with &lt;code&gt;nodes/proxy GET&lt;/code&gt; can execute commands in any Pod in the cluster, &lt;strong&gt;including system Pods&lt;/strong&gt; (&lt;code&gt;etcd&lt;/code&gt;, &lt;code&gt;kube-apiserver&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Exploitation via websocat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;websocat --insecure &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --header &lt;span class="s2"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --protocol &lt;span class="s2"&gt;&amp;#34;v4.channel.k8s.io&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;wss://&lt;/span&gt;&lt;span class="nv"&gt;$NODE_IP&lt;/span&gt;&lt;span class="s2"&gt;:10250/exec/default/nginx/nginx?output=1&amp;amp;error=1&amp;amp;command=id&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that&amp;rsquo;s not all. &lt;strong&gt;Commands executed via this method do not generate any Kubernetes audit logs&lt;/strong&gt; (well, assuming you even collect them 🙈). The access goes directly through the Kubelet, which does not report events back to the API server.&lt;/p&gt;
&lt;p&gt;The official Kubernetes status on this: &lt;strong&gt;Won&amp;rsquo;t Fix&lt;/strong&gt;. It is a &amp;ldquo;design behavior&amp;rdquo; (note the quotes), addressed via a feature gate (&lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/2862-fine-grained-kubelet-authz/README.md" target="_blank" rel="noopener"
&gt;KEP-2862&lt;/a&gt;, see below).&lt;/p&gt;
&lt;p&gt;Ouch.&lt;/p&gt;
&lt;h2 id="the-audit-vulnerable-serviceaccounts-on-our-clusters"&gt;The Audit: Vulnerable ServiceAccounts on our clusters
&lt;/h2&gt;&lt;p&gt;In January 2026, following the publication of Graham Helton&amp;rsquo;s article, a lot of people had to urgently audit their clusters. You can either manually audit all your Roles / ClusterRoles, or use a &lt;a class="link" href="https://gist.github.com/grahamhelton/f5c8ce265161990b0847ac05a74e466a" target="_blank" rel="noopener"
&gt;detection script&lt;/a&gt; provided by the researcher.&lt;/p&gt;
&lt;p&gt;As an example, here are three relatively common components that make great candidates for a juicy privilege escalation:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;ClusterRole&lt;/th&gt;
&lt;th&gt;ServiceAccounts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenTelemetry Collector&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;otel-otelcol-k8sobjects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opentelemetry-collector-daemonset-collector&lt;/code&gt;, &lt;code&gt;opentelemetry-collector-deployment-collector&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenTelemetry Operator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;otel-operator-resources&lt;/code&gt; / &lt;code&gt;opentelemetry-operator-manager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opentelemetry-operator&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rook-Ceph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rook-ceph-global&lt;/code&gt;, &lt;code&gt;rook-ceph-mgr-cluster&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rook-ceph-system&lt;/code&gt;, &lt;code&gt;rook-ceph-mgr&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Note: there are many more. Graham Helton added an &amp;ldquo;Appendix: Affected Helm Charts&amp;rdquo; section at the end of his article, referencing AT LEAST 69 affected Helm charts according to him.&lt;/p&gt;
&lt;h3 id="the-critical-case-rook-ceph-system"&gt;The critical case: rook-ceph-system
&lt;/h3&gt;&lt;p&gt;In the official chart, the &lt;code&gt;rook-ceph-system&lt;/code&gt; ServiceAccount combined two particularly dangerous permissions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;nodes/proxy GET&lt;/code&gt;&lt;/strong&gt; - the RCE&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;secrets GET/LIST/WATCH&lt;/code&gt;&lt;/strong&gt; across the &lt;strong&gt;entire cluster&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Accessible secrets can include LUKS keys for volume encryption, Ceph admin keyrings, dashboard passwords&amp;hellip; this kind of access makes it a prime target for an attacker.&lt;/p&gt;
&lt;p&gt;The attack scenario: a compromise of the &lt;code&gt;rook-ceph-operator&lt;/code&gt; Pod (via CVE, supply chain, or a malicious image) would allow reading all Ceph secrets, and then executing code in any Pod (including &lt;code&gt;etcd&lt;/code&gt;), leading to a full compromise of the cluster and encrypted data.&lt;/p&gt;
&lt;p&gt;To manually check if a ServiceAccount is vulnerable:&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 auth can-i get nodes --subresource&lt;span class="o"&gt;=&lt;/span&gt;proxy &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --as&lt;span class="o"&gt;=&lt;/span&gt;system:serviceaccount:&amp;lt;namespace&amp;gt;:&amp;lt;serviceaccount&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="examples-of-fixes-to-apply"&gt;Examples of fixes to apply
&lt;/h2&gt;&lt;h3 id="rook-ceph-upstream-fix"&gt;Rook-Ceph: Upstream fix
&lt;/h3&gt;&lt;p&gt;For Rook-Ceph, the fix came from upstream: PR &lt;a class="link" href="https://github.com/rook/rook/pull/16979" target="_blank" rel="noopener"
&gt;rook/rook#16979&lt;/a&gt; removed &lt;code&gt;nodes/proxy&lt;/code&gt; from ClusterRoles. This fix is included in Rook v1.19.1, so updating the affected clusters was enough.&lt;/p&gt;
&lt;p&gt;After updating, checking across all clusters:&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 clusterroles rook-ceph-global -o yaml &lt;span class="p"&gt;|&lt;/span&gt; grep -A3 nodes/proxy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# -&amp;gt; nothing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="otel--otel-operator"&gt;OTel / OTel operator
&lt;/h3&gt;&lt;p&gt;For OpenTelemetry, the situation is potentially more complex. If you are using &lt;code&gt;otel-operator&lt;/code&gt; and &lt;strong&gt;OtelCollector&lt;/strong&gt; Custom Resources, you likely have to manage your own RBAC manifests &lt;strong&gt;yourself&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Having had to do it myself, it&amp;rsquo;s quite painful. Depending on your collector type and the receivers you enabled, you need to cross-reference multiple documents on the official OTel and otel-operator websites.&lt;/p&gt;
&lt;p&gt;Upstream merged a conditional approach in &lt;a class="link" href="https://github.com/open-telemetry/opentelemetry-helm-charts/pull/2083" target="_blank" rel="noopener"
&gt;open-telemetry/opentelemetry-helm-charts#2083&lt;/a&gt; based on the Kubernetes version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Upstream approach (opentelemetry-helm-charts#2083)&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="l"&gt;if semverCompare &amp;#34;&amp;gt;=1.33-0&amp;#34; .Capabilities.KubeVersion.Version }}&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;nodes/pods&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="l"&gt;else }}&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;nodes/proxy&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="l"&gt;end }}&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;Here again, if all your clusters are up to date, you can simply replace &lt;code&gt;nodes/proxy&lt;/code&gt; with &lt;code&gt;nodes/pods&lt;/code&gt; directly (without the condition).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;otel-collector-crb.yaml&lt;/code&gt;&lt;/strong&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="c"&gt;# Before&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;rules&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;apiGroups&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;&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;resources&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;nodes&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;nodes/proxy &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# &amp;lt;- RCE risk&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;nodes/spec&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;nodes/stats&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;verbs&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;get&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# After&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;rules&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;apiGroups&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;&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;resources&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;nodes&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;# nodes/pods replaces nodes/proxy (RCE risk, see [https://grahamhelton.com/blog/nodes-proxy-rce](https://grahamhelton.com/blog/nodes-proxy-rce))&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;# Requires K8s &amp;gt;= 1.33 (KEP-2862 fine-grained kubelet authz)&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;nodes/pods&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;nodes/spec&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;nodes/stats&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;verbs&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;get&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;&lt;code&gt;otel-operator-rbac.yaml&lt;/code&gt;&lt;/strong&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="c"&gt;# Before&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;apiGroups&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;&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;resources&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;nodes/proxy &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# &amp;lt;- RCE risk&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;verbs&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;get&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# After&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;# nodes/pods replaces nodes/proxy (RCE risk, see [https://grahamhelton.com/blog/nodes-proxy-rce](https://grahamhelton.com/blog/nodes-proxy-rce))&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;# Requires K8s &amp;gt;= 1.33 (KEP-2862 fine-grained kubelet authz)&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;apiGroups&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;&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;resources&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;nodes/pods&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;verbs&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;get&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;h2 id="preventive-measures"&gt;Preventive Measures
&lt;/h2&gt;&lt;h3 id="kep-2862-fine-grained-kubelet-api-authorization"&gt;KEP-2862: Fine-Grained Kubelet API Authorization
&lt;/h3&gt;&lt;p&gt;As teased earlier, the real long-term solution is &lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/2862-fine-grained-kubelet-authz/README.md" target="_blank" rel="noopener"
&gt;KEP-2862&lt;/a&gt; (Fine-Grained Kubelet API Authorization). It introduces granular subresources (&lt;code&gt;nodes/pods&lt;/code&gt;, &lt;code&gt;nodes/metrics&lt;/code&gt;, &lt;code&gt;nodes/stats&lt;/code&gt;, &lt;code&gt;nodes/log&lt;/code&gt;, etc.) allowing precise access without using &lt;code&gt;nodes/proxy&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;K8s Version&lt;/th&gt;
&lt;th&gt;KEP-2862 Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.32&lt;/td&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Beta, enabled by default&lt;/strong&gt; - &lt;code&gt;nodes/proxy GET&lt;/code&gt; no longer grants access to &lt;code&gt;/exec&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.36&lt;/td&gt;
&lt;td&gt;GA (locked to enabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;But this requires going through EVERY chart in use and checking all deployed manifests now &lt;strong&gt;and in the future&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="ciliumnetworkpolicy-blocking-the-kubelet-port"&gt;CiliumNetworkPolicy: Blocking the Kubelet port
&lt;/h3&gt;&lt;p&gt;While waiting for the K8s upgrade, or as defense-in-depth, you can block access to port 10250 from the affected pods using NetworkPolicies (or CiliumNetworkPolicy if you use Cilium as your CNI plugin).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: This only applies to components that do &lt;em&gt;not&lt;/em&gt; need to access the Kubelet. The OTel collector potentially needs it to gather Kubelet metrics. In that case, you have no choice but to fix the RBAC.&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;cilium.io/v2&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;CiliumNetworkPolicy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deny-kubelet-api-access&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;namespace&amp;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="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;endpointSelector&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;matchLabels&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;&amp;lt;app-label&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;value&amp;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;egressDeny&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;toEntities&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;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="l"&gt;remote-node&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;toPorts&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;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;port&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;10250&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;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TCP&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;h3 id="kyverno-blocking-the-creation-of-new-roles-with-nodesproxy"&gt;Kyverno: Blocking the creation of new Roles with nodes/proxy
&lt;/h3&gt;&lt;p&gt;To prevent any regression (remember, we need to protect ourselves &lt;strong&gt;in the future&lt;/strong&gt;), we can add a Kyverno &lt;code&gt;ClusterPolicy&lt;/code&gt; that rejects the creation or modification of a &lt;code&gt;ClusterRole&lt;/code&gt; or &lt;code&gt;Role&lt;/code&gt; containing &lt;code&gt;nodes/proxy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Luckily, there are ready-to-use examples on the official Kyverno website:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kyverno.io/policies/other-vpol/restrict-clusterrole-nodesproxy/restrict-clusterrole-nodesproxy/" target="_blank" rel="noopener"
&gt;validating variant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kyverno.io/policies/other-cel/restrict-clusterrole-nodesproxy/restrict-clusterrole-nodesproxy/" target="_blank" rel="noopener"
&gt;CEL variant&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note : &lt;a class="link" href="https://github.com/kyverno/policies/issues/1492" target="_blank" rel="noopener"
&gt;there probably is a hole in the official Kyverno policy, I opened an issue&lt;/a&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;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;kyverno.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ClusterPolicy&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;restrict-nodes-proxy&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;validationFailureAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Audit &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# switch to Enforce after validation&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;background&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;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deny-nodes-proxy-in-clusterroles&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;match&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;any&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;resources&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;kinds&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;ClusterRole&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;Role&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;exclude&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;any&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;resources&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;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;system:kubelet-api-admin&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# built-in K8s, unmodifiable&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;validate&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; nodes/proxy grants RCE capability via Kubelet WebSocket exec.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Use nodes/pods (requires K8s &amp;gt;= 1.33, KEP-2862) instead.&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;deny&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;conditions&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;any&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;nodes/proxy&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;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;AnyIn&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;value&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;{{ request.object.rules[].resources[] }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Deployment is done in two stages: first in &lt;code&gt;Audit&lt;/code&gt; mode to ensure there are no remaining vulnerable manifests (which you should fix &lt;strong&gt;before&lt;/strong&gt; blocking), then in &lt;code&gt;Enforce&lt;/code&gt; mode to actually block them.&lt;/p&gt;
&lt;h3 id="monitoring-the-audit-log"&gt;Monitoring the Audit Log
&lt;/h3&gt;&lt;p&gt;Even if commands executed &lt;em&gt;via&lt;/em&gt; the Kubelet leave no trace, we can monitor &lt;strong&gt;SubjectAccessReviews&lt;/strong&gt; to detect attempts at enumerating &lt;code&gt;nodes/proxy&lt;/code&gt; permissions.&lt;/p&gt;
&lt;p&gt;The configuration in the Kubernetes audit policy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# audit-policy.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Request&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;verbs&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;create&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;resources&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;group&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;authorization.k8s.io&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;resources&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;subjectaccessreviews&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then a Prometheus/Alertmanager alert on SARs related to &lt;code&gt;nodes/proxy&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-promql" data-lang="promql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Detect SARs targeting nodes/proxy&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="kr"&gt;increase&lt;/span&gt;&lt;span class="o"&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="nv"&gt;apiserver_audit_event_total&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="nl"&gt;verb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#34;&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="p"&gt;&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="nl"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#34;&lt;/span&gt;&lt;span class="s"&gt;subjectaccessreviews&lt;/span&gt;&lt;span class="p"&gt;&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="p"&gt;}[&lt;/span&gt;&lt;span class="s"&gt;5m&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="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;h2 id="references"&gt;References
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://grahamhelton.com/blog/nodes-proxy-rce" target="_blank" rel="noopener"
&gt;Kubernetes RCE via nodes/proxy GET&lt;/a&gt; - Graham Helton&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://gist.github.com/grahamhelton/f5c8ce265161990b0847ac05a74e466a" target="_blank" rel="noopener"
&gt;Detection Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/2862-fine-grained-kubelet-authz/README.md" target="_blank" rel="noopener"
&gt;KEP-2862 Fine-Grained Kubelet API Authorization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/concepts/security/rbac-good-practices/#access-to-proxy-subresource-of-nodes" target="_blank" rel="noopener"
&gt;Kubernetes RBAC Good Practices - nodes/proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/rook/rook/pull/16979" target="_blank" rel="noopener"
&gt;rook/rook#16979&lt;/a&gt; - Fix upstream Rook-Ceph&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/open-telemetry/opentelemetry-helm-charts/pull/2083" target="_blank" rel="noopener"
&gt;open-telemetry/opentelemetry-helm-charts#2083&lt;/a&gt; - Fix upstream OTel&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>101 ways to deploy Kubernetes: a brand new UI to explore 118+ solutions</title><link>https://blog.zwindler.fr/en/2026/02/09/101-ways-to-deploy-kubernetes-a-brand-new-ui-to-explore-118-solutions/</link><pubDate>Mon, 09 Feb 2026 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2026/02/09/101-ways-to-deploy-kubernetes-a-brand-new-ui-to-explore-118-solutions/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/02/101-kubernetes-ui-screenshot.webp" alt="Featured image of post 101 ways to deploy Kubernetes: a brand new UI to explore 118+ solutions" /&gt;&lt;h2 id="from-google-sheet-to-a-real-web-application"&gt;From Google Sheet to a real web application
&lt;/h2&gt;&lt;p&gt;You might remember my previous posts about this project: first a &lt;a class="link" href="https://blog.zwindler.fr/en/2025/11/02/93-ways-to-deploy-kubernetes-ive-cataloged-almost-all-existing-methods/" target="_blank" rel="noopener"
&gt;simple Google Sheet with 93 methods&lt;/a&gt;, then a &lt;a class="link" href="https://blog.zwindler.fr/en/2026/01/04/101-ways-to-deploy-kubernetes-v2/" target="_blank" rel="noopener"
&gt;GitHub repository with over 100 entries&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Today, I can present the latest iteration of this project: &lt;strong&gt;a real web interface&lt;/strong&gt; to explore all these solutions!&lt;/p&gt;
&lt;p&gt;👉 &lt;a class="link" href="https://zwindler.github.io/101-ways-to-deploy-kubernetes/" target="_blank" rel="noopener"
&gt;101-ways-to-deploy-kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/02/101-kubernetes-ui-screenshot.avif"
loading="lazy"
alt="Web interface for the 101 ways to deploy Kubernetes project"
&gt;&lt;/p&gt;
&lt;h2 id="why-a-ui"&gt;Why a UI?
&lt;/h2&gt;&lt;p&gt;The Markdown table on GitHub was already better than the Google Sheet for collaboration, but barely. Hard to parse, hard to add columns without it becoming a mess (it already was, haha), and above all, incredibly UGLY.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/01/101-kubernetes-v2-screenshot.avif"
loading="lazy"
alt="Old Markdown table on GitHub, hard to read"
&gt;&lt;/p&gt;
&lt;p&gt;I therefore decided to turn all of this into a modern and intuitive interface, with the help of an LLM.&lt;/p&gt;
&lt;h2 id="tech-stack-astro--tailwind"&gt;Tech stack: Astro + Tailwind
&lt;/h2&gt;&lt;p&gt;For this project, I chose a simple but effective stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://astro.build/" target="_blank" rel="noopener"
&gt;Astro&lt;/a&gt;&lt;/strong&gt;: a modern framework that generates ultra-fast static sites&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://tailwindcss.com/" target="_blank" rel="noopener"
&gt;Tailwind CSS&lt;/a&gt;&lt;/strong&gt;: for hassle-free responsive design&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result? A lightweight, fairly fast site that works on both &lt;strong&gt;desktop&lt;/strong&gt; and &lt;strong&gt;mobile&lt;/strong&gt; (though the desktop experience remains more comfortable given the amount of data).&lt;/p&gt;
&lt;h2 id="features"&gt;Features
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Cards for each solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No more spreadsheet-style table worthy of a backend dev (no, worse, a kube engineer&amp;hellip;)! Each tool now has its own animated &amp;ldquo;card&amp;rdquo; with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The project logo&lt;/li&gt;
&lt;li&gt;The license type (OSS or proprietary)&lt;/li&gt;
&lt;li&gt;The GitHub star count&lt;/li&gt;
&lt;li&gt;Direct links to the project and third-party resources (independent blogs, experience reports, tutorials&amp;hellip;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/02/101-kubernetes-zoom.avif"
loading="lazy"
alt="Detailed view of a Kubernetes solution card with logo, license, and GitHub stars"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Powerful filters&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Looking only for open source solutions? Tools for local development? Management platforms?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Category&lt;/strong&gt; filters make navigation easy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Desktop (local development)&lt;/li&gt;
&lt;li&gt;Managed (cloud offerings)&lt;/li&gt;
&lt;li&gt;Self-hosted (on-premise automation)&lt;/li&gt;
&lt;li&gt;Infra As Code&lt;/li&gt;
&lt;li&gt;Kubernetes OS (specialized operating systems)&lt;/li&gt;
&lt;li&gt;Management Platform&lt;/li&gt;
&lt;li&gt;Kubernetes in Kubernetes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also filter by &lt;strong&gt;status&lt;/strong&gt; (active, abandoned) or show only &lt;strong&gt;open source&lt;/strong&gt; or &lt;strong&gt;production ready&lt;/strong&gt; solutions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A search bar&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Know what you&amp;rsquo;re looking for? Just type the name in the search bar to find the solution instantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tags for refinement&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Beyond categories, tags help quickly identify underlying technologies (kubeadm, k3s, k0s&amp;hellip;).&lt;/p&gt;
&lt;h2 id="did-you-know-at-least-18-tools-use-kubeadm"&gt;Did you know? At least 18 tools use kubeadm!
&lt;/h2&gt;&lt;p&gt;While compiling all this data, I discovered something fascinating: &lt;strong&gt;at least 18 tools&lt;/strong&gt; use &lt;code&gt;kubeadm&lt;/code&gt; as a backend to deploy Kubernetes! 🤯&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s not even counting the managed Kubernetes offerings from cloud providers!&lt;/p&gt;
&lt;p&gt;This is exactly the kind of information you can now visualize instantly thanks to this new interface.&lt;/p&gt;
&lt;h2 id="a-collaborative-project"&gt;A collaborative project
&lt;/h2&gt;&lt;p&gt;The project remains &lt;strong&gt;100% open source&lt;/strong&gt; and collaborative. The data is still stored in the GitHub repository, and the UI is automatically generated from that data (I even added PR previews).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is a tool or provider missing?&lt;/strong&gt; Spotted a bug? Feel free to &lt;a class="link" href="https://github.com/zwindler/101-ways-to-deploy-kubernetes/issues" target="_blank" rel="noopener"
&gt;open an issue&lt;/a&gt; or submit a Pull Request!&lt;/p&gt;
&lt;p&gt;The project now lists &lt;strong&gt;118 solutions&lt;/strong&gt; (and I know there are certainly more missing), each with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Up-to-date links&lt;/li&gt;
&lt;li&gt;Project status&lt;/li&gt;
&lt;li&gt;External references (tutorials, experience reports&amp;hellip;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="try-it-comment-share"&gt;Try it, comment, share
&lt;/h2&gt;&lt;p&gt;Head over to &lt;a class="link" href="https://zwindler.github.io/101-ways-to-deploy-kubernetes/" target="_blank" rel="noopener"
&gt;zwindler.github.io/101-ways-to-deploy-kubernetes&lt;/a&gt; to explore all these solutions!&lt;/p&gt;
&lt;p&gt;OK, this is a shameless &amp;ldquo;call to action&amp;rdquo; like you see on every social network. Fair enough.&lt;/p&gt;
&lt;p&gt;However, I can&amp;rsquo;t know if this is useful (or not) if you don&amp;rsquo;t tell me. I can leave it as is (it&amp;rsquo;s not a big deal, I have plenty of other projects waiting for my spare time) or keep it alive, if you like it / find it useful.&lt;/p&gt;
&lt;p&gt;And if you do find this project useful, please:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Star&lt;/strong&gt; the project on &lt;a class="link" href="https://github.com/zwindler/101-ways-to-deploy-kubernetes" target="_blank" rel="noopener"
&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share&lt;/strong&gt; it with your colleagues in the Cloud Native community&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribute&lt;/strong&gt; by adding missing tools or fixing errors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks in advance! 🙏&lt;/p&gt;</description></item><item><title>101 ways to deploy Kubernetes v2</title><link>https://blog.zwindler.fr/en/2026/01/04/101-ways-to-deploy-kubernetes-v2/</link><pubDate>Sun, 04 Jan 2026 10:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2026/01/04/101-ways-to-deploy-kubernetes-v2/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/01/101-kubernetes-v2-screenshot.webp" alt="Featured image of post 101 ways to deploy Kubernetes v2" /&gt;&lt;h2 id="a-new-version-a-new-home"&gt;A new version, a new home
&lt;/h2&gt;&lt;p&gt;A few months ago, I created a &lt;a class="link" href="https://blog.zwindler.fr/en/2025/11/02/93-ways-to-deploy-kubernetes-ive-cataloged-almost-all-existing-methods/" target="_blank" rel="noopener"
&gt;spreadsheet cataloging 93 different ways to deploy Kubernetes&lt;/a&gt;, hosted on Google Sheets.&lt;/p&gt;
&lt;p&gt;Following a remark from a frenchie from the Cloud Native community (Ludovic Piot) who would have liked to contribute one or two missing tools, I took some (well, a lot, actually) time during these holidays to &lt;strong&gt;migrate everything to Github&lt;/strong&gt;: &lt;a class="link" href="https://github.com/zwindler/101-ways-to-deploy-kubernetes" target="_blank" rel="noopener"
&gt;101-ways-to-deploy-kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/01/101-kubernetes-v2-screenshot2.avif"
loading="lazy"
alt="Screenshot of the 101 ways to deploy Kubernetes GitHub repository"
&gt;&lt;/p&gt;
&lt;p&gt;Why on a &lt;code&gt;git&lt;/code&gt; repository rather than on a Google Sheet like at the beginning? Because &lt;strong&gt;it allows me to easily make this project truly collaborative&lt;/strong&gt;, especially through PRs (and more importantly their validation or not).&lt;/p&gt;
&lt;p&gt;Google Sheets was fine to start with, when it was a personal tool to help me write my book, but for collaboration, it wasn&amp;rsquo;t viable.&lt;/p&gt;
&lt;h2 id="whats-new-in-this-v2"&gt;What&amp;rsquo;s new in this v2?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Easier contributions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The project now has a &lt;strong&gt;&lt;a class="link" href="https://github.com/zwindler/101-ways-to-deploy-kubernetes/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener"
&gt;CONTRIBUTING.md&lt;/a&gt;&lt;/strong&gt; that explains how to participate.&lt;/p&gt;
&lt;p&gt;Do you know a solution that&amp;rsquo;s not listed? Did you spot an error? A broken link? Just submit a Pull Request!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Many new methods (I haven&amp;rsquo;t counted but probably a dozen)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The project has grown from 93 to &lt;strong&gt;over 100 methods&lt;/strong&gt;! I&amp;rsquo;ve continued my explorations and added a dozen new solutions, some of which you suggested to me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;License information&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve enriched the table with &lt;strong&gt;license details&lt;/strong&gt; for each project. This is particularly important to quickly identify whether a product is truly open source or if it&amp;rsquo;s a proprietary or commercial license solution.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/01/101-kubernetes-v2-screenshot.avif"
loading="lazy"
alt="Table of Kubernetes deployment methods with license information"
&gt;&lt;/p&gt;
&lt;p&gt;I had this debate with Ludovic actually, who wanted to understand why I hadn&amp;rsquo;t listed &amp;ldquo;MKE (Mirantis Kubernetes Engine)&amp;rdquo;. I had responded at the time that it was because I couldn&amp;rsquo;t access a trial version as a &amp;ldquo;random from the Internet&amp;rdquo; without going through a salesperson, but the argument wasn&amp;rsquo;t very solid.&lt;/p&gt;
&lt;p&gt;To find a &amp;ldquo;unique&amp;rdquo; rule to indicate in the &lt;a class="link" href="https://github.com/zwindler/101-ways-to-deploy-kubernetes/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener"
&gt;CONTRIBUTING.md&lt;/a&gt;, I therefore reintegrated some of the solutions I had &amp;ldquo;censored&amp;rdquo;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;While the heart of the cloud native ecosystem is open source, both open source and closed source (proprietary) tools are accepted in this list. What matters is whether the tool helps deploy Kubernetes clusters, not its licensing model.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Numerous external references&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I did &lt;strong&gt;significant work&lt;/strong&gt; on the &amp;ldquo;external references&amp;rdquo; section. The idea of the repo, beyond just providing a laundry list, is to reference third-party articles showing &lt;strong&gt;how&lt;/strong&gt; to set up these solutions and benefit from the community&amp;rsquo;s feedback.&lt;/p&gt;
&lt;p&gt;With the idea of making the project more international (it will be just as useful to English speakers as to the little frenchies in my community), I prioritized English links for as many solutions as possible on this list. There are still some without them, but it&amp;rsquo;s progressing.&lt;/p&gt;
&lt;p&gt;Important note: beyond the official documentation (which exists very often for the vast majority of projects), I wanted to catalog:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quality community tutorials&lt;/li&gt;
&lt;li&gt;Detailed blog articles&lt;/li&gt;
&lt;li&gt;Concrete feedback and experience reports&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-project-still-under-cc-by-sa-40"&gt;A project still under CC BY-SA 4.0
&lt;/h2&gt;&lt;p&gt;The content remains under &lt;a class="link" href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener"
&gt;CC BY-SA 4.0 (Attribution - Share Alike)&lt;/a&gt; license.&lt;/p&gt;
&lt;p&gt;This means you are free to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Share&lt;/strong&gt;: copy and redistribute the content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adapt&lt;/strong&gt;: remix, transform, and build upon the material&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Under the following conditions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Attribution&lt;/strong&gt;: you must credit the project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share Alike&lt;/strong&gt;: if you adapt the content, you must distribute it under the same license&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-to-contribute"&gt;How to contribute?
&lt;/h2&gt;&lt;p&gt;It&amp;rsquo;s simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fork the &lt;a class="link" href="https://github.com/zwindler/101-ways-to-deploy-kubernetes" target="_blank" rel="noopener"
&gt;101-ways-to-deploy-kubernetes&lt;/a&gt; repository&lt;/li&gt;
&lt;li&gt;Add or modify the content (following the table format)&lt;/li&gt;
&lt;li&gt;Submit a Pull Request&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Read the &lt;a class="link" href="https://github.com/zwindler/101-ways-to-deploy-kubernetes/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener"
&gt;CONTRIBUTING.md&lt;/a&gt; for more details on the process and best practices.&lt;/p&gt;
&lt;p&gt;All contributions are welcome: new entries, corrections, enhancements, suggestions&amp;hellip;&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next?
&lt;/h2&gt;&lt;p&gt;The project will continue to evolve with new discoveries and your contributions.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also continuing to explore new solutions and enrich existing information. And who knows, maybe we&amp;rsquo;ll exceed 150 methods by the end of the year? 😄&lt;/p&gt;
&lt;p&gt;This is the moment where I play influencer and ask you not to hesitate to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Star&lt;/strong&gt; the project on Github if you find it useful ⭐⭐⭐&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribute&lt;/strong&gt; by adding your discoveries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share&lt;/strong&gt; around you if you find it useful&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See you at &lt;a class="link" href="https://github.com/zwindler/101-ways-to-deploy-kubernetes" target="_blank" rel="noopener"
&gt;github.com/zwindler/101-ways-to-deploy-kubernetes&lt;/a&gt;!&lt;/p&gt;</description></item><item><title>93 ways to deploy Kubernetes: I've cataloged (almost) all existing methods</title><link>https://blog.zwindler.fr/en/2025/11/02/93-ways-to-deploy-kubernetes-ive-cataloged-almost-all-existing-methods/</link><pubDate>Sun, 02 Nov 2025 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2025/11/02/93-ways-to-deploy-kubernetes-ive-cataloged-almost-all-existing-methods/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/11/93-kubernetes-screenshot.webp" alt="Featured image of post 93 ways to deploy Kubernetes: I've cataloged (almost) all existing methods" /&gt;&lt;h2 id="a-slightly-crazy-documentary-project"&gt;A slightly crazy documentary project
&lt;/h2&gt;&lt;p&gt;When I started writing my book &lt;a class="link" href="https://www.editions-eyrolles.com/livre/kubernetes" target="_blank" rel="noopener"
&gt;&amp;ldquo;Kubernetes: 50 solutions for development workstations and production clusters&amp;rdquo;&lt;/a&gt;, I quickly realized a problem: &lt;strong&gt;there are an infinite number of ways to deploy Kubernetes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Well, not &lt;em&gt;literally&lt;/em&gt; infinite, but still&amp;hellip; many. Too many?&lt;/p&gt;
&lt;p&gt;To structure my book and choose which solutions I would cover, I did what any good nerd would do: &lt;strong&gt;I created a spreadsheet&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A very large spreadsheet.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Google Sheet&lt;/strong&gt; that currently lists &lt;strong&gt;93 different methods&lt;/strong&gt; for deploying Kubernetes. Since I don&amp;rsquo;t like to &amp;ldquo;waste&amp;rdquo;, I&amp;rsquo;m sharing it with you today &lt;a class="link" href="https://creativecommons.org/licenses/by-sa/4.0/deed.en" target="_blank" rel="noopener"
&gt;under CC BY-SA 4.0 (Attribution - Share Alike)&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.google.com/spreadsheets/d/1zjOLU8MblsN8VPwUyHNDFbBQ7EihHM2joD2Q9Tv2DiI/edit?usp=sharing" target="_blank" rel="noopener"
&gt;93 ways to deploy Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/93-kubernetes-screenshot.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="what-does-this-spreadsheet-contain"&gt;What does this spreadsheet contain?
&lt;/h2&gt;&lt;p&gt;The spreadsheet is structured with several columns to help you navigate this jungle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Product name (and publisher when interesting)&lt;/li&gt;
&lt;li&gt;Product URL: I tried to restrict to open source products (or &lt;strong&gt;public&lt;/strong&gt; managed services) although there are a few exceptions&lt;/li&gt;
&lt;li&gt;Solution type (I&amp;rsquo;ll come back to this)&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Based on&amp;rdquo;: this is quite funny&amp;hellip; many projects are layers on top of kubeadm, k3s or k0s. Not all of them say it openly, and I realized it by trying them or digging in the code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And maybe a bit less interesting for some of you (maybe it will disappear)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do I talk about it in my book?&lt;/li&gt;
&lt;li&gt;Do I talk about it on my blog?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-different-tool-categories"&gt;The different tool categories
&lt;/h2&gt;&lt;p&gt;To structure first my thinking, and then my book, I tried to classify these methods into categories.&lt;/p&gt;
&lt;p&gt;Some are quite obvious (a managed offering, you can immediately see what it&amp;rsquo;s about), others, a bit more personal (and therefore debatable).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes on desktop (Local Development)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tools for developing locally on your machine. We&amp;rsquo;re talking about Minikube, kind and other Docker Desktop&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tools that allow you to describe Kubernetes deployment via code (opentofu, crossplane, pulumi&amp;hellip;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes in Kubernetes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because why make it simple when you can make it&amp;hellip; recursive? 🤯. It&amp;rsquo;s currently limited to vCluster and k3k.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Specialized OSes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Operating systems designed specifically to run Kubernetes. I&amp;rsquo;m obviously thinking of Talos Linux, but not only ;-P.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Managed Kubernetes (turnkey cloud offerings)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No need to draw a picture, we immediately think of the EKS / AKS / GKE triplet, but also French solutions (OVHcloud Managed Kubernetes, soon Clever Cloud :smirk:)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cluster management platforms&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here, it&amp;rsquo;s a somewhat separate category, which will allow us to manage many Kubernetes clusters and even generate new clusters managed by clusters&amp;hellip; Often good Rube Goldberg machines like Gardener or worse Kubermatic Kubernetes Platform. We still have some slightly more fun things like Kamaji.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Automation tools for self-hosted&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Solutions that automate deployment on your own machines, like kubeadm, k3s and k0s (the triplet, basis of about 50% of other market solutions).&lt;/p&gt;
&lt;h2 id="the-revelation-everyone-copies-from-their-neighbor"&gt;The revelation: everyone copies from their neighbor
&lt;/h2&gt;&lt;p&gt;While filling in this spreadsheet, I discovered something funny: &lt;strong&gt;a large majority of tools don&amp;rsquo;t reinvent the wheel&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Many projects are actually &lt;strong&gt;layers&lt;/strong&gt; or &lt;strong&gt;wrappers&lt;/strong&gt; around three basic solutions I just mentioned (kubeadm, k3s and k0s). And sometimes, we &lt;strong&gt;also&lt;/strong&gt; have layers on slightly more confidential solutions.&lt;/p&gt;
&lt;p&gt;The information isn&amp;rsquo;t always available, I sometimes discovered it in a blog post, or even by digging into the solution&amp;rsquo;s internals.&lt;/p&gt;
&lt;p&gt;This is typically the kind of interesting column to really realize that beyond the apparent diversity of these deployment solutions, there&amp;rsquo;s actually a big standard and a few variations.&lt;/p&gt;
&lt;p&gt;I hope to manage to find more similar clues to fill this column even more :).&lt;/p&gt;
&lt;h2 id="a-living-document"&gt;A living document
&lt;/h2&gt;&lt;p&gt;This spreadsheet is not static. The number of tools evolves regularly (I discover new ones almost every week).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you know a method that&amp;rsquo;s not listed&lt;/strong&gt;, don&amp;rsquo;t hesitate to comment on social networks.&lt;/p&gt;
&lt;p&gt;Note: I remind you that I primarily target &lt;strong&gt;open source&lt;/strong&gt; solutions or &lt;strong&gt;public&lt;/strong&gt; managed services (I just removed Mirantis Kubernetes Engine for this reason).&lt;/p&gt;
&lt;h2 id="so-what-do-i-still-have-to-test"&gt;So, what do I still have to test?
&lt;/h2&gt;&lt;p&gt;I&amp;rsquo;ve already teased on reputable social networks, in the list of things I haven&amp;rsquo;t tested yet but that could motivate me, there are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kamaji.clastix.io" target="_blank" rel="noopener"
&gt;kamaji&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/portainer/kubesolo" target="_blank" rel="noopener"
&gt;kubesolo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kubeclipper/kubeclipper" target="_blank" rel="noopener"
&gt;kubeclipper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tarook.cloud/en" target="_blank" rel="noopener"
&gt;tarook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>