Con la aparición en los últimos tiempos de múltiples vulnerabilidades relacionadas con SSL/TLS, tales como BEAST y POODLE, y el bloqueo de cifrados obsoletos por parte de algunos navegadores, muchos clientes están teniendo problemas en sus aplicaciones web, ya que su stack tecnológico es antiguo y no soporta encriptación HTTPS moderna. La actualización de esas aplicaciones a menudo conlleva una larga y costosa migración que implica modernizar todo el stack (sistema operativo, servidor de aplicaciones, desarrollo...). Una solución sencilla y económica es utilizar NGINX como frontal HTTPS y así liberar de esta función a su actual infraestructura.
Otros clientes, sin embargo, buscan mejorar los tiempos de respuesta y el SEO de su aplicación web, o su solución de comercio electrónico, aprovechando las capacidades de NGINX y el soporte de HTTP/2.
NGINX es un servidor web opensource que fue desarrollado para solucionar el llamado C10K Problem. A diferencia de otros servidores, NGINX, gracias a su modelo de threading basado en eventos, es capaz de servir gran cantidad de peticiones de forma concurrente consumiendo muy pocos recursos.
NGINX se suele configurar como proxy inverso para conseguir dos grandes mejoras. Por un lado, libera a los servidores existentes de la gestión de las conexiones con el cliente, lo que también aporta seguridad pues evita la exposición directa a Internet de la plataforma actual, y por otro, el tratamiento del tráfico HTTPS, cuya encriptación tiene un alto coste de recursos de procesador.
Por otro lado, NGINX incorpora otras funcionalidades, tales como el cacheo de contenido y la compresión de tráfico, que aceleran la respuesta de las aplicaciones web, descargan a los servidores de backend y reducen el consumo de ancho de banda, mejorando de esta forma la experiencia del usuario al obtener respuestas más rápidas.
En el mercado existen soluciones que aportan funcionalidades similares en forma de appliances como F5 BIG-IP pero a un coste mucho mayor y con menor versatilidad.
Instalación
Vamos con la instalación y configuración. En este caso la instalación es para CentOS 7 por lo que agregamos el repositorio oficial de nginx y utilizamos yum para instalar el paquete:
rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
yum update -y openssl
yum install -y nginx
systemctl enable nginx
/bin/systemctl start nginx.service
En primer lugar necesitaremos un certificado, por lo que generaremos uno autofirmado con el siguiente comando:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/www.misitio.com.key -out /etc/nginx/www.misitio.com.crt
Generating a 2048 bit RSA private key
......+++
.................................+++
writing new private key to '/etc/nginx/www.misitio.com.key'
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [XX]:ES
State or Province Name (full name) []:Madrid
Locality Name (eg, city) [Default City]:Las Rozas
Organization Name (eg, company) [Default Company Ltd]:atSistemas
Organizational Unit Name (eg, section) []:SAAR
Common Name (eg, your name or your server's hostname) []:www.misitio.com
Email Address []:
Configuración
A continuación configuramos un nuevo servidor en /etc/nginx/conf.d/www.misitio.com.conf
server {
listen 443 ssl;
server_name www.misitio.com;
ssl_certificate /etc/nginx/www.misitio.com.crt;
ssl_certificate_key /etc/nginx/www.misitio.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparams.pem;
}
En las últimas versiones de nginx, ssl_protocols y ssl_ciphers tienen los valores por defecto indicados. Es importante en cualquier caso deshabilitar SSLv3 por ser inseguro. Los cifrados (ssl_ciphers) por defecto ofrecen un buen equilibrio entre seguridad y compatibilidad con clientes, aunque algunos de ellos no son demasiado robustos. Mozilla mantiene actualizada una lista de cifrados recomendados. Estos son los recomendados por Mozilla para seguridad y compatibilidad con un amplio rango de clientes:
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
Con ssl_prefer_server_ciphers indicamos que tiene preferencia el orden de cifrados del servidor frente al del cliente.
Los cifrados que utilizan el protocolo Diffie-Hellman son vulnerables debido a que por defecto utiliza parámetros de 1024 bits. Para evitar ésto generamos uno aleatorio de 2048 bits utilizando openssl:
openssl dhparam -out /etc/nginx/dhparams.pem 2048
Y agregamos la siguiente entrada en la configuración de NGINX:
ssl_dhparam /etc/nginx/dhparams.pem;
Con esta configuración, se consigue la siguiente calificación en SSL Labs a la vez que se soporta un amplio número de navegadores y clientes. Nótese que no se obtiene la calificación "A" debido a que estamos utilizando un certificado autofirmado.
A continuación definimos los servidores de backend y el proxy. Si agregamos varios servidores, NGINX distribuirá las peticiones entre ellos:
upstream backend {
server 10.0.0.11:80;
server 10.0.0.12:80;
keepalive 10;
}
server {
...
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
location / {
proxy_pass http://backend;
}
...
}
Con esta configuración (proxy_http_version y proxy_set_header Connection) forzamos el uso de HTTP 1.1 y Keepalive con los servidores de backend para reutilizar las conexiones.
La directiva keepalive actúa como un pool manteniendo abiertas el número de conexiones inactivas indicadas por cada worker contra los servidores de backend.
Con proxy_set_header Host remitimos la cabecera HTTP Host enviada por el cliente a los servidores de backend.
Optimización de HTTPS
HTTPS consume recursos de procesador debido a la encriptación, y principalmente durante el intercambio de claves inicial o handshake. Para mejorar los tiempos de respuesta del servidor y reducir el consumo de recursos podemos seguir la siguiente estrategia:
- Servir múltiples peticiones en una conexión y mantener las conexiones abiertas el mayor tiempo posible utilizando keepalive:
keepalive_requests 1000;
keepalive_timeout 75 75;
- Cachear las sesiones SSL para evitar generar una nueva sesión con cada conexión:
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 30m;
- Reducir la cantidad de tráfico utilizando compresión con gzip:
gzip on;
gzip_types text/html text/plain text/javascript text/css;
gzip_proxied any;
gzip_vary on;
- Reducir el número de peticiones al servidor cacheando en el cliente los ficheros estáticos por mucho tiempo:
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
expires max;
add_header Cache-Control "public";
}
- Utilizar HTTP/2, la nueva versión del HiperText Transfer Protocol. HTTP/2 está basado en protocolo SPDY de Google y permite realizar múltiples peticiones paralelas utilizando una sola conexión frente a HTTP 1.1, donde las peticiones son secuenciales utilizando keepalive. Sin embargo, por el momento solo está soportado por las últimas versiones de algunos navegadores modernos.
listen 443 ssl http2;
Ejemplo
upstream backend {
server 10.0.0.11:80;
server 10.0.0.12:80;
keepalive 10;
}
server {
listen 443 ssl http2;
server_name www.misitio.com;
ssl_certificate /etc/nginx/www.misitio.com.crt;
ssl_certificate_key /etc/nginx/www.misitio.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_dhparam /etc/nginx/dhparams.pem;
keepalive_requests 1000;
keepalive_timeout 75 75;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 30m;
gzip on;
gzip_types text/html text/plain text/javascript text/css;
gzip_proxied any;
gzip_vary on;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
expires max;
add_header Cache-Control "public";
proxy_pass http://backend;
}
location / {
proxy_pass http://backend;
}
}
Conclusión
En este artículo hemos visto cómo solventar los quebraderos de cabeza de una encriptación HTTPS moderna sobre nuestra infraestructura actual de una forma sencilla y económica, empleando y configurando NGINX como frontal HTTPS.
Si te ha gustado ¡Sigue a “En mi local funciona” en Twitter para estar al día de éste y otros temas de interés!