Qué consigues realmente al ocultar el origen
Tres cosas, y merece la pena ser precisos al respecto. Primero, la inundación deja de llegar a la máquina que no puede absorberla: un único VPS tiene un uplink finito, y cuando la dirección que se come los paquetes es un edge construido justo para eso, en lugar de la máquina que aloja tu base de datos, un ataque volumétrico se convierte en el problema de ingeniería de otro. Segundo, la aplicación deja de ser alcanzable esquivando sus propias defensas —cualquiera que pueda conectar directamente al origen se salta con total trivialidad los límites de tasa, las reglas antibot, el WAF y el geobloqueo, y la mayoría de quienes los despliegan nunca comprueban si eso sigue siendo posible. Tercero, la dirección que sirve tu contenido deja de ser la dirección que guarda tus datos, que es la separación que hace que una denuncia de abuso, un escaneo o una sonda dirigida acaben cayendo en algo barato y reemplazable.
Y ahora la mitad honesta. Ocultar un origen no es anonimato —oculta una dirección, no a una persona, y el rastro de pago, el registro del dominio y la cuenta que hay detrás de ambos son un problema aparte que tiene su propia guía. No parchea tu aplicación: un origen que nadie puede encontrar sigue siendo explotable en el momento en que alguien lo encuentra, y los orígenes se acaban encontrando. No oculta nada a tu proveedor, que por definición sabe qué máquina responde en qué dirección. Trátalo como una capa más que encarece un ataque, situada encima de una máquina que fue endurecida correctamente primero —no como un sustituto de ninguna de las dos cosas.
SP·02Todas las formas en que se filtra una IP de origen
La razón por la que tantos orígenes ocultos no están realmente ocultos es que la gente cierra un canal y da por hecho que los demás lo siguieron. No lo hacen. Aquí está la lista que repasamos, en el orden aproximado de la frecuencia con la que resulta ser la que de verdad ha quemado a alguien:
- DNS histórico. Los recolectores de DNS pasivo llevan registrando tu registro A desde mucho antes de que te pusieras detrás de un proxy. La dirección que usabas el año pasado es una consulta permanente, buscable y gratuita.
- Transparencia de certificados. Todo certificado de confianza pública se publica en registros append-only, junto con cada hostname que cubre. Un certificado emitido para
origin.example.comanuncia ese nombre al mundo entero, y su registro A hace el resto. - Subdominios que nunca se movieron.
mail,ftp,webmail,cpanel,dev,staging,vpn,monitor—el dominio raíz pasó detrás del CDN y estos se quedaron apuntando a la máquina. - Correo. Un registro MX en el origen delata la dirección directamente; lo mismo hace una cabecera
Received:en un correo que envió tu aplicación, algo que cualquiera puede provocar con un formulario de restablecimiento de contraseña. - Peticiones salientes. Webhooks, descargas de avatares, lecturas de RSS, vistas previas de enlaces, comprobaciones de actualización, callbacks de OAuth. Cada una revela la dirección del origen a quien opere el otro extremo —y una función de vista previa de URL deja que sea el atacante quien elija ese otro extremo.
- Responder en la dirección desnuda. Si el origen sigue sirviendo tu sitio ante una petición sin cabecera
Hostcoincidente, los escáneres a escala de toda internet ya lo han indexado: el hash del favicon, el título de la página, el fingerprint del certificado y el orden de las cabeceras HTTP son todos buscables. - El registro IPv6 que olvidaste. El registro A se movió al proxy; el registro AAAA sigue apuntando a casa.
- La aplicación hablando de sí misma. URLs absolutas en la configuración de un CMS, redirecciones a un hostname interno, stack traces, cabeceras
Server, source maps, un endpoint de estado sin autenticación.
Fíjate en lo que casi todas estas cosas tienen en común: son permanentes. Los registros de certificados son append-only y el DNS pasivo es un archivo histórico. No puedes retirar una dirección una vez publicada —solo puedes dejar de usarla, que es exactamente por lo que importa el orden de las operaciones que viene más abajo.
SP·03Dos formas de edge: un CDN o una máquina propia
Un CDN comercial te da una capacidad anycast que ningún servidor individual puede igualar, en decenas de ciudades, muchas veces en un plan gratuito. La contrapartida es que el TLS termina en una infraestructura que no controlas: el operador ve tu tráfico en claro, sabe a qué cuenta pertenece, y puede verse obligado a actuar en base a ese conocimiento, o simplemente decidir una mañana cualquiera que tu contenido ya no es bienvenido. También hay un problema más sutil, específico de los frentes compartidos —si el firewall de tu origen permite los rangos de direcciones publicados por el CDN, entonces cualquier otra persona con una cuenta en ese mismo CDN está dentro de tu lista de permitidos y puede apuntar su propio hostname hacia tu origen. Eso es un bypass real, no teórico, y es la razón por la que existe el authenticated origin pull.
Un edge que gestionas tú mismo es la contrapartida opuesta. Nadie más que tú tiene la clave privada, la máquina está en una jurisdicción que elegiste deliberadamente, y cuesta $8.00/mes por el plan más pequeño —literalmente un error de redondeo comparado con lo que protege. Lo que no obtienes es anycast: una inundación de 200 Gbps saturará el uplink del edge por muy elegante que sea tu configuración de nginx, así que la absorción a nivel de red tiene que venir de algún sitio. En nuestro caso, eso son los 1.5 Tbps de mitigación upstream por delante de cada máquina de la flota, que es lo que hace que un edge autogestionado sea viable en lugar de un único punto de colapso. Las dos variantes también se pueden combinar: CDN por delante para alcance y volumen, tu propio nodo detrás para la parte que te niegas a entregar. Elige según qué fallo prefieras tener que explicar.
SP·04El túnel es la parte en la que la gente se equivoca
La configuración habitual es un origen que escucha en 0.0.0.0:443 con un firewall cuya lista de permitidos son las direcciones del proxy. Funciona, y es el eslabón más débil del diseño. Las listas de permitidos se desactualizan —los rangos publicados cambian y la actualización nunca llega a aplicarse—; son compartidas, así que en un CDN público admiten a todos los demás clientes; y fallan en abierto justo en la dirección que importa, porque el origen sigue siendo un listener público activo todo el tiempo, esperando un único error de configuración o un ufw disable durante una sesión de depuración.
La versión que aguanta lo invierte: el origen no tiene ningún listener público en absoluto. Se establece un túnel WireGuard entre el edge y el origen, el servidor web se enlaza solo a la dirección del túnel, y la interfaz pública tiene una política de denegación por defecto en ambas familias de IP, sin excepción para el 80 ni el 443. A partir de ahí, la alcanzabilidad no es una regla que alguien pueda olvidarse de renovar —es la ausencia de una ruta. WireGuard es la herramienta adecuada aquí porque es un módulo del kernel con una superficie de ataque mínima, es silencioso frente a escáneres no autenticados (un paquete no autenticado no recibe ninguna respuesta, así que el puerto UDP ni siquiera parece existir), y cuesta microsegundos de un solo dígito por paquete. Si nunca has montado uno, la guía de WireGuard cubre los fundamentos; aquí solo necesitamos un enlace punto a punto de dos peers.
Una regla antes de empezar, y es la que te salva la tarde: mantén abierta una segunda sesión SSH todo el tiempo. Bloquear el SSH detrás de un túnel que todavía estás reconfigurando es la forma típica en que la gente pierde una máquina, y en un proveedor sin ninguna identidad registrada no hay ninguna vía de soporte a la que acudir, ni nadie que pueda concederte una sesión de consola —la vía de vuelta es un redespliegue y una restauración, que es rápida si tus copias de seguridad están al día, y definitiva si no lo están.
SP·05Certificados, y el registro que publica tus hostnames
La transparencia de certificados es algo genuinamente bueno que puede arruinarte la semana sin ningún problema. Todo certificado que emite una CA pública se envía a registros append-only que cualquiera puede consultar, y la entrada contiene todos los nombres del certificado. Emite uno para origin.example.com o direct.example.com y habrás publicado, de forma permanente y en un formato estructurado, el hostname exacto que estabas intentando no anunciar. Peor aún, la costumbre de meter los hostnames de staging y de administración en la misma lista SAN convierte una renovación descuidada en un mapa de tu infraestructura.
La disciplina es sencilla. Los certificados públicos viven solo en el edge, cubriendo solo los nombres que el público usa de verdad. El origen recibe un certificado autofirmado, o uno de una CA privada pequeña, anclado en la configuración del proxy con proxy_ssl_trusted_certificate —nada en un enlace que solo habla tu propio proxy necesita ser de confianza pública, y emitir un certificado público para eso no te da nada más que una entrada en un registro. Si necesitas muchos subdominios públicos, un wildcard publica un solo nombre en vez de treinta. Y cambia ACME al challenge DNS-01: el HTTP-01 exige que algo responda en el puerto 80 para el hostname que se está validando, que es precisamente el listener público que acabas de eliminar. Por último, acepta la asimetría —los registros son append-only, así que un hostname que ya publicaste no se puede retirar. Si resolvía hacia el origen, el origen necesita una dirección nueva.
Correo, y los demás servicios que responden en la dirección equivocada
El correo es el bypass clásico porque, por definición, tiene que ser alcanzable. Si el registro MX de tu dominio apunta al origen, el ejercicio termina antes de empezar —el registro es público y un solo dig acaba con la búsqueda. Incluso con el MX en otro sitio, una aplicación que envía correo directamente desde el origen estampa la dirección de la máquina emisora en la cadena de cabeceras Received: de cada mensaje, y cualquier formulario que le mande un correo a un usuario bajo demanda convierte eso en una consulta de autoservicio. La solución es hacer que el origen sea un cliente y nunca un servidor: retransmite el correo saliente a través de un servicio de submission o de una máquina aparte, mantén el MX en una máquina a la que se le permita ser encontrada, y lee las cabeceras completas de un mensaje de prueba antes de darlo por terminado. Montar tu propio correo a gran escala es un proyecto en sí mismo, y no tiene sitio en la máquina que estás intentando ocultar.
Después, haz un barrido en busca de todo lo demás que esté escuchando calladamente. Agentes de monitorización, paneles de contenedores, puertos de bases de datos abiertos "temporalmente", un endpoint de métricas en el 9100, un panel de control en un puerto alto, un daemon SSH en la interfaz pública. Cada uno de ellos es un servicio que responde en la dirección que estás intentando mantener privada, y los escáneres encuentran los puertos altos con la misma facilidad que los bajos. La auditoría es un único comando —ss -tulpn— y el resultado correcto es una lista en la que nada está vinculado a una dirección pública. El paso tres de más abajo es lo que hace eso cierto, y lo mantiene cierto.
Egress: las conexiones que inicia tu origen
Un origen que no acepta nada todavía puede delatarse a sí mismo, porque no solo recibe conexiones —también las abre. Mirrors de paquetes, NTP, un webhook hacia un procesador de pagos, una API de bots, una imagen descargada para una vista previa de enlace, una comprobación de licencia, una sesión SMTP saliente, un remoto de Git, un servicio de reporte de errores. Para el otro extremo de cada una de esas conexiones, la dirección pública del origen es sencillamente la dirección desde la que sale la conexión. La mayoría de las veces esto es inofensivo, porque tú elegiste ese otro extremo y confías en él. El problema es el puñado de endpoints que elige un atacante: pega un enlace en cualquier cosa que renderice una vista previa, registra un webhook, o encuentra un SSRF en un importador de imágenes, y el origen resuelve y se conecta a un host que el atacante está vigilando. Eso es una desanonimización de dos minutos sin que haga falta ningún exploit.
Hay dos respuestas defendibles. La estricta enruta todo el egress a través del túnel y deja que el edge le haga NAT, de modo que la dirección de origen del tráfico saliente del origen pasa a ser la del edge —un solo ajuste en la configuración del peer, más el reenvío de paquetes y una regla de masquerade en el otro extremo. wg-quick se encarga del bucle de enrutamiento por ti: con una ruta 0.0.0.0/0 instala una regla de fwmark para que los propios paquetes del túnel sigan llegando directamente al endpoint, que es justo la parte que la gente rompe cuando escribe las rutas a mano. La respuesta pragmática mantiene el egress directo para el tráfico que controlas y pone un proxy delante de todo lo que obtenga una URL proporcionada por el usuario. Lo que no tiene defensa posible es no saber cuál de las dos tienes. Decídelo de forma deliberada, y verifícalo con una petición a un host que controles y echando un vistazo a la dirección de origen en sus logs.
Qué cuesta, y cómo demostrar que funciona
La partida de gasto es un VPS más. El plan más pequeño, a $8.00/mes, termina el TLS y hace de proxy para un sitio pequeño sin ni notar la carga —un proxy inverso es, sobre todo, una copia de sockets, y 2 vCPU con 4 GB de RAM van sobrados mucho más allá del punto en que el origen que hay detrás se convierte en el cuello de botella. Ponlo en una región distinta a la del origen, para que un único instrumento legal o un único problema en las instalaciones no alcance a ambos, y no te olvides de la latencia: un salto adicional añade milisegundos reales, así que un edge en Ámsterdam delante de un origen en Kuala Lumpur es una decisión de diseño, no un accidente. Los pares dentro del mismo continente suelen costar milisegundos de un solo dígito, y la reutilización de sesión TLS que ganas en el edge a menudo compensa eso de sobra en una carga de página real.
Demostrar que funciona es lo que separa una configuración de un control, y es una tarea recurrente, no algo que se hace una sola vez —cada subdominio nuevo, cada certificado nuevo, cada integración nueva es una nueva oportunidad de volver a publicar la dirección. La batería de pruebas del paso siete lleva unos diez minutos: intenta llegar a tu sitio directamente por la dirección del origen, enumera todos los hostnames que hayas certificado alguna vez, recorre los nombres de subdominio obvios, comprueba el registro AAAA que se te olvidó, y envíate un correo a ti mismo. Ejecútala después de cada cambio en la infraestructura. Y ten presente la conclusión mientras lo haces: si el origen responde, el arreglo correcto no es otra regla de firewall —es una dirección nueva, porque la antigua ya está en el archivo de alguien. Dentro de la pila, esta capa va después del endurecimiento y junto a las copias de seguridad fuera de sitio: el endurecimiento decide lo difícil que es romper la máquina, las copias de seguridad deciden lo rápido que te recuperas, y esto decide lo difícil que es encontrar la máquina en primer lugar.
SP·09Paso a paso
-
01
Despliega el edge y dale exactamente una tarea
Despliega un segundo VPS en una región que no sea donde vive el origen, y trátalo como una máquina de un único propósito: terminación de TLS, un proxy inverso, y nada más. Sin base de datos, sin código de aplicación, sin scripts de shell que nadie fuera a echar en falta. Aplícale la lista de comprobación de la primera hora —SSH solo por clave, firewall de denegación por defecto en ambas familias de IP, actualizaciones de seguridad desatendidas— y después abre exactamente tres puertos.
ssh root@198.51.100.20 apt update && apt full-upgrade -y hostnamectl set-hostname edge-01 apt install -y nginx wireguard-tools ufw unattended-upgrades ufw default deny incoming && ufw default allow outgoing ufw limit 22/tcp ufw allow 80,443/tcp ufw allow 51820/udp ufw --force enable
-
02
Levanta el túnel antes de tocar el DNS
Dos peers, un enlace. Genera un par de claves en cada máquina y dale al túnel su propia subred pequeña —el origen acabará siendo alcanzable en
10.66.0.2y en ningún otro sitio. El origen es quien llama al edge (es el lado que se quedará sin puertos abiertos), así que es quien lleva elEndpointy un keepalive; el edge solo escucha.# on both machines umask 077; wg genkey | tee privkey | wg pubkey > pubkey # edge-01 — /etc/wireguard/wg0.conf [Interface] Address = 10.66.0.1/24 ListenPort = 51820 PrivateKey = <edge-privkey> [Peer] PublicKey = <origin-pubkey> AllowedIPs = 10.66.0.2/32 # origin-01 — /etc/wireguard/wg0.conf [Interface] Address = 10.66.0.2/24 PrivateKey = <origin-privkey> [Peer] PublicKey = <edge-pubkey> Endpoint = 198.51.100.20:51820 AllowedIPs = 10.66.0.1/32 PersistentKeepalive = 25
Actívalo en ambos extremos y confirma el handshake antes de seguir adelante —un túnel que solo funciona hasta el próximo reinicio es peor que no tener ninguno.
systemctl enable --now wg-quick@wg0 wg show # expect a recent handshake and non-zero transfer ping -c3 10.66.0.1 # from the origin
-
03
Haz que el origen sea inalcanzable desde la internet pública
Este es el paso que hace el trabajo de verdad, y también aquel en el que la gente se queda fuera de su propia máquina. Abre una segunda sesión SSH y déjala conectada antes de ejecutar nada de lo que viene abajo —aquí no hay ninguna consola de soporte a la que recurrir. Después, enlaza el servidor web a la dirección del túnel, elimina todo en la interfaz pública, y permite únicamente el túnel más el propio endpoint de WireGuard.
# /etc/nginx/sites-available/app — listen on the tunnel only listen 10.66.0.2:8080; ufw --force reset ufw default deny incoming ufw default allow outgoing ufw allow in on wg0 to any port 8080 proto tcp ufw allow in on wg0 to any port 22 proto tcp ufw allow from 198.51.100.20 to any port 51820 proto udp ufw --force enable # the audit: nothing may be bound to a public address ss -tulpn | grep -Ev '10\.66\.0\.2|127\.0\.0|\[::1\]'
Si ese último comando imprime algún servicio, es una fuga —arregla la dirección de bind en lugar de añadirle una regla de firewall alrededor. Solo dos listeners tienen permiso para sobrevivir: WireGuard en el 51820, y sshd si todavía no lo has movido al túnel.
-
04
Termina el TLS en el edge y haz de proxy a través del túnel
Emite el certificado público en el edge, para los nombres que el público usa de verdad, y haz proxy upstream hacia la dirección del túnel. El segundo bloque server no es un adorno opcional: es lo que impide que el edge le sirva tu sitio a un escáner que se conecta por IP sin cabecera
Host, que es como se acaba generando un fingerprint de la puerta principal.# edge-01 — /etc/nginx/sites-available/example.com server { listen 443 ssl; http2 on; server_name example.com www.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; add_header Strict-Transport-Security "max-age=63072000" always; location / { proxy_pass http://10.66.0.2:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; } } # anything that is not a known hostname gets nothing at all server { listen 80 default_server; listen 443 ssl default_server; ssl_reject_handshake on; return 444; } -
05
Devuélvele al origen la IP real del cliente
Detrás de un proxy, todas las peticiones llegan desde
10.66.0.1. Si no haces nada, tus logs de acceso se vuelven inútiles, la limitación de tasa por IP le pone el freno al túnel en lugar de al atacante, yfail2banacaba baneando al edge y tirando el sitio abajo —una forma genuinamente popular de provocarse una caída mientras se está endureciendo un servidor. Confía en la cabecera reenviada, pero solo desde la dirección del túnel, nunca desde el resto del mundo.# origin-01 — /etc/nginx/conf.d/realip.conf set_real_ip_from 10.66.0.1; real_ip_header X-Forwarded-For; real_ip_recursive off;
Haz el equivalente en la aplicación —
ProxyFixen Flask,TRUSTED_PROXIESen Laravel,set_real_ip_frommás la propia lista de proxies de confianza del framework— y pon la limitación de tasa en el edge, que es donde la dirección real del cliente existe de forma nativa:# edge-01 — /etc/nginx/nginx.conf (http block) limit_req_zone $binary_remote_addr zone=front:10m rate=20r/s; # then, inside the location block limit_req zone=front burst=40 nodelay;
-
06
Saca el correo y el egress de la dirección del origen
Apunta el MX a una máquina a la que se le permita ser encontrada, envía el correo saliente a través de un relay en lugar de directamente desde el origen, y cambia la renovación de certificados al challenge DNS-01 para que nada tenga que responder en el puerto 80. Después decide qué pasa con el resto del tráfico saliente. Para enrutarlo todo a través del edge, amplía el
AllowedIPsdel origen y deja que el edge haga masquerade —wg-quickinstala la regla de fwmark que mantiene el propio túnel alcanzable, así que no hace falta escribir a mano una ruta para el endpoint.# origin-01 — /etc/wireguard/wg0.conf, in [Peer] AllowedIPs = 0.0.0.0/0, ::/0 # edge-01 — forward and NAT the tunnel echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-fwd.conf && sysctl --system ufw route allow in on wg0 out on eth0 # in /etc/ufw/before.rules, above the *filter block: # *nat # :POSTROUTING ACCEPT [0:0] # -A POSTROUTING -s 10.66.0.0/24 -o eth0 -j MASQUERADE # COMMIT systemctl restart ufw && wg-quick down wg0 && wg-quick up wg0
Después, compruébalo desde el origen:
curl -s https://ifconfig.codebe devolver la dirección del edge, no la suya propia. -
07
Sal a cazar tu propio origen, y luego escribe el runbook
Atácalo como lo haría cualquier otra persona. El primer comando es el importante —si el origen todavía sirve tu sitio cuando se le pregunta directamente, nada de lo anterior está funcionando todavía.
# does the origin answer for your hostname? curl -sk --resolve example.com:443:203.0.113.10 https://example.com/ \ -o /dev/null -w '%{http_code}\n' # want: a timeout, not 200 # every hostname you have ever certified, from the public logs curl -s 'https://crt.sh/?q=%25.example.com&output=json' \ | grep -o '"name_value":"[^"]*"' | sort -u # records that never moved dig +short example.com A; dig +short example.com AAAA; dig +short example.com MX for h in www mail ftp webmail cpanel dev staging vpn monitor origin direct; do printf '%-9s %s\n' "$h" "$(dig +short $h.example.com | tr '\n' ' ')" doneDespués, envíate un correo desde la aplicación y lee la cadena completa de cabeceras
Received:, y pega un enlace a un host que controles en cualquier función que renderice vistas previas, y comprueba qué dirección lo ha descargado. Anota cómo tiene que verse un resultado correcto para cada comprobación, y repite todo el lote después de cada cambio de DNS, cada certificado nuevo y cada integración nueva. Si alguna de ellas saca a la luz el origen, reconstrúyelo en una dirección nueva —la publicada ya está archivada, y ninguna regla de firewall te la va a devolver.


