<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sysadmin on Adur</title><link>https://adurrr.github.io/categories/sysadmin/</link><description>Recent content in Sysadmin on Adur</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 11 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://adurrr.github.io/categories/sysadmin/index.xml" rel="self" type="application/rss+xml"/><item><title>OPNsense: auditoría, automatización y prácticas avanzadas</title><link>https://adurrr.github.io/p/opnsense-auditor%C3%ADa-automatizaci%C3%B3n-y-pr%C3%A1cticas-avanzadas/</link><pubDate>Sat, 11 Apr 2026 00:00:00 +0000</pubDate><guid>https://adurrr.github.io/p/opnsense-auditor%C3%ADa-automatizaci%C3%B3n-y-pr%C3%A1cticas-avanzadas/</guid><description>&lt;h2 id="dónde-estamos"&gt;Dónde estamos
&lt;/h2&gt;&lt;p&gt;En los dos primeros posts de la serie montamos OPNsense desde cero y lo llevamos hasta una configuración que ya no es trivial. Conviene hacer un repaso rápido antes de seguir.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Capa&lt;/th&gt;
 &lt;th&gt;Qué se configuró&lt;/th&gt;
 &lt;th&gt;Post&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Hardware&lt;/td&gt;
 &lt;td&gt;Mini PC con Intel N100/N200, NICs Intel i226-V&lt;/td&gt;
 &lt;td&gt;Primero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Conectividad&lt;/td&gt;
 &lt;td&gt;PPPoE en WAN, bridge en LAN, interfaz wireless&lt;/td&gt;
 &lt;td&gt;Primero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Detección&lt;/td&gt;
 &lt;td&gt;Suricata IDS/IPS con rulesets ET Open, Abuse.ch, Feodo&lt;/td&gt;
 &lt;td&gt;Primero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Inteligencia compartida&lt;/td&gt;
 &lt;td&gt;CrowdSec con bouncer de firewall y colecciones comunitarias&lt;/td&gt;
 &lt;td&gt;Primero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VPN&lt;/td&gt;
 &lt;td&gt;WireGuard con peers configurados&lt;/td&gt;
 &lt;td&gt;Primero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Reglas de firewall&lt;/td&gt;
 &lt;td&gt;Reglas básicas para LAN, WireGuard y WAN&lt;/td&gt;
 &lt;td&gt;Primero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Offloading&lt;/td&gt;
 &lt;td&gt;Checksum, TSO, tunning de buffers TCP&lt;/td&gt;
 &lt;td&gt;Primero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Usuarios&lt;/td&gt;
 &lt;td&gt;Admin dedicado con OTP, root restringido, WebUI protegida&lt;/td&gt;
 &lt;td&gt;Primero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Backups&lt;/td&gt;
 &lt;td&gt;Cifrados con AES-256-CBC, automáticos, almacenamiento externo&lt;/td&gt;
 &lt;td&gt;Segundo&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DPI&lt;/td&gt;
 &lt;td&gt;Zenarmor con políticas por interfaz y categoría&lt;/td&gt;
 &lt;td&gt;Segundo&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Segmentación&lt;/td&gt;
 &lt;td&gt;5 VLANs (Main, Guests, IoT, Servers, Management) con reglas inter-VLAN&lt;/td&gt;
 &lt;td&gt;Segundo&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SSH&lt;/td&gt;
 &lt;td&gt;Solo claves ed25519, puerto no estándar, acceso restringido por IP&lt;/td&gt;
 &lt;td&gt;Segundo&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DNS&lt;/td&gt;
 &lt;td&gt;Unbound con DNS over TLS hacia Quad9 y Cloudflare&lt;/td&gt;
 &lt;td&gt;Segundo&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Logging&lt;/td&gt;
 &lt;td&gt;Syslog remoto con TCP/TLS hacia Grafana+Loki o ELK&lt;/td&gt;
 &lt;td&gt;Segundo&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Es una configuración sólida. Pero hasta ahora todo se ha hecho a mano, desde la interfaz web, sin un proceso formal de revisión ni una forma de reproducir la configuración si algo se rompe más allá del backup XML. Este post cubre lo que falta: prácticas avanzadas, automatización, un marco de auditoría serio y una comparativa con las alternativas para tomar decisiones informadas.&lt;/p&gt;
&lt;h2 id="revisión-de-la-postura-de-seguridad"&gt;Revisión de la postura de seguridad
&lt;/h2&gt;&lt;h3 id="defensa-en-profundidad-lo-que-tenemos"&gt;Defensa en profundidad: lo que tenemos
&lt;/h3&gt;&lt;p&gt;Si miramos lo configurado como capas de defensa, la arquitectura tiene cierta profundidad:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Capa de defensa&lt;/th&gt;
 &lt;th&gt;Componente OPNsense&lt;/th&gt;
 &lt;th&gt;Estado actual&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Perímetro&lt;/td&gt;
 &lt;td&gt;Reglas WAN deny-all + WireGuard entrante&lt;/td&gt;
 &lt;td&gt;Funcional&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Detección de intrusiones&lt;/td&gt;
 &lt;td&gt;Suricata IPS con ET Open y Abuse.ch&lt;/td&gt;
 &lt;td&gt;Funcional, tuning por defecto&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Análisis de logs&lt;/td&gt;
 &lt;td&gt;CrowdSec con bouncer + inteligencia comunitaria&lt;/td&gt;
 &lt;td&gt;Funcional&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Inspección de aplicaciones&lt;/td&gt;
 &lt;td&gt;Zenarmor DPI con políticas por VLAN&lt;/td&gt;
 &lt;td&gt;Funcional&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Segmentación&lt;/td&gt;
 &lt;td&gt;5 VLANs con reglas inter-VLAN deny-all por defecto&lt;/td&gt;
 &lt;td&gt;Funcional&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Control de acceso&lt;/td&gt;
 &lt;td&gt;Admin dedicado, OTP, SSH con claves ed25519&lt;/td&gt;
 &lt;td&gt;Funcional&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cifrado DNS&lt;/td&gt;
 &lt;td&gt;Unbound con DoT hacia Quad9/Cloudflare&lt;/td&gt;
 &lt;td&gt;Funcional&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Backups&lt;/td&gt;
 &lt;td&gt;Cifrados, automáticos, almacenamiento externo&lt;/td&gt;
 &lt;td&gt;Funcional&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Cinco capas de detección y prevención, segmentación real y control de acceso razonable. Para un homelab o una oficina pequeña es más de lo que tiene la mayoría. Pero hay huecos.&lt;/p&gt;
&lt;h3 id="huecos-que-quedan"&gt;Huecos que quedan
&lt;/h3&gt;&lt;p&gt;Siendo honesto, hay varias cosas que no se han tocado y que importan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No hay filtrado por geolocalización&lt;/strong&gt;. Todo el planeta puede intentar conectar a los puertos expuestos en WAN. La mayoría de ataques automatizados vienen de rangos de IP con los que no se tiene ninguna relación legítima.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No hay reverse proxy&lt;/strong&gt;. Si se exponen servicios auto-alojados (Nextcloud, Jellyfin), van directamente por NAT sin TLS terminado ni protección a nivel de aplicación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suricata está con la configuración por defecto&lt;/strong&gt;. Las reglas se activan, pero no se han ajustado los parámetros de rendimiento ni se han eliminado categorías irrelevantes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Todo se ha configurado a mano&lt;/strong&gt;. Si mañana hay que reconstruir OPNsense desde cero, el backup XML es la única opción. No hay playbooks, no hay control de versiones de la configuración, no hay forma de revisar qué cambió y cuándo sin abrir el historial de la interfaz web.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No hay cadencia formal de auditoría&lt;/strong&gt;. En el segundo post se mencionó una rutina semanal/mensual/trimestral, pero sin un marco de referencia detrás es fácil que se quede en buenas intenciones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No hay feeds de amenazas automatizados&lt;/strong&gt; más allá de las actualizaciones de reglas de Suricata. Las listas de bloqueo por IP no se actualizan solas.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Los siguientes apartados cubren estos huecos.&lt;/p&gt;
&lt;h2 id="prácticas-avanzadas"&gt;Prácticas avanzadas
&lt;/h2&gt;&lt;h3 id="bloqueo-por-geoip-con-maxmind"&gt;Bloqueo por GeoIP con MaxMind
&lt;/h3&gt;&lt;p&gt;La idea es simple: si no hay razón legítima para que una conexión venga de ciertos países, bloquear esos rangos de IP reduce el ruido. No es una medida de seguridad real, porque cualquier atacante con una VPN la esquiva, pero sí elimina una cantidad significativa de escaneos automatizados y ataques de fuerza bruta.&lt;/p&gt;
&lt;p&gt;OPNsense usa las bases de datos GeoLite2 de MaxMind, que son gratuitas pero requieren una cuenta.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Registrar una cuenta gratuita en MaxMind y generar una license key.&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;Firewall &amp;gt; Aliases &amp;gt; GeoIP settings&lt;/strong&gt;, introducir la license key.&lt;/li&gt;
&lt;li&gt;Crear un alias de tipo &lt;strong&gt;GeoIP&lt;/strong&gt; en &lt;strong&gt;Firewall &amp;gt; Aliases&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Nombre: &lt;code&gt;GeoIP_Block&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tipo: GeoIP&lt;/li&gt;
&lt;li&gt;Contenido: seleccionar los países a bloquear.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;Firewall &amp;gt; Rules &amp;gt; WAN&lt;/strong&gt;, añadir una regla al principio:
&lt;ul&gt;
&lt;li&gt;Acción: Block&lt;/li&gt;
&lt;li&gt;Origen: &lt;code&gt;GeoIP_Block&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Destino: *&lt;/li&gt;
&lt;li&gt;Descripción: Bloqueo geográfico&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Una advertencia: mantener esta lista actualizada. MaxMind actualiza las bases de datos semanalmente. Configurar la actualización automática en los ajustes de GeoIP para que no se queden obsoletas.&lt;/p&gt;
&lt;h3 id="tuning-avanzado-de-suricata"&gt;Tuning avanzado de Suricata
&lt;/h3&gt;&lt;p&gt;OPNsense 26.1 incluye Suricata 8, que mejora el rendimiento multi-hilo y añade soporte para nuevos protocolos. Pero la configuración por defecto es conservadora. Si el hardware tiene margen, ajustar estos parámetros marca diferencia.&lt;/p&gt;
&lt;p&gt;En &lt;strong&gt;Services &amp;gt; Intrusion Detection &amp;gt; Administration&lt;/strong&gt;, la sección avanzada permite configurar parámetros que no están en la interfaz estándar. Los más relevantes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# /usr/local/opnsense/service/conf/actions.d/conf.d/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Ajustar según la RAM disponible (estos valores son para 8 GB)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Memoria máxima para seguimiento de flujos&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memcap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;256mb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hash-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65536&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Memoria máxima para reconstrucción de streams TCP&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memcap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;512mb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;reassembly.memcap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;256mb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Tamaño del ring buffer para af-packet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;af-packet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;igb0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ring-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cluster-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cluster_flow&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Además del tuning de memoria, revisar las categorías de reglas activas. Si no hay servidores SCADA, bases de datos expuestas ni servicios SMTP en la red, desactivar esas categorías. Cada regla activa consume CPU en cada paquete inspeccionado.&lt;/p&gt;
&lt;p&gt;Para identificar las reglas que más ruido generan, analizar el log EVE JSON:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Top 10 alertas por SID en las últimas 24 horas&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /var/log/suricata/eve.json &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq -r &lt;span class="s1"&gt;&amp;#39;select(.event_type==&amp;#34;alert&amp;#34;) | .alert.signature_id&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sort &lt;span class="p"&gt;|&lt;/span&gt; uniq -c &lt;span class="p"&gt;|&lt;/span&gt; sort -rn &lt;span class="p"&gt;|&lt;/span&gt; head -10
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Si una regla genera cientos de alertas diarias sin que ninguna sea un verdadero positivo, desactivarla o ajustar su umbral.&lt;/p&gt;
&lt;h3 id="reverse-proxy-con-haproxy-y-acme"&gt;Reverse proxy con HAProxy y ACME
&lt;/h3&gt;&lt;p&gt;Si se exponen servicios auto-alojados a internet, hacerlo directamente con port forwarding es funcional pero inseguro. Un reverse proxy permite terminar TLS con certificados válidos, aplicar rate limiting y tener un punto centralizado de control.&lt;/p&gt;
&lt;p&gt;Instalar los plugins &lt;code&gt;os-haproxy&lt;/code&gt; y &lt;code&gt;os-acme-client&lt;/code&gt; desde &lt;strong&gt;System &amp;gt; Firmware &amp;gt; Plugins&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Certificados con Let&amp;rsquo;s Encrypt (ACME)&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;En &lt;strong&gt;Services &amp;gt; ACME Client &amp;gt; Accounts&lt;/strong&gt;, crear una cuenta ACME.&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;Services &amp;gt; ACME Client &amp;gt; Challenge Types&lt;/strong&gt;, configurar un desafío DNS-01. Es preferible al HTTP-01 porque no requiere abrir el puerto 80 y soporta wildcards.&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;Services &amp;gt; ACME Client &amp;gt; Certificates&lt;/strong&gt;, crear los certificados para cada servicio.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Configuración de HAProxy&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;En &lt;strong&gt;Services &amp;gt; HAProxy &amp;gt; Real Servers&lt;/strong&gt;, crear un backend por cada servicio interno (Nextcloud en &lt;code&gt;192.168.40.10:443&lt;/code&gt;, Jellyfin en &lt;code&gt;192.168.40.11:8096&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;Services &amp;gt; HAProxy &amp;gt; Rules &amp;amp; Checks &amp;gt; Conditions&lt;/strong&gt;, crear condiciones basadas en el SNI o el hostname.&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;Services &amp;gt; HAProxy &amp;gt; Virtual Services &amp;gt; Public Services&lt;/strong&gt;, crear un frontend que escuche en el puerto 443, vincule los certificados ACME y enrute a los backends según las condiciones.&lt;/li&gt;
&lt;li&gt;Activar HSTS en las cabeceras HTTP para forzar HTTPS.&lt;/li&gt;
&lt;li&gt;Configurar rate limiting por IP para mitigar ataques de fuerza bruta contra formularios de login.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="feeds-de-amenazas-y-alias-programados"&gt;Feeds de amenazas y alias programados
&lt;/h3&gt;&lt;p&gt;Las listas de bloqueo por IP pierden valor si no se actualizan. OPNsense permite crear alias de tipo URL Table que se descargan automáticamente.&lt;/p&gt;
&lt;p&gt;En &lt;strong&gt;Firewall &amp;gt; Aliases&lt;/strong&gt;, crear alias con estas fuentes:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Alias&lt;/th&gt;
 &lt;th&gt;URL&lt;/th&gt;
 &lt;th&gt;Frecuencia&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Spamhaus_DROP&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;https://www.spamhaus.org/drop/drop.txt&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Diaria&lt;/td&gt;
 &lt;td&gt;Rangos secuestrados o usados para spam&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Spamhaus_EDROP&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;https://www.spamhaus.org/drop/edrop.txt&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Diaria&lt;/td&gt;
 &lt;td&gt;Extensión de DROP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Abusech_Feodo&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;https://feodotracker.abuse.ch/downloads/ipblocklist.txt&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Cada 6h&lt;/td&gt;
 &lt;td&gt;IPs de botnets bancarias&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Abusech_SSLBL&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;https://sslbl.abuse.ch/blacklist/sslipblacklist.txt&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Cada 6h&lt;/td&gt;
 &lt;td&gt;IPs con certificados maliciosos&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Aplicar estos alias como origen en reglas de bloqueo en WAN. Los alias de tipo URL Table se actualizan automáticamente según la frecuencia configurada.&lt;/p&gt;
&lt;h3 id="snapshots-zfs-para-rollback"&gt;Snapshots ZFS para rollback
&lt;/h3&gt;&lt;p&gt;Si durante la instalación se eligió ZFS como sistema de ficheros (en lugar de UFS), se puede usar una de sus mejores características: snapshots instantáneos con rollback.&lt;/p&gt;
&lt;p&gt;Antes de cualquier cambio importante (actualización de firmware, cambio de reglas masivo, instalación de plugins), crear un snapshot:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Crear un snapshot antes de actualizar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zfs snapshot zroot/ROOT/default@pre-update-&lt;span class="k"&gt;$(&lt;/span&gt;date +%Y%m%d&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Listar snapshots existentes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zfs list -t snapshot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Si algo sale mal, rollback al snapshot anterior&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zfs rollback zroot/ROOT/default@pre-update-20260413
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Esto devuelve el sistema de ficheros completo al estado del snapshot en segundos. Es un seguro contra actualizaciones fallidas que complementa los backups de configuración XML. El snapshot recupera todo el sistema; el backup XML solo recupera la configuración.&lt;/p&gt;
&lt;h2 id="infrastructure-as-code-para-opnsense"&gt;Infrastructure as Code para OPNsense
&lt;/h2&gt;&lt;p&gt;Hacer todo desde la interfaz web funciona, pero tiene problemas conocidos: no hay historial de cambios legible, no se puede revisar qué se modificó antes de aplicarlo, y reconstruir la configuración requiere seguir una guía paso a paso o restaurar un backup opaco.&lt;/p&gt;
&lt;h3 id="la-api-rest-de-opnsense"&gt;La API REST de OPNsense
&lt;/h3&gt;&lt;p&gt;OPNsense expone una API REST bastante completa. La documentación está en &lt;code&gt;/api/&lt;/code&gt; y cubre la mayoría de funcionalidades de la interfaz web: aliases, reglas de firewall, configuración de interfaces, IDS, CrowdSec, Unbound, HAProxy y más.&lt;/p&gt;
&lt;p&gt;Para usar la API, crear un par de clave/secreto en &lt;strong&gt;System &amp;gt; Access &amp;gt; Users&lt;/strong&gt;, seleccionar el usuario administrador y generar una API key. OPNsense genera un archivo con la key y el secret.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Consultar los alias de firewall&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -k -u &lt;span class="s2"&gt;&amp;#34;API_KEY:API_SECRET&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://192.168.1.1/api/firewall/alias/searchItem
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Crear un nuevo alias&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -k -u &lt;span class="s2"&gt;&amp;#34;API_KEY:API_SECRET&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -X POST &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;alias&amp;#34;:{&amp;#34;name&amp;#34;:&amp;#34;test_alias&amp;#34;,&amp;#34;type&amp;#34;:&amp;#34;host&amp;#34;,&amp;#34;content&amp;#34;:&amp;#34;10.0.0.1&amp;#34;}}&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://192.168.1.1/api/firewall/alias/addItem
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Aplicar cambios pendientes en el firewall&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -k -u &lt;span class="s2"&gt;&amp;#34;API_KEY:API_SECRET&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -X POST &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://192.168.1.1/api/firewall/alias/reconfigure
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La API no cubre el 100% de la interfaz web. Algunas funciones de plugins (como partes de Zenarmor) no están expuestas. Pero para la gestión del firewall, aliases, reglas, interfaces y la mayoría de servicios core es suficiente.&lt;/p&gt;
&lt;h3 id="ansible-ansibleguycollection_opnsense"&gt;Ansible: ansibleguy/collection_opnsense
&lt;/h3&gt;&lt;p&gt;La colección &lt;code&gt;ansibleguy.opnsense&lt;/code&gt; es la opción más madura para gestionar OPNsense como código en 2026. Envuelve la API REST en módulos Ansible idempotentes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Instalar la colección&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ansible-galaxy collection install ansibleguy.opnsense
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Un ejemplo de playbook que gestiona aliases y reglas de firewall:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Configurar firewall OPNsense&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;opnsense&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;httpapi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible_httpapi_port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible_httpapi_use_ssl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible_httpapi_validate_certs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Crear alias RFC1918&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansibleguy.opnsense.alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RFC1918&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;network&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;10.0.0.0/8&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;172.16.0.0/12&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;192.168.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Redes privadas RFC1918&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Crear alias de administración&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansibleguy.opnsense.alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Admin_IPs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;host&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;192.168.50.10&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;192.168.50.11&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;IPs de administración&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Bloquear IoT hacia redes internas&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansibleguy.opnsense.rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;opt3&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# VLAN 30 IoT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;block&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;source_net&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;IoT net&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;destination_net&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;RFC1918&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;IoT sin acceso a redes internas&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ansible soporta modo check (&lt;code&gt;--check&lt;/code&gt;) para ver qué cambiaría sin aplicar nada. Esto es especialmente útil para revisar cambios de firewall antes de ejecutarlos, algo que la interfaz web no permite.&lt;/p&gt;
&lt;p&gt;La limitación principal es que no todos los módulos de OPNsense están cubiertos por la colección. Servicios como Zenarmor, CrowdSec o configuraciones avanzadas de Suricata pueden requerir llamadas directas a la API con el módulo &lt;code&gt;uri&lt;/code&gt; de Ansible.&lt;/p&gt;
&lt;h3 id="control-de-versiones-del-configxml"&gt;Control de versiones del config.xml
&lt;/h3&gt;&lt;p&gt;El enfoque más directo para tener la configuración bajo control de versiones: exportar el &lt;code&gt;config.xml&lt;/code&gt;, cifrarlo y guardarlo en un repositorio Git privado. Es lo que ya se hace con los backups del segundo post, pero integrado en un flujo de trabajo Git.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# export_config.sh - Ejecutar desde cron o manualmente antes de cambios&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/root/config-backups&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;REPO_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/root/opnsense-config&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date +%Y%m%d_%H%M&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Exportar configuración actual&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp /conf/config.xml &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config_&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Cifrar con age (más simple que OpenSSL para este uso)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;age -r age1publickey... &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config_&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.xml.age&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config_&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Limpiar archivo sin cifrar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config_&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Commit al repositorio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;config: backup &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Con esto se tiene un historial de cambios en la configuración con timestamps y la posibilidad de hacer &lt;code&gt;git diff&lt;/code&gt; entre versiones cifradas (o descifrar dos versiones y compararlas con &lt;code&gt;diff&lt;/code&gt;). No es tan limpio como el modelo de VyOS donde la configuración es texto plano, pero funciona.&lt;/p&gt;
&lt;h3 id="pipeline-cicd-para-validación"&gt;Pipeline CI/CD para validación
&lt;/h3&gt;&lt;p&gt;Llevar la automatización un paso más allá: validar los cambios de configuración antes de aplicarlos. Un pipeline ligero en GitLab CI o GitHub Actions que:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Descifre el &lt;code&gt;config.xml&lt;/code&gt; en un entorno efímero.&lt;/li&gt;
&lt;li&gt;Valide la estructura XML con &lt;code&gt;xmllint&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compruebe anti-patrones con reglas personalizadas (reglas con &lt;code&gt;source=any destination=any action=pass&lt;/code&gt;, usuarios sin OTP, servicios innecesarios habilitados).&lt;/li&gt;
&lt;li&gt;Ejecute el playbook de Ansible en modo check contra un entorno de staging (si existe) o simplemente valide la sintaxis.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Esto no reemplaza las pruebas manuales, pero detecta errores evidentes antes de que lleguen a producción.&lt;/p&gt;
&lt;p&gt;En términos de madurez de IaC, OPNsense está en un punto intermedio:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Aspecto&lt;/th&gt;
 &lt;th&gt;OPNsense&lt;/th&gt;
 &lt;th&gt;VyOS&lt;/th&gt;
 &lt;th&gt;OpenWrt&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;API REST&lt;/td&gt;
 &lt;td&gt;Completa&lt;/td&gt;
 &lt;td&gt;Completa&lt;/td&gt;
 &lt;td&gt;Limitada (ubus/JSON-RPC)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Terraform&lt;/td&gt;
 &lt;td&gt;Sin provider maduro&lt;/td&gt;
 &lt;td&gt;Provider oficial (Foltik/vyos)&lt;/td&gt;
 &lt;td&gt;Sin provider&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Ansible&lt;/td&gt;
 &lt;td&gt;ansibleguy.opnsense&lt;/td&gt;
 &lt;td&gt;vyos.vyos (oficial)&lt;/td&gt;
 &lt;td&gt;Community roles&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Formato de config&lt;/td&gt;
 &lt;td&gt;XML (config.xml)&lt;/td&gt;
 &lt;td&gt;Texto plano CLI&lt;/td&gt;
 &lt;td&gt;UCI (texto plano)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Workflow Git&lt;/td&gt;
 &lt;td&gt;Export manual o scripted&lt;/td&gt;
 &lt;td&gt;Nativo (text config)&lt;/td&gt;
 &lt;td&gt;Export manual&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;VyOS gana en IaC por diseño: su configuración es texto plano desde el primer día, con commits atómicos y rollback nativo. OPNsense compensa con una API REST sólida y la colección de Ansible, pero requiere más esfuerzo para llegar al mismo nivel de automatización.&lt;/p&gt;
&lt;h2 id="marco-de-auditoría-iso-27001-y-ens"&gt;Marco de auditoría: ISO 27001 y ENS
&lt;/h2&gt;&lt;h3 id="por-qué-importa-aunque-sea-un-homelab"&gt;Por qué importa aunque sea un homelab
&lt;/h3&gt;&lt;p&gt;Es tentador pensar que un marco de auditoría formal es solo para empresas. Pero la realidad es más pragmática: un marco de auditoría es una checklist que personas con más experiencia que tú ya validaron. Seguirlo evita el problema de &amp;ldquo;se me olvidó revisar X&amp;rdquo; que aparece cuando la rutina de seguridad depende solo de la memoria.&lt;/p&gt;
&lt;p&gt;Hay una razón adicional si estás en España: el Esquema Nacional de Seguridad (ENS, Real Decreto 311/2022) es obligatorio para el sector público y sus proveedores tecnológicos&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Esto incluye cada vez más a freelancers y pequeñas empresas que prestan servicios a administraciones públicas. Tener la infraestructura alineada con el ENS, aunque sea a nivel básico, no es solo buena práctica, es un requisito potencial.&lt;/p&gt;
&lt;h3 id="controles-relevantes-de-iso-270012022"&gt;Controles relevantes de ISO 27001:2022
&lt;/h3&gt;&lt;p&gt;ISO 27001:2022 organiza los controles de seguridad en el Anexo A&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. Los que aplican directamente a lo configurado en esta serie:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Control&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;th&gt;Implementación en OPNsense&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;A.8.20&lt;/td&gt;
 &lt;td&gt;Seguridad de redes&lt;/td&gt;
 &lt;td&gt;Firewall WAN deny-all, IPS, CrowdSec, GeoIP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.8.22&lt;/td&gt;
 &lt;td&gt;Segregación de redes&lt;/td&gt;
 &lt;td&gt;5 VLANs con reglas inter-VLAN restrictivas&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.8.23&lt;/td&gt;
 &lt;td&gt;Filtrado web&lt;/td&gt;
 &lt;td&gt;Zenarmor DPI con políticas por categoría&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.8.15&lt;/td&gt;
 &lt;td&gt;Logging&lt;/td&gt;
 &lt;td&gt;Syslog remoto con TLS, EVE JSON de Suricata&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.8.16&lt;/td&gt;
 &lt;td&gt;Actividades de monitorización&lt;/td&gt;
 &lt;td&gt;Alertas Suricata, dashboards CrowdSec, Zenarmor&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.8.17&lt;/td&gt;
 &lt;td&gt;Sincronización de relojes&lt;/td&gt;
 &lt;td&gt;NTP configurado en System &amp;gt; General (imprescindible para correlación de logs)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.5.15&lt;/td&gt;
 &lt;td&gt;Control de acceso&lt;/td&gt;
 &lt;td&gt;Política least-privilege, admin dedicado&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.5.17&lt;/td&gt;
 &lt;td&gt;Información de autenticación&lt;/td&gt;
 &lt;td&gt;Contraseñas 16+ caracteres, OTP habilitado&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.5.18&lt;/td&gt;
 &lt;td&gt;Derechos de acceso&lt;/td&gt;
 &lt;td&gt;Revisión periódica de usuarios y permisos&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A.8.2&lt;/td&gt;
 &lt;td&gt;Acceso privilegiado&lt;/td&gt;
 &lt;td&gt;Root restringido, admin separado, SSH solo con claves&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La norma no prescribe frecuencias exactas de revisión, pero los auditores esperan: revisión de reglas de firewall al menos trimestral, revisión de accesos privilegiados trimestral, y monitorización de logs continua con revisión manual semanal.&lt;/p&gt;
&lt;h3 id="medidas-del-ens-aplicables"&gt;Medidas del ENS aplicables
&lt;/h3&gt;&lt;p&gt;El ENS (RD 311/2022) clasifica los sistemas en tres niveles: básico, medio y alto. Las medidas relevantes para un firewall:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Medida ENS&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;th&gt;Nivel mínimo&lt;/th&gt;
 &lt;th&gt;Implementación&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;mp.com.1&lt;/td&gt;
 &lt;td&gt;Perímetro seguro&lt;/td&gt;
 &lt;td&gt;Medio&lt;/td&gt;
 &lt;td&gt;Firewall WAN, GeoIP, deny-all por defecto&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;mp.com.2&lt;/td&gt;
 &lt;td&gt;Protección de confidencialidad&lt;/td&gt;
 &lt;td&gt;Medio&lt;/td&gt;
 &lt;td&gt;WireGuard VPN, DoT para DNS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;mp.com.4&lt;/td&gt;
 &lt;td&gt;Segregación de redes&lt;/td&gt;
 &lt;td&gt;Medio&lt;/td&gt;
 &lt;td&gt;VLANs con reglas inter-VLAN&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;op.exp.2&lt;/td&gt;
 &lt;td&gt;Configuración de seguridad&lt;/td&gt;
 &lt;td&gt;Básico&lt;/td&gt;
 &lt;td&gt;Hardening SSH, servicios deshabilitados&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;op.exp.8&lt;/td&gt;
 &lt;td&gt;Registro de actividad&lt;/td&gt;
 &lt;td&gt;Básico&lt;/td&gt;
 &lt;td&gt;Syslog remoto (retención 2 años para medio/alto)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;op.acc.1&lt;/td&gt;
 &lt;td&gt;Identificación&lt;/td&gt;
 &lt;td&gt;Básico&lt;/td&gt;
 &lt;td&gt;Usuarios únicos, sin cuentas compartidas&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;op.acc.5&lt;/td&gt;
 &lt;td&gt;Mecanismo de autenticación&lt;/td&gt;
 &lt;td&gt;Básico&lt;/td&gt;
 &lt;td&gt;Contraseñas fuertes; OTP para medio/alto&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;op.acc.7&lt;/td&gt;
 &lt;td&gt;Acceso remoto&lt;/td&gt;
 &lt;td&gt;Básico&lt;/td&gt;
 &lt;td&gt;WireGuard VPN obligatorio para administración remota&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;op.mon.1&lt;/td&gt;
 &lt;td&gt;Detección de intrusiones&lt;/td&gt;
 &lt;td&gt;Medio&lt;/td&gt;
 &lt;td&gt;Suricata IPS + CrowdSec&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Las guías CCN-STIC del Centro Criptológico Nacional proporcionan instrucciones detalladas de implementación. En particular, la CCN-STIC-811 para interconexión y la CCN-STIC-408 para seguridad perimetral son las más relevantes para este contexto&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Un detalle importante del ENS: la retención de logs para nivel medio y alto es de dos años mínimo. Si el syslog remoto no tiene capacidad para eso, hay que planificarlo. Con Loki y compresión, los logs de firewall de un homelab no ocupan mucho, pero hay que tenerlo en cuenta desde el diseño.&lt;/p&gt;
&lt;h3 id="calendario-de-auditoría"&gt;Calendario de auditoría
&lt;/h3&gt;&lt;p&gt;Combinando las recomendaciones de ISO 27001 y ENS con lo que es realista para un entorno pequeño:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Frecuencia&lt;/th&gt;
 &lt;th&gt;Tarea&lt;/th&gt;
 &lt;th&gt;Tipo&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Diaria&lt;/td&gt;
 &lt;td&gt;Revisión automatizada de alertas IDS/IPS y decisiones CrowdSec&lt;/td&gt;
 &lt;td&gt;Automática&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Semanal&lt;/td&gt;
 &lt;td&gt;Revisión manual de logs: eventos bloqueados, intentos de login fallidos, tráfico anómalo&lt;/td&gt;
 &lt;td&gt;Manual&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Mensual&lt;/td&gt;
 &lt;td&gt;Revisión de reglas de firewall y actualización de alias. Verificar que los feeds de amenazas se actualizan&lt;/td&gt;
 &lt;td&gt;Manual&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Trimestral&lt;/td&gt;
 &lt;td&gt;Revisión completa de reglas: identificar reglas sin hits, reglas demasiado permisivas, reglas temporales olvidadas&lt;/td&gt;
 &lt;td&gt;Manual&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Trimestral&lt;/td&gt;
 &lt;td&gt;Revisión de accesos: usuarios activos, permisos, claves SSH vigentes, tokens API&lt;/td&gt;
 &lt;td&gt;Manual&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Semestral&lt;/td&gt;
 &lt;td&gt;Test de exposición básico: &lt;code&gt;nmap&lt;/code&gt; desde fuera de la red contra la IP pública&lt;/td&gt;
 &lt;td&gt;Manual&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Anual&lt;/td&gt;
 &lt;td&gt;Revisión completa de la postura de seguridad. Comparar con el estado del año anterior&lt;/td&gt;
 &lt;td&gt;Manual&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Para la revisión diaria automatizada, un script básico que se ejecuta con cron:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /root/scripts/daily_audit.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Ejecutar con: crontab -e -&amp;gt; 0 7 * * * /root/scripts/daily_audit.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/var/log/daily_audit_&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date +%Y%m%d&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;=== Auditoría diaria &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; ===&amp;#34;&lt;/span&gt; &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Alertas críticas de Suricata en las últimas 24h&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;\n--- Alertas Suricata (severity 1) ---&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /var/log/suricata/eve.json &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq -r &lt;span class="s1"&gt;&amp;#39;select(.event_type==&amp;#34;alert&amp;#34; and .alert.severity==1) | 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;\(.timestamp) \(.alert.signature) src=\(.src_ip) dst=\(.dest_ip)&amp;#34;&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tail -50 &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Decisiones activas de CrowdSec&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;\n--- CrowdSec decisiones activas ---&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli decisions list -o raw 2&amp;gt;/dev/null &lt;span class="p"&gt;|&lt;/span&gt; wc -l &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Antigüedad del último backup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;\n--- Último backup ---&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;LAST_BACKUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;ls -t /conf/backup/*.xml 2&amp;gt;/dev/null &lt;span class="p"&gt;|&lt;/span&gt; head -1&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LAST_BACKUP&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; stat -f &lt;span class="s2"&gt;&amp;#34;%Sm&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LAST_BACKUP&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;No se encontraron backups&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Estado del sistema&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;\n--- Recursos del sistema ---&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CPU: &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;sysctl -n dev.cpu.0.temperature 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;N/A&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;RAM: &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;sysctl -n vm.stats.vm.v_free_count&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;States: &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pfctl -si 2&amp;gt;/dev/null &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;&amp;#39;current entries&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Enviar por email o copiar a syslog&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;logger -t daily_audit &lt;span class="s2"&gt;&amp;#34;Auditoría completada. Ver &lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Este script no pretende ser un SIEM. Es una primera línea de revisión automática que señala si hay algo que requiere atención. Para un análisis serio de logs, la combinación de Grafana + Loki o un Wazuh dedicado es lo adecuado.&lt;/p&gt;
&lt;h2 id="opnsense-frente-a-las-alternativas"&gt;OPNsense frente a las alternativas
&lt;/h2&gt;&lt;p&gt;Antes de seguir invirtiendo tiempo en OPNsense, vale la pena comparar con las otras opciones serias de firewall/router open source. No para migrar ahora, sino para saber qué hay fuera y tomar decisiones informadas si las necesidades cambian.&lt;/p&gt;
&lt;h3 id="vyos"&gt;VyOS
&lt;/h3&gt;&lt;p&gt;VyOS es un sistema operativo de red basado en Debian con un modelo de configuración CLI inspirado en JunOS. No tiene interfaz web.&lt;/p&gt;
&lt;p&gt;Lo que hace mejor que OPNsense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configuración como texto plano&lt;/strong&gt;. La config de VyOS es un archivo de texto legible, con commits atómicos y rollback nativo. Es el sueño del IaC: &lt;code&gt;git diff&lt;/code&gt; funciona directamente sobre la configuración.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provider de Terraform oficial&lt;/strong&gt; (Foltik/vyos). Infraestructura de red declarativa real.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Routing avanzado&lt;/strong&gt;. BGP, OSPF, IS-IS a escala. Soporta tablas de routing con más de un millón de prefijos BGP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rendimiento&lt;/strong&gt;. El dataplane VPP permite throughput de 10 Gbps+ con hardware adecuado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud-native&lt;/strong&gt;. Despliegue directo en AWS, Azure y GCP con soporte oficial.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lo que hace peor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sin GUI&lt;/strong&gt;. Todo es CLI o API. La curva de aprendizaje es pronunciada si no vienes de networking empresarial.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Seguridad integrada limitada&lt;/strong&gt;. No hay equivalente a Suricata con GUI, no hay DPI tipo Zenarmor, no hay CrowdSec integrado. Se puede instalar Suricata manualmente, pero sin la integración que ofrece OPNsense.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LTS de pago&lt;/strong&gt;. Desde 2024, las imágenes LTS estables requieren suscripción. Las rolling son gratuitas pero sin garantía de estabilidad.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="openwrt"&gt;OpenWrt
&lt;/h3&gt;&lt;p&gt;OpenWrt es un sistema operativo para routers basado en Linux. Su fuerte es el soporte de hardware embebido.&lt;/p&gt;
&lt;p&gt;Lo que hace mejor que OPNsense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Soporte de hardware masivo&lt;/strong&gt;. Funciona en más de 500 modelos de routers comerciales, además de x86.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WiFi nativo&lt;/strong&gt;. Gestiona directamente las interfaces wireless, con soporte excelente de drivers y configuración avanzada de APs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ligero&lt;/strong&gt;. Puede funcionar con 128 MB de RAM y 16 MB de flash en hardware embebido.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ecosistema de paquetes&lt;/strong&gt;. Más de 27.000 paquetes disponibles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lo que hace peor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Seguridad básica&lt;/strong&gt;. Firewall nftables sin IDS/IPS integrado. Los paquetes de Suricata y Snort son comunitarios, mal mantenidos y con problemas de rendimiento en hardware limitado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sin DPI&lt;/strong&gt;. No hay equivalente a Zenarmor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IaC limitado&lt;/strong&gt;. UCI es scriptable pero no hay provider de Terraform ni API REST madura.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actualizaciones complicadas&lt;/strong&gt;. En x86, actualizar OpenWrt implica reinstalar y restaurar configuración. OPNsense actualiza con un clic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cuándo-elegir-cada-uno"&gt;Cuándo elegir cada uno
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Criterio&lt;/th&gt;
 &lt;th&gt;OPNsense&lt;/th&gt;
 &lt;th&gt;VyOS&lt;/th&gt;
 &lt;th&gt;OpenWrt&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Seguridad integrada&lt;/td&gt;
 &lt;td&gt;Excelente (Suricata, CrowdSec, Zenarmor)&lt;/td&gt;
 &lt;td&gt;Básica&lt;/td&gt;
 &lt;td&gt;Mínima&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;IaC y automatización&lt;/td&gt;
 &lt;td&gt;Buena (API + Ansible)&lt;/td&gt;
 &lt;td&gt;Excelente (Terraform + text config)&lt;/td&gt;
 &lt;td&gt;Limitada (UCI)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Rendimiento 10G+&lt;/td&gt;
 &lt;td&gt;Moderado&lt;/td&gt;
 &lt;td&gt;Excelente (VPP)&lt;/td&gt;
 &lt;td&gt;No aplica&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Curva de aprendizaje&lt;/td&gt;
 &lt;td&gt;Moderada (GUI)&lt;/td&gt;
 &lt;td&gt;Pronunciada (CLI)&lt;/td&gt;
 &lt;td&gt;Moderada&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Coste&lt;/td&gt;
 &lt;td&gt;Gratuito&lt;/td&gt;
 &lt;td&gt;LTS de pago, rolling gratuito&lt;/td&gt;
 &lt;td&gt;Gratuito&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Caso de uso ideal&lt;/td&gt;
 &lt;td&gt;Firewall/UTM perimetral&lt;/td&gt;
 &lt;td&gt;Router enterprise o cloud edge&lt;/td&gt;
 &lt;td&gt;AP WiFi, gateway ligero&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Para un homelab o una oficina pequeña donde la seguridad es la prioridad, OPNsense sigue siendo la mejor opción. La combinación de Suricata, CrowdSec y Zenarmor con una interfaz web usable no tiene equivalente en las alternativas.&lt;/p&gt;
&lt;p&gt;VyOS tiene sentido si el entorno crece hacia routing complejo (múltiples uplinks con BGP, SD-WAN) o si la infraestructura se gestiona exclusivamente con Terraform.&lt;/p&gt;
&lt;p&gt;OpenWrt tiene sentido como complemento: un AP WiFi corriendo OpenWrt detrás de un OPNsense es una combinación sólida. Pero como firewall perimetral para seguridad, se queda corto.&lt;/p&gt;
&lt;h2 id="conclusiones-y-próximos-pasos"&gt;Conclusiones y próximos pasos
&lt;/h2&gt;&lt;p&gt;En tres posts hemos pasado de un mini PC sin sistema operativo a una red segmentada con cinco VLANs, tres capas de detección (Suricata, CrowdSec, Zenarmor), VPN con WireGuard, DNS cifrado, backups automáticos, automatización con Ansible y un marco de auditoría basado en estándares reales.&lt;/p&gt;
&lt;p&gt;No es perfecto. El IaC de OPNsense no llega al nivel de VyOS. Zenarmor tiene un modelo de licencias que limita las funciones avanzadas en la versión gratuita. Y mantener una rutina de auditoría requiere disciplina que es fácil dejar de lado cuando todo parece funcionar bien.&lt;/p&gt;
&lt;p&gt;Pero es una base sólida sobre la que seguir construyendo. Hay temas que deliberadamente se han dejado fuera de esta serie porque merecen profundidad propia:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Integración con Wazuh como SIEM&lt;/strong&gt;: correlación de eventos de Suricata, CrowdSec y los logs del sistema en un solo lugar, con alertas y dashboards centralizados. Es el paso natural para quien quiera monitorización de seguridad real.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-WAN y failover&lt;/strong&gt;: configurar dos conexiones a internet con balanceo de carga y failover automático. Relevante cuando la disponibilidad importa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HAProxy avanzado&lt;/strong&gt;: mutual TLS (mTLS) con certificados de cliente, autenticación por certificado para servicios internos, OAuth2 como capa de autenticación frente a aplicaciones auto-alojadas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitorización de red con NetFlow/Insight&lt;/strong&gt;: análisis detallado del tráfico por protocolo, host y puerto para detectar anomalías que los IDS no capturan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatización completa con Terraform&lt;/strong&gt;: si OPNsense llega a tener un provider de Terraform maduro (o si se migra parcialmente a VyOS para el routing), la gestión declarativa de toda la red.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si hay interés, un cuarto post puede cubrir la integración con Wazuh y la monitorización avanzada. Es donde el salto de &amp;ldquo;homelab seguro&amp;rdquo; a &amp;ldquo;infraestructura con visibilidad real&amp;rdquo; se hace más evidente.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Real Decreto 311/2022, de 3 de mayo, por el que se regula el Esquema Nacional de Seguridad. BOE-A-2022-7191.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;ISO/IEC 27001:2022 — Information security, cybersecurity and privacy protection — Information security management systems — Requirements.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Las guías CCN-STIC del Centro Criptológico Nacional proporcionan instrucciones detalladas para la implementación del ENS. En particular, la CCN-STIC-811 cubre la interconexión de sistemas y la CCN-STIC-408 la seguridad perimetral.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>OPNsense: segmentación de red, VLANs y hardening</title><link>https://adurrr.github.io/p/opnsense-segmentaci%C3%B3n-de-red-vlans-y-hardening/</link><pubDate>Fri, 03 Apr 2026 00:00:00 +0000</pubDate><guid>https://adurrr.github.io/p/opnsense-segmentaci%C3%B3n-de-red-vlans-y-hardening/</guid><description>&lt;h2 id="backups-cifrados-nativos"&gt;Backups cifrados nativos
&lt;/h2&gt;&lt;p&gt;Perder la configuración de OPNsense después de horas de ajustes es el tipo de desastre que solo pasa una vez. Después de esa vez, se configuran los backups automáticos.&lt;/p&gt;
&lt;p&gt;OPNsense tiene un sistema de backup nativo que exporta toda la configuración en un archivo XML. Desde la versión 24.1, estos backups se pueden cifrar directamente desde la interfaz web.&lt;/p&gt;
&lt;h3 id="configuración-de-backups-cifrados"&gt;Configuración de backups cifrados
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;System &amp;gt; Configuration &amp;gt; Backups&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ir a la sección &lt;strong&gt;Google Drive / Nextcloud&lt;/strong&gt; si se quiere backup remoto, o quedarse con el backup local.&lt;/li&gt;
&lt;li&gt;Marcar la casilla &lt;strong&gt;Encrypt backup&lt;/strong&gt; e introducir una contraseña de cifrado. Esta contraseña es independiente de las credenciales del sistema. Guardarla en un gestor de contraseñas, porque sin ella el backup es irrecuperable.&lt;/li&gt;
&lt;li&gt;En la sección de backup automático (&lt;strong&gt;Scheduled&lt;/strong&gt;), configurar la frecuencia. Un backup diario es razonable para la mayoría de entornos.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="backup-manual-desde-la-interfaz"&gt;Backup manual desde la interfaz
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;System &amp;gt; Configuration &amp;gt; Backups&lt;/strong&gt;, el botón &lt;strong&gt;Download configuration&lt;/strong&gt; genera un XML con toda la configuración actual. Si se marcó la opción de cifrado, el archivo descargado estará cifrado con AES-256-CBC.&lt;/p&gt;
&lt;h3 id="backup-desde-la-consola"&gt;Backup desde la consola
&lt;/h3&gt;&lt;p&gt;Para automatizar backups desde la línea de comandos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Exportar la configuración&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp /conf/config.xml /root/backup_&lt;span class="k"&gt;$(&lt;/span&gt;date +%Y%m%d&lt;span class="k"&gt;)&lt;/span&gt;.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Cifrar con OpenSSL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openssl enc -aes-256-cbc -salt -pbkdf2 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -in /root/backup_&lt;span class="k"&gt;$(&lt;/span&gt;date +%Y%m%d&lt;span class="k"&gt;)&lt;/span&gt;.xml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -out /root/backup_&lt;span class="k"&gt;$(&lt;/span&gt;date +%Y%m%d&lt;span class="k"&gt;)&lt;/span&gt;.xml.enc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Eliminar el archivo sin cifrar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm /root/backup_&lt;span class="k"&gt;$(&lt;/span&gt;date +%Y%m%d&lt;span class="k"&gt;)&lt;/span&gt;.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Es recomendable copiar los backups cifrados a un almacenamiento externo: un NAS, un bucket S3, o incluso un repositorio Git privado (el XML cifrado es pequeño, unos pocos cientos de KB).&lt;/p&gt;
&lt;h3 id="restauración"&gt;Restauración
&lt;/h3&gt;&lt;p&gt;Para restaurar, ir a &lt;strong&gt;System &amp;gt; Configuration &amp;gt; Backups&lt;/strong&gt;, subir el archivo y, si está cifrado, introducir la contraseña. OPNsense aplica la configuración y reinicia los servicios afectados.&lt;/p&gt;
&lt;h2 id="asignación-de-ips-estáticas"&gt;Asignación de IPs estáticas
&lt;/h2&gt;&lt;p&gt;Hay dispositivos que necesitan tener siempre la misma IP: servidores, NAS, impresoras, cámaras de vigilancia. Se puede hacer de dos formas: configurando la IP fija en el propio dispositivo o, lo que es más limpio, asignando reservas DHCP en OPNsense.&lt;/p&gt;
&lt;h3 id="reservas-dhcp-la-forma-recomendada"&gt;Reservas DHCP (la forma recomendada)
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;Services &amp;gt; DHCPv4 &amp;gt; [interfaz]&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ir a la sección &lt;strong&gt;DHCP Static Mappings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Añadir una nueva entrada con:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MAC Address&lt;/strong&gt;: la dirección MAC del dispositivo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IP Address&lt;/strong&gt;: la IP que se quiere asignar siempre.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hostname&lt;/strong&gt;: un nombre descriptivo.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;La ventaja de hacerlo así es que la gestión queda centralizada en OPNsense. Si cambias de router mañana, los dispositivos no necesitan reconfigurarse.&lt;/p&gt;
&lt;h3 id="convención-de-rangos"&gt;Convención de rangos
&lt;/h3&gt;&lt;p&gt;Una convención que funciona bien para organizar la red:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Rango&lt;/th&gt;
 &lt;th&gt;Uso&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;.1&lt;/td&gt;
 &lt;td&gt;Gateway (OPNsense)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;.2 - .19&lt;/td&gt;
 &lt;td&gt;Infraestructura (switches, APs, NAS)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;.20 - .49&lt;/td&gt;
 &lt;td&gt;Servidores y servicios&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;.50 - .99&lt;/td&gt;
 &lt;td&gt;Dispositivos con IP fija (impresoras, cámaras)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;.100 - .254&lt;/td&gt;
 &lt;td&gt;Pool DHCP dinámico&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Esto hace que con solo ver la IP de un dispositivo ya sepas en qué categoría cae.&lt;/p&gt;
&lt;h2 id="zenarmor-sensei"&gt;Zenarmor (Sensei)
&lt;/h2&gt;&lt;p&gt;Zenarmor es un plugin de deep packet inspection (DPI) para OPNsense. Va más allá de lo que hacen Suricata o CrowdSec porque inspecciona el tráfico a nivel de aplicación: puede distinguir entre Netflix y YouTube, entre Telegram y WhatsApp, entre tráfico legítimo y aplicaciones potencialmente peligrosas.&lt;/p&gt;
&lt;h3 id="qué-hace-exactamente"&gt;Qué hace exactamente
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clasificación de tráfico por aplicación&lt;/strong&gt;: identifica más de 300 aplicaciones y protocolos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filtrado de contenido por categorías&lt;/strong&gt;: permite bloquear categorías enteras (gambling, malware, adult content) sin necesidad de mantener listas manualmente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Análisis de tráfico cifrado (TLS)&lt;/strong&gt;: Zenarmor puede clasificar tráfico HTTPS sin descifrarlo, utilizando metadatos como SNI, JA3 fingerprints y patrones de conexión.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reporting detallado&lt;/strong&gt;: dashboards con el consumo por dispositivo, aplicación y categoría.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="instalación"&gt;Instalación
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;System &amp;gt; Firmware &amp;gt; Plugins&lt;/strong&gt;, buscar &lt;code&gt;os-sunnyvalley&lt;/code&gt; e instalar. Tras la instalación, Zenarmor aparece en el menú principal.&lt;/p&gt;
&lt;p&gt;Al iniciar Zenarmor por primera vez, se ejecuta un asistente de configuración:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Modo de despliegue&lt;/strong&gt;: elegir &lt;strong&gt;Routed Mode&lt;/strong&gt; para inspeccionar todo el tráfico que pasa por OPNsense. El modo &lt;strong&gt;Bridge&lt;/strong&gt; es para casos específicos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Motor de base de datos&lt;/strong&gt;: Zenarmor usa una base de datos local para los logs. Para hardware modesto (N100), seleccionar &lt;strong&gt;SQLite&lt;/strong&gt;. Para hardware más potente, &lt;strong&gt;Elasticsearch&lt;/strong&gt; da mejor rendimiento en las consultas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interfaces a proteger&lt;/strong&gt;: seleccionar las interfaces LAN que se quieren inspeccionar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Política por defecto&lt;/strong&gt;: empezar con una política permisiva (solo monitorizar) y ajustar después de ver el tráfico real.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="configuración-de-políticas"&gt;Configuración de políticas
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;Zenarmor &amp;gt; Policies&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Las políticas se aplican por interfaz o por grupo de dispositivos. Una configuración razonable:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Política general (LAN principal)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bloquear categorías: Malware, Phishing, Cryptomining, C2 (Command &amp;amp; Control).&lt;/li&gt;
&lt;li&gt;Monitorizar pero permitir: Streaming, Social Media, Gaming.&lt;/li&gt;
&lt;li&gt;Permitir todo lo demás.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Política para IoT&lt;/strong&gt; (la crearemos con VLANs más adelante):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bloquear todo excepto los dominios necesarios para cada dispositivo.&lt;/li&gt;
&lt;li&gt;Los dispositivos IoT no deberían poder acceder a internet libremente.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Política para invitados&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bloquear: P2P, Tor, VPN (para evitar bypass del filtrado).&lt;/li&gt;
&lt;li&gt;Limitar ancho de banda por dispositivo.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="diferencia-con-suricata-y-crowdsec"&gt;Diferencia con Suricata y CrowdSec
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Característica&lt;/th&gt;
 &lt;th&gt;Suricata (IDS/IPS)&lt;/th&gt;
 &lt;th&gt;CrowdSec&lt;/th&gt;
 &lt;th&gt;Zenarmor&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Inspección&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Paquetes y firmas&lt;/td&gt;
 &lt;td&gt;Logs y patrones&lt;/td&gt;
 &lt;td&gt;Aplicación (DPI)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Qué detecta&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Exploits, malware, C2&lt;/td&gt;
 &lt;td&gt;Fuerza bruta, escaneos&lt;/td&gt;
 &lt;td&gt;Aplicaciones, categorías&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Bloqueo&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Por firma/regla&lt;/td&gt;
 &lt;td&gt;Por IP (ban temporal)&lt;/td&gt;
 &lt;td&gt;Por aplicación/categoría&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Recurso principal&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;CPU (alto)&lt;/td&gt;
 &lt;td&gt;CPU (bajo)&lt;/td&gt;
 &lt;td&gt;CPU (medio) + RAM&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Complementarios&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Sí&lt;/td&gt;
 &lt;td&gt;Sí&lt;/td&gt;
 &lt;td&gt;Sí&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Los tres se complementan. Suricata busca amenazas conocidas en el tráfico. CrowdSec detecta comportamientos maliciosos en los logs y comparte inteligencia. Zenarmor clasifica y filtra a nivel de aplicación. Usarlos juntos da una cobertura de seguridad difícil de superar en un equipo doméstico.&lt;/p&gt;
&lt;h2 id="deshabilitar-ssh-inseguro"&gt;Deshabilitar SSH inseguro
&lt;/h2&gt;&lt;p&gt;SSH es la forma habitual de acceder a la consola de OPNsense de forma remota. Pero la configuración por defecto tiene aspectos que conviene cambiar.&lt;/p&gt;
&lt;h3 id="configuración-segura-de-ssh"&gt;Configuración segura de SSH
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;System &amp;gt; Settings &amp;gt; Administration&lt;/strong&gt;, sección SSH:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Deshabilitar el login de root por SSH&lt;/strong&gt;. Crear un usuario específico con acceso SSH y permisos de sudo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cambiar el puerto por defecto&lt;/strong&gt;. El puerto 22 es el primero que escanean los bots. Cambiar a un puerto alto (por ejemplo, 2222 o algo menos predecible).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deshabilitar autenticación por contraseña&lt;/strong&gt;. Usar exclusivamente claves SSH:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# En tu máquina local, generar un par de claves si no tienes uno&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-keygen -t ed25519 -C &lt;span class="s2"&gt;&amp;#34;opnsense-admin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Copiar la clave pública&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat ~/.ssh/id_ed25519.pub
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;Pegar la clave pública en el perfil del usuario en &lt;strong&gt;System &amp;gt; Access &amp;gt; Users &amp;gt; [usuario] &amp;gt; Authorized Keys&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;En la configuración SSH, desmarcar &lt;strong&gt;Permit Password Login&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="restricción-de-acceso-ssh-por-firewall"&gt;Restricción de acceso SSH por firewall
&lt;/h3&gt;&lt;p&gt;Crear una regla de firewall que solo permita SSH desde IPs específicas:&lt;/p&gt;
&lt;p&gt;En &lt;strong&gt;Firewall &amp;gt; Rules &amp;gt; LAN&lt;/strong&gt;, crear una regla:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Acción: Pass&lt;/li&gt;
&lt;li&gt;Protocolo: TCP&lt;/li&gt;
&lt;li&gt;Origen: alias con las IPs de administración&lt;/li&gt;
&lt;li&gt;Destino: This Firewall&lt;/li&gt;
&lt;li&gt;Puerto destino: el puerto SSH configurado&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Y otra regla que bloquee SSH desde cualquier otro origen.&lt;/p&gt;
&lt;h2 id="vlans-segmentación-de-red"&gt;VLANs: segmentación de red
&lt;/h2&gt;&lt;p&gt;La segmentación con VLANs es probablemente el cambio más importante que se puede hacer en la seguridad de una red doméstica. Sin segmentación, una bombilla WiFi comprometida tiene acceso directo al NAS con las fotos familiares. Con VLANs, cada tipo de dispositivo vive en su propio segmento aislado.&lt;/p&gt;
&lt;h3 id="diseño-de-vlans"&gt;Diseño de VLANs
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;VLAN ID&lt;/th&gt;
 &lt;th&gt;Nombre&lt;/th&gt;
 &lt;th&gt;Subred&lt;/th&gt;
 &lt;th&gt;Propósito&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;Main&lt;/td&gt;
 &lt;td&gt;192.168.10.0/24&lt;/td&gt;
 &lt;td&gt;Dispositivos de confianza: portátiles, sobremesas, móviles personales&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;Guests&lt;/td&gt;
 &lt;td&gt;192.168.20.0/24&lt;/td&gt;
 &lt;td&gt;Dispositivos de invitados, sin acceso a la red interna&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;30&lt;/td&gt;
 &lt;td&gt;IoT&lt;/td&gt;
 &lt;td&gt;192.168.30.0/24&lt;/td&gt;
 &lt;td&gt;Dispositivos IoT: cámaras, sensores, bombillas, aspiradoras&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;40&lt;/td&gt;
 &lt;td&gt;Servers&lt;/td&gt;
 &lt;td&gt;192.168.40.0/24&lt;/td&gt;
 &lt;td&gt;Servidores, NAS, servicios auto-alojados&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;50&lt;/td&gt;
 &lt;td&gt;Management&lt;/td&gt;
 &lt;td&gt;192.168.50.0/24&lt;/td&gt;
 &lt;td&gt;Gestión de infraestructura: switches, APs, el propio OPNsense&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="creación-de-vlans-en-opnsense"&gt;Creación de VLANs en OPNsense
&lt;/h3&gt;&lt;p&gt;Para cada VLAN:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ir a &lt;strong&gt;Interfaces &amp;gt; Other Types &amp;gt; VLAN&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Crear una nueva VLAN:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parent interface&lt;/strong&gt;: la interfaz física a la que se conecta el switch gestionable (por ejemplo, &lt;code&gt;igb1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VLAN tag&lt;/strong&gt;: el ID de la tabla anterior (10, 20, 30, 40, 50).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Description&lt;/strong&gt;: el nombre de la VLAN.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ir a &lt;strong&gt;Interfaces &amp;gt; Assignments&lt;/strong&gt; y asignar cada VLAN como una nueva interfaz.&lt;/li&gt;
&lt;li&gt;Configurar cada interfaz:
&lt;ul&gt;
&lt;li&gt;Habilitar la interfaz.&lt;/li&gt;
&lt;li&gt;Asignar IP estática: la IP del gateway para esa subred (por ejemplo, &lt;code&gt;192.168.10.1/24&lt;/code&gt; para VLAN 10).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Configurar DHCP en &lt;strong&gt;Services &amp;gt; DHCPv4&lt;/strong&gt; para cada VLAN con su rango correspondiente.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="configuración-del-switch"&gt;Configuración del switch
&lt;/h3&gt;&lt;p&gt;El switch gestionable necesita configurarse para que entienda las VLANs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;El &lt;strong&gt;puerto troncal&lt;/strong&gt; (trunk) que va a OPNsense debe ser &lt;strong&gt;tagged&lt;/strong&gt; para todas las VLANs (10, 20, 30, 40, 50).&lt;/li&gt;
&lt;li&gt;Los &lt;strong&gt;puertos de acceso&lt;/strong&gt; se configuran como &lt;strong&gt;untagged&lt;/strong&gt; en la VLAN correspondiente. Por ejemplo, el puerto donde se conecta un AP para invitados se pone untagged en VLAN 20.&lt;/li&gt;
&lt;li&gt;Si el AP soporta múltiples SSIDs con VLANs (como los Ubiquiti o TP-Link Omada), se puede crear un SSID por VLAN y el AP se encarga de tagear el tráfico.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="reglas-de-firewall-entre-vlans"&gt;Reglas de firewall entre VLANs
&lt;/h3&gt;&lt;p&gt;Este es el punto donde se define realmente la segmentación. Sin reglas de firewall, las VLANs comparten el mismo router y pueden comunicarse entre sí. Hay que crear reglas explícitas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Principio general&lt;/strong&gt;: denegar todo entre VLANs por defecto y permitir solo lo necesario.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VLAN 10 (Main)&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acción&lt;/th&gt;
 &lt;th&gt;Origen&lt;/th&gt;
 &lt;th&gt;Destino&lt;/th&gt;
 &lt;th&gt;Puerto&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;Main net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Acceso completo a internet&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;Main net&lt;/td&gt;
 &lt;td&gt;Servers net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Acceso a servicios internos&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Block&lt;/td&gt;
 &lt;td&gt;Main net&lt;/td&gt;
 &lt;td&gt;Management net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;No acceder directamente a gestión&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Block&lt;/td&gt;
 &lt;td&gt;Main net&lt;/td&gt;
 &lt;td&gt;IoT net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Aislamiento de IoT&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;VLAN 20 (Guests)&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acción&lt;/th&gt;
 &lt;th&gt;Origen&lt;/th&gt;
 &lt;th&gt;Destino&lt;/th&gt;
 &lt;th&gt;Puerto&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;Guests net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;80, 443, 53&lt;/td&gt;
 &lt;td&gt;Solo navegación web y DNS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Block&lt;/td&gt;
 &lt;td&gt;Guests net&lt;/td&gt;
 &lt;td&gt;RFC1918&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Sin acceso a redes internas&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;VLAN 30 (IoT)&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acción&lt;/th&gt;
 &lt;th&gt;Origen&lt;/th&gt;
 &lt;th&gt;Destino&lt;/th&gt;
 &lt;th&gt;Puerto&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;IoT net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;443, 8883&lt;/td&gt;
 &lt;td&gt;Solo HTTPS y MQTT para cloud&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;IoT net&lt;/td&gt;
 &lt;td&gt;IoT gateway&lt;/td&gt;
 &lt;td&gt;53&lt;/td&gt;
 &lt;td&gt;DNS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Block&lt;/td&gt;
 &lt;td&gt;IoT net&lt;/td&gt;
 &lt;td&gt;RFC1918&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Sin acceso a redes internas&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;VLAN 40 (Servers)&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acción&lt;/th&gt;
 &lt;th&gt;Origen&lt;/th&gt;
 &lt;th&gt;Destino&lt;/th&gt;
 &lt;th&gt;Puerto&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;Servers net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;80, 443, 53&lt;/td&gt;
 &lt;td&gt;Acceso a internet para actualizaciones&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;Servers net&lt;/td&gt;
 &lt;td&gt;Servers net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Comunicación entre servicios&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Block&lt;/td&gt;
 &lt;td&gt;Servers net&lt;/td&gt;
 &lt;td&gt;Main net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Los servidores no inician conexiones a Main&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;VLAN 50 (Management)&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acción&lt;/th&gt;
 &lt;th&gt;Origen&lt;/th&gt;
 &lt;th&gt;Destino&lt;/th&gt;
 &lt;th&gt;Puerto&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;Management net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Acceso total (solo admins)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Para implementar la regla de bloqueo de redes RFC1918 (que cubre todas las subredes privadas), crear un alias en &lt;strong&gt;Firewall &amp;gt; Aliases&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nombre: &lt;code&gt;RFC1918&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tipo: Network&lt;/li&gt;
&lt;li&gt;Contenido: &lt;code&gt;10.0.0.0/8&lt;/code&gt;, &lt;code&gt;172.16.0.0/12&lt;/code&gt;, &lt;code&gt;192.168.0.0/16&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Este alias se usa como destino en las reglas de bloqueo para evitar que VLANs como Guests o IoT accedan a cualquier red interna.&lt;/p&gt;
&lt;h2 id="hardening-y-buenas-prácticas"&gt;Hardening y buenas prácticas
&lt;/h2&gt;&lt;h3 id="actualizaciones"&gt;Actualizaciones
&lt;/h3&gt;&lt;p&gt;Lo primero y lo más básico: mantener OPNsense actualizado. Las actualizaciones de seguridad se publican con regularidad y los parches se aplican rápido.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configurar notificaciones de actualización por email.&lt;/li&gt;
&lt;li&gt;Aplicar las actualizaciones en horarios de bajo uso.&lt;/li&gt;
&lt;li&gt;Antes de actualizar, hacer un backup (ya está automatizado si se siguió la primera sección).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dns-sobre-tls-dot"&gt;DNS sobre TLS (DoT)
&lt;/h3&gt;&lt;p&gt;Configurar Unbound (el resolver DNS de OPNsense) para usar DNS sobre TLS:&lt;/p&gt;
&lt;p&gt;En &lt;strong&gt;Services &amp;gt; Unbound DNS &amp;gt; General&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Habilitar &lt;strong&gt;DNS over TLS&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;Custom forwarding&lt;/strong&gt;, añadir servidores DNS que soporten DoT:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Quad9 (filtrado de malware incluido)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;9.9.9.9@853#dns.quad9.net
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;149.112.112.112@853#dns.quad9.net
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Cloudflare
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1.1.1.1@853#cloudflare-dns.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1.0.0.1@853#cloudflare-dns.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Esto cifra las consultas DNS entre OPNsense y el resolver, evitando que el ISP vea qué dominios consulta cada dispositivo.&lt;/p&gt;
&lt;h3 id="deshabilitar-servicios-innecesarios"&gt;Deshabilitar servicios innecesarios
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;System &amp;gt; Settings &amp;gt; Administration&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deshabilitar &lt;strong&gt;UPnP&lt;/strong&gt; salvo que sea estrictamente necesario (y aun así, limitarlo a interfaces específicas).&lt;/li&gt;
&lt;li&gt;Deshabilitar &lt;strong&gt;SNMP&lt;/strong&gt; si no se usa para monitorización.&lt;/li&gt;
&lt;li&gt;Revisar los plugins instalados y desinstalar los que no se usen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="logging-centralizado"&gt;Logging centralizado
&lt;/h3&gt;&lt;p&gt;OPNsense puede enviar logs a un servidor syslog externo. Si se tiene un stack de monitorización (Grafana + Loki, o ELK), configurar el envío en &lt;strong&gt;System &amp;gt; Settings &amp;gt; Logging &amp;gt; Remote&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Servidor: la IP del servidor syslog.&lt;/li&gt;
&lt;li&gt;Protocolo: TCP con TLS si es posible.&lt;/li&gt;
&lt;li&gt;Facility: seleccionar qué logs enviar (firewall, sistema, IDS).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="auditoría-regular"&gt;Auditoría regular
&lt;/h3&gt;&lt;p&gt;Establecer una rutina de revisión:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Semanal&lt;/strong&gt;: revisar los logs de IDS/IPS y CrowdSec. Buscar patrones recurrentes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mensual&lt;/strong&gt;: revisar las reglas de firewall. ¿Hay reglas que ya no tienen sentido? ¿Se ha añadido algún dispositivo nuevo que necesita reglas específicas?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trimestral&lt;/strong&gt;: revisar los usuarios y permisos. ¿Sigue siendo necesario cada usuario? ¿Las claves SSH están vigentes?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En el tercer y último post de la serie repasaremos todo lo configurado, revisaremos la postura de seguridad en conjunto y veremos prácticas avanzadas.&lt;/p&gt;</description></item><item><title>OPNsense: del hardware al firewall funcional</title><link>https://adurrr.github.io/p/opnsense-del-hardware-al-firewall-funcional/</link><pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate><guid>https://adurrr.github.io/p/opnsense-del-hardware-al-firewall-funcional/</guid><description>&lt;h2 id="qué-hardware-necesita-opnsense"&gt;Qué hardware necesita OPNsense
&lt;/h2&gt;&lt;p&gt;Antes de abrir el instalador conviene saber qué pide OPNsense y qué merece la pena comprar. La documentación oficial distingue entre mínimos y recomendados, pero en la práctica hay matices que importan bastante.&lt;/p&gt;
&lt;h3 id="requisitos-mínimos-oficiales"&gt;Requisitos mínimos oficiales
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Recurso&lt;/th&gt;
 &lt;th&gt;Mínimo&lt;/th&gt;
 &lt;th&gt;Recomendado&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;CPU&lt;/td&gt;
 &lt;td&gt;x86-64 de 64 bits, 1 GHz&lt;/td&gt;
 &lt;td&gt;Multi-core reciente con AES-NI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RAM&lt;/td&gt;
 &lt;td&gt;2 GB&lt;/td&gt;
 &lt;td&gt;8 GB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Almacenamiento&lt;/td&gt;
 &lt;td&gt;40 GB SSD&lt;/td&gt;
 &lt;td&gt;120 GB SSD&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NICs&lt;/td&gt;
 &lt;td&gt;2 interfaces de red&lt;/td&gt;
 &lt;td&gt;2+ interfaces Intel&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;AES-NI es obligatorio desde OPNsense 24.1. Sin esta instrucción el instalador ni arranca. Cualquier procesador Intel de sexta generación o posterior la incluye, y en AMD está presente desde Ryzen en adelante.&lt;/p&gt;
&lt;h3 id="opciones-económicas-que-funcionan"&gt;Opciones económicas que funcionan
&lt;/h3&gt;&lt;p&gt;La opción más barata con la que he tenido buena experiencia es un mini PC con procesador Intel N100 o N200. Están pensados para bajo consumo y tienen AES-NI, lo que cubre el requisito principal. Se encuentran con cuatro puertos Ethernet Intel i226-V por menos de 150 euros.&lt;/p&gt;
&lt;p&gt;Algunos modelos concretos que cumplen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Topton N100/N200 con 4x i226-V&lt;/strong&gt;: entre 120 y 170 euros según la configuración. Vienen sin RAM ni SSD, que se compran aparte. Un módulo de 8 GB DDR5 y un SSD de 128 GB añaden unos 30 euros más.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protectli VP2420 o VP2410&lt;/strong&gt;: más caros (en torno a 300 euros), pero con soporte oficial y carcasa de aluminio que disipa bien. Buena opción si prefieres algo con garantía seria.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware reciclado con dos NICs&lt;/strong&gt;: un Dell OptiPlex o Lenovo ThinkCentre con una tarjeta Intel dual-port PCIe funciona perfectamente. Se encuentran por 50-80 euros en mercados de segunda mano.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lo que realmente importa: que las interfaces de red sean Intel. Las Realtek funcionan, pero dan problemas con offloading y rendimiento bajo carga. Merece la pena pagar la diferencia.&lt;/p&gt;
&lt;h2 id="instalación"&gt;Instalación
&lt;/h2&gt;&lt;p&gt;La instalación de OPNsense es directa. Se descarga la imagen ISO desde la web oficial, se graba en un USB con &lt;code&gt;dd&lt;/code&gt; o con Rufus en Windows, y se arranca desde el USB.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Grabar la imagen en un USB (cuidado con seleccionar el dispositivo correcto)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dd &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OPNsense-24.7-dvd-amd64.iso &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/sdX &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4M &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;progress
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Al arrancar aparece un entorno live con la opción de probar antes de instalar. El usuario por defecto es &lt;code&gt;installer&lt;/code&gt; con contraseña &lt;code&gt;opnsense&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Durante la instalación:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Seleccionar el disco destino para la instalación (el SSD interno, no el USB).&lt;/li&gt;
&lt;li&gt;Elegir el sistema de ficheros. &lt;strong&gt;UFS&lt;/strong&gt; es la opción simple y estable. &lt;strong&gt;ZFS&lt;/strong&gt; tiene ventajas (snapshots, compresión), pero para un firewall con un solo disco UFS es suficiente.&lt;/li&gt;
&lt;li&gt;Definir la contraseña de root.&lt;/li&gt;
&lt;li&gt;Retirar el USB y reiniciar.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tras el reinicio, OPNsense arranca directamente y presenta una consola de texto con un menú básico. Desde ahí se puede asignar interfaces y configurar la dirección IP de la LAN para acceder a la interfaz web.&lt;/p&gt;
&lt;h2 id="configuración-de-pppoe-en-wan"&gt;Configuración de PPPoE en WAN
&lt;/h2&gt;&lt;p&gt;Si tu ISP utiliza PPPoE (como ocurre con muchas conexiones de fibra en España y Latinoamérica), hay que configurarlo en la interfaz WAN.&lt;/p&gt;
&lt;p&gt;En &lt;strong&gt;Interfaces &amp;gt; WAN&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cambiar el tipo de configuración IPv4 a &lt;strong&gt;PPPoE&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Introducir el usuario y contraseña que proporciona el ISP.&lt;/li&gt;
&lt;li&gt;En la mayoría de proveedores no hace falta tocar el MTU, pero si notas problemas de fragmentación, ajustarlo a &lt;strong&gt;1492&lt;/strong&gt; (el estándar para PPPoE sobre Ethernet con MTU 1500).&lt;/li&gt;
&lt;li&gt;Guardar y aplicar.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Si la conexión no levanta, verificar que el cable del ONT va directamente al puerto asignado como WAN. Algunos ONT del ISP necesitan estar en modo bridge para que OPNsense negocie la sesión PPPoE directamente.&lt;/p&gt;
&lt;h2 id="lan-en-modo-bridge"&gt;LAN en modo bridge
&lt;/h2&gt;&lt;p&gt;Hay situaciones en las que interesa agrupar varios puertos físicos en un mismo segmento de red, por ejemplo cuando el mini PC tiene cuatro puertos y queremos que tres de ellos funcionen como un switch sin necesidad de hardware adicional.&lt;/p&gt;
&lt;p&gt;Para configurar un bridge en OPNsense:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ir a &lt;strong&gt;Interfaces &amp;gt; Other Types &amp;gt; Bridge&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Crear un nuevo bridge y añadir las interfaces que quieres agrupar (por ejemplo, &lt;code&gt;igb1&lt;/code&gt;, &lt;code&gt;igb2&lt;/code&gt;, &lt;code&gt;igb3&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Ir a &lt;strong&gt;Interfaces &amp;gt; Assignments&lt;/strong&gt; y asignar el bridge recién creado como la interfaz LAN.&lt;/li&gt;
&lt;li&gt;Configurar la IP estática de la LAN sobre la interfaz bridge (por ejemplo, &lt;code&gt;192.168.1.1/24&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Habilitar el servidor DHCP en &lt;strong&gt;Services &amp;gt; DHCPv4&lt;/strong&gt; apuntando a la interfaz bridge.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Con esto los tres puertos comparten el mismo segmento de red y el DHCP sirve direcciones para todos ellos.&lt;/p&gt;
&lt;h2 id="interfaz-wireless-y-firmware"&gt;Interfaz wireless y firmware
&lt;/h2&gt;&lt;p&gt;OPNsense soporta algunas tarjetas WiFi, pero el soporte es limitado comparado con Linux. Las tarjetas Atheros son las que mejor funcionan, pero muchas necesitan firmware adicional que no viene incluido por defecto.&lt;/p&gt;
&lt;p&gt;Para instalar el firmware necesario:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Desde la consola de OPNsense (opción 8 del menú para shell)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg install wifi-firmware-atheros
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# O para tarjetas Intel:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg install wifi-firmware-intel
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Después de instalar el firmware, reiniciar el sistema. La interfaz wireless debería aparecer en &lt;strong&gt;Interfaces &amp;gt; Assignments&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Para configurar el punto de acceso:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ir a &lt;strong&gt;Interfaces &amp;gt; Wireless&lt;/strong&gt; y crear un nuevo dispositivo en modo &lt;strong&gt;Access Point&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Seleccionar el estándar (802.11ac/ax si la tarjeta lo soporta).&lt;/li&gt;
&lt;li&gt;Configurar el SSID y la seguridad WPA2/WPA3.&lt;/li&gt;
&lt;li&gt;Asignar la interfaz wireless y darle una IP en un rango diferente al de la LAN cableada, o añadirla al bridge existente si se quiere que esté en el mismo segmento.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Una advertencia honesta: el WiFi integrado en OPNsense funciona, pero no esperes el rendimiento ni la estabilidad de un access point dedicado. Para un uso serio es mejor usar un AP externo (Ubiquiti, TP-Link Omada) y dejar que OPNsense se encargue solo del routing y el firewall.&lt;/p&gt;
&lt;h2 id="ids-e-ips-detección-y-prevención-de-intrusiones"&gt;IDS e IPS: detección y prevención de intrusiones
&lt;/h2&gt;&lt;p&gt;OPNsense incluye Suricata como motor de IDS/IPS. La diferencia entre ambos modos es simple: IDS detecta y registra, IPS detecta y bloquea.&lt;/p&gt;
&lt;h3 id="configuración-inicial"&gt;Configuración inicial
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;Services &amp;gt; Intrusion Detection&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Habilitar IDS&lt;/strong&gt;: marcar la casilla de activación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modo IPS&lt;/strong&gt;: si se quiere que bloquee tráfico, cambiar el modo a IPS. Esto requiere que Suricata funcione en modo inline, que es el comportamiento por defecto en OPNsense.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interfaces&lt;/strong&gt;: seleccionar WAN como mínimo. Si se quiere inspeccionar también tráfico interno, añadir LAN, pero esto consume bastante más CPU.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pattern matcher&lt;/strong&gt;: seleccionar &lt;strong&gt;Hyperscan&lt;/strong&gt; si el hardware lo soporta (procesadores con SSSE3). Es significativamente más rápido que el matcher por defecto.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="conjuntos-de-reglas-recomendados-en-2026"&gt;Conjuntos de reglas recomendados en 2026
&lt;/h3&gt;&lt;p&gt;No se trata de activar todas las reglas disponibles. Eso consume recursos y genera falsos positivos. Una selección razonable:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Conjunto de reglas&lt;/th&gt;
 &lt;th&gt;Para qué sirve&lt;/th&gt;
 &lt;th&gt;Recomendación&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;ET Open (Emerging Threats)&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Amenazas conocidas, malware, C2&lt;/td&gt;
 &lt;td&gt;Activar. Es la base&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Abuse.ch SSL Blacklist&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Certificados asociados a malware&lt;/td&gt;
 &lt;td&gt;Activar&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Abuse.ch URLhaus&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;URLs de distribución de malware&lt;/td&gt;
 &lt;td&gt;Activar&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;ET Open Compromised&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;IPs comprometidas conocidas&lt;/td&gt;
 &lt;td&gt;Activar&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Feodo Tracker&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Botnets bancarias&lt;/td&gt;
 &lt;td&gt;Activar&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;ET Open Tor&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Tráfico Tor&lt;/td&gt;
 &lt;td&gt;Solo si quieres bloquear Tor&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Snort VRT&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Reglas comerciales de Snort&lt;/td&gt;
 &lt;td&gt;Requiere suscripción, no imprescindible&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="buenas-prácticas-para-idsips-en-2026"&gt;Buenas prácticas para IDS/IPS en 2026
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No activar todas las reglas&lt;/strong&gt;. Seleccionar las que tengan sentido para tu entorno. Una red doméstica no necesita reglas de SCADA o de servidores SQL.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actualización automática de reglas&lt;/strong&gt;: configurar la descarga programada de reglas. En &lt;strong&gt;Schedule&lt;/strong&gt; dentro de IDS, establecer una actualización diaria.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Revisar los logs antes de pasar a modo IPS&lt;/strong&gt;. Dejar el sistema en modo IDS durante al menos una semana para identificar falsos positivos. Si algo legítimo dispara alertas, crear una excepción antes de empezar a bloquear.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitorizar el consumo de CPU&lt;/strong&gt;. Suricata puede consumir mucho en hardware modesto. Si el procesador se mantiene por encima del 80% de uso con IPS activo, reducir el número de reglas o limitar la inspección a la interfaz WAN.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Usar EVE JSON logging&lt;/strong&gt; para exportar eventos a un SIEM o a una herramienta de análisis. El formato JSON facilita la integración con Elasticsearch, Grafana o Wazuh.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No confiar únicamente en IDS/IPS&lt;/strong&gt;. Es una capa más de defensa. No sustituye unas buenas reglas de firewall, segmentación de red ni actualizaciones regulares.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="crowdsec"&gt;CrowdSec
&lt;/h2&gt;&lt;p&gt;CrowdSec complementa a Suricata con un enfoque diferente: análisis de logs y decisiones compartidas con la comunidad. Mientras que Suricata inspecciona paquetes en tiempo real, CrowdSec analiza los logs de los servicios y aplica bans basados en patrones de comportamiento.&lt;/p&gt;
&lt;h3 id="instalación-1"&gt;Instalación
&lt;/h3&gt;&lt;p&gt;CrowdSec tiene un plugin oficial para OPNsense:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ir a &lt;strong&gt;System &amp;gt; Firmware &amp;gt; Plugins&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Buscar &lt;code&gt;os-crowdsec&lt;/code&gt; e instalarlo.&lt;/li&gt;
&lt;li&gt;Tras la instalación, aparece en &lt;strong&gt;Services &amp;gt; CrowdSec&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="configuración-y-colecciones-recomendadas"&gt;Configuración y colecciones recomendadas
&lt;/h3&gt;&lt;p&gt;(Opcional) Después de la instalación, registrar la instancia en la consola central de CrowdSec:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Desde la shell de OPNsense&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli console enroll &amp;lt;tu-enrollment-key&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Las colecciones definen qué patrones de ataque detecta CrowdSec. Las recomendadas para un firewall doméstico o de pequeña oficina:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Colección base para firewalls (ya viene instalada normalmente)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli collections install crowdsecurity/freebsd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Detección de escaneo de puertos y fuerza bruta SSH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli collections install crowdsecurity/sshd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli collections install crowdsecurity/iptables
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Protección HTTP si expones servicios web&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli collections install crowdsecurity/nginx
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli collections install crowdsecurity/base-http-scenarios
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Detección de escaneos agresivos&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli collections install crowdsecurity/http-cve
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Listas de bloqueo comunitarias (IPs maliciosas conocidas)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli collections install crowdsecurity/whitelist-good-actors
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Habilitar el &lt;strong&gt;bouncer de firewall&lt;/strong&gt; para que CrowdSec pueda crear reglas de bloqueo directamente en el firewall de OPNsense:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli bouncers add opnsense-firewall
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;El token generado se introduce en la configuración del plugin en la interfaz web, en &lt;strong&gt;Services &amp;gt; CrowdSec &amp;gt; Bouncer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;La ventaja real de CrowdSec es la inteligencia compartida. Cuando un miembro de la comunidad detecta una IP atacante, esa información se distribuye a todos los demás. Es como tener un sistema de reputación de IPs colaborativo.&lt;/p&gt;
&lt;h2 id="wireguard"&gt;WireGuard
&lt;/h2&gt;&lt;p&gt;WireGuard es la opción más limpia para VPN en 2026. Más rápido, más simple y con mejor criptografía que OpenVPN o IPsec.&lt;/p&gt;
&lt;h3 id="configuración-del-servidor"&gt;Configuración del servidor
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;VPN &amp;gt; WireGuard&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Crear una instancia&lt;/strong&gt;: ir a la pestaña &lt;strong&gt;Instances&lt;/strong&gt; (o &lt;strong&gt;Local&lt;/strong&gt; en versiones anteriores) y añadir una nueva.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generar un par de claves (se hace automáticamente al crear la instancia).&lt;/li&gt;
&lt;li&gt;Puerto de escucha: &lt;code&gt;51820&lt;/code&gt; (o el que prefieras).&lt;/li&gt;
&lt;li&gt;Dirección del túnel: &lt;code&gt;10.10.10.1/24&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Añadir un peer&lt;/strong&gt;: en la pestaña &lt;strong&gt;Peers&lt;/strong&gt;, crear un nuevo par.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clave pública del cliente (se genera en el dispositivo cliente).&lt;/li&gt;
&lt;li&gt;Allowed IPs: &lt;code&gt;10.10.10.2/32&lt;/code&gt; (la IP que tendrá el cliente dentro del túnel).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Asignar la interfaz&lt;/strong&gt;: ir a &lt;strong&gt;Interfaces &amp;gt; Assignments&lt;/strong&gt;, asignar la interfaz WireGuard (&lt;code&gt;wg0&lt;/code&gt; o &lt;code&gt;wg1&lt;/code&gt;), habilitarla y no tocar la configuración de IP (ya está definida en WireGuard).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="configuración-del-cliente"&gt;Configuración del cliente
&lt;/h3&gt;&lt;p&gt;En el cliente (móvil, portátil), la configuración es un archivo sencillo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Interface]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;PrivateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;clave-privada-del-cliente&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.10.10.2/24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;DNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.10.10.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Peer]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;PublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;clave-publica-del-servidor&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;ip-publica-o-ddns&amp;gt;:51820&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;AllowedIPs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0/0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;PersistentKeepalive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;25&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Con &lt;code&gt;AllowedIPs = 0.0.0.0/0&lt;/code&gt; todo el tráfico del cliente pasa por el túnel. Si solo se quiere acceder a la red local, cambiar a &lt;code&gt;AllowedIPs = 192.168.1.0/24, 10.10.10.0/24&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="reglas-de-firewall"&gt;Reglas de firewall
&lt;/h2&gt;&lt;p&gt;De nada sirve configurar servicios si las reglas de firewall no permiten el tráfico correcto. OPNsense bloquea todo por defecto en WAN, lo cual es correcto. El trabajo está en permitir lo necesario en LAN y WireGuard.&lt;/p&gt;
&lt;h3 id="reglas-para-lan"&gt;Reglas para LAN
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;Firewall &amp;gt; Rules &amp;gt; LAN&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acción&lt;/th&gt;
 &lt;th&gt;Protocolo&lt;/th&gt;
 &lt;th&gt;Origen&lt;/th&gt;
 &lt;th&gt;Destino&lt;/th&gt;
 &lt;th&gt;Puerto destino&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;IPv4+6&lt;/td&gt;
 &lt;td&gt;LAN net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Permitir salida LAN&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;IPv4&lt;/td&gt;
 &lt;td&gt;LAN net&lt;/td&gt;
 &lt;td&gt;LAN address&lt;/td&gt;
 &lt;td&gt;53&lt;/td&gt;
 &lt;td&gt;DNS al firewall&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;IPv4&lt;/td&gt;
 &lt;td&gt;LAN net&lt;/td&gt;
 &lt;td&gt;LAN address&lt;/td&gt;
 &lt;td&gt;443&lt;/td&gt;
 &lt;td&gt;Acceso WebUI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La primera regla es la más permisiva y permite que la LAN salga a internet. En el segundo post de la serie veremos cómo restringir esto con VLANs.&lt;/p&gt;
&lt;h3 id="reglas-para-wireguard"&gt;Reglas para WireGuard
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;Firewall &amp;gt; Rules &amp;gt; WireGuard&lt;/strong&gt; (o la interfaz asignada):&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acción&lt;/th&gt;
 &lt;th&gt;Protocolo&lt;/th&gt;
 &lt;th&gt;Origen&lt;/th&gt;
 &lt;th&gt;Destino&lt;/th&gt;
 &lt;th&gt;Puerto destino&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;IPv4&lt;/td&gt;
 &lt;td&gt;WireGuard net&lt;/td&gt;
 &lt;td&gt;LAN net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Acceso a LAN desde VPN&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;IPv4&lt;/td&gt;
 &lt;td&gt;WireGuard net&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;Salida a internet desde VPN&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="regla-en-wan-para-wireguard"&gt;Regla en WAN para WireGuard
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;Firewall &amp;gt; Rules &amp;gt; WAN&lt;/strong&gt;, añadir una regla para permitir la conexión entrante al puerto de WireGuard:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acción&lt;/th&gt;
 &lt;th&gt;Protocolo&lt;/th&gt;
 &lt;th&gt;Origen&lt;/th&gt;
 &lt;th&gt;Destino&lt;/th&gt;
 &lt;th&gt;Puerto destino&lt;/th&gt;
 &lt;th&gt;Descripción&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;UDP&lt;/td&gt;
 &lt;td&gt;*&lt;/td&gt;
 &lt;td&gt;WAN address&lt;/td&gt;
 &lt;td&gt;51820&lt;/td&gt;
 &lt;td&gt;WireGuard entrante&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="offloading-y-tunning-de-hardware"&gt;Offloading y tunning de hardware
&lt;/h2&gt;&lt;p&gt;Cuando todo está funcionando, llega el momento de exprimir el rendimiento. OPNsense corre sobre FreeBSD, y tiene varias opciones de offloading que pueden marcar una diferencia notable.&lt;/p&gt;
&lt;h3 id="qué-es-el-offloading"&gt;Qué es el offloading
&lt;/h3&gt;&lt;p&gt;Offloading significa delegar ciertas operaciones de red al hardware de la tarjeta de red en lugar de procesarlas por software en la CPU. Esto libera ciclos de CPU para otras tareas (como Suricata o CrowdSec) y reduce la latencia.&lt;/p&gt;
&lt;h3 id="opciones-disponibles"&gt;Opciones disponibles
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;Interfaces &amp;gt; Settings&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hardware CRC (Checksum Offloading)&lt;/strong&gt;: delega el cálculo de checksums TCP/UDP/IP a la tarjeta de red. Activar si la NIC lo soporta (las Intel i210/i225/i226 sí). Reduce carga de CPU de forma medible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware TSO (TCP Segmentation Offloading)&lt;/strong&gt;: la tarjeta de red se encarga de dividir los paquetes TCP grandes en segmentos más pequeños. Mejora el throughput en transferencias grandes. Puede causar problemas con algunas configuraciones de IPS, así que probar y verificar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware LRO (Large Receive Offloading)&lt;/strong&gt;: agrupa paquetes pequeños entrantes en bloques más grandes antes de pasarlos a la CPU. Reduce interrupciones. &lt;strong&gt;No activar si se usa IPS en modo inline&lt;/strong&gt;, ya que interfiere con la inspección de paquetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VLAN Hardware Filtering&lt;/strong&gt;: si se usan VLANs (lo veremos en el segundo post), dejar que la NIC filtre por VLAN ID en hardware.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tunning-adicional-del-sistema"&gt;Tunning adicional del sistema
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;System &amp;gt; Settings &amp;gt; Tunables&lt;/strong&gt;, algunos ajustes útiles:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Aumentar los buffers de red
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;net.inet.tcp.recvspace=65536
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;net.inet.tcp.sendspace=65536
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Habilitar RACK y BBR si el hardware lo soporta (FreeBSD 14+)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;net.inet.tcp.functions_default=bbr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Ajustar el número de colas de la NIC
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hw.igb.num_queues=4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;No hay que obsesionarse con el tunning. En la mayoría de conexiones domésticas (hasta 1 Gbps simétrico), un N100 con las opciones por defecto ya da el rendimiento máximo. El tunning empieza a ser relevante cuando se quiere exprimir conexiones de 2.5 Gbps o más, o cuando Suricata consume demasiada CPU.&lt;/p&gt;
&lt;h2 id="gestión-de-usuarios-y-seguridad-de-la-interfaz-web"&gt;Gestión de usuarios y seguridad de la interfaz web
&lt;/h2&gt;&lt;p&gt;Usar &lt;code&gt;root&lt;/code&gt; para el día a día en la interfaz web es una mala práctica. Si alguien compromete esas credenciales, tiene control total.&lt;/p&gt;
&lt;h3 id="crear-un-nuevo-usuario-administrador"&gt;Crear un nuevo usuario administrador
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;System &amp;gt; Access &amp;gt; Users&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Crear un nuevo usuario con nombre descriptivo (no &lt;code&gt;admin&lt;/code&gt;, algo menos predecible).&lt;/li&gt;
&lt;li&gt;Asignar una contraseña fuerte. Mínimo 16 caracteres, generada con un gestor de contraseñas.&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;Effective Privileges&lt;/strong&gt;, asignar el grupo &lt;code&gt;admins&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Activar la autenticación OTP si es posible. OPNsense soporta TOTP nativo, así que se puede usar con cualquier app de autenticación.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="cambiar-la-contraseña-de-root"&gt;Cambiar la contraseña de root
&lt;/h3&gt;&lt;p&gt;En &lt;strong&gt;System &amp;gt; Access &amp;gt; Users&lt;/strong&gt;, seleccionar &lt;code&gt;root&lt;/code&gt; y cambiar la contraseña a algo largo y aleatorio. Guardar esta contraseña en un lugar seguro (gestor de contraseñas) y no usarla para el acceso diario.&lt;/p&gt;
&lt;p&gt;Otra opción más radical: deshabilitar el login de root en la interfaz web. Esto se puede hacer quitando los privilegios de acceso web al usuario root, dejando solo el acceso por consola física como último recurso.&lt;/p&gt;
&lt;h3 id="restringir-el-acceso-a-la-interfaz-web"&gt;Restringir el acceso a la interfaz web
&lt;/h3&gt;&lt;p&gt;Por defecto, la interfaz web es accesible desde toda la LAN. Esto se puede restringir:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cambiar el puerto HTTPS&lt;/strong&gt;: en &lt;strong&gt;System &amp;gt; Settings &amp;gt; Administration&lt;/strong&gt;, cambiar el puerto de &lt;code&gt;443&lt;/code&gt; a otro no estándar (por ejemplo, &lt;code&gt;8443&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restringir por IP de origen&lt;/strong&gt;: crear un alias en &lt;strong&gt;Firewall &amp;gt; Aliases&lt;/strong&gt; con las IPs desde las que se permite el acceso a la interfaz web. Luego, en las reglas de firewall de la LAN, crear una regla que permita el acceso al puerto de la WebUI solo desde ese alias, y una regla de bloqueo para el resto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Habilitar HTTPS con un certificado propio&lt;/strong&gt;: en &lt;strong&gt;System &amp;gt; Trust &amp;gt; Certificates&lt;/strong&gt;, generar un certificado autofirmado o importar uno de Let&amp;rsquo;s Encrypt. Esto elimina las advertencias del navegador y asegura que la conexión está cifrada correctamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protección contra fuerza bruta&lt;/strong&gt;: en &lt;strong&gt;System &amp;gt; Settings &amp;gt; Administration&lt;/strong&gt;, configurar el número máximo de intentos fallidos y el tiempo de bloqueo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deshabilitar HTTP&lt;/strong&gt;: asegurarse de que solo HTTPS está habilitado. El acceso HTTP sin cifrar a la interfaz de administración de un firewall no tiene sentido.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;En el siguiente post de la serie veremos cómo llevar la seguridad más lejos con backups cifrados, VLANs, Zenarmor y hardening del sistema.&lt;/p&gt;</description></item><item><title>Montando un repositorio de IaC para el homelab</title><link>https://adurrr.github.io/p/montando-un-repositorio-de-iac-para-el-homelab/</link><pubDate>Mon, 20 Nov 2023 00:00:00 +0000</pubDate><guid>https://adurrr.github.io/p/montando-un-repositorio-de-iac-para-el-homelab/</guid><description>&lt;h2 id="el-detonante"&gt;El detonante
&lt;/h2&gt;&lt;p&gt;Hace un par de meses estuve bastionando el cluster K3s. Pasé un fin de semana entero cambiando configuraciones, ajustando parámetros del kernel, instalando Cilium en lugar de Flannel, escribiendo políticas de red. Al final el cluster quedó bastante mejor de lo que estaba.&lt;/p&gt;
&lt;p&gt;Pero lo había hecho todo a mano.&lt;/p&gt;
&lt;p&gt;Si mañana tengo que recrear ese nodo desde cero, ¿cuánto tardo? Probablemente dos o tres días buscando en mis propias notas dispersas entre archivos de texto, el historial del terminal y comentarios en el chat. Y aun así me dejaría cosas. Eso no es sostenible.&lt;/p&gt;
&lt;p&gt;Así que decidí construir un repositorio de infraestructura real. No una demo, no una prueba de concepto: el repositorio donde vive la definición de todo lo que corro en casa, sanitizado para poder publicarlo.&lt;/p&gt;
&lt;h2 id="qué-hay-en-el-homelab"&gt;Qué hay en el homelab
&lt;/h2&gt;&lt;p&gt;Antes de hablar de la estructura del repositorio, conviene explicar lo que hay que gestionar. El setup es:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un servidor principal con Proxmox donde corren varias VMs&lt;/li&gt;
&lt;li&gt;Dos nodos físicos adicionales que forman el cluster K3s&lt;/li&gt;
&lt;li&gt;Un router con OpenWrt&lt;/li&gt;
&lt;li&gt;Un NAS con TrueNAS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No es un entorno enorme, pero tiene suficiente variedad como para que gestionar todo a mano sea un problema real. Especialmente porque Proxmox, las VMs, el cluster y el NAS tienen configuraciones que interactúan entre sí: IPs, DNS interno, certificados, usuarios.&lt;/p&gt;
&lt;h2 id="estructura-del-repositorio"&gt;Estructura del repositorio
&lt;/h2&gt;&lt;p&gt;Después de unas pruebas, esta es la organización que me funciona:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;homelab&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;infra&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;proxmox&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;dns&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;vlan&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;environments&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tfvars&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;group_vars&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;playbooks&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;bootstrap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;k3s&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;k3s&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;hardening&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;roles&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;cis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;level1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;k3s&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;kubernetes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;monitoring&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;networking&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;policies&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;gatekeeper&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;seccomp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ci&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;README&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Tres capas bien separadas: provisioning (Terraform), configuración de nodos (Ansible) y workloads de Kubernetes. Las políticas de seguridad viven dentro de &lt;code&gt;kubernetes/policies/&lt;/code&gt; porque son recursos de Kubernetes, pero conceptualmente las considero una capa aparte.&lt;/p&gt;
&lt;h2 id="terraform-provisioning-con-proxmox"&gt;Terraform: provisioning con Proxmox
&lt;/h2&gt;&lt;p&gt;El proveedor de Proxmox para Terraform es el de Telmate (&lt;code&gt;telmate/proxmox&lt;/code&gt;). No es oficial, pero es el más usado y funciona razonablemente bien.&lt;/p&gt;
&lt;p&gt;El módulo &lt;code&gt;proxmox-vm&lt;/code&gt; encapsula la creación de VMs con los parámetros que uso habitualmente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# terraform/modules/proxmox-vm/main.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_vm_qemu&amp;#34; &amp;#34;vm&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; target_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;target_node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; full_clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;cores&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;memory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; sockets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;disk&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;disk_size&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;virtio&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;storage_pool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; discard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;network&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;virtio&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; bridge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;network_bridge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;vlan_tag&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ipconfig0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt; &amp;#34;ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;${var.ip_address}/24,gw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="err"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;gateway&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; nameserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;nameserver&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; searchdomain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;searchdomain&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ciuser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ssh_user&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; sshkeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ssh_public_key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;lifecycle&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ignore_changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;network&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Las variables sensibles — la contraseña de la API de Proxmox, las claves SSH — no están en el repositorio. Uso SOPS para cifrar el fichero &lt;code&gt;terraform.tfvars&lt;/code&gt; con age:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# .sops.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;creation_rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path_regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.*\.tfvars$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path_regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ansible/inventory/group_vars/.*\.yml$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;El fichero cifrado (&lt;code&gt;terraform.tfvars&lt;/code&gt;) sí está en Git. Descifrarlo requiere la clave privada de age, que está en el servidor de CI y en mi máquina local, nunca en el repositorio.&lt;/p&gt;
&lt;h2 id="ansible-configuración-de-nodos"&gt;Ansible: configuración de nodos
&lt;/h2&gt;&lt;p&gt;Una vez que Terraform provisiona las VMs, Ansible se encarga de configurarlas. El playbook &lt;code&gt;bootstrap.yml&lt;/code&gt; hace lo mínimo necesario para que un nodo recién creado esté en condiciones:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ansible/playbooks/bootstrap.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Bootstrap new nodes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;all&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;become&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;common&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;cis-level1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Configure K3s server nodes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;k3s_servers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;become&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;k3s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;El rol &lt;code&gt;common&lt;/code&gt; instala los paquetes base, configura NTP, ajusta SSH, establece los parámetros de sysctl y crea los usuarios del sistema. El rol &lt;code&gt;cis-level1&lt;/code&gt; aplica las recomendaciones del CIS Benchmark Level 1 para Debian.&lt;/p&gt;
&lt;p&gt;El rol CIS no lo escribí desde cero. Partí del rol de la comunidad &lt;code&gt;dev-sec/ansible-collection-hardening&lt;/code&gt; y lo adapté. Hay bastantes tareas que el rol por defecto hace que no encajan en un homelab — cosas pensadas para servidores de producción con requisitos de auditoría muy estrictos. Lo que hice fue revisar cada tarea, entender qué hacía y decidir si aplicaba a mi caso.&lt;/p&gt;
&lt;p&gt;Algunas cosas que desactivé:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ansible/roles/cis-level1/defaults/main.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;os_auth_pam_pwquality_enable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# No tengo usuarios locales con contraseña&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;os_security_users_allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;vagrant&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# En el entorno de dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;os_filesystem_whitelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;vfat &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Necesario para arranque UEFI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Y algunas que añadí específicamente para K3s:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Parámetros kernel requeridos por K3s con protect-kernel-defaults&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kernel_parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- {&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;kernel.panic&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;, value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- {&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;kernel.panic_on_oops&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;, value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- {&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;vm.overcommit_memory&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;, value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- {&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;vm.panic_on_oom&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;, value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- {&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fs.inotify.max_user_watches&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;, value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;524288&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- {&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fs.inotify.max_user_instances&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;, value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;512&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="kubernetes-manifiestos-con-kustomize"&gt;Kubernetes: manifiestos con Kustomize
&lt;/h2&gt;&lt;p&gt;Para los manifiestos de Kubernetes uso Kustomize en lugar de Helm cuando puedo. Helm es más potente para cosas complejas, pero para mis propias aplicaciones Kustomize es suficiente y produce YAML legible.&lt;/p&gt;
&lt;p&gt;La estructura básica con Kustomize:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubernetes/apps/monitoring/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── base/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── kustomization.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── namespace.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── prometheus-deployment.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── grafana-deployment.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── overlays/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── homelab/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── kustomization.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── patches/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── resource-limits.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;El overlay &lt;code&gt;homelab&lt;/code&gt; añade los ajustes específicos de mi entorno sin modificar los manifiestos base:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# kubernetes/apps/monitoring/overlays/homelab/patches/resource-limits.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;prometheus&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;monitoring&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;prometheus&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;256Mi&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;100m&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;512Mi&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;500m&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="opagatekeeper-políticas-como-código"&gt;OPA/Gatekeeper: políticas como código
&lt;/h2&gt;&lt;p&gt;Gatekeeper es un admission controller para Kubernetes que usa OPA (Open Policy Agent) para evaluar políticas escritas en Rego. En lugar de dejar que cualquier pod se despliegue con cualquier configuración, las políticas rechazan los manifiestos que no cumplen los requisitos de seguridad.&lt;/p&gt;
&lt;p&gt;Las políticas que tengo activas:&lt;/p&gt;
&lt;h3 id="no-contenedores-como-root"&gt;No contenedores como root
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# kubernetes/policies/gatekeeper/no-root-containers.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;constraints.gatekeeper.sh/v1beta1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;K8sPSPAllowedUsers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;psp-pods-allowed-user-ranges&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kinds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kinds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Pod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;excludedNamespaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;kube-system&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;falco&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MustRunAsNonRoot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MustRunAs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ranges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65535&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="límites-de-recursos-obligatorios"&gt;Límites de recursos obligatorios
&lt;/h3&gt;&lt;p&gt;Sin límites de recursos, un pod puede consumir toda la memoria del nodo y tumbar el cluster. Esta política lo impide:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# kubernetes/policies/gatekeeper/require-resource-limits.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;templates.gatekeeper.sh/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ConstraintTemplate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;k8srequiredresources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;crd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;K8sRequiredResources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;admission.k8s.gatekeeper.sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rego&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; package k8srequiredresources
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; violation[{&amp;#34;msg&amp;#34;: msg}] {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; container := input.review.object.spec.containers[_]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; not container.resources.limits.memory
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; msg := sprintf(&amp;#34;El contenedor &amp;#39;%v&amp;#39; no tiene límite de memoria definido&amp;#34;, [container.name])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; violation[{&amp;#34;msg&amp;#34;: msg}] {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; container := input.review.object.spec.containers[_]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; not container.resources.limits.cpu
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; msg := sprintf(&amp;#34;El contenedor &amp;#39;%v&amp;#39; no tiene límite de CPU definido&amp;#34;, [container.name])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;constraints.gatekeeper.sh/v1beta1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;K8sRequiredResources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;require-resource-limits&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kinds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kinds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Pod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;excludedNamespaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;kube-system&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="registro-de-imágenes-de-confianza"&gt;Registro de imágenes de confianza
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# kubernetes/policies/gatekeeper/allowed-registries.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;templates.gatekeeper.sh/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ConstraintTemplate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;k8sallowedrepos&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;crd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;K8sAllowedRepos&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;openAPIV3Schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;array&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;admission.k8s.gatekeeper.sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rego&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; package k8sallowedrepos
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; violation[{&amp;#34;msg&amp;#34;: msg}] {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; container := input.review.object.spec.containers[_]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; not any_repo_matches(container.image)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; msg := sprintf(&amp;#34;Imagen &amp;#39;%v&amp;#39; no proviene de un registro autorizado&amp;#34;, [container.image])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; any_repo_matches(image) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; repo := input.parameters.repos[_]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; startswith(image, repo)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;constraints.gatekeeper.sh/v1beta1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;K8sAllowedRepos&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;allowed-registries&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kinds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kinds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Pod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;excludedNamespaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;kube-system&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;registry.homelab.internal/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;ghcr.io/mi-usuario/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;quay.io/prometheus/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;grafana/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="perfiles-seccomp"&gt;Perfiles seccomp
&lt;/h2&gt;&lt;p&gt;Los perfiles seccomp limitan las llamadas al sistema que un contenedor puede realizar. Kubernetes tiene un perfil por defecto (&lt;code&gt;RuntimeDefault&lt;/code&gt;) que ya es razonable, pero para aplicaciones que conozco bien defino perfiles más restrictivos.&lt;/p&gt;
&lt;p&gt;Los perfiles viven en el repositorio y se despliegan como ConfigMaps o directamente en los nodos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// kubernetes/policies/seccomp/web-app-profile.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;defaultAction&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SCMP_ACT_ERRNO&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;architectures&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SCMP_ARCH_X86_64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;syscalls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;names&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;accept4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bind&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;brk&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;clone&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;close&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;connect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;epoll_create1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;epoll_ctl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;epoll_wait&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;execve&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;exit_group&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fcntl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fstat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;futex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;getdents64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;getpid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;getsockname&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;getsockopt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;listen&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lstat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;mmap&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;mprotect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;munmap&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nanosleep&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;newfstatat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;openat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;poll&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;prctl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;read&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;recvfrom&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rt_sigaction&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;rt_sigprocmask&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rt_sigreturn&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sendto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;set_robust_list&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;setsockopt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sigaltstack&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;socket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SCMP_ACT_ALLOW&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Y se referencia desde el pod:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;seccompProfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Localhost&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;localhostProfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;web-app-profile.json&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Construir un perfil seccomp desde cero es tedioso. Lo que hago es arrancar primero con &lt;code&gt;RuntimeDefault&lt;/code&gt;, usar &lt;code&gt;strace&lt;/code&gt; para ver qué syscalls hace la aplicación, y luego elaborar un perfil más ajustado para las aplicaciones que quiero restringir más.&lt;/p&gt;
&lt;h2 id="pipeline-de-cicd"&gt;Pipeline de CI/CD
&lt;/h2&gt;&lt;p&gt;El repositorio tiene un pipeline de GitLab CI que automatiza la aplicación de los cambios. El flujo es:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;En merge requests: &lt;code&gt;terraform plan&lt;/code&gt; y &lt;code&gt;ansible-lint&lt;/code&gt; para detectar problemas antes de mergear&lt;/li&gt;
&lt;li&gt;Al mergear a &lt;code&gt;main&lt;/code&gt;: &lt;code&gt;terraform apply&lt;/code&gt; y, si hay cambios en Ansible, el playbook correspondiente&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;span class="lnt"&gt;54
&lt;/span&gt;&lt;span class="lnt"&gt;55
&lt;/span&gt;&lt;span class="lnt"&gt;56
&lt;/span&gt;&lt;span class="lnt"&gt;57
&lt;/span&gt;&lt;span class="lnt"&gt;58
&lt;/span&gt;&lt;span class="lnt"&gt;59
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# .gitlab-ci.yml (fragmento)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;validate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;plan&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;apply&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TF_ROOT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${CI_PROJECT_DIR}/terraform/environments&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ANSIBLE_CONFIG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${CI_PROJECT_DIR}/ansible/ansible.cfg&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;terraform-validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;validate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hashicorp/terraform:1.6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;cd &amp;#34;$TF_ROOT&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform init -backend=false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform validate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform/**/*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;terraform-plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;plan&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hashicorp/terraform:1.6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;cd &amp;#34;$TF_ROOT&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform init&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform plan -out=tfplan&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;artifacts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;${TF_ROOT}/tfplan&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;expire_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;week&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;$CI_PIPELINE_SOURCE == &amp;#34;merge_request_event&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform/**/*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;terraform-apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apply&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hashicorp/terraform:1.6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;cd &amp;#34;$TF_ROOT&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform init&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform apply -auto-approve&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;terraform/**/*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;manual&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;ansible-lint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;validate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;pip install ansible ansible-lint&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ansible-lint ansible/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ansible/**/*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;El &lt;code&gt;terraform apply&lt;/code&gt; es manual — no quiero que la infraestructura cambie automáticamente sin que yo lo apruebe. El plan se ejecuta automáticamente en el MR para tener visibilidad.&lt;/p&gt;
&lt;h2 id="lo-que-saniticé"&gt;Lo que saniticé
&lt;/h2&gt;&lt;p&gt;Publicar el repositorio requirió revisar qué no debía estar ahí:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IPs internas&lt;/strong&gt;: reemplazadas por rangos de ejemplo (&lt;code&gt;192.168.1.x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nombres de dominio&lt;/strong&gt;: el dominio interno del homelab (&lt;code&gt;homelab.internal&lt;/code&gt; en el repo, algo diferente en producción)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Usuarios&lt;/strong&gt;: los nombres de usuario reales no están en el repo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claves públicas SSH&lt;/strong&gt;: sustituidas por placeholders&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hashes de contraseñas&lt;/strong&gt;: eliminados del inventario de Ansible&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secretos de aplicaciones&lt;/strong&gt;: cifrados con SOPS o eliminados, con un &lt;code&gt;.example&lt;/code&gt; en su lugar&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La regla que seguí: si alguien con acceso a mi red local pudiera usar esa información para atacar algo, no va al repositorio en claro. Todo lo demás puede estar.&lt;/p&gt;
&lt;h2 id="lo-que-quedó-pendiente"&gt;Lo que quedó pendiente
&lt;/h2&gt;&lt;p&gt;Todavía hay cosas que gestiono a mano que debería codificar:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OpenWrt&lt;/strong&gt;: la configuración del router es la más difícil de meter en IaC. Hay un módulo de Terraform para OpenWrt que no está muy mantenido. Por ahora lo gestiono con un script de Ansible que hace backup de la configuración y otro que la restaura. No es idempotente, pero funciona.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TrueNAS&lt;/strong&gt;: tiene una API REST bastante completa. Hay un proveedor de Terraform en desarrollo. Lo tengo en el radar para la siguiente iteración.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backups&lt;/strong&gt;: tengo backups, pero el proceso no está codificado en el repositorio. Está en otro script suelto que algún día llegará aquí.&lt;/p&gt;
&lt;p&gt;El repositorio nunca está &amp;ldquo;terminado&amp;rdquo;. Lo que importa es que el estado actual del homelab esté representado en él, y que cualquier cambio pase por Git.&lt;/p&gt;</description></item></channel></rss>