BORRADOR: Evaluación de Apache HTTP server como proxy/caché HTTPS en ambos extremos

Advertencia

Este documento se escribió en junio de 2013 como un borrador de informe para un cliente. El informe no llegó a completarse nunca porque al cliente le bastó el borrador para avanzar al siguiente paso del proyecto.

Por tanto, se trata de un documento incompleto y que, además, refleja el estado de la tecnología en junio de 2013. Las cosas han evolucionado desde entonces, naturalmente.

La necesidad concreta del cliente es un proxy HTTPS cuyo backend sea también HTTPS.

Este documento se ha publicado por interés histórico y con el permiso explícito del cliente.

1   Introducción

DXXXl CXXXn se pone en contacto telefónico conmigo la tarde del domingo 28 de abril de 2013, indicándome que las divisiones de banca del grupo SXXXr han sido amenazadas con ataques de denegación de servicio, que se realizarían a partir del 7 de mayo.

Con el fin de protegerse en lo posible, se está procediendo a configurar sistemas y a contratar servicios externos de paliación de ataques DDoS. Simultáneamente se planea desarrollar tecnología interna con este fin, con el propósito de disponer de varias vías de defensa alternativas, con diferentes costes y calidades.

En la conversación telefónica se me encarga la evaluación del servidor web Apache como plataforma proxy para la detección y prevención de ataques a servicios HTTPS. El plazo disponible es de una semana y el trabajo se tasa en XXX €.

2   Requisitos

El proyecto requiere cumplir estos requisitos:

  1. El servicio debe atender peticiones HTTPS en la parte de cliente.
  2. El servicio debe actuar de proxy de la forma más transparente posible (al usuario) hacia el sistema backend.
  3. La conexión al sistema backend debe hacerse también por HTTPS.
  4. El servicio debe escalar vertical (conexiones simultáneas en un mismo servidor) y horizontalmente (múltiples servidores proxy).
  5. Un mismo proxy prestará servicios a muchos backends HTTPS distintos.
  6. El servicio debe ser capaz de afrontar y notificar ataques DDoS básicos.
  7. Idealmente, el servicio debe poder realizar también análisis de las peticiones para detectar y notificar (e idealmente, detener) ataques a nivel HTTP y a nivel de aplicación.
  8. El proxy debe realizar también cacheo de objetos HTTP para reducir la carga en los servidores de backend, aunque para conseguir un nivel de “hits” importantes se requerirá modificar las aplicaciones de backend para añadir las cabeceras HTTP necesarias, etc.

DXXXl CXXXn sugiere utilizar el servidor web Apache porque se trata de un servidor capaz, con una gran reputación y presencia en el mercado, muy maduro y versátil. Además, el personal actual de PXXXn ya está familiarizado con él, así que soportar un producto basado en esta tecnología sería simple y tendría una curva de aprendizaje baja. Adicionalmente, el servidor web Apache dispone ya de módulos externos como “mod_security”, que se juzgan una buena base para este proyecto.

3   Evaluación preliminar

3.1   Apache HTTP 2.4

Las versiones antiguas de 1.x de Apache HTTP procesan cada petición web en un proceso separado. Esto ocasiona un consumo de memoria elevado y proporcional al número de conexiones simultáneas que se están atendiendo.

La versión 2.2.x de Apache HTTP permite lanzar hilos para atender las peticiones, además de procesos (combinando ambas). La gestión es más eficiente, pero el consumo de memoria sigue siendo proporcional (con una constante menor) al número de peticiones simultáneas al sistema. En el caso de los hilos, el consumo fundamental de memoria es el “stack” privado para cada hilo, que en una configuración Linux típica es de 8 megabytes. Dependiendo de la versión de kernel de Linux que se utilice, de los módulos Apache empleados y de configuraciones finas, puede ser posible reducir el consumo de memoria a 1 megabyte por hilo. En cualquier caso, el consumo de memoria es proporcional al número de conexiones simultáneas.

La última versión de Apache HTTP es la 2.4.x. La principal innovación que nos interesa aquí es la disponibilidad de un modelo “Event”, en el que las conexiones actualmente “idle” no consumen un hilo dedicado, sino que se gestionan por un único hilo especializado. En ese sentido, el número de hilos (y, por tanto, ocupación de memoria) no es proporcional al número de conexiones al sistema, sino al número de conexiones activas. El tráfico de las conexiones que ya han realizado una petición y a las que se está enviando la respuesta, se maneja también por este hilo especializado.

Este enfoque es prometedor y permite albergar esperanzas de que esta versión de Apache HTTP pueda cubrir nuestras necesidades.

Apache HTTP en sí tiene muy pocos controles para paliar y notificar un ataque DDoS, básicamente se limita a poder configurar algunos timeouts en las peticiones y en los “keep-alive”, y algunos tamaños máximos en cabeceras y cuerpos HTTP. No obstante Apache HTTP es muy extensible y existe un módulo “mod_security” que pretende cubrir esas carencias. Una revisión superficial de “mod_security” me confirma que puede ser una buena base.

3.2   Estudio de la competencia de Apache HTTP

Conocidos los problemas de escalabilidad tradicionales de Apache (a falta de evaluar el modelo “event” del nuevo Apache 2.4.x), se revisaron someramente otros productos proxy/cache con una escalabilidad y rendimiento bien reputados.

Es de destacar que prácticamente todas las publicaciones de rendimiento de estos productos son para entornos de proxy/caché HTTP <-> HTTP. Cualquier decisión basada en rendimiento necesitaría verificar de primera mano las métricas publicadas.

Ninguno de estos productos dispone de un módulo comparable a “mod_security”, aunque algunos permiten integrar código propio de forma simple.

3.2.1   Varnish

Varnish es interesante porque permite escribir módulos muy fácilmente que se conectan a eventos críticos dentro de una conexión HTTP, como puede ser la propia conexión TCP/IP. De esta forma es simple escribir un módulo de detección DDoS que se ejecute a velocidad nativa.

Pero Varnish no admite HTTPS y, de hecho, su autor ha expresado su oposición a implementar HTTPS en Varnish de forma rotunda [1]. La solución habitual consiste en situar un proxy [2] HTTPS <-> HTTP delante, pero eso penaliza en rendimiento y complica la detección DDoS.

[1] Why no SSL? https://www.varnish-cache.org/docs/trunk/phk/ssl.html.
[2] Pound: http://www.apsis.ch/pound/

3.2.2   Nginx

Nginx puede actuar como servidor HTTPS de cara al cliente y también permite conectar al servidor de backend mediante HTTPS.

No se ha evaluado si nginx permite ser mejorado con módulos para dotarlo de protección DDoS.

Es un contendiente a evaluar con más atención.

3.2.3   lighttpd

Lighttpd soporta HTTPS de cara al cliente, pero la documentación no muestra que soporte HTTPS en el backend.

No se ha evaluado si lighttpd permite ser mejorado con módulos para dotarlo de protección DDoS.

3.2.4   ha-proxy

La versión de ha-proxy actualmente en producción es la 1.4.x. No contiene soporte HTTPS. La posición oficial del autor del software es que la gestión de SSL/TLS debe realizarse por software/hardware especializado separado.

Debido a la presión de la industria, no obstante, la versión 1.5 del producto, actualmente en desarrollo, contiene soporte HTTPS [3]. Este soporte es experimental y es de esperar que haya que esperar un tiempo hasta que sea utilizable en entornos de producción exigentes. En cualquier caso, la versión 1.5 aún no está disponible.

[3]

http://haproxy.1wt.eu/

Having SSL in the load balancer itself means that it becomes the bottleneck. When the load balancer's CPU is saturated, the overall response times will increase and the only solution will be to multiply the load balancer with another load balancer in front of them. the only scalable solution is to have an SSL/Cache layer between the clients and the load balancer. Anyway for small sites it still makes sense to embed SSL, and it's currently being studied. There has been some work on the CyaSSL library to ease integration with HAProxy, as it appears to be the only one out there to let you manage your memory yourself. Update [2012/09/11] : native SSL support was implemented in 1.5-dev12. The points above about CPU usage are still valid though.

3.2.5   Apache Traffic Server

Este producto no fue evaluado durante el trabajo inicial, pero lo puse sobre la mesa una vez que determiné que Apache HTTP es inadecuado para las necesidades a cubrir.

Apache Traffic Server permite acceso HTTPS desde los clientes y permite que los backends sean accedidos por HTTPS.

Además dispone de un API documentado (aunque la documentación es parcial y anticuada) para escribir plugins capaces de actuar en puntos estratégicos de la gestión de las conexiones, como la propia conexión TCP/IP en sí.

Por último, se trata de un servidor proxy/caché con capacidad para poder ser integrado en una jerarquía más compleja, mediante configuración directa, WCCP e ICP.

3.2.6   Squid

Squid permite que los clientes se conecten por HTTPS, pero para que el backend sea accesible a través de HTTPS es necesario definirlo como un miembro de la jerarquía de cachés (un “peer”) y no permite verificar su certificado SSL.

Squid no dispone de un API que permita implementar módulos para dotarlo de protección DDoS.

Squid provee algunas funcionalidades interesantes, como permitir autentificación SSL de cliente en un entorno HTTPS <-> HTTPS [4].

[4]

http://wiki.squid-cache.org/Features/BumpSslServerFirst

When a server asks for a client certificate, Squid may be able to ask the client and then forward the client certificate to the server. Such client certificate handling may not be possible with the bump-client-first scheme because it would have to be done after the SSL handshake.

3.2.7   g-wan

G-wan no fue evaluado durante el trabajo inicial, es un producto descubierto con posterioridad.

Soporta HTTPS en la parte cliente. La documentación es bastante caótica y no es evidente determinar si también permite acceso HTTPS al backend.

No se ha evaluado si g-wan permite ser mejorado con módulos para dotarlo de protección DDoS.

4   Evaluación

PXXXn realizará la instalación final usando Red Hat Linux, pero para las pruebas yo voy a utilizar una máquina virtual con Ubuntu 12.04 64 bits, con 2GB de RAM y 4 GB de disco duro: 3GB en “root”, 1GB de SWAP, bajo LVM. Sin cifrado de disco.

Instalamos OpenSSH y servidor DNS (ojo con su seguridad). Servidor completamente parcheado. Sin cortafuegos, ya que estamos en una máquina virtual.

Usuario: jcea/abc.

En la máquina virtual, mapeo de puertos: 11122->22, 11180->80, 11443->443.

Copio mi clave pública SSH para poder acceder desde mi portátil de forma simple.

La compilación de todo el software específico se realiza a través de scripts de configuración para tener configuraciones documentadas y reproducibles.

Dichos scripts y la configuración de los diferentes servicios se mantienen en un entorno de control de versiones.

Para intentar tener un entorno cercano a producción, genero certificados X.509 con claves RSA de 2048 bits. Usamos dos niveles, un CA y los certificados finales.

En el servidor Apache se configuran tres servidores virtuales: un servidor de monitorización, un servidor SSL externo, al que acceden los clientes, y un servidor SSL interno, haciendo de servidor “upstream” HTTPS.

Configuro Apache HTTP 2.4.4 para que se ejecute en el arranque. Pongo un “/server-status” y un “/server-info” en 11180. Sin control de acceso: http://www-status.XXX:11180/server-status .

Pongo “www-status.XXX” en mi “/etc/host”, mapeado a 127.0.0.1.

5   Detalles a verificar

  • Revisar los permisos de las claves secretas SSL.

  • Las claves secretas SSL no están protegidas con clave.

  • Los errores en el proxy no deben filtrar información sobre los servidores “upstream” (sus nombres o sus IPs, por ejemplo).

  • El proxy debe verificar los certificados X.509 de los servidores “upstream”.

  • ¿Cómo vamos a canalizar las IPs de origen a través de los proxies?

  • ¿Soporte SNI en el frontal? Problemático con clientes antiguos.

  • Revisar con atención la configuración de las conexiones persistentes entre el proxy y los servidores “upstream”.

  • ¿Las aplicaciones y los balanceadores intermedios funcionan si se canalizan peticiones de distintos usuarios a través de una única conexión persistente con el “upstream”?.

  • ¿Activar la compresión entre el proxy y el “upstream”? ¿Entre el proxy y los clientes?

  • Configurar correctamente la negociación de la calidad del cifrado SSL/TLS.

  • Método “TRACE”.

  • “mod_log_debug” puede configurarse para hacer log de los “timeouts” de los clientes.

  • A la hora de realizar las pruebas de rendimiento, tengo que configurar mi portátil para que mantenga la CPU a 2.4Ghz y obtener así resultados estables y representativos.

    Por algún extraño motivo, obtengo más rendimiento con mi máquina cargada que con mi máquina en idle. Posiblemente un tema de “scheduling” entre mi kernel y la máquina virtual. Obtengo 65.6 peticiones por segundo, 34.8 si tengo la CPU a 800Mhz. Estas cifras sirven para poder compararse entre sí, no como valoración de rendimiento del entorno de producción.

  • Configurar ProxyVia/ProxyReverseHost.

  • Configurar ProxyPass con parámetros.

  • Configurar ProxyErrorOverride.

  • Configuro ProxyStatus, pero no sale nada. Insistir. ¿Tal vez no funciona para proxies inversos?.

  • Evaluar “mod_proxy_express”.

  • Evaluar “mod_proxy_html”.

  • Evaluar “mod_proxy_balancer”. Activar el balancer-manager. Configuración en caliente.

  • ¿Estamos delante o detrás del balanceador?. Importante para el “pool”, “stickiness”, “balanceador apache”.

  • Configurar SSLProxy*.

  • Evaluar SSLStapling*.

  • Para evitar la sobrecarga de la negociación SSL/TLS, es imprescindible que los clientes reutilicen en lo posible las sesiones SSL/TLS. Configuro “SSL session cache”, pero no parece funcionar.

    El “lock” se guarda en un fichero borrado (pero accesible para los hijos del servidor Apache), pero no el caché SSL.

    Con “dbm” funciona, crea el fichero, pero no me parece usable en absoluto.

    apt_shm_create

    Si manipulo el código, veo que se crea el fichero, pero no guarda nada en él.

    Parcheando con “debug” a saco, veo que busca sesiones en la base de datos, pero también veo que no escribe nunca.

    Tras días investigando el asunto y escribiendo herramientas “ad hoc” para ello, identifico el problema: Si el cliente lo soporta, Apache utiliza “tickets” TLS, definidos en RFC 5077.

    Si la librería OpenSSL está compilada con soporte de “tickets” TLS, entonces Apache los utiliza incondicionalmente. No es configurable. Manipulo el código para desactivar el soporte de “tickets” TLS y entonces Apache sí emplea adecuadamente la base de datos de sesiones SSL.

    Todo esto es importante porque en la visualización del estado y las estadísticas Apache hay una sección sobre sesiones SSL, cuántas están activas y detalles de su reutilización. Cuando el cliente emplea “tickets” TLS, que he comprobado que es la norma, esa situación no se refleja en esas estadísticas, causando confusión y haciendo que la evaluación sea imposible. Lo que aparece es que el primer acceso de un cliente es un “miss”, pero los accesos subsiguientes, con “tickets” sencillamente no se ven en la estadística SSL/TLS. Entiendo que esto es un bug de Apache HTTP, tal vez porque la librería OpenSSL no exporta esa información. Verificarlo y notificarlo.

    Si desactivo el manejo de sesiones SSL en Apache, los clientes siguen pudiendo usar “tickets”. No es configurable en Apache.

    Me escribo un cliente que comprueba la recuperación de sesiones SSL y del uso de “tickets” TLS, y compruebo los puntos anteriores con detalle.

    Advertencia

    La idea de todo esto es poder monitorizar la efectividad del sistema de "tickets" TLS, porque un mecanismo de ataque evidente al servicio es obligarle a hacer negociaciones TLS redundantes o espúreas.

  • Si tenemos varios proxies, todos ellos deberían usar la misma clave para proteger los “tickets” TLS, aunque por seguridad dicha clave debería cambiarse con frecuencia (horas).

  • El uso de “tickets” TLS tiene implicaciones de seguridad importantes, como la desaparición de “forward secrecy” en caso de compromiso del servidor o de la clave de protección de los “tickets” TLS.

  • Esto es aún más crítico cuando queremos que la clave de protección de “tickets” TLS esté compartida entre todos los proxies para reutilizar el máximo posible de sesiones TLS. Esto puede no ser un problema si solo tenemos un proxy por “cuenca” BGP.

  • Falta verificación de certificados proxy.

  • mod_security

    La protección DDoS no es muy capaz. Se limita a controlar el número de conexiones por IP, esté el servidor saturado o no. Para eso se puede hacer lo mismo a nivel de kernel, con “iptables -m connlimit”.

  • Debido a la ocupación de memoria y a que el uso avanzado de módulos Apache que vamos a hacer es muy limitado, debemos usar pocos procesos con muchos hilos. Aún así, con la configuración de kernel “normal”, estamos hablando de 8 megabytes por conexión.

  • Aunque Apache 2.4 tiene un modelo “Event”, que permitiría aliviar sobremanera este problema, no veo que haga nada en absoluto.

    Parcheando el código veo que sigue manteniendo un hilo por conexión, aunque estén “idle” o aunque estén sirviendo contenido ya generado. Invierto mucho tiempo investigando el asunto.

    Compruebo que cuando las conexiones no van cifradas, el modelo “event” sí funciona como se espera. Resulta que está documentado que no funciona (sin ningún tipo de “warning”) cuando se trabaja con conexiones SSL/TLS [5]. Esto hace que mis esperanzas de escalabilidad con Apache HTTP en nuestro entorno, 100% HTTPS, se frustren y descarta el producto. Vuelta al tablero de dibujo.

  • Preguntados sobre el particular, aparentemente Apache HTTP 2.5, la versión de desarrollo no apta para producción, soluciona este problema. Pero no hay fecha de publicación de la versión estable 2.6.

  • Tengo problemas también para configurar de forma separada los “timeouts” de los “keep-alive” en el proxy, en la parte “cliente” y en la parte “upstream” [6]. La documentación indica que se trata de un proceso delicado, pero incluso siguiendo el procedimiento indicado [7], no funciona. Parece ser un bug. Investigar.

[5]

https://httpd.apache.org/docs/2.4/mod/event.html

The improved connection handling does not yet work for certain connection filters, in particular SSL. For SSL connections, this MPM will fall back to the behaviour of the worker MPM and reserve one worker thread per connection.

[6]

“Apache: How to configure different KeepAliveTimeout in different named virtual hosts”:

http://superuser.com/questions/591616/apache-how-to-configure-different-keepalivetimeout-in-different-named-virtual-h

[7]

https://httpd.apache.org/docs/2.4/mod/core.html#keepalivetimeout

In a name-based virtual host context, the value of the first defined virtual host best matching the local IP and port will be used.

6   Conclusión y recomendación final

Apache HTTP 2.4 consume un mínimo de 8 megabytes de memoria por conexión simultánea HTTPS al sistema. Solo este hecho excluye el uso de este producto para la aplicación demandada. Es cierto que la mayor parte de esa memoria no se va a utilizar y solo es preciso que esté disponible en el SWAP (para evitar el “overcommiting” de memoria), pero Apache HTTP no es un servidor proxy de alto rendimiento, sino un servidor web extremadamente versátil, con capacidad de actuar como proxy caché.

Adicionalmente, dotar de soporte HTTPS a un producto proxy/caché ya existente tiene un coste apreciable y requiere de unos conocimientos muy especializados del producto, sobre todo en productos asíncronos, que son precisamente los más escalables con el estado del arte actual.

De los productos alternativos analizados, solo nginx y Apache Traffic Server parecen merecer una evaluación más detallada.

Dado que el grupo SXXXr quiere contribuir a la fundación Apache y que la documentación del Apache Traffic Server parece ser muy completa y detallada, sugiero evaluar primero este producto.

Advertencia

Recuerda que este documento es un borrador. Contiene errores, trabajo en progreso y no está completo.