Control de Salida (Egress Control) en Serverless: cómo evitar que tus funciones filtren datos

En serverless, el perímetro clásico desaparece rápido: no gestionas hosts, el escalado es automático y las funciones heredan conectividad que muchas veces no se cuestiona. El problema es que el egress (a dónde puede “salir” una función) suele quedarse en el modo más cómodo: Internet abierta. Cuando una función procesa datos sensibles o tiene acceso a secretos, esa salida es el canal perfecto para exfiltración o para conectar a un Command & Control (C2) si alguien logra inyectar código o abusar de dependencias.

Controlar el egress no es “poner una VPC y ya”. En empresas, el objetivo real es que solo se pueda hablar con lo necesario (servicios cloud internos, APIs corporativas, bases de datos) y que cualquier intento de salir a destinos no autorizados falle y quede registrado. A continuación, un enfoque operativo centrado exclusivamente en egress control para serverless.

Qué significa egress control en serverless cuando el atacante ya está “dentro”

La mayoría de incidentes que he visto con funciones no empiezan con “alguien hackeó Lambda/Cloud Functions”, sino con algo más mundano: una dependencia comprometida, una variable de entorno expuesta por un error de logging, credenciales temporales usadas fuera de contexto o un input que termina ejecutándose donde no debía. En ese punto, el atacante no necesita persistencia sofisticada: le basta con que la función pueda hacer conexiones salientes sin restricciones.

Si tu función puede resolver DNS libremente y abrir sockets a Internet, los patrones de abuso son previsibles: subir datos a un bucket externo, enviar fragmentos a un endpoint HTTPS controlado por el atacante o mantener beaconing a un C2 para iterar comandos. En corporaciones, esto se agrava cuando la función también tiene permisos de lectura a almacenes internos (S3/Blob, bases de datos, colas). El egress control actúa como “última barrera”: aunque el runtime esté comprometido, el tráfico no debería poder salir a destinos arbitrarios.

La consecuencia práctica de no hacerlo suele ser doble: pérdida de datos (por exfiltración silenciosa) y tiempos de contención largos, porque sin controles de salida y sin telemetría, cuesta demostrar qué datos pudieron salir y a dónde.

Diseño de conectividad: VPC, endpoints privados y salida controlada (sin depender de “bloqueos por aplicación”)

El patrón más efectivo en cloud es forzar a que la función opere en una red donde no exista ruta directa a Internet y donde el acceso a servicios gestionados sea por endpoints privados. En AWS, eso suele implicar: Lambda dentro de VPC, subredes privadas, VPC Endpoints (Gateway/Interface) para servicios necesarios, y rutas que eviten un 0.0.0.0/0 hacia un Internet Gateway. Si necesitas salida puntual, se concentra en un único punto (NAT o firewall) con controles.

Esto no solo reduce superficie; también cambia el juego operativo: cuando el equipo de seguridad necesita bloquear un destino o investigar un flujo, ya no depende de “¿qué librería está haciendo la llamada?” sino de reglas de red y logs centralizados. En entornos con auditorías, este enfoque facilita demostrar que “por diseño” la función no puede hablar con Internet salvo excepciones explícitas.

  • VPC Endpoints para servicios cloud: permiten que la función consuma S3/Secrets Manager/DynamoDB, etc., sin salir a Internet.

    La implicación real es que una función comprometida seguirá pudiendo hablar con esos servicios, pero solo con los aprobados. Además, puedes acotar el acceso con políticas del endpoint (por ejemplo, a buckets concretos), reduciendo el radio de exfiltración incluso dentro del propio cloud.

  • NAT o egress firewall como único punto de salida: si hay dependencias externas inevitables (APIs de terceros), el tráfico debe salir por un punto controlado.

    En empresas, esto habilita allowlists por FQDN/IP, inspección TLS cuando aplica a políticas corporativas, y sobre todo registro de flujos. El trade-off es coste y complejidad: un NAT “abierto” no es control; un firewall bien gestionado sí, pero requiere operación diaria.

  • Subredes privadas sin ruta a Internet: si no hay requerimiento explícito de Internet, elimina el camino por completo.

    Es la diferencia entre “confiamos en que nadie llame fuera” y “aunque lo intente, no hay ruta”. Esto reduce incidentes donde un cambio menor (p. ej., una nueva dependencia que hace telemetría) empieza a sacar datos sin que nadie lo note.

Cómo hacerlo en la práctica en AWS: cerrar Internet y permitir solo endpoints (con ejemplos verificables)

El objetivo operativo es claro: sin ruta a Internet desde las subredes de Lambda, y acceso a AWS gestionado a través de VPC Endpoints con políticas restrictivas. El cambio típico en corporaciones falla por “medias medidas”: se mete la función en VPC, se crea un NAT por comodidad y se deja 0.0.0.0/0 sin controles. Eso es conectividad, no egress control.

Acciones concretas (mínimas) que suelo exigir antes de dar por “cerrado” el egress:

  • Crear subredes privadas para las funciones y asociar tablas de rutas sin 0.0.0.0/0 hacia Internet Gateway.

    Validación real: revisar la route table efectiva de esas subredes. Si existe una ruta por defecto a IGW o a un NAT “sin policy”, estás dejando puerta abierta (directa o indirecta).

  • Crear VPC Endpoints para los servicios necesarios (por ejemplo, S3 como Gateway Endpoint; Secrets Manager/KMS/STS como Interface Endpoints según el caso).

    Validación real: desde la función, forzar llamadas a esos servicios y comprobar que resuelven a IPs privadas (Interface endpoints) o que el tráfico no atraviesa NAT (Flow Logs). Si el tráfico va al NAT, no estás usando el endpoint.

  • Restringir el endpoint con policy cuando el servicio lo permite.

    Ejemplo típico con S3 Gateway Endpoint: permitir solo buckets corporativos específicos. Esto limita exfiltración “dentro de S3” (p. ej., subir a un bucket que no debería usarse).

Ejemplo de policy para un VPC Endpoint de S3 que solo permite acceso a un bucket concreto y niega el resto. Ajusta ARN, región y cuenta a tu caso:

Policy del VPC Endpoint (S3)

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSpecificBucketOnly",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::mi-bucket-corporativo",
"arn:aws:s3:::mi-bucket-corporativo/*"
]
},
{
"Sid": "DenyAllOtherBuckets",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "*"
}
]
}

Para verificar que el egress está realmente cerrado: habilita VPC Flow Logs en las subredes/ENIs relevantes y busca intentos de conexión a destinos públicos (REJECT/ACCEPT). Si aparece tráfico a Internet, la pregunta no es “¿por qué la función lo hace?”, sino “¿por qué la red lo permite?”.

Políticas de egress: security groups, NACLs, DNS y el problema real de los allowlists por FQDN

En serverless dentro de VPC, el primer control tangible suele ser el security group de la función: ahí puedes reducir puertos y destinos, pero con una limitación conocida: normalmente trabajas con IPs/CIDRs, no con nombres. En corporaciones, muchos terceros exponen endpoints con IPs cambiantes (CDNs), y una allowlist por IP se vuelve frágil: o se rompe el servicio cada semana, o terminas abriendo demasiado.

La alternativa práctica cuando necesitas allowlists por destino lógico es introducir un componente que sí entienda FQDN o que aplique políticas a nivel de aplicación/red: un egress firewall gestionado o un proxy corporativo. Si ese componente no está, el control de egress acaba degradándose a “permitimos 443 a cualquier sitio”, que es exactamente lo que querías evitar.

  • Security Groups como “mínimo común denominador”: limitar salidas a puertos estrictamente necesarios (p. ej., 443) y a destinos internos (CIDRs corporativos, subredes de bases de datos, endpoints privados).

    Esto evita exfiltración por canales triviales (SMTP/IRC/puertos raros) y reduce la superficie de C2, pero no resuelve por sí solo el acceso a Internet por 443 si lo dejas abierto.

  • NACLs para controles adicionales cuando hay necesidades de segmentación más dura o requisitos de auditoría.

    En la práctica, NACLs suelen introducir complejidad operativa (stateless, orden de reglas). Úsalas para reforzar “no hay salida” o para bloquear rangos obvios, no como sustituto del diseño con endpoints y un punto único de egress.

  • DNS controlado como parte del egress: si la función puede resolver cualquier dominio usando resolvers no controlados, pierdes visibilidad.

    Forzar el uso de resolvers corporativos (y registrar consultas) ayuda a detectar C2 por dominios y reduce el tiempo de investigación. Ojo: bloquear por DNS sin bloquear por red suele ser evadible si aún hay ruta IP directa.

Un anti-patrón frecuente en empresas es implantar “bloqueo por DNS” como control principal mientras se mantiene un NAT abierto. Cuando llega un incidente, el atacante no necesita DNS: puede usar IPs directas o DoH hacia un destino permitido. El control de egress debe estar anclado en rutas y políticas de salida, no solo en resolución de nombres.

Señales de exfiltración y cómo validar que el egress control funciona (antes del incidente)

Si tu control de salida es real, debe producir evidencia operable: intentos bloqueados, métricas, y trazabilidad de “qué función intentó salir, a dónde y cuándo”. En la vida real, el primer indicador suele ser sutil: aumentos de latencia por timeouts (cuando bloqueas correctamente un destino), picos de errores de conexión, o patrones de DNS extraños desde funciones que “nunca” deberían hablar fuera.

La validación debería formar parte de tu rutina de cambios. Cada vez que una función nueva o un proveedor externo entra en juego, se valida que el flujo permitido es el mínimo y que lo denegado queda registrado. Sin esa disciplina, el egress control se degrada con el tiempo por excepciones urgentes que nadie revierte.

Pruebas que funcionan bien en entornos corporativos:

  • Prueba negativa controlada: desde una función de test, intenta conectar a un dominio público no permitido (por ejemplo, un endpoint propio de pruebas) y confirma que falla.

    La clave es que el fallo tenga trazabilidad: en Flow Logs debe verse el REJECT (o el bloqueo en firewall/proxy), y en los logs de ejecución de la función debe quedar el error esperado. Si “falla” pero no hay rastro en red, no podrás investigar un caso real.

  • Prueba positiva mínima: valida que los endpoints necesarios funcionan (S3/Secrets Manager/DB interna) sin pasar por NAT.

    Esto evita el clásico escenario donde “todo funciona” porque el NAT está haciendo de bypass, y nadie se da cuenta hasta que intentas cerrarlo y rompes producción.

Cuando el control está bien implementado, el impacto en incident response es directo: puedes responder con contención rápida (revocar una ruta/policy), y puedes acotar exposición con evidencia de red en lugar de suposiciones.

Recomendaciones para entornos corporativos

El control de salida en serverless es efectivo cuando se apoya en diseño de red: subredes privadas sin ruta a Internet, consumo de servicios mediante VPC Endpoints y, si hace falta Internet, un único punto de egress con políticas y logging. Eso reduce drásticamente la probabilidad de exfiltración o C2 aunque una función sea comprometida.

En operación, el estándar debería ser: endpoints privados y policies restrictivas primero; NAT abierto nunca como “atajo”; y validación continua con pruebas negativas/positivas y telemetría (Flow Logs y logs del firewall/proxy). El resultado es menos incidentes silenciosos y, cuando ocurre uno, una investigación mucho más corta y defendible ante auditoría.


¿Te interesa la seguridad en Cloud?

Comparto análisis técnicos, laboratorios prácticos y experiencias reales sobre Cloud Security.

Política de privacidad