gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 188Rust 55Shell 30Markdown 281211 Definiciones

el cortafuegos puesto en la caja — y el primer baneado fui yo

`table inet tawasuyu_input` con policy drop, 15 reglas de puerto, y el set `ban4` llenandose solo:
a los pocos minutos, 27 IPs baneadas y paquetes tirados por el contador. El reemplazo de fail2ban
funcionando sobre trafico real, dentro del kernel y sin daemon.

🧨 EL PRIMER BANEADO FUI YO, EN SEGUNDOS. Aplicada la primera version: ssh y https bien, y `http`,
proxy y git-ssh muertos; al minuto la caja entera dejo de contestarme. La causa no es una regla mal
escrita: **el set @ban4 es UNO SOLO para todos los servicios** y `ip saddr @ban4 drop` esta antes
que todo, asi que pasarse de tasa en UN puerto te tira en TODOS — y el `nuevas_por_min: 5` del
puerto de administracion es exactamente lo que me pasa a mi, porque cada comando remoto es una
conexion nueva. Lo devolvio el INTERRUPTOR DE HOMBRE MUERTO (un `nft flush ruleset` programado a
180 s que sólo se desarma si la verificacion externa sale bien). Sin consola serie, esa es la
diferencia entre un susto y un rescue.

⚠ Y el tipo ya lo avisaba: `PoliticaEntrada` tiene un campo `confiables` cuyo comentario describe
palabra por palabra lo que me paso. Copie el .ron de EJEMPLO en vez de leer el TIPO. La cura fue una
linea con el hub y el worker, que se aceptan ANTES de la logica de tasa.

⚠ `nft -c` rechazo 5 reglas antes de aplicar nada: `ct count over N` es CONFIG_NFT_CONNLIMIT y este
kernel no lo trae. Quedan COMENTADAS y no borradas en el fichero que se aplica — lo que la politica
declara y el kernel no puede tiene que verse. El simbolo ya entro a la receta del kernel.

Y para que sobreviva al reinicio, `nftables` declara su [[service]] con `lifecycle = "oneshot"`:
cargar un reglaset no es un daemon, es un acto. Va PRIMERO en el genesis — entre que la red esta y
que las reglas cargan, la caja esta abierta. Probado con `nft flush ruleset` + `arjectl start`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

24a6dd3b2ffc74b9d32a064cde3cffaa9397307f · por Sergio · (hace 1 mes) · padre a0b3e27ef · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

3 archivos · +172 −0

diff --git a/docs/28-servidor-de-produccion.md b/docs/28-servidor-de-produccion.md
index c68a283b..22e4e825 100644
--- a/docs/28-servidor-de-produccion.md
+++ b/docs/28-servidor-de-produccion.md
@@ -1830,6 +1830,66 @@ El kernel ya no estorba; falta **la política**: `PoliticaEntrada` (.ron) → `g
 `flush` automático si nadie confirma): una regla mal puesta en una caja **sin consola serie** deja
 la única salida en `hcloud server enable-rescue`.
 
+### 6.32 🧱 El cortafuegos, puesto — y el primer baneado fui yo *(2026-09-15)*
+
+La caja filtra: `table inet tawasuyu_input`, **`policy drop`**, 15 reglas de puerto, y el set `ban4`
+llenándose solo con lo que golpea desde internet. A los pocos minutos: **27 IPs baneadas y paquetes
+tirados por el contador**. Eso es el reemplazo de fail2ban funcionando sobre tráfico real, dentro del
+kernel y sin daemon.
+
+La política vive en `scripts/servidor/cortafuegos-entrada.ron` (formato `PoliticaEntrada` del
+cortafuegos de tawasuyu) y declara los cinco puertos públicos: `22022` admin, `2345` git, `80`, `443`
+y `1137` el proxy. El reglaset se genera con `cortafuegos generate-input` **en el hub** —el binario es
+glibc y la caja es musl— y se aplica allá con `nft -f`.
+
+#### 🧨 El primer baneado fui yo, en segundos
+
+Aplicada la primera versión, desde fuera: `ssh` ✅, `https` ✅… y **`http`, el proxy y el git por SSH
+muertos**. Al minuto, la caja entera dejó de contestarme.
+
+La causa no es una regla mal escrita: **el set `@ban4` es UNO SOLO para todos los servicios**, y la
+regla `ip saddr @ban4 drop` está antes que todo. O sea que pasarse de tasa en *un* puerto te tira en
+*todos*. Y el `nuevas_por_min: 5` del puerto de administración —correcto contra internet— es
+exactamente lo que **me** pasa a mí: cada comando remoto de esta sesión es una conexión nueva.
+
+Lo que devolvió la máquina fue el **interruptor de hombre muerto**: antes de aplicar se deja
+programado un `nft flush ruleset` a 180 s, y sólo se desarma si la verificación desde fuera sale
+bien. Sin consola serie, esa es la diferencia entre un susto y un rescue.
+
+⚠ **Y el tipo ya lo avisaba.** `PoliticaEntrada` tiene un campo `confiables` cuyo comentario dice,
+palabra por palabra, lo que me acababa de pasar: *«un `nuevas_por_min` chico en el puerto de SSH es
+exactamente lo que se quiere contra internet y exactamente lo que te deja afuera cuando el que abre
+seis sesiones en un minuto sos vos»*. Copié el `.ron` de ejemplo en vez de leer el tipo. Es
+[[lei-hasta-donde-me-daba-la-razon]] otra vez, y la cura fue una línea:
+`confiables: ["204.168.193.248", "154.197.1.13"]` — el hub y el worker, que se aceptan **antes** de
+la lógica de tasa y por lo tanto no pueden entrar al set.
+
+#### Lo que este kernel todavía no puede, dicho en voz alta
+
+`nft -c` rechazó **cinco reglas** antes de aplicar nada:
+
+    tcp dport 22022 ct count over 10 drop
+                    ^^^^^^^^  Error: Could not process rule: No such file or directory
+
+`ct count` es `CONFIG_NFT_CONNLIMIT`, que el kernel de hoy no trae (el `.config` lo dice:
+`# CONFIG_NFT_CONNLIMIT is not set`). Es el tope de conexiones **concurrentes** por servicio — el ban
+por tasa, que es lo que reemplaza a fail2ban, no depende de él. Las cinco reglas quedan
+**comentadas, no borradas**, en el fichero que se aplica: *lo que la política declara y el kernel no
+puede tiene que verse*. El símbolo ya entró a `recipes/linux-generic.toml` y vuelve con el próximo
+kernel; hasta entonces el mensaje —que no nombra la opción que falta— queda explicado ahí mismo.
+
+#### Que sobreviva al reinicio: un servicio `oneshot`
+
+`recipes/nftables.toml` declara ahora su `[[service]]` con `lifecycle = "oneshot"`: **cargar un
+reglaset no es un daemon, es un acto**. Va **primero en el genesis**, antes que sshd y los demás:
+entre que la red está y que las reglas cargan, la caja está abierta, y ese hueco se hace lo más
+corto posible. Probado de verdad — `nft flush ruleset` y después `arjectl start cortafuegos`: las 15
+reglas vuelven.
+
+Su guarda sale **78 nombrando el fichero** si el reglaset no está, en vez de dejar la caja sin
+filtrar creyendo que está protegida: *un cortafuegos ausente y uno vacío se ven igual desde afuera
+hasta que alguien prueba*.
+
 ## 7. Reusar los scripts que ya existen, y no escribir de nuevo
 
 Pedido explícito del usuario. El inventario de lo que ya hace el trabajo:
diff --git a/recipes/nftables.toml b/recipes/nftables.toml
index d097da32..2240f5b9 100644
--- a/recipes/nftables.toml
+++ b/recipes/nftables.toml
@@ -59,3 +59,29 @@ install   = "make install DESTDIR=/out && find /out -name '*.la' -delete"
 
 [deps]
 build = ["binutils", "busybox", "make", "pkgconf", "libmnl", "libnftnl", "gmp"]
+
+# ── EL SERVICIO QUE ESTE PAQUETE TRAE (SDD 30) ──────────────────────────────────────────────────
+# Fuera de `hash_inputs`: declarar esto NO re-hashea nftables.
+#
+# `Oneshot`: cargar un reglaset no es un daemon, es un acto. Se aplica al arrancar y termina; si
+# vuelve a hacer falta, se vuelve a correr (`arjectl start cortafuegos`).
+#
+# ⚠ EL REGLASET ES DATO DEL SITIO y por eso NO viene en el paquete: qué puertos expone una máquina
+# y a quién no banea nunca es una decisión de quien la administra. Se genera con el cortafuegos de
+# tawasuyu (`cortafuegos generate-input --policy <.ron>`) y se deja en `/etc/takana/`. El servicio
+# sale 78 nombrando el fichero en vez de dejar la caja ABIERTA creyendo que está protegida — que es
+# el modo de fallo peligroso: un cortafuegos ausente y un cortafuegos vacío se ven igual desde
+# afuera hasta que alguien prueba.
+#
+# ⚠⚠ Y la lección que costó la primera aplicación: el set de baneados es UNO SOLO para todos los
+# servicios, así que pasarse de tasa en el puerto de ssh te tira TAMBIÉN el https, el proxy y el
+# git. Sin `confiables` en la política, el primer baneado es el operador. Ver SDD 28 §6.32.
+[[service]]
+label      = "cortafuegos"
+id         = "01M2EKDA00NFT4B135C0RTAF9G"
+exec       = "/bin/busybox"
+argv = ["sh", "-c", "test -f /etc/takana/cortafuegos-entrada.nft || { echo 'cortafuegos: falta /etc/takana/cortafuegos-entrada.nft — el reglaset es dato del SITIO, se genera con `cortafuegos generate-input`. La caja arranca SIN filtrar y eso no se hace en silencio' >&2; exit 78; }; /usr/sbin/nft -c -f /etc/takana/cortafuegos-entrada.nft || { echo 'cortafuegos: el kernel RECHAZA el reglaset (¿falta alguna opción de nf_tables?) — no aplico nada' >&2; exit 78; }; exec /usr/sbin/nft -f /etc/takana/cortafuegos-entrada.nft"]
+envp       = [["PATH", "/usr/sbin:/usr/bin:/sbin:/bin"]]
+networking = "full"
+cgroup     = "arje.slice/cortafuegos"
+lifecycle  = "oneshot"
diff --git a/scripts/servidor/cortafuegos-entrada.ron b/scripts/servidor/cortafuegos-entrada.ron
new file mode 100644
index 00000000..1c674b43
--- /dev/null
+++ b/scripts/servidor/cortafuegos-entrada.ron
@@ -0,0 +1,86 @@
+(
+    // Política de ENTRADA de la caja `takana` (2.29.29.217) — SDD 28 §6.32.
+    // Formato: `PoliticaEntrada` del cortafuegos de tawasuyu (shared/cortafuegos/SDD-ENTRADA.md).
+    //
+    //     cortafuegos generate-input --policy scripts/servidor/cortafuegos-entrada.ron
+    //     cortafuegos check-input    --policy …            (valida con `nft -c`)
+    //     cortafuegos apply-input    --policy … --yes      (root; deniega TODO lo no listado)
+    //
+    // ⚠️ APLICAR ESTO SIN RED DE SEGURIDAD PUEDE DEJARTE FUERA: la caja NO tiene consola serie
+    // (hcloud), así que una regla mal puesta sólo se arregla por `hcloud server enable-rescue`.
+    // Se aplica con interruptor de hombre muerto: aplicar → comprobar desde fuera → confirmar, y
+    // si nadie confirma, un `nft flush ruleset` programado devuelve la máquina a como estaba.
+    //
+    // Esto es lo que REEMPLAZA a fail2ban, que no se mudó de gioser: el ban por IP lo hace el
+    // kernel con sets dinámicos, sin daemon que lea logs. Necesita `CONFIG_NF_TABLES` — el kernel
+    // de la caja no lo traía y por eso hubo que reconstruirlo (§6.30/§6.31).
+    // ── QUIÉN NUNCA SE BANEA ────────────────────────────────────────────────────────────────
+    // ⚠️ MEDIDO EL 2026-09-15, Y EL TIPO YA LO AVISABA: sin esto, el primer baneado fui yo. El
+    // `nuevas_por_min: 5` de administración es lo correcto contra internet y es exactamente lo que
+    // te deja afuera cuando el que abre seis sesiones por minuto sos vos — cada comando remoto es
+    // una conexión nueva. Y como el set `@ban4` es UNO SOLO para todos los servicios, caer en él
+    // por el puerto de ssh te tira TAMBIÉN el https, el proxy y el git: el `ip saddr @ban4 drop`
+    // está antes que todo. La caja quedó inaccesible en segundos y la devolvió el interruptor de
+    // hombre muerto.
+    //
+    // `confiables` acepta ANTES de la lógica de tasa, así que estas fuentes no pueden entrar al set.
+    confiables: [
+        // gioser: el hub que administra, cosecha y respalda esta caja. Se va a borrar cuando la
+        // mudanza termine, y entonces esta línea sale con él.
+        "204.168.193.248",
+        // dev.gioser.net: el worker de la granja.
+        "154.197.1.13",
+    ],
+    drop_estados_invalidos: true,
+    icmp_echo_por_seg: 10,
+    syn_nuevas_por_seg: 200,
+    servicios: [
+        (
+            puerto: 22022,
+            proto: Tcp,
+            descripcion: "ssh de ADMINISTRACIÓN — el único acceso a la caja",
+            // Apretado a propósito: por acá no entra nadie más que el operador, y es el puerto
+            // que fail2ban cuidaba en gioser.
+            conexiones_max: 10,
+            nuevas_por_min: 5,
+            ban_timeout_seg: 600,
+        ),
+        (
+            puerto: 2345,
+            proto: Tcp,
+            descripcion: "ssh del gitea (git clone/push)",
+            // Más holgado que el de administración: un `git clone` abre varias conexiones y los
+            // clones legítimos vienen a ráfagas.
+            conexiones_max: 20,
+            nuevas_por_min: 15,
+            ban_timeout_seg: 600,
+        ),
+        (
+            puerto: 80,
+            proto: Tcp,
+            descripcion: "http — caddy (redirección y ACME)",
+            conexiones_max: 200,
+            nuevas_por_min: 60,
+            ban_timeout_seg: 600,
+        ),
+        (
+            puerto: 443,
+            proto: Tcp,
+            descripcion: "https — los siete dominios mudados",
+            conexiones_max: 200,
+            nuevas_por_min: 120,
+            ban_timeout_seg: 600,
+        ),
+        (
+            puerto: 1137,
+            proto: Tcp,
+            descripcion: "squid — el proxy de salida, con usuarios reales detrás",
+            // ⚠️ EL MÁS HOLGADO DE TODOS, Y NO ES DESCUIDO: cada pestaña de un navegador es un
+            // CONNECT nuevo. Un límite pensado para ssh acá BANEA A UN USUARIO LEGÍTIMO, que es
+            // peor que no tener regla — el abuso que esto para es un flood, no un navegador.
+            conexiones_max: 300,
+            nuevas_por_min: 300,
+            ban_timeout_seg: 300,
+        ),
+    ],
+)