<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hardening on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/hardening/</link><description>Recent content in Hardening on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Thu, 11 Jun 2026 11:00:00 +0000</lastBuildDate><atom:link href="https://blog.lo0.es/tags/hardening/index.xml" rel="self" type="application/rss+xml"/><item><title>Hardening y secretos del stack LLM soberano: defensa en profundidad</title><link>https://blog.lo0.es/posts/hardening-secretos-stack-llm-soberano/</link><pubDate>Thu, 11 Jun 2026 11:00:00 +0000</pubDate><guid>https://blog.lo0.es/posts/hardening-secretos-stack-llm-soberano/</guid><description>&lt;blockquote>
&lt;p>Parte de la serie operativa sobre exprimir un clúster LLM on-premise genérico 4×H100 SXM 80GB. Las piezas hermanas: la &lt;a href="https://blog.lo0.es/posts/ingesta-documental-rag-pdf-a-chunk-indexado/">ingesta documental de PDF a chunk indexado&lt;/a> que llena la base vectorial, el &lt;a href="https://blog.lo0.es/posts/servir-embeddings-rerankers-tei-produccion/">servicio de embeddings y rerankers con TEI en producción&lt;/a> que la alimenta, y —la más directamente acoplada a este post— el &lt;a href="https://blog.lo0.es/posts/gitops-stack-inferencia-llm-flux/">GitOps del stack de inferencia con Flux&lt;/a>, porque GitOps y secretos comparten un problema huevo-gallina que aquí resolvemos. El ensamblado completo de todo esto en un asistente conversacional (LibreChat + LiteLLM + RAG) lo cubre otro post de la serie, todavía en borrador.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Un stack de inferencia LLM soberano son, como mínimo, seis servicios: un &lt;strong>gateway&lt;/strong> (LiteLLM o equivalente), un &lt;strong>motor de inferencia&lt;/strong> (vLLM), un &lt;strong>servicio de embeddings/rerankers&lt;/strong> (TEI), una &lt;strong>base vectorial&lt;/strong>, una &lt;strong>base de estado&lt;/strong> (conversaciones, usuarios) y un &lt;strong>front&lt;/strong> (LibreChat). Seis servicios son seis superficies de ataque, y el asistente &amp;ldquo;funciona&amp;rdquo; en cuanto el gateway devuelve tokens — mucho antes de estar endurecido. Este post recorre la defensa en profundidad capa por capa:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Secretos&lt;/strong>: nunca en claro en git. &lt;code>sealed-secrets&lt;/code> (cifrado asimétrico, controlador en el clúster) vs &lt;strong>SOPS/age&lt;/strong> (cifrado de fichero, simple, GitOps puro) vs &lt;strong>External Secrets Operator + Vault&lt;/strong> (secretos dinámicos, rotación nativa). El problema huevo-gallina del GitOps.&lt;/li>
&lt;li>&lt;strong>Red&lt;/strong>: &lt;code>NetworkPolicy&lt;/code> default-deny + allow explícito; L3/L4 con la NetworkPolicy estándar, &lt;strong>L7 y egress por DNS&lt;/strong> con Cilium (eBPF); control de egress para que el dato no salga del perímetro; mTLS interno.&lt;/li>
&lt;li>&lt;strong>Pod security&lt;/strong>: &lt;code>runAsNonRoot&lt;/code>, &lt;code>readOnlyRootFilesystem&lt;/code>, drop de capabilities, seccomp &lt;code>RuntimeDefault&lt;/code>, Pod Security Standards &lt;strong>restricted&lt;/strong>, nada privilegiado.&lt;/li>
&lt;li>&lt;strong>Cadena de suministro&lt;/strong>: pin por &lt;strong>digest&lt;/strong>, escaneo con &lt;strong>Trivy&lt;/strong>, firma con &lt;strong>cosign&lt;/strong>, admission control que rechaza lo no firmado.&lt;/li>
&lt;li>&lt;strong>AuthN/Z&lt;/strong>: claves virtuales de LiteLLM, OIDC/LDAP en el front, RBAC de Kubernetes con mínimo privilegio.&lt;/li>
&lt;li>&lt;strong>Runtime&lt;/strong>: detección y, opcionalmente, enforcement con &lt;strong>Tetragon&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Dato en reposo&lt;/strong>: cifrado de almacenamiento, credenciales de la base vectorial y de la base de estado.&lt;/li>
&lt;/ol>
&lt;p>La tesis: el hardening &lt;strong>reduce&lt;/strong> el radio de impacto, no lo elimina. Por eso se prioriza por impacto, y el primer trabajo en un 4×H100 es el par gateway↔vector store y el egress-deny.&lt;/p>
&lt;h2 id="la-analogía-el-despacho-soberano">La analogía: el despacho soberano&lt;/h2>
&lt;p>Imagina un despacho que custodia documentación sensible — el corpus que alimenta el RAG, las conversaciones de los usuarios, las credenciales del motor. No basta con que la puerta de la calle cierre. La seguridad de verdad es &lt;strong>defensa en profundidad&lt;/strong>: varias capas, cada una asumiendo que la anterior puede fallar.&lt;/p>
&lt;div class="diagram" style="max-width:760px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 760 520" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Defensa en profundidad como capas concéntricas: perímetro, control de accesos, tabiques, caja fuerte, cámaras, datos">
&lt;defs>&lt;marker id="hd1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#888"/>&lt;/marker>&lt;/defs>
&lt;rect x="20" y="20" width="720" height="480" rx="14" fill="none" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="380" y="44" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="#3b82f6">Perímetro — control de egress (el dato no sale)&lt;/text>
&lt;rect x="58" y="58" width="644" height="404" rx="12" fill="none" stroke="#22c55e" stroke-width="2"/>
&lt;text x="380" y="80" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="600" fill="#22c55e">Control de accesos en cada puerta — NetworkPolicy default-deny&lt;/text>
&lt;rect x="100" y="94" width="560" height="332" rx="10" fill="none" stroke="#f59e0b" stroke-width="2"/>
&lt;text x="380" y="114" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="600" fill="#f59e0b">Tabiques cortafuegos — Pod Security restricted + namespaces&lt;/text>
&lt;rect x="142" y="128" width="476" height="262" rx="9" fill="none" stroke="#888" stroke-width="1.6"/>
&lt;text x="380" y="148" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="600" fill="currentColor">Portero que verifica credenciales — admission control (cosign)&lt;/text>
&lt;rect x="184" y="162" width="392" height="194" rx="8" fill="none" stroke="#888" stroke-width="1.4"/>
&lt;text x="380" y="182" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="600" fill="currentColor">Cámaras — Tetragon/Falco (registro y detección)&lt;/text>
&lt;rect x="248" y="208" width="264" height="120" rx="10" fill="#ef4444" fill-opacity="0.12" stroke="#ef4444" stroke-width="2"/>
&lt;text x="380" y="238" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="#ef4444">Caja fuerte&lt;/text>
&lt;text x="380" y="262" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor">secretos cifrados + llave rotada&lt;/text>
&lt;text x="380" y="284" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor">corpus RAG · conversaciones&lt;/text>
&lt;text x="380" y="306" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor">credenciales del motor&lt;/text>
&lt;text x="380" y="490" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="#888">Cada capa asume que la de fuera puede caer. Atravesarlas todas es caro; ese coste es la seguridad.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>El mapeo es directo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>El perímetro del edificio&lt;/strong> es el &lt;strong>control de egress&lt;/strong>: que un secreto filtrado o un proceso comprometido no pueda exfiltrar el corpus a un servidor externo. Es la capa más infravalorada y la primera que pongo en un despliegue soberano.&lt;/li>
&lt;li>&lt;strong>El control de accesos en cada puerta interior&lt;/strong> es la &lt;code>NetworkPolicy&lt;/code> default-deny: el front no habla con la base vectorial directamente porque no tiene por qué; solo los pares estrictamente necesarios están abiertos.&lt;/li>
&lt;li>&lt;strong>Los tabiques cortafuegos&lt;/strong> son el aislamiento de pod (&lt;code>restricted&lt;/code>) y los namespaces: si un servicio arde, el fuego no salta al de al lado.&lt;/li>
&lt;li>&lt;strong>El portero que verifica credenciales&lt;/strong> es el admission control que comprueba la &lt;strong>firma cosign&lt;/strong> de cada imagen antes de dejarla entrar al clúster.&lt;/li>
&lt;li>&lt;strong>Las cámaras&lt;/strong> son Tetragon/Falco: registran qué hizo cada proceso y detectan (o matan) lo anómalo.&lt;/li>
&lt;li>&lt;strong>La caja fuerte con llave rotada&lt;/strong> son los secretos: cifrados en reposo, fuera de git, y con rotación para que una llave robada caduque.&lt;/li>
&lt;/ul>
&lt;p>Ninguna capa es suficiente sola. El edificio es seguro porque atravesarlas todas es caro.&lt;/p>
&lt;h2 id="capa-1--secretos-el-problema-huevo-gallina-del-gitops">Capa 1 — Secretos: el problema huevo-gallina del GitOps&lt;/h2>
&lt;p>El stack se despliega por GitOps: la pieza hermana de &lt;a href="https://blog.lo0.es/posts/gitops-stack-inferencia-llm-flux/">Flux&lt;/a> reconcilia el estado declarado en git contra el clúster. Eso es excelente para los manifiestos — pero hay un problema fundacional: el motor de inferencia necesita el token de Hugging Face para descargar el modelo, la base de estado necesita su contraseña, el gateway necesita su clave maestra. &lt;strong>Esos secretos no pueden ir en claro en git.&lt;/strong> Cualquiera con acceso de lectura al repo —y en una organización con &lt;code>read&lt;/code> generoso eso es mucha gente, más todo backup del repo, más todo fork— los leería.&lt;/p>
&lt;p>El huevo y la gallina: GitOps quiere que &lt;strong>todo&lt;/strong> el estado esté en git, pero los secretos no pueden estar en git &lt;strong>en claro&lt;/strong>. La solución, en sus tres familias:&lt;/p>
&lt;h3 id="familia-a--sealed-secrets-cifrado-asimétrico-controlador-en-el-clúster">Familia A — sealed-secrets (cifrado asimétrico, controlador en el clúster)&lt;/h3>
&lt;p>&lt;a href="https://github.com/bitnami-labs/sealed-secrets">Bitnami Sealed Secrets&lt;/a> es un controlador que corre en el clúster más una CLI cliente, &lt;code>kubeseal&lt;/code>. Usa &lt;strong>criptografía asimétrica&lt;/strong>: hay un par de claves. La &lt;strong>pública&lt;/strong> la tienen los desarrolladores y sirve para cifrar; la &lt;strong>privada&lt;/strong> vive &lt;strong>solo&lt;/strong> en el controlador del clúster y sirve para descifrar. El flujo:&lt;/p>
&lt;ol>
&lt;li>El desarrollador toma un &lt;code>Secret&lt;/code> normal y lo cifra con &lt;code>kubeseal&lt;/code>, que obtiene la clave pública del controlador. Resultado: un recurso &lt;code>SealedSecret&lt;/code>.&lt;/li>
&lt;li>Ese &lt;code>SealedSecret&lt;/code> &lt;strong>sí&lt;/strong> se commitea a git en claro — está cifrado, no es legible.&lt;/li>
&lt;li>El controlador en el clúster lo detecta, lo descifra con su clave privada y crea el &lt;code>Secret&lt;/code> de Kubernetes real en el namespace destino.&lt;/li>
&lt;/ol>
&lt;p>La propiedad clave: como la clave privada &lt;strong>nunca sale del clúster&lt;/strong>, ni el desarrollador ni nadie con acceso a git puede descifrar. Y el cifrado &lt;strong>incluye el nombre del namespace&lt;/strong>: un &lt;code>SealedSecret&lt;/code> sellado para &lt;code>inferencia&lt;/code> no se puede mover a &lt;code>front&lt;/code> y descifrarlo allí — se comporta como si cada namespace tuviera su propia llave. El controlador gestiona también la &lt;strong>rotación&lt;/strong> de las claves de sellado, etiquetándolas como &lt;code>active&lt;/code> o &lt;code>compromised&lt;/code>.&lt;/p>
&lt;p>Ventaja: encaja perfecto en GitOps puro, el secreto cifrado vive con el resto del estado. Limitación: el secreto, ya descifrado, acaba siendo un &lt;code>Secret&lt;/code> de Kubernetes normal — está en &lt;code>etcd&lt;/code>, y hay que cifrar &lt;code>etcd&lt;/code> aparte (lo veremos en la capa 7).&lt;/p>
&lt;h3 id="familia-b--sops--age-cifrado-de-fichero-simple">Familia B — SOPS + age (cifrado de fichero, simple)&lt;/h3>
&lt;p>&lt;a href="https://github.com/getsops/sops">SOPS&lt;/a> cifra el fichero YAML/JSON entero (o solo sus valores) y lo deja en git cifrado, descifrándolo solo en el momento del despliegue. Soporta KMS de nube (AWS/GCP/Azure), PGP y —lo relevante para soberanía on-premise— &lt;strong>age&lt;/strong>, un esquema de cifrado offline simple sin servidor. El operador de GitOps (Flux trae integración nativa de SOPS) descifra al reconciliar usando la clave age guardada en el clúster.&lt;/p>
&lt;p>Ventaja: simple, scriptable, sin servidor que mantener; el peso operativo recae en custodiar la clave age. Es la recomendación habitual para equipos pequeños o para arrancar. Limitación: la rotación es manual (re-cifrar todo con la nueva clave) y no hay secretos dinámicos.&lt;/p>
&lt;h3 id="familia-c--external-secrets-operator--vault-secretos-dinámicos-rotación-nativa">Familia C — External Secrets Operator + Vault (secretos dinámicos, rotación nativa)&lt;/h3>
&lt;p>El &lt;a href="https://external-secrets.io/">External Secrets Operator (ESO)&lt;/a> &lt;strong>no guarda secretos en git en absoluto&lt;/strong>. En git solo va un &lt;code>ExternalSecret&lt;/code>: una referencia que dice &amp;ldquo;el campo &lt;code>password&lt;/code> viene del path &lt;code>secret/data/vectordb&lt;/code> de tal &lt;code>SecretStore&lt;/code>&amp;rdquo;. El operador lee de un almacén externo —típicamente &lt;a href="https://developer.hashicorp.com/vault">HashiCorp Vault&lt;/a>— y sincroniza el &lt;code>Secret&lt;/code> de Kubernetes. Vault guarda los secretos en su propio backend cifrado y los sirve por API autenticada, de modo que &lt;strong>nunca residen en git&lt;/strong> y, con sus motores dinámicos, puede emitir credenciales de base de datos de &lt;strong>vida corta&lt;/strong> que caducan solas.&lt;/p>
&lt;p>Ventaja: rotación nativa, secretos dinámicos, auditoría centralizada, un único punto de gobierno. Limitación: hay que operar Vault (sellado/desellado, alta disponibilidad, política de acceso), lo que es trabajo real.&lt;/p>
&lt;h3 id="cómo-elegir-y-la-rotación">Cómo elegir, y la rotación&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Criterio&lt;/th>
&lt;th>sealed-secrets&lt;/th>
&lt;th>SOPS + age&lt;/th>
&lt;th>ESO + Vault&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Secreto en git&lt;/td>
&lt;td>cifrado&lt;/td>
&lt;td>cifrado&lt;/td>
&lt;td>solo referencia&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Servidor extra&lt;/td>
&lt;td>controlador ligero&lt;/td>
&lt;td>ninguno&lt;/td>
&lt;td>Vault (pesado)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rotación&lt;/td>
&lt;td>claves de sellado auto&lt;/td>
&lt;td>manual (re-cifrar)&lt;/td>
&lt;td>nativa / dinámica&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Secretos dinámicos (vida corta)&lt;/td>
&lt;td>no&lt;/td>
&lt;td>no&lt;/td>
&lt;td>sí&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Encaje GitOps puro&lt;/td>
&lt;td>excelente&lt;/td>
&lt;td>excelente&lt;/td>
&lt;td>bueno (referencias)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Curva operativa&lt;/td>
&lt;td>baja&lt;/td>
&lt;td>muy baja&lt;/td>
&lt;td>alta&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Para un 4×H100 soberano arrancando, &lt;strong>SOPS/age o sealed-secrets&lt;/strong> cubren el 90% con coste mínimo. Cuando el sistema crece y aparecen requisitos de rotación frecuente y auditoría —típico en escenarios ENS Categoría Alta— se migra a &lt;strong>ESO + Vault&lt;/strong>. La regla de la rotación: &lt;strong>un secreto que nunca rota es un secreto que, una vez filtrado, está filtrado para siempre&lt;/strong>. La rotación no impide la filtración; acota su ventana de validez. Volveremos a ello al hablar de radio de impacto.&lt;/p>
&lt;h2 id="capa-2--red-default-deny-egress-y-la-matemática-de-la-superficie">Capa 2 — Red: default-deny, egress y la matemática de la superficie&lt;/h2>
&lt;h3 id="la-matemática-de-los-pares-de-comunicación">La matemática de los pares de comunicación&lt;/h3>
&lt;p>Sin política de red, en Kubernetes &lt;strong>todos los pods pueden hablar con todos los pods&lt;/strong>. Esa es la postura por defecto, y es la peor para un sistema soberano. Con $N$ servicios, el número de &lt;strong>pares ordenados de comunicación&lt;/strong> posibles (quién-puede-llamar-a-quién) es:&lt;/p>
&lt;p>$$P_{\text{abierto}} = N \cdot (N-1) \approx N^2$$&lt;/p>
&lt;p>Con nuestros $N = 8$ componentes (gateway, vLLM, TEI, base vectorial, base de estado, front, además del controlador de secretos y el de observabilidad), eso son $8 \cdot 7 = 56$ pares dirigidos posibles. Cincuenta y seis caminos por los que un servicio comprometido podría pivotar lateralmente.&lt;/p>
&lt;p>Ahora aplicamos &lt;code>NetworkPolicy&lt;/code> &lt;strong>default-deny&lt;/strong>: nada habla con nada salvo lo explícitamente permitido. La whitelist real de un stack LLM es pequeña. Los pares estrictamente necesarios:&lt;/p>
&lt;ul>
&lt;li>front → gateway&lt;/li>
&lt;li>gateway → vLLM&lt;/li>
&lt;li>gateway → TEI&lt;/li>
&lt;li>gateway → base vectorial&lt;/li>
&lt;li>gateway → base de estado&lt;/li>
&lt;li>front → base de estado (sesiones/usuarios)&lt;/li>
&lt;/ul>
&lt;p>Eso son $E = 6$ aristas. La superficie de comunicación cae de $56$ a $6$:&lt;/p>
&lt;p>$$\frac{E}{P_{\text{abierto}}} = \frac{6}{56} \approx 0{,}107$$&lt;/p>
&lt;p>Casi un &lt;strong>89% de los caminos posibles quedan cerrados&lt;/strong>. El front ya no puede tocar vLLM ni TEI ni la base vectorial directamente; si alguien compromete el front, no tiene ruta de red hacia el corpus. Esto es la diferencia entre $\sim N^2$ y una whitelist $E \ll N^2$.&lt;/p>
&lt;div class="diagram" style="max-width:720px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 720 380" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Grafo de servicios con default-deny: aristas permitidas en verde, bloqueadas en rojo punteado">
&lt;text x="360" y="24" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">Modelo default-deny — verde permitido, rojo bloqueado&lt;/text>
&lt;!-- nodes -->
&lt;circle cx="120" cy="90" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="120" y="94" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="currentColor">front&lt;/text>
&lt;circle cx="360" cy="90" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="360" y="94" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="currentColor">gateway&lt;/text>
&lt;circle cx="600" cy="60" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="600" y="64" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="currentColor">vLLM&lt;/text>
&lt;circle cx="600" cy="160" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="600" y="164" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="currentColor">TEI&lt;/text>
&lt;circle cx="360" cy="270" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="360" y="267" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="600" fill="currentColor">vector&lt;/text>
&lt;text x="360" y="280" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="600" fill="currentColor">DB&lt;/text>
&lt;circle cx="120" cy="270" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="120" y="267" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="600" fill="currentColor">estado&lt;/text>
&lt;text x="120" y="280" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="600" fill="currentColor">DB&lt;/text>
&lt;!-- allowed edges (green) -->
&lt;path d="M154,90 L326,90" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M392,78 L568,66" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M392,102 L568,150" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M372,122 L360,236" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M340,120 L140,238" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M120,124 L120,236" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;!-- blocked edges (red dashed) -->
&lt;path d="M146,116 L334,250" stroke="#ef4444" stroke-width="1.6" stroke-dasharray="5 4" fill="none"/>
&lt;path d="M150,108 L572,150" stroke="#ef4444" stroke-width="1.6" stroke-dasharray="5 4" fill="none"/>
&lt;path d="M154,96 L566,62" stroke="#ef4444" stroke-width="1.6" stroke-dasharray="5 4" fill="none"/>
&lt;path d="M388,256 L572,170" stroke="#ef4444" stroke-width="1.6" stroke-dasharray="5 4" fill="none"/>
&lt;text x="360" y="350" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#22c55e">6 aristas permitidas&lt;/text>
&lt;text x="360" y="366" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#ef4444">~50 caminos restantes cerrados&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="l3l4-estándar-l7-y-egress-con-cilium">L3/L4 estándar, L7 y egress con Cilium&lt;/h3>
&lt;p>La &lt;code>NetworkPolicy&lt;/code> nativa de Kubernetes opera a &lt;strong>L3/L4&lt;/strong>: selecciona pods por label y permite/deniega por puerto y protocolo. Sirve para el grueso de la whitelist. Pero tiene límites: no entiende DNS ni HTTP. Aquí entra &lt;a href="https://docs.cilium.io/en/stable/network/kubernetes/policy/">Cilium&lt;/a>, que aplica las políticas en el &lt;strong>kernel vía eBPF&lt;/strong> y extiende con &lt;code>CiliumNetworkPolicy&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Egress por FQDN/DNS&lt;/strong>: en vez de fijar IPs (que cambian), permitir &lt;code>egress&lt;/code> solo a &lt;code>huggingface.co&lt;/code> para que vLLM descargue el modelo y bloquear el resto. Crítico para el control de egress.&lt;/li>
&lt;li>&lt;strong>L7&lt;/strong>: permitir solo ciertos métodos/paths HTTP entre gateway y un servicio interno.&lt;/li>
&lt;li>&lt;strong>Modos de enforcement&lt;/strong>: en modo por defecto, un endpoint sin política seleccionándolo tiene todo abierto; en modo &lt;em>always&lt;/em>, &lt;strong>todo está denegado&lt;/strong> hasta que una política lo abra explícitamente. Para un sistema soberano, &lt;em>always&lt;/em> + default-deny es la postura objetivo.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>El control de egress es la pieza soberana por excelencia.&lt;/strong> Que un servicio interno &lt;strong>no pueda&lt;/strong> abrir conexiones a Internet salvo a un puñado de destinos explícitos significa que, aunque comprometan vLLM o el gateway, &lt;strong>el corpus no puede salir del perímetro&lt;/strong>. El RAG es el activo de valor; el egress-deny es lo que impide que se fugue.&lt;/p>
&lt;h3 id="mtls-interno">mTLS interno&lt;/h3>
&lt;p>La &lt;code>NetworkPolicy&lt;/code> dice &lt;em>quién&lt;/em> puede hablar con quién, pero no cifra ni autentica el tráfico este-oeste. Para eso, &lt;strong>mTLS&lt;/strong> (TLS mutuo: cliente y servidor se autentican entre sí). Cilium ofrece autenticación mutua nativa; alternativas como Linkerd o Istio dan una malla de servicios completa. Para un stack de seis servicios, la malla completa de Istio suele ser sobreingeniería; la autenticación mutua de Cilium o Linkerd (más ligero) es proporcional. El efecto: un atacante en la red del clúster no puede suplantar al gateway ni espiar el tráfico interno en claro.&lt;/p>
&lt;h2 id="capa-3--pod-security-restricted-y-nada-privilegiado">Capa 3 — Pod security: restricted, y nada privilegiado&lt;/h2>
&lt;p>Las &lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/">Pod Security Standards&lt;/a> definen tres perfiles: &lt;code>privileged&lt;/code> (sin restricciones), &lt;code>baseline&lt;/code> (lo mínimo razonable) y &lt;strong>&lt;code>restricted&lt;/code>&lt;/strong> (endurecido). El objetivo para todo el stack LLM es &lt;code>restricted&lt;/code>, aplicado vía Pod Security Admission con label de namespace en modo &lt;code>enforce&lt;/code>. El perfil &lt;code>restricted&lt;/code> exige:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>runAsNonRoot: true&lt;/code>&lt;/strong> y &lt;code>runAsUser&lt;/code> distinto de 0. Ningún contenedor corre como root.&lt;/li>
&lt;li>&lt;strong>&lt;code>readOnlyRootFilesystem: true&lt;/code>&lt;/strong>. El sistema de ficheros raíz es de solo lectura; lo que necesite escribir va a un &lt;code>emptyDir&lt;/code> o volumen explícito. Un atacante no puede dejar binarios persistentes en el contenedor.&lt;/li>
&lt;li>&lt;strong>&lt;code>drop: [&amp;quot;ALL&amp;quot;]&lt;/code>&lt;/strong> de capabilities de Linux. Sin capacidades que no se necesiten.&lt;/li>
&lt;li>&lt;strong>seccomp &lt;code>RuntimeDefault&lt;/code>&lt;/strong>, que aplica el perfil del runtime y bloquea las syscalls peligrosas.&lt;/li>
&lt;li>&lt;strong>&lt;code>allowPrivilegeEscalation: false&lt;/code>&lt;/strong>, nada &lt;code>privileged&lt;/code>, sin host namespaces.&lt;/li>
&lt;/ul>
&lt;p>El matiz con GPU: los pods que usan H100 cargan el device plugin de NVIDIA, lo que históricamente tentaba a relajar el &lt;code>securityContext&lt;/code>. No hace falta correr el contenedor de inferencia como privilegiado para usar la GPU; el acceso al dispositivo se gestiona por el plugin, y el pod de vLLM puede y debe correr &lt;code>restricted&lt;/code>. Esto es un tabique cortafuegos: si comprometen vLLM, el atacante tiene un proceso no-root, sin capabilities, con FS de solo lectura y syscalls recortadas — un punto de partida pésimo para escalar.&lt;/p>
&lt;h2 id="capa-4--cadena-de-suministro-de-imágenes">Capa 4 — Cadena de suministro de imágenes&lt;/h2>
&lt;p>La imagen de contenedor es código de terceros que corre con acceso a tus datos. Tres controles compuestos:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Pin por digest, no por tag.&lt;/strong> &lt;code>vllm/vllm-openai:latest&lt;/code> es un blanco móvil; &lt;code>@sha256:...&lt;/code> es inmutable. Fijar el digest garantiza que lo que se desplegó es exactamente lo auditado.&lt;/li>
&lt;li>&lt;strong>Escaneo con &lt;a href="https://github.com/aquasecurity/trivy">Trivy&lt;/a>.&lt;/strong> Antes de promover una imagen, Trivy enumera sus CVEs y compone el SBOM (inventario de componentes). Falla el pipeline si hay vulnerabilidades críticas sin mitigar.&lt;/li>
&lt;li>&lt;strong>Firma con &lt;a href="https://docs.sigstore.dev/cosign/signing/signing_with_containers/">cosign&lt;/a> y verificación en admission.&lt;/strong> El proyecto Sigstore (cosign para firmar, Fulcio como CA de certificados efímeros vía OIDC, Rekor como log de transparencia inmutable) permite firmar la imagen. Y el &lt;a href="https://github.com/sigstore/policy-controller">policy-controller de Sigstore&lt;/a> —o Kyverno— es un &lt;strong>admission controller&lt;/strong> que verifica la firma &lt;strong>antes&lt;/strong> de admitir el pod: una imagen sin firma válida no entra al clúster.&lt;/li>
&lt;/ol>
&lt;p>Este es el portero de la analogía: comprueba la credencial (firma) de cada imagen en la puerta. Una imagen envenenada subida a un registro, o un tag secuestrado, se queda fuera porque no lleva firma del emisor de confianza.&lt;/p>
&lt;h2 id="capa-5--authnz-claves-virtuales-oidc-y-rbac-mínimo">Capa 5 — AuthN/Z: claves virtuales, OIDC y RBAC mínimo&lt;/h2>
&lt;p>Tres puntos de control de identidad, de fuera adentro:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Front (OIDC/LDAP).&lt;/strong> El usuario humano se autentica contra el proveedor de identidad corporativo. El front no inventa su propio sistema de usuarios; delega en OIDC. Esto da SSO, MFA y revocación centralizada.&lt;/li>
&lt;li>&lt;strong>Gateway (claves virtuales de LiteLLM).&lt;/strong> El gateway emite &lt;strong>claves virtuales&lt;/strong>: cada equipo, aplicación o usuario tiene su clave con presupuesto, &lt;em>rate limit&lt;/em> y modelos permitidos. La clave maestra del gateway —y las API keys reales hacia el motor— &lt;strong>nunca&lt;/strong> las ve el cliente; solo manejan su clave virtual, revocable individualmente. Si se filtra una clave virtual, se revoca esa y solo esa.&lt;/li>
&lt;li>&lt;strong>Kubernetes RBAC con mínimo privilegio.&lt;/strong> Los &lt;code>ServiceAccount&lt;/code> de cada servicio tienen los permisos justos. El pod de vLLM no necesita listar &lt;code>Secret&lt;/code> de otros namespaces ni crear pods. RBAC restrictivo significa que un token de service account robado abre muy poco.&lt;/li>
&lt;/ul>
&lt;h2 id="capa-6--runtime-las-cámaras-con-tetragon">Capa 6 — Runtime: las cámaras con Tetragon&lt;/h2>
&lt;p>Las capas anteriores son preventivas. Falta la &lt;strong>detección&lt;/strong>: ¿y si algo, pese a todo, se ejecuta donde no debe? &lt;a href="https://tetragon.io/">Tetragon&lt;/a> es seguridad observable y enforcement en runtime sobre eBPF, &lt;em>Kubernetes-aware&lt;/em>. Hooka eventos del kernel —&lt;code>process_exec&lt;/code>, &lt;code>tcp_connect&lt;/code>, &lt;code>security_file_open&lt;/code>— con sobrecarga típica por debajo del 1%, y puede pasar de &lt;strong>observar&lt;/strong> (registrar el evento) a &lt;strong>enforcement&lt;/strong> (matar el proceso o cortar la conexión en el kernel, &lt;code>Sigkill&lt;/code>).&lt;/p>
&lt;p>La regla operativa, que detallo en el &lt;a href="https://blog.lo0.es/posts/runbook-aislar-agentes-ia-bubblewrap-tetragon/">runbook de bubblewrap + Tetragon&lt;/a>: &lt;strong>adopta primero, bloquea después&lt;/strong>. Primero se despliega en modo observación para levantar un baseline del comportamiento normal del stack —qué binarios ejecuta vLLM, a qué se conecta el gateway— sin falsos positivos. Solo después se promueven a enforcement las reglas claras: matar cualquier proceso que intente leer rutas de secretos, o cortar todo &lt;code>tcp_connect&lt;/code> a destinos fuera de la whitelist de egress. &lt;a href="https://falco.org/">Falco&lt;/a> es la alternativa detection-only sobre eBPF; Tetragon añade enforcement. Estas cámaras producen, además, la evidencia de auditoría: qué ejecutó cada servicio y qué intento se bloqueó.&lt;/p>
&lt;h2 id="capa-7--dato-en-reposo">Capa 7 — Dato en reposo&lt;/h2>
&lt;p>Lo último que protegemos es el dato parado:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Cifrado del almacenamiento.&lt;/strong> Los volúmenes persistentes —donde viven la base vectorial y la base de estado— sobre cifrado de disco (LUKS/dm-crypt) o cifrado a nivel de Ceph. Un disco robado del CPD no revela el corpus.&lt;/li>
&lt;li>&lt;strong>Cifrado de &lt;code>etcd&lt;/code>.&lt;/strong> Recuerda que los &lt;code>Secret&lt;/code> de Kubernetes, una vez descifrados por sealed-secrets o ESO, son objetos normales en &lt;code>etcd&lt;/code>. Hay que activar el &lt;em>encryption at rest&lt;/em> de &lt;code>etcd&lt;/code>, o el secreto está en claro en el plano de control.&lt;/li>
&lt;li>&lt;strong>Credenciales de las bases.&lt;/strong> La contraseña de la base vectorial y la de la base de estado son secretos de primera (capa 1), nunca embebidas en el manifiesto ni en variables de entorno en claro en git.&lt;/li>
&lt;/ul>
&lt;h2 id="tabla-de-exposición-servicio--puerto--egress-externo">Tabla de exposición: servicio × puerto × ¿egress externo?&lt;/h2>
&lt;p>Esta tabla es el insumo para escribir las &lt;code>NetworkPolicy&lt;/code>. La columna de egress es la que decide qué sale del perímetro.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Servicio&lt;/th>
&lt;th>Puerto interno&lt;/th>
&lt;th>Llamantes permitidos&lt;/th>
&lt;th>¿Egress externo?&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>front&lt;/td>
&lt;td>3080&lt;/td>
&lt;td>(ingress humano vía OIDC)&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>gateway (LiteLLM)&lt;/td>
&lt;td>4000&lt;/td>
&lt;td>front&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>vLLM&lt;/td>
&lt;td>8000&lt;/td>
&lt;td>gateway&lt;/td>
&lt;td>&lt;strong>Solo&lt;/strong> &lt;code>huggingface.co&lt;/code> para descarga inicial; cero en operación&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TEI (embeddings/reranker)&lt;/td>
&lt;td>8080&lt;/td>
&lt;td>gateway&lt;/td>
&lt;td>&lt;strong>Solo&lt;/strong> descarga del modelo; cero en operación&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>base vectorial&lt;/td>
&lt;td>6333&lt;/td>
&lt;td>gateway&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>base de estado&lt;/td>
&lt;td>5432&lt;/td>
&lt;td>gateway, front&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>controlador de secretos&lt;/td>
&lt;td>—&lt;/td>
&lt;td>(plano de control)&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>observabilidad&lt;/td>
&lt;td>9090&lt;/td>
&lt;td>scraping interno&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La lectura: &lt;strong>ningún servicio necesita egress externo en operación normal&lt;/strong>. vLLM y TEI lo necesitan &lt;strong>una vez&lt;/strong> para bajar el modelo, y ese permiso puede ser temporal o restringido por FQDN a &lt;code>huggingface.co&lt;/code>. Todo lo demás es egress-deny total. Si un servicio empieza a intentar conexiones de salida que esta tabla no contempla, Tetragon lo registra y, en enforcement, lo corta.&lt;/p>
&lt;h2 id="radio-de-impacto-un-secreto-filtrado-con-y-sin-defensa">Radio de impacto: un secreto filtrado, con y sin defensa&lt;/h2>
&lt;p>El &lt;strong>radio de impacto&lt;/strong> (&lt;em>blast radius&lt;/em>) mide cuánto daño hace un compromiso. Modelémoslo para el peor caso realista: se filtra la credencial de la base de estado.&lt;/p>
&lt;p>&lt;strong>Sin hardening&lt;/strong> (red plana, secreto sin rotar, sin egress-deny, sin detección):&lt;/p>
&lt;ul>
&lt;li>El secreto da acceso a la base de estado desde cualquier pod (red plana → 56 pares abiertos).&lt;/li>
&lt;li>El secreto no rota → es válido &lt;strong>indefinidamente&lt;/strong>; la ventana de explotación es $\infty$ hasta que alguien lo note.&lt;/li>
&lt;li>Sin egress-deny, el atacante vuelca la base entera a un servidor externo.&lt;/li>
&lt;li>Sin Tetragon, nadie se entera hasta el incidente público.&lt;/li>
&lt;li>Radio de impacto: &lt;strong>toda la base de estado, exfiltrada, sin detección, por tiempo indefinido&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Con hardening&lt;/strong> (default-deny, ESO + Vault con credenciales dinámicas, egress-deny, Tetragon en enforcement):&lt;/p>
&lt;ul>
&lt;li>El secreto solo es usable desde el pod que tiene ruta de red a la base de estado (1-2 pares, no 56).&lt;/li>
&lt;li>Con credenciales dinámicas de Vault, el secreto &lt;strong>caduca&lt;/strong> —digamos en una ventana $T$ de horas—; pasado $T$, no vale nada.&lt;/li>
&lt;li>El egress-deny impide el volcado a Internet: el atacante puede leer, pero no sacar.&lt;/li>
&lt;li>Tetragon registra el acceso anómalo y, en enforcement, mata el proceso que intenta la conexión de salida.&lt;/li>
&lt;/ul>
&lt;p>La reducción cualitativa es enorme, pero seamos cuantitativos con la ventana. Si un secreto estático vale para siempre y uno rotado con periodo $T$ vale como mucho $T$, y los compromisos llegan a tasa $\lambda$, el número esperado de secretos &lt;em>vivos y explotables&lt;/em> en un instante dado pasa de crecer sin techo a quedar acotado por $\lambda \cdot T$. Con rotación cada 24 h ($T = 1$ día) frente a &amp;ldquo;nunca&amp;rdquo;, la ventana de un secreto concreto cae de meses a un día: una reducción de &lt;strong>uno a dos órdenes de magnitud&lt;/strong> en la exposición temporal. Combinado con la reducción de pares de red ($56 \to \sim 2$, factor $\sim 28\times$) y el egress-deny (de exfiltración posible a imposible por la ruta directa), el radio de impacto se reduce drásticamente.&lt;/p>
&lt;p>&lt;strong>Pero seamos honestos: no llega a cero.&lt;/strong> El atacante con la credencial &lt;em>puede leer&lt;/em> la base de estado durante la ventana $T$ desde el pod comprometido. El hardening convirtió &amp;ldquo;catástrofe indefinida y silenciosa&amp;rdquo; en &amp;ldquo;incidente acotado, detectado y sin exfiltración por la vía directa&amp;rdquo;. Eso es exactamente lo que la defensa en profundidad promete: no la invulnerabilidad, sino que &lt;strong>el coste de un compromiso completo sea alto y su radio, pequeño&lt;/strong>.&lt;/p>
&lt;h2 id="ángulo-ens--nis2">Ángulo ENS / NIS2&lt;/h2>
&lt;p>Estas capas no son higiene voluntaria: materializan controles concretos. El mapeo lo desarrolla en detalle el &lt;a href="https://blog.lo0.es/posts/controles-tecnicos-ens-42001-eu-ai-act/">post de controles técnicos ENS × ISO 42001 × EU AI Act&lt;/a>; aquí, el resumen accionable:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Capa de hardening&lt;/th>
&lt;th>Medida ENS (RD 311/2022)&lt;/th>
&lt;th>Vínculo NIS2 / marco&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Secretos cifrados + rotación&lt;/td>
&lt;td>&lt;code>op.exp.11&lt;/code> (claves criptográficas), &lt;code>mp.info.3&lt;/code> (cifrado)&lt;/td>
&lt;td>Gestión de credenciales; en Cat. Alta, HSM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NetworkPolicy default-deny + segmentación&lt;/td>
&lt;td>&lt;code>mp.com.1&lt;/code> (perímetro), &lt;code>mp.com.4&lt;/code> (separación de flujos)&lt;/td>
&lt;td>NIS2 art. 21: medidas de seguridad de red&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Control de egress&lt;/td>
&lt;td>&lt;code>mp.com.1&lt;/code> + &lt;code>op.mon.1&lt;/code>&lt;/td>
&lt;td>Prevención de exfiltración&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>mTLS interno&lt;/td>
&lt;td>&lt;code>mp.com.2-3&lt;/code> (confidencialidad/integridad en tránsito)&lt;/td>
&lt;td>TLS obligatorio&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Pod Security restricted&lt;/td>
&lt;td>&lt;code>op.exp.2&lt;/code> (config endurecida)&lt;/td>
&lt;td>Hardening de configuración (CIS)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Trivy + cosign + admission&lt;/td>
&lt;td>&lt;code>op.exp.6&lt;/code> (código dañino), &lt;code>op.ext.3&lt;/code> (cadena de suministro)&lt;/td>
&lt;td>NIS2 supply chain&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OIDC + claves virtuales + RBAC&lt;/td>
&lt;td>&lt;code>op.acc.1-2-5&lt;/code> (identificación, acceso, autenticación)&lt;/td>
&lt;td>MFA en Cat. Alta&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tetragon runtime&lt;/td>
&lt;td>&lt;code>op.mon.1&lt;/code> (detección de intrusión)&lt;/td>
&lt;td>Monitorización y respuesta&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cifrado en reposo + etcd&lt;/td>
&lt;td>&lt;code>mp.info.3&lt;/code> (cifrado), &lt;code>mp.si&lt;/code> (soportes)&lt;/td>
&lt;td>Dato en reposo&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La nota honesta para auditoría: &lt;strong>el hardening reduce el riesgo, no lo elimina&lt;/strong>, y la madurez se demuestra &lt;strong>priorizando por impacto&lt;/strong>. Un auditor competente no quiere ver las nueve capas a medias; quiere ver que el egress-deny del activo crítico (el corpus) y la gestión de secretos están sólidos antes que el mTLS perfecto entre servicios de baja sensibilidad. Para el contexto de gestión y gobernanza, ver también &lt;a href="https://blog.lo0.es/posts/iso-42001-aims-llm-on-premise/">ISO/IEC 42001 como AIMS&lt;/a> y el &lt;a href="https://blog.lo0.es/posts/eu-ai-act-mapeo-arquitectura-llm-on-premise/">mapeo del EU AI Act sobre la arquitectura&lt;/a>.&lt;/p>
&lt;h2 id="aplicado-al-clúster-genérico-4h100">Aplicado al clúster genérico 4×H100&lt;/h2>
&lt;p>En un despliegue real sobre 4×H100 SXM 80GB, no se endurecen las nueve capas a la vez. El orden, priorizado por impacto:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Egress-deny sobre vLLM, TEI y la base vectorial, primero.&lt;/strong> Es la barrera que impide que el corpus salga del perímetro, y se pone en cuanto los servicios arrancan. Permite egress por FQDN a &lt;code>huggingface.co&lt;/code> solo durante la descarga inicial del modelo; después, cero. Es la capa de mayor retorno por hora invertida.&lt;/li>
&lt;li>&lt;strong>Secretos del motor de inferencia y de las bases, fuera de git.&lt;/strong> El token de Hugging Face que usa vLLM para descargar el modelo, las contraseñas de la base vectorial y de la base de estado, la clave maestra del gateway: a sealed-secrets o, si ya hay requisito de rotación, a ESO + Vault. Nunca en el &lt;code>values.yaml&lt;/code> en claro.&lt;/li>
&lt;li>&lt;strong>NetworkPolicy default-deny + la whitelist de 6 aristas.&lt;/strong> El gateway y la base vectorial son los servicios más expuestos —el gateway porque recibe todo el tráfico, la base vectorial porque custodia el RAG embebido—. Cerrar todo lo que no sea la whitelist recorta el pivoteo lateral del $\sim N^2$ a las 6 aristas reales.&lt;/li>
&lt;li>&lt;strong>Pod Security &lt;code>restricted&lt;/code> en el namespace de inferencia&lt;/strong>, incluido el pod de vLLM con GPU (no necesita privilegios para usar la H100).&lt;/li>
&lt;li>&lt;strong>cosign + admission&lt;/strong> para que solo entren imágenes firmadas; &lt;strong>Trivy&lt;/strong> en el pipeline de GitOps de &lt;a href="https://blog.lo0.es/posts/gitops-stack-inferencia-llm-flux/">Flux&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Tetragon en observación&lt;/strong>, baseline, y luego enforcement sobre el egress de los pods sensibles.&lt;/li>
&lt;li>&lt;strong>Cifrado en reposo&lt;/strong> de los volúmenes de las bases y de &lt;code>etcd&lt;/code>.&lt;/li>
&lt;/ol>
&lt;p>El gateway y la base vectorial son los dos que endurezco primero dentro de la whitelist: el gateway por ser la cara expuesta, la base vectorial por contener el activo que el egress-deny protege. El resto se construye encima, capa a capa, asumiendo siempre que la anterior puede fallar.&lt;/p>
&lt;h2 id="lo-que-el-hardening-no-resuelve">Lo que el hardening NO resuelve&lt;/h2>
&lt;p>Para cerrar con honestidad, lo que estas capas &lt;strong>no&lt;/strong> cubren y necesita otras piezas:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Ataques a nivel de prompt&lt;/strong> (jailbreak, inyección indirecta vía el corpus RAG): eso es trabajo de &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">guardrails&lt;/a> y &lt;a href="https://blog.lo0.es/posts/llm-guard-fundamentos/">LLM Guard&lt;/a>, no de NetworkPolicy.&lt;/li>
&lt;li>&lt;strong>Agentes con permisos legítimos que hacen algo dañino&lt;/strong>: el aislamiento de runtime del &lt;a href="https://blog.lo0.es/posts/aislar-agentes-ia-cliente-cluster/">post de aislar agentes&lt;/a> acota qué puede tocar un agente, pero un permiso concedido es un permiso usable.&lt;/li>
&lt;li>&lt;strong>El factor humano&lt;/strong>: un secreto bien gestionado pero compartido por Slack sigue filtrado. La rotación acota la ventana, no elimina el error.&lt;/li>
&lt;li>&lt;strong>Vulnerabilidades zero-day&lt;/strong> en los propios componentes: Trivy detecta lo conocido; lo desconocido pasa hasta que se publica el CVE.&lt;/li>
&lt;/ul>
&lt;p>El hardening es un multiplicador del coste de atacar, no un muro infranqueable. Su valor está en hacer que un compromiso individual quede &lt;strong>acotado, detectado y sin salida&lt;/strong> — y eso, para un sistema soberano que custodia datos sensibles, es exactamente el objetivo.&lt;/p>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/gitops-stack-inferencia-llm-flux/">GitOps del stack de inferencia con Flux&lt;/a> — la pieza hermana: GitOps reconcilia el estado, y comparte con este post el problema huevo-gallina de los secretos.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/controles-tecnicos-ens-42001-eu-ai-act/">Controles técnicos: ENS × ISO 42001 × EU AI Act&lt;/a> — el mapeo detallado de cada capa de hardening a medida ENS, control 42001 y artículo del AI Act.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/iso-42001-aims-llm-on-premise/">ISO/IEC 42001: el AIMS del LLM on-premise&lt;/a> — el sistema de gestión que enmarca el hardening como control documentado.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/eu-ai-act-mapeo-arquitectura-llm-on-premise/">EU AI Act: mapeo sobre la arquitectura LLM on-premise&lt;/a> — los artículos de robustez y ciberseguridad (Art. 15) que estas capas satisfacen.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/llm-guard-fundamentos/">LLM Guard: fundamentos&lt;/a> — la capa de seguridad a nivel de prompt/contenido que el hardening de infraestructura no cubre.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety en LLMs&lt;/a> — el WAF semántico complementario a la NetworkPolicy.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/aislar-agentes-ia-cliente-cluster/">Aislar agentes de IA: del workstation al clúster&lt;/a> — el modelo de amenaza del aislamiento de runtime.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/runbook-aislar-agentes-ia-bubblewrap-tetragon/">Runbook: enjaular al agente de IA con bubblewrap y Tetragon&lt;/a> — el procedimiento operativo de Tetragon (observar primero, bloquear después) referenciado en la capa 6.&lt;/li>
&lt;/ul></description></item></channel></rss>