Contactar →
← Todas las entradas
Seguridad2026.06.24

Denegación por defecto, coste real

Cada segmento de red de esta instalación deniega por defecto el tráfico que cruza hacia otro segmento. Nada se enruta de una VLAN a otra a menos que exista una regla explícita que nombre el origen, el destino y el puerto. Es una recomendación normal (la denegación por defecto es la respuesta de manual sobre cómo deberían tratarse los segmentos entre sí) y no voy a fingir que he descubierto algo nuevo por aplicarla. Lo que quiero dejar escrito con honestidad es lo que realmente cuesta en el día a día, porque el marco del «respuesta de manual» tiende a saltarse esa parte, y ya llevo suficiente tiempo conviviendo con esta decisión como para saber dónde muerde.

Lo que se obtiene

La ventaja es genuinamente sencilla y no quiero restarle mérito antes de pasar a las quejas: nada cruza un límite de confianza en silencio. Si un servicio de un segmento empieza a intentar hablar con algo de otro segmento (porque añadí una nueva integración, porque un contenedor recibió una variable de entorno nueva que apunta a donde no debería, porque algo está haciendo reconocimiento tras un compromiso), simplemente no funciona. No es «funciona hasta que alguien se da cuenta», no es «funciona porque una regla comodín de hace tres años resulta que lo cubre». Falla, de inmediato, de forma visible, hasta que existe una regla que dice explícitamente lo contrario. Es una capa dentro de un modelo de alcanzabilidad más amplio: consulta Seis capas de accesibilidad para ver cómo encaja una regla de firewall junto al enrutamiento, el DNS y la existencia del túnel, como una de seis cosas independientes que todas tienen que superarse antes de que una petición llegue a alguna parte.

Esa propiedad se acumula con el tiempo de una forma fácil de infravalorar. Una red construida sobre permitir por defecto (o sobre reglas tan amplias que son, en la práctica, permitir por defecto) acumula rutas invisibles: conexiones que funcionan porque nada las bloquea, no porque alguien decidiera que debían existir. Nadie recuerda por qué la mitad de ellas son posibles. La denegación por defecto invierte eso: toda ruta que existe, existe porque alguien la puso ahí a propósito. Cuando miro mis reglas de firewall, estoy mirando un mapa real de las relaciones de confianza pretendidas, no una excavación arqueológica de lo que resultó seguir abierto.

Lo que realmente cuesta

Esta es la parte en la que quiero ser honesto, porque buena parte de lo que se escribe sobre este tema se queda en «es más seguro» y trata el intercambio como algo obviamente rentable sin describir en qué consiste ese intercambio.

Cada integración nueva es una interrupción deliberada y visible de lo que estás intentando hacer. Supongamos que levanto un servicio nuevo en un segmento y necesita hablar con una base de datos que vive en otro. En una red plana o de permitir por defecto, eso son cero pasos extra: la conexión funciona o hay un bug en el código. Aquí es un cambio de firewall: averiguar en qué segmento ha acabado realmente el servicio nuevo, averiguar en qué segmento está la base de datos, añadir una regla para ese origen, destino y puerto concretos, aplicarla, y solo entonces empieza a funcionar lo que de verdad estaba construyendo. No es una cantidad enorme de trabajo en sí misma, pero es un impuesto real sobre el impulso de avance. Cuando estoy en mitad de levantar algo y me topo con una conexión rechazada, la reacción honesta en ese momento es una ligera irritación, no gratitud hacia el modelo de seguridad.

Es fácil olvidar que la regla existe hasta que algo se rompe. El modo de fallo no es «no sé cómo añadir una regla». Es que añadir la regla es un paso separado del paso en el que realmente estaba pensando, así que es exactamente el tipo de cosa que se cae de la memoria de trabajo. Más de una vez he perdido tiempo con un servicio que «no funciona» sin motivo aparente, he perseguido logs y configuraciones durante un rato, y al final he recordado que el problema real era un límite de segmento que yo mismo había creado y luego olvidado cruzar. La denegación por defecto no solo cuesta tiempo cuando añades la regla: vuelve a costarte tiempo después, cuando olvidas que la necesitabas.

Hay una tentación real de ampliar de más una regla solo para que funcione. Esta es, creo, la que más importa, porque es donde la propiedad de seguridad puede erosionarse silenciosamente desde dentro. Cuando algo no conecta y estoy intentando que funcione, el camino rápido es: permitir todo el segmento en lugar de un único host, permitir un rango de puertos en lugar del que el servicio realmente necesita, permitirlo en ambos sentidos en lugar de en uno solo. Cada uno de esos atajos hace que el problema inmediato desaparezca más rápido que averiguar la regla precisa. Y cada uno de esos atajos es exactamente el tipo de expansión de alcance que convierte una allow-list deliberadamente estrecha en algo que solo es denegación por defecto de nombre. Me he sorprendido a mí mismo haciendo esto: escribiendo una regla más amplia de lo necesario porque quería que aquello funcionara ya y me decía a mí mismo que luego la ajustaría. A veces vuelvo y la ajusto. A veces la regla más amplia sigue ahí hoy porque nada se ha roto como consecuencia de que sea amplia, que es exactamente el tipo de deriva silenciosa y cómoda que se supone que la denegación por defecto debe evitar.

No escala gratis con el número de segmentos. El número de reglas crece más rápido que el número de segmentos a medida que más cosas necesitan hablar con más cosas. Dos segmentos es una posible regla. Cinco segmentos donde la mayoría de los pares necesita algo de tráfico legítimo ya es una matriz real que hay que llevar controlada, y cada segmento nuevo que añado es una fuente de contabilidad más o menos cuadrática, no lineal. A la escala a la que llevo esto (un único homelab, una sola persona manteniéndolo) es manejable. Es un coste que se agudizaría, no que se suavizaría, si esto llegara a crecer más allá de una escala en la que una sola persona pueda tener todo el conjunto de reglas en la cabeza.

Por qué la mantengo de todos modos

Nada de eso es motivo para abandonar la denegación por defecto: es motivo para ser honesto en que «más seguro» y «más fricción» son la misma decisión, no dos decisiones separadas de las que puedas quedarte con una y descartar la otra. La fricción es el mecanismo, no un efecto secundario de él. La razón por la que una integración nueva no puede simplemente «funcionar por accidente» es exactamente la misma razón por la que hace falta un paso deliberado para que funcione a propósito. No puedes tener «nada cruza en silencio» sin tener también «todo requiere un acto explícito», y el acto explícito es, por definición, más molesto en el momento que no tener que pensar en ello.

En lo que me he asentado es en intentar que el propio paso deliberado sea lo más barato y lo más estrecho posible, en lugar de intentar evitar pagarlo. Cuando añado una regla, anoto para qué sirve en el sistema de seguimiento de cambios que esté usando, para que mi yo futuro persiguiendo la pregunta «por qué funciona esto» tenga una respuesta en vez de un proyecto arqueológico. Intento por defecto la versión más estrecha de una regla que consiga hacer funcionar, y trato el «luego la ajusto» como una deuda que de verdad vuelvo a pagar, no como una excusa permanente. Ninguna de las dos cosas cierra del todo la brecha entre lo ideal (cada regla perfectamente acotada, siempre) y la realidad (una persona con algo de presión de tiempo tomando de vez en cuando el atajo más amplio). Pero la alternativa (abandonar la postura de denegación por defecto porque mantenerla correctamente es trabajo de verdad) cambia un coste conocido y acotado por uno desconocido y sin acotar. Prefiero seguir pagando el peaje que puedo ver.

Lo que realmente me empujó a mantenerla

El momento que hizo que esto se afianzara no fue un incidente de seguridad (no he tenido ninguno que esta política haya frenado específicamente, y estaría inventándome una batallita si dijera lo contrario). Fue algo más pequeño y más mundano: en un momento dado me puse a investigar por qué un job en segundo plano de un segmento podía llegar silenciosamente a un servicio de otro segmento que no tenía nada que ver con él, y la respuesta honesta fue que la regla que lo permitía se había escrito meses antes por un motivo puntual completamente distinto, se había dejado ahí después de que ese motivo dejara de aplicarse, y nadie, es decir, yo, en otro momento, había vuelto a eliminarla. Esa regla no estaba haciendo nada malicioso. Simplemente era más amplia de lo necesario, ahí sentada como una ruta latente que había sobrevivido a su propia justificación. En una red más plana, ese tipo de ruta sobrante es invisible, porque no existe el equivalente de «una regla que ya no debería estar ahí»: solo hay conectividad, punto, sin ningún registro de si alguien pretendió que existiera. La denegación por defecto, como mínimo, hace visible ese resto como una línea concreta en un conjunto de reglas concreto, algo que puedo mirar, cuestionar y eliminar, en lugar de una suposición cocinada en silencio dentro de «cómo se comporta la red por costumbre».

Es una victoria más pequeña que «esto frenó a un atacante», pero es la más honesta. Lo que la denegación por defecto principalmente me compra no es una prevención dramática de una brecha hipotética: es un registro permanente e inspeccionable de cada cruce intencionado entre segmentos, que convierte «por qué esto puede llegar a aquello» de una investigación abierta en la simple lectura de una regla y su comentario. La fricción descrita antes es real y la pago con regularidad. El beneficio es igual de real, y se manifiesta menos como un único rescate dramático y más como el hecho de que cada auditoría futura sea leer una lista de reglas en vez de adivinar.

Una revisión periódica en lugar de una configuración de una sola vez

Como la presión hacia la ampliación excesiva es constante y no un riesgo puntual, he empezado a tratar el propio conjunto de reglas como algo que revisar con cierta periodicidad, no como algo que se hace bien una vez y se deja estar. De vez en cuando repaso la allow-list existente y me pregunto, regla por regla, si sigue estando acotada tan estrechamente como podría estarlo y si la integración para la que existe es algo que de verdad sigo ejecutando. Las reglas para servicios que desde entonces he dado de baja son el tipo de deriva más fácil de pasar por alto, porque nada se rompe cuando una regla obsoleta simplemente se queda ahí sin usarse: no hay ningún error que notar, ningún aviso de permiso caducado, solo una superficie de ataque ligeramente más amplia de lo que exige el conjunto real y actual de integraciones. Esa revisión no es un trabajo glamuroso y es tentador saltársela a favor de cualquier cosa que se sienta más como avance, pero es lo más parecido que tengo a una comprobación frente al modo de fallo concreto descrito antes: una regla que sobrevive a la razón por la que se escribió, en silencio, porque nada obligó a nadie a volver a mirarla.

← Todas las entradas