Restringe el acceso a un servidor y pon un WAF delante de tu aplicación

Desde el primer despliegue, erpc worker permite SSH solo desde tu dirección y descarta todas las demás conexiones entrantes. Una tabla [waf] pone un proxy de filtrado delante de tu API.
Esta página describe ERPC CLI v0.1.0-rc.4, una versión preliminar.

El firewall

Un despliegue configura el firewall del servidor antes de que la aplicación arranque por primera vez:
  • SSH solo desde firewall.ssh_allow y desde la dirección de la que procede el despliegue;
  • todas las demás conexiones entrantes descartadas, excepto run.port desde firewall.app_allow; para una aplicación detrás de un WAF, el puerto del WAF en su lugar, y solo donde el WAF del servidor ya aplica las reglas que [waf] define ahora (más abajo).
Cada cambio se ejecuta con un temporizador de rollback de 120 segundos y solo se conserva una vez que una conexión nueva consigue entrar, así que un cambio erróneo no puede dejarte fuera.
Sin embargo, la lista blanca sobrevive a la dirección desde la que se creó: si cambia la dirección de tu casa, la forma de volver a entrar es el soporte de ERPC para un servidor de ERPC, o la consola del proveedor para un servidor propio. Mantén en firewall.ssh_allow una segunda dirección desde la que puedas llegar al servidor (una oficina, una salida de VPN) antes de confiar en la de tu casa.
Si tu dirección cambia, añade todas las direcciones fijas que usan tus administradores:
bash
erpc worker firewall allow --to ssh 203.0.113.10 198.51.100.7
ComandoQué hace
erpc worker firewall listMuestra quién puede llegar al servidor: erpc.toml junto a la tabla del servidor
erpc worker firewall allow --to ssh|app <addresses>Permite la entrada a las direcciones y lo aplica. SSH se permite desde direcciones individuales; una red solo se acepta con --to app
erpc worker firewall deny --to ssh|app <addresses>Deja de permitirles la entrada
erpc worker firewall syncHace que el firewall del servidor vuelva a ser lo que dice erpc.toml

Poner un WAF delante de tu aplicación

Añade una tabla a erpc.toml:
toml
[waf]
port = 8080
Después, erpc worker deploy ejecuta un proxy inverso HTTP en el servidor, en una unidad systemd propia (erpc-waf-<app>-<env>): un usuario dinámico sin privilegios, sin clave, sin secreto y sin nada del worker, limitado a 1 GiB de memoria. Escucha en waf.port, lee cada solicitud antes que la aplicación, rechaza lo que nombran sus reglas y reenvía el resto a la aplicación en 127.0.0.1:<run.port>.
  • La aplicación se queda en el loopback. Su propio puerto nunca se abre desde fuera. erpc worker api y la comprobación de salud llegan a la aplicación por el loopback, sin pasar por el WAF.
  • Quién puede conectarse sigue siendo la lista del firewall, y el puerto del WAF es el único sitio donde esa lista puede decir «todos»:
    sh
    erpc worker firewall allow --to app 0.0.0.0/0     # and ::/0
    Si no hay nadie en la lista, el WAF escucha y no se deja entrar a nadie, y el despliegue lo indica.
  • El puerto se abre una vez que el WAF escucha en él, nunca antes. Un despliegue cuyo WAF no arranca falla y dice por qué, con la aplicación todavía en ejecución.
  • Cuando cambias [waf], el puerto no se abre a nadie nuevo hasta que un despliegue haya dejado aplicadas las reglas nuevas.
  • Quitar [waf] de erpc.toml y desplegar elimina el WAF del servidor.

Ajustes del WAF

Solo port es obligatorio. Todas las claves, con su valor predeterminado:
toml
[waf]
port = 8080          # the public port, 1024 or above; the app keeps [run] port
methods = ["GET", "POST", "PUT", "DELETE"]
allow_paths = []     # every path
deny_paths = []
max_body_bytes = 1048576
max_header_bytes = 16384
rate = { per_second = 50, burst = 100 }
max_connections_per_address = 32
ban = { after = 100, window_secs = 60, for_secs = 600 }
block_probes = true
header_timeout_secs = 10
body_timeout_secs = 30
upstream_timeout_secs = 30
new_connections = { per_second = 50, burst = 100 }
ClaveQué haceQué recibe lo rechazado
methodsLos métodos que se reenvían, en mayúsculas (16 como máximo; nunca CONNECT)405, con Allow
allow_pathsLas rutas que se reenvían; si no se indica ninguna, son todas las rutas. Un patrón es una ruta, o todo lo que hay bajo un prefijo cuando termina en *: /api/* es /api y todo lo que hay debajo. Se compara con la ruta una vez decodificada por porcentaje, distinguiendo mayúsculas y minúsculas404
deny_pathsLas rutas que se rechazan, escritas de la misma forma y comprobadas primero403
max_body_bytesEl cuerpo de solicitud más grande, hasta 64 MiB413
max_header_bytesEl tamaño máximo de la línea de solicitud y las cabeceras juntas, de 1024 a 65.536431
rateLas solicitudes por segundo desde una dirección, y cuántas a la vez antes de que se aplique la tasa429, con Retry-After
max_connections_per_addressLas conexiones que una dirección mantiene a la vezLa conexión se cierra, sin respuesta
banTras after solicitudes bloqueadas dentro de window_secs, la dirección se rechaza durante for_secs (hasta 30 días)Sus conexiones se cierran, sin respuesta
block_probesRechaza lo que piden los escáneres (/.env, /.git, /wp-login.php y similares)403
header_timeout_secsCuánto tiempo tiene un cliente para enviar la cabecera de una solicitud, de 1 a 120. También cuánto se espera a un cliente que se ha detenido a mitad de un cuerpo o que ha dejado de recibir su respuesta408 para la cabecera; 400 para un cuerpo que se detuvo; la conexión se cierra para una respuesta que no se recibe
body_timeout_secsCuánto puede tardar en total el cuerpo de una solicitud, de 1 a 600, sin contar el tiempo que el WAF espera a la aplicación408
upstream_timeout_secsCuánto tiempo tiene la aplicación para aceptar la conexión y empezar a responder, de 1 a 600504; 502 cuando la aplicación no está escuchando
new_connectionsEl límite propio del firewall de conexiones TCP nuevas por segundo desde una dirección al puerto del WAF, que nftables descarta antes de que el proxy las vea. Mientras el conjunto de direcciones de nftables está lleno, una dirección que no está en él no se cuenta, así que este límite no se le aplica hasta que el conjunto vuelva a tener espacio; los límites propios del proxy (rate, max_connections_per_address, ban) se siguen aplicandoEl paquete se descarta
rate, max_connections_per_address, ban y new_connections se desactivan con false (ban = false), y una clave omitida toma su valor predeterminado. erpc worker check indica qué hará la tabla [waf], y qué tiene de incorrecto, antes de cualquier despliegue.

Lo que el WAF hace sin necesidad de ajustes

  • Una solicitud que no se lee como una única solicitud HTTP/1 inequívoca recibe 400, y lo mismo una ruta que no está en forma normal.
  • Una solicitud para cambiar de protocolo (Upgrade) recibe 501.
  • Cada rechazo cuenta para el veto, y cada rechazo cierra su conexión.
  • Una dirección es una dirección IPv4, o un /64 de IPv6.
  • El WAF no reenvía las cabeceras de la conexión del cliente con el proxy (Connection y las cabeceras que nombra, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, TE, Trailer), Expect, las cabeceras cuyo nombre lleva un guion bajo, ni las cabeceras Forwarded, X-Real-IP y X-Forwarded-Proto que envía el propio cliente. Establece X-Real-IP en la dirección de la que vino la conexión y añade esa dirección al final de X-Forwarded-For; solo la última entrada de X-Forwarded-For procede del proxy.

Ver lo que hace el WAF

ComandoMuestra
erpc worker statusEl puerto del WAF, adónde reenvía, desde cuándo y los recuentos desde entonces
erpc worker firewall listQuién puede llegar al puerto del WAF y qué se ha bloqueado, por regla
erpc worker logs --wafCada solicitud bloqueada: la regla, el estado, la dirección del cliente, el método y los primeros 200 bytes del destino
Una solicitud reenviada no deja ninguna línea en el log, y nunca se registra ninguna cabecera ni ningún cuerpo; la consulta de una solicitud bloqueada sí, hasta donde alcanzan los 200 bytes. Lo que envía un cliente no puede escribir en tu terminal: el WAF escribe su journal como ASCII imprimible.

Lo que el WAF no hace

  • Sin TLS. Habla HTTP/1.1 sin cifrar en su puerto.
  • Sin HTTP/2: le responde con 400.
  • Sin WebSocket, ni ningún otro Upgrade: 501.
  • No mira dentro de los cuerpos. Un cuerpo se mide, nunca se lee. La lista de sondeos es corta y es un filtro de ruido, no una garantía: la aplicación sigue encargándose de comprobar su entrada.
  • Sin autenticación. Quién puede llamar a la aplicación lo decide la aplicación, y la lista del firewall.
  • Una dirección es un cliente. Los clientes detrás de un mismo NAT, proxy o CDN comparten una tasa y un veto; un cliente con muchas direcciones tiene tantas tasas como direcciones.
  • Los vetos y los recuentos se olvidan cuando el WAF se reinicia.
  • Recuerda como máximo 8192 direcciones y mantiene como máximo 1024 conexiones a la vez.
  • Solo escucha en IPv4, así que una entrada IPv6 en app_allow todavía no llega a nada.
  • Una ruta se compara como los bytes a los que se decodifica. A una aplicación que lee las rutas de forma más laxa (sin distinguir mayúsculas y minúsculas, con parámetros ;, decodificando dos veces) se puede llegar con un nombre que deny_paths no cubre. allow_paths no tiene ese hueco: lo que no está en la lista no se reenvía.
  • A un lector lento se le sirve hasta el final. A un cliente que sigue recibiendo una respuesta, por lento que sea, se le sirve hasta el final.
  • Una avalancha puede cortar una subida grande. El WAF se ejecuta en un solo hilo, así que una avalancha puede cortar una subida grande con 408. Una aplicación que acepta cuerpos grandes sube body_timeout_secs junto con max_body_bytes.
La guía del worker tiene todas las reglas, incluido cuándo un cambio en app_allow se retiene hasta el siguiente despliegue.