<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Firewall on inetshell</title><link>https://inet.sh/en/tags/firewall/</link><description>Recent content in Firewall on inetshell</description><generator>Hugo</generator><language>en</language><lastBuildDate>Mon, 05 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/en/tags/firewall/index.xml" rel="self" type="application/rss+xml"/><item><title>OPNsense: port-forward unreachable from the same WAN subnet (reply-to)</title><link>https://inet.sh/en/posts/opnsense/reply-to-same-wan-subnet/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/opnsense/reply-to-same-wan-subnet/</guid><description>Why an OPNsense port-forward works from the internet but times out from an IP in the same /24 as the WAN, and how “Disable reply-to on WAN rules” fixes it.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p><strong>Firewall → Settings → Advanced → Disable reply-to on WAN rules</strong> → check → <strong>Save</strong> → <strong>Apply</strong>.</p>
<p>Symptom: a port-forward or WAN rule answers from any internet IP, but from a host that lives in <strong>the same subnet as the firewall&rsquo;s WAN</strong> everything times out (SSH, HTTPS, even <code>ping</code>), even though the rule says <code>from any</code>.</p>
<p>With a single WAN this is safe: the firewall stops forcing replies through the gateway and goes back to its routing table.</p>
<h2 id="the-setup">The setup</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">                203.0.113.0/24  (same L2 segment)
</span></span><span class="line"><span class="cl">   ┌────────────────────┬────────────────────┬────────────────────┐
</span></span><span class="line"><span class="cl">   │                    │                    │                    │
</span></span><span class="line"><span class="cl"> gateway             fw01 (OPNsense)      neighbor             internet
</span></span><span class="line"><span class="cl"> 203.0.113.1         WAN 203.0.113.40     203.0.113.50         (via gateway)
</span></span><span class="line"><span class="cl">                        │ rdr :22 → 192.168.1.10
</span></span><span class="line"><span class="cl">                        ▼
</span></span><span class="line"><span class="cl">                     LAN server
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>From the internet: <code>ssh -p 22 203.0.113.40</code> reaches the LAN server. Fine.</li>
<li>From <code>neighbor</code> (203.0.113.50): every port times out.</li>
</ul>
<h2 id="what-reply-to-does">What <code>reply-to</code> does</h2>
<p>OPNsense generates the rules on WAN-type interfaces (those with a gateway) with <code>reply-to</code> pointing at that gateway:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pfctl -sr <span class="p">|</span> grep reply-to
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pass in quick on vtnet0 reply-to (vtnet0 203.0.113.1) inet proto tcp from any to 192.168.1.10 port = ssh flags S/SA keep state
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>reply-to</code> is a <strong>pf</strong> option. When a packet comes in and creates a state, pf records in that state that <strong>every reply</strong> must leave through <code>vtnet0</code> toward <code>203.0.113.1</code>. Those replies skip the routing table: pf picks the next hop on its own.</p>
<p>It exists for <strong>multi-WAN</strong>. If you have two providers and a client comes in on WAN2, the reply must leave through WAN2. If it leaves through the default route (WAN1), it carries a source IP that provider 1 does not recognize and gets lost. <code>reply-to</code> guarantees a symmetric return path without thinking about routes.</p>
<h2 id="why-it-breaks-neighbors">Why it breaks neighbors</h2>
<p>For an internet client, &ldquo;reply to the gateway&rdquo; is exactly what the routing table would do anyway, so nothing looks wrong.</p>
<p>For the neighbor it is different. Its IP (203.0.113.50) is on a <strong>connected network</strong> of the firewall. Without <code>reply-to</code>, the firewall would ARP for its MAC and answer directly. With <code>reply-to</code>, the SYN-ACK leaves with the <strong>gateway&rsquo;s MAC</strong> as destination:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># on the OPNsense box (csh shell: wrap in sh -c)</span>
</span></span><span class="line"><span class="cl">sh -c <span class="s2">&#34;tcpdump -lnei vtnet0 host 203.0.113.50 and tcp port 22&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&lt;neighbor-mac&gt; &gt; &lt;fw01-mac&gt;          203.0.113.50.49612 &gt; 203.0.113.40.22: Flags [S]    ← arrives directly
</span></span><span class="line"><span class="cl">&lt;fw01-mac&gt;     &gt; 00:00:5e:00:01:&lt;vrid&gt; 203.0.113.40.22 &gt; 203.0.113.50.49612: Flags [S.]   ← leaves to the gateway
</span></span></code></pre></td></tr></table>
</div>
</div><p>(<code>00:00:5e:00:01:xx</code> is the VRRP virtual MAC: the gateway was a redundant pair.)</p>
<p>From there it depends on the gateway. To it, the packet arrives on the same interface it would have to leave through, and if it runs a stateful filter it sees a SYN-ACK for a connection whose SYN it never saw. In the lab, the gateway simply did not send it back onto the segment. On the neighbor you only see retried SYNs and no <code>[S.]</code>.</p>
<p>The fingerprint on the firewall is a half-open state:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pfctl -ss <span class="p">|</span> grep 203.0.113.50
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">all tcp 192.168.1.10:22 (203.0.113.40:22) &lt;- 203.0.113.50:57792   SYN_SENT:ESTABLISHED
</span></span></code></pre></td></tr></table>
</div>
</div><p>The server already answered (<code>ESTABLISHED</code>) and the client is still waiting (<code>SYN_SENT</code>): <strong>the reply is lost on the way back</strong>, not on the way in. The filter is not blocking anything.</p>
<h2 id="what-the-fix-changes">What the fix changes</h2>
<p>With <strong>Disable reply-to on WAN rules</strong> checked, OPNsense regenerates the rules without <code>reply-to</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pass in quick on vtnet0 inet proto tcp from any to 192.168.1.10 port = ssh flags S/SA keep state
</span></span></code></pre></td></tr></table>
</div>
</div><p>The state no longer pins the next hop, so replies go through the normal routing table:</p>
<table>
  <thead>
      <tr>
          <th>Reply destination</th>
          <th>With <code>reply-to</code></th>
          <th>Without <code>reply-to</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Internet client</td>
          <td>gateway</td>
          <td>gateway (default route)</td>
      </tr>
      <tr>
          <td>Neighbor in 203.0.113.0/24</td>
          <td>gateway ✗</td>
          <td>direct via ARP ✓</td>
      </tr>
      <tr>
          <td>Client that came in on another WAN (multi-WAN)</td>
          <td>its gateway ✓</td>
          <td>default route ✗</td>
      </tr>
  </tbody>
</table>
<p>The last row is the cost. With multi-WAN, do not disable it globally. Instead, drop <code>reply-to</code> only on the affected port-forward&rsquo;s rule: in the rule, <strong>Advanced → reply-to</strong>. Check which options your version offers.</p>
<h2 id="verify">Verify</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># on the OPNsense box</span>
</span></span><span class="line"><span class="cl">sh -c <span class="s2">&#34;pfctl -sr | grep -c reply-to; grep -n disablereplyto /conf/config.xml&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># expected: 0  and  &lt;disablereplyto&gt;yes&lt;/disablereplyto&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># from the neighbor</span>
</span></span><span class="line"><span class="cl">nc -z -w5 203.0.113.40 <span class="m">22</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> open
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="traps">Traps</h2>
<ul>
<li><strong>&ldquo;Disable force gateway&rdquo; is not this.</strong> It sits on the same page with a similar name, but it only affects traffic <strong>originated by the firewall itself</strong> (DNS, NTP, updates). It does not touch replies to inbound traffic.</li>
<li><strong>A static route on the gateway does not help.</strong> The gateway already has the subnet as connected; the problem is that it will not send the packet back onto the same segment, not that it does not know the way.</li>
<li><strong>csh shell.</strong> Over SSH, OPNsense drops you into <code>csh</code>, and things like <code>2&gt;/dev/null</code> fail with <code>Ambiguous output redirect</code>. Use <code>sh -c &quot;...&quot;</code> or <code>ssh root@fw01 sh &lt;&lt;'EOF'</code>.</li>
<li><strong>Test every port, not one.</strong> If only one port fails it is a different problem (rule, NAT, service). If they all fail, <code>ping</code> included, and only from the same subnet, suspect <code>reply-to</code>.</li>
</ul>
<h2 id="see-also">See also</h2>
<ul>
<li>OPNsense docs, firewall settings: <a href="https://docs.opnsense.org/manual/firewall_settings.html">https://docs.opnsense.org/manual/firewall_settings.html</a></li>
<li><code>pf.conf(5)</code>, <code>route-to</code> / <code>reply-to</code> options: <a href="https://man.freebsd.org/cgi/man.cgi?query=pf.conf">https://man.freebsd.org/cgi/man.cgi?query=pf.conf</a></li>
</ul>
]]></content:encoded></item><item><title>Vault Agent Injector on a private GKE cluster: no sidecar and pods take 30 s (the control plane cannot reach the webhook on 8080)</title><link>https://inet.sh/en/posts/kubernetes/vault-agent-injector-gke-private-cluster-no-sidecar/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/kubernetes/vault-agent-injector-gke-private-cluster-no-sidecar/</guid><description>On a private GKE cluster, pods annotated with vault.hashicorp.com/agent-inject start without the vault-agent-init and vault-agent containers, take half a minute longer to be created, and the injector logs no request at all. The control plane can only open connections to nodes on ports 443 and 10250; the injector listens on 8080 and the webhook fails silently because failurePolicy is Ignore. Firewall rule from the control plane range, how to validate in the injector logs, and the same problem with any other admission webhook.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">CLUSTER</span><span class="o">=</span>my-cluster<span class="p">;</span> <span class="nv">REGION</span><span class="o">=</span>us-central1<span class="p">;</span> <span class="nv">NETWORK</span><span class="o">=</span>my-vpc
</span></span><span class="line"><span class="cl"><span class="nv">MASTER_CIDR</span><span class="o">=</span><span class="k">$(</span>gcloud container clusters describe <span class="nv">$CLUSTER</span> --region <span class="nv">$REGION</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --format<span class="o">=</span><span class="s1">&#39;value(privateClusterConfig.masterIpv4CidrBlock)&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">NODE_TAG</span><span class="o">=</span><span class="k">$(</span>gcloud compute firewall-rules list --filter<span class="o">=</span><span class="s2">&#34;name~^gke-</span><span class="nv">$CLUSTER</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --format<span class="o">=</span><span class="s1">&#39;value(targetTags.list())&#39;</span> <span class="p">|</span> head -1<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud compute firewall-rules create gke-<span class="nv">$CLUSTER</span>-master-to-vault-injector <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --network<span class="o">=</span><span class="nv">$NETWORK</span> --direction<span class="o">=</span>INGRESS --action<span class="o">=</span>ALLOW <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --source-ranges<span class="o">=</span><span class="nv">$MASTER_CIDR</span> --target-tags<span class="o">=</span><span class="nv">$NODE_TAG</span> --rules<span class="o">=</span>tcp:8080
</span></span></code></pre></td></tr></table>
</div>
</div><p>Delete an annotated pod and check that the injector gets the call:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl -n app delete pod -l <span class="nv">app</span><span class="o">=</span>api
</span></span><span class="line"><span class="cl">kubectl -n vault logs deploy/vault-agent-injector <span class="p">|</span> grep mutate
</span></span><span class="line"><span class="cl"><span class="c1"># [INFO]  handler: Request received: Method=POST URL=/mutate?timeout=30s</span>
</span></span><span class="line"><span class="cl">kubectl -n app get pod -l <span class="nv">app</span><span class="o">=</span>api -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.items[0].spec.initContainers[*].name} {.items[0].spec.containers[*].name}{&#34;\n&#34;}&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># vault-agent-init api vault-agent</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="the-symptom">The symptom</h2>
<ul>
<li>The pod carries <code>vault.hashicorp.com/agent-inject: &quot;true&quot;</code> and the rest of the annotations, but starts <strong>with the application container only</strong>: no <code>vault-agent-init</code>, no <code>vault-agent</code>.</li>
<li>Creating the pod <strong>takes about 30 seconds longer</strong> than usual.</li>
<li><code>kubectl get events</code> says nothing about the webhook. The injector logs do not have a single <code>Request received</code> line.</li>
<li>On other clusters (public zonal, kind, k3s) the same chart and the same annotations work.</li>
</ul>
<h2 id="why">Why</h2>
<p>On a <strong>private</strong> GKE cluster the control plane lives in a Google-owned VPC and reaches your nodes through the firewall rule GKE creates by itself (<code>gke-&lt;cluster&gt;-&lt;hash&gt;-master</code>). That rule only allows <strong>TCP 443 and 10250</strong> from the control plane range to the nodes. Everything else is closed.</p>
<p>An admission webhook does not go through the Service like a normal client would: the API server resolves the Service to its endpoints and <strong>connects straight to the pod IP on the <code>targetPort</code></strong>. The injector&rsquo;s Service (<code>vault-agent-injector-svc</code>) exposes 443, but the pod listens on <strong>8080</strong> (<code>injector.port</code> in the chart), so the actual connection goes to 8080 and the firewall drops it.</p>
<p>What happens next is explained by two chart defaults:</p>
<table>
  <thead>
      <tr>
          <th>Value</th>
          <th>Default</th>
          <th>Effect</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>injector.webhook.timeoutSeconds</code></td>
          <td><code>30</code></td>
          <td>The API server waits 30 s for the webhook&rsquo;s answer</td>
      </tr>
      <tr>
          <td><code>injector.webhook.failurePolicy</code></td>
          <td><code>Ignore</code></td>
          <td>If the webhook does not answer, the pod is created <strong>unmutated</strong>, with no error</td>
      </tr>
  </tbody>
</table>
<p>Hence the 30-second delay and the pod with no sidecar and no message. With <code>failurePolicy: Fail</code> you would have seen <code>failed calling webhook &quot;vault.hashicorp.com&quot;: … context deadline exceeded</code> in the ReplicaSet events, which at least points at the right place.</p>
<h2 id="not-just-vault">Not just Vault</h2>
<p>Any admission webhook, CRD conversion webhook or aggregated API listening on a port other than 443 or 10250 fails the same way on private GKE: the OpenTelemetry operator (9443), Kyverno (9443), Gatekeeper (8443)… cert-manager listens on <strong>10250</strong> precisely to avoid needing this rule. The GKE documentation lists it as a requirement for &ldquo;admission webhooks, aggregated API servers, webhook conversion&rdquo;: one rule per port, from <code>masterIpv4CidrBlock</code> to the node tags.</p>
<p>If the cluster lives in Terraform, keep the rule next to the cluster so it survives a rebuild:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;google_compute_firewall&#34; &#34;master_to_vault_injector&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  name</span>          <span class="o">=</span> <span class="s2">&#34;gke-${google_container_cluster.main.name}-master-to-vault-injector&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  network</span>       <span class="o">=</span> <span class="k">google_compute_network</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">name</span>
</span></span><span class="line"><span class="cl"><span class="n">  direction</span>     <span class="o">=</span> <span class="s2">&#34;INGRESS&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  source_ranges</span> <span class="o">=</span> <span class="p">[</span><span class="k">google_container_cluster</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">private_cluster_config</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="k">master_ipv4_cidr_block</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">  target_tags</span>   <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;gke-${google_container_cluster.main.name}&#34;</span><span class="p">]</span><span class="c1">   # the tag your node pools use
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">allow</span> {
</span></span><span class="line"><span class="cl"><span class="n">    protocol</span> <span class="o">=</span> <span class="s2">&#34;tcp&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    ports</span>    <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;8080&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="traps">Traps</h2>
<ul>
<li><strong>The wrong tag</strong>: nodes carry <code>gke-&lt;cluster&gt;-&lt;hash&gt;-node</code>; copy it from the existing <code>gke-&lt;cluster&gt;-*</code> rules instead of typing it. With several node pools, check that all of them have the tag.</li>
<li><strong>The control plane range changes if you recreate the cluster</strong>; a hand-written rule with the old CIDR silently stops working.</li>
<li><strong><code>failurePolicy: Fail</code></strong> makes the failure visible, but while the webhook is down <strong>no pod gets created</strong> in the namespaces matching the <code>namespaceSelector</code>; the chart excludes the injector itself, but not Vault if they share namespace and labels. Use it with <code>replicas: 2</code>.</li>
<li><strong>The injector does receive the request and there is still no sidecar</strong>: then it is no longer the network. Read the <code>vault-agent-init</code> logs: <code>permission denied</code> is the Kubernetes auth role (<code>bound_service_account_names</code>, <code>bound_service_account_namespaces</code>) or the policy; <code>connection refused</code> is <code>vault.hashicorp.com/service</code> or the Vault address.</li>
<li><strong>One port per rule does not scale</strong>: as webhooks pile up, one rule with <code>--rules=tcp:8080,8443,9443</code> from the control plane is easier to maintain than five.</li>
</ul>
<p>References: <a href="https://github.com/hashicorp/vault-k8s/issues/46">vault-k8s #46</a>, <a href="https://github.com/hashicorp/vault-k8s/issues/32">vault-k8s #32</a> and <a href="https://docs.cloud.google.com/kubernetes-engine/docs/how-to/private-clusters">firewall rules on private clusters</a> in the GKE documentation.</p>
<h2 id="see-also">See also</h2>
<ul>
<li><a href="/en/posts/kubernetes/cert-manager-http01-308-ingressclassname/">cert-manager: the HTTP-01 challenge returns 308</a> — another component that &ldquo;does nothing&rdquo; until you look in the right place.</li>
<li><a href="/en/posts/gcp/github-actions-workload-identity-federation/">GitHub Actions → Google Cloud without keys</a> — to deploy to that GKE without JSON keys.</li>
</ul>
]]></content:encoded></item></channel></rss>