<?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>Vault on inetshell</title><link>https://inet.sh/es/tags/vault/</link><description>Recent content in Vault on inetshell</description><generator>Hugo</generator><language>es</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/es/tags/vault/index.xml" rel="self" type="application/rss+xml"/><item><title>Vault Agent Injector en GKE privado: el sidecar no aparece y el pod tarda 30 s (el control plane no llega al webhook en el 8080)</title><link>https://inet.sh/es/posts/kubernetes/vault-agent-injector-gke-private-cluster-sin-sidecar/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/kubernetes/vault-agent-injector-gke-private-cluster-sin-sidecar/</guid><description>En un clúster GKE privado, los pods con vault.hashicorp.com/agent-inject arrancan sin los contenedores vault-agent-init y vault-agent, tardan medio minuto más en crearse y el injector no registra ninguna petición. El control plane solo puede abrir conexiones a los nodos en los puertos 443 y 10250; el injector escucha en el 8080 y el webhook falla en silencio porque failurePolicy es Ignore. Regla de firewall desde el rango del control plane, cómo validar en los logs del injector, y el mismo problema en cualquier otro webhook de admisión.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</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>Borra un pod anotado y comprueba que el injector recibe la llamada:</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="el-síntoma">El síntoma</h2>
<ul>
<li>El pod lleva <code>vault.hashicorp.com/agent-inject: &quot;true&quot;</code> y el resto de anotaciones, pero arranca <strong>solo con el contenedor de la aplicación</strong>: ni <code>vault-agent-init</code> ni <code>vault-agent</code>.</li>
<li>Crear el pod <strong>tarda unos 30 segundos más</strong> de lo normal.</li>
<li><code>kubectl get events</code> no dice nada del webhook. En los logs del injector no hay ni una línea de <code>Request received</code>.</li>
<li>En otros clústeres (zonales públicos, kind, k3s) el mismo chart y las mismas anotaciones funcionan.</li>
</ul>
<h2 id="por-qué">Por qué</h2>
<p>En un clúster <strong>privado</strong> de GKE el control plane vive en una VPC de Google y llega a tus nodos a través de la regla de firewall que GKE crea sola (<code>gke-&lt;cluster&gt;-&lt;hash&gt;-master</code>). Esa regla solo permite <strong>TCP 443 y 10250</strong> desde el rango del control plane hacia los nodos. Todo lo demás está cerrado.</p>
<p>Un webhook de admisión no pasa por el Service como lo haría un cliente normal: el API server resuelve el Service a sus endpoints y <strong>conecta directamente con la IP del pod en el <code>targetPort</code></strong>. El Service del injector (<code>vault-agent-injector-svc</code>) publica el 443, pero el pod escucha en el <strong>8080</strong> (<code>injector.port</code> del chart), así que la conexión real es al 8080 y el firewall la descarta.</p>
<p>Lo que pasa después lo explican dos valores por defecto del chart de Vault:</p>
<table>
  <thead>
      <tr>
          <th>Valor</th>
          <th>Por defecto</th>
          <th>Efecto</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>injector.webhook.timeoutSeconds</code></td>
          <td><code>30</code></td>
          <td>El API server espera 30 s la respuesta del webhook</td>
      </tr>
      <tr>
          <td><code>injector.webhook.failurePolicy</code></td>
          <td><code>Ignore</code></td>
          <td>Si el webhook no contesta, el pod se crea <strong>sin mutar</strong>, sin error</td>
      </tr>
  </tbody>
</table>
<p>De ahí los 30 segundos de retraso y el pod sin sidecar y sin ningún mensaje. Con <code>failurePolicy: Fail</code> habrías visto <code>failed calling webhook &quot;vault.hashicorp.com&quot;: … context deadline exceeded</code> en los eventos del ReplicaSet, que al menos apunta al sitio correcto.</p>
<h2 id="no-es-solo-vault">No es solo Vault</h2>
<p>Cualquier webhook de admisión, conversión de CRDs o API agregada que escuche en un puerto que no sea 443 o 10250 falla igual en GKE privado: el operador de OpenTelemetry (9443), Kyverno (9443), Gatekeeper (8443)… cert-manager escucha en el <strong>10250</strong> precisamente para no necesitar esta regla. La documentación de GKE lo lista como requisito de &ldquo;admission webhooks, aggregated API servers, webhook conversion&rdquo;: una regla por puerto, desde <code>masterIpv4CidrBlock</code> hacia los tags de los nodos.</p>
<p>Si el clúster está en Terraform, deja la regla junto al clúster para que no se pierda al recrearlo:</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">   # el tag que usan tus node pools
</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="trampas">Trampas</h2>
<ul>
<li><strong>El tag equivocado</strong>: los nodos llevan <code>gke-&lt;cluster&gt;-&lt;hash&gt;-node</code>; cópialo de las reglas <code>gke-&lt;cluster&gt;-*</code> existentes en vez de escribirlo a mano. Con varios node pools, comprueba que todos tengan el tag.</li>
<li><strong>El rango del control plane cambia si recreas el clúster</strong>; una regla escrita a mano con el CIDR viejo deja de servir sin avisar.</li>
<li><strong><code>failurePolicy: Fail</code></strong> hace visible el fallo, pero mientras el webhook esté caído <strong>no se crea ningún pod</strong> en los namespaces que casen con el <code>namespaceSelector</code>; el chart excluye al propio injector, pero no a Vault si comparten namespace y etiquetas. Úsalo con <code>replicas: 2</code>.</li>
<li><strong>El injector sí recibe la petición y sigue sin sidecar</strong>: entonces ya no es red. Mira los logs de <code>vault-agent-init</code>: <code>permission denied</code> es el rol de Kubernetes auth (<code>bound_service_account_names</code>, <code>bound_service_account_namespaces</code>) o la política; <code>connection refused</code> es <code>vault.hashicorp.com/service</code> o la dirección de Vault.</li>
<li><strong>Un solo puerto por regla no escala</strong>: si acumulas webhooks, una regla con <code>--rules=tcp:8080,8443,9443</code> desde el control plane es más fácil de mantener que cinco.</li>
</ul>
<p>Referencias: <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> y <a href="https://docs.cloud.google.com/kubernetes-engine/docs/how-to/private-clusters">reglas de firewall en clústeres privados</a> en la documentación de GKE.</p>
<h2 id="ver-también">Ver también</h2>
<ul>
<li><a href="/es/posts/kubernetes/cert-manager-http01-308-ingressclassname/">cert-manager: el reto HTTP-01 devuelve 308</a> — otro componente que &ldquo;no hace nada&rdquo; hasta que miras el sitio correcto.</li>
<li><a href="/es/posts/gcp/github-actions-workload-identity-federation/">GitHub Actions → Google Cloud sin llaves</a> — para desplegar en ese GKE sin llaves JSON.</li>
</ul>
]]></content:encoded></item></channel></rss>