La aparición a mediados de 1998 de los VirtualHosts basados en nombre en la versión 1.3 del servidor web de Apache significó un cambio importante en el mundo de los servidores web ya que permitía configurar webs en diferentes dominios, todos ellos compartiendo la misma dirección IP. Tampoco hay que olvidar que esa mejora fue posible gracias a que los navegadores (por aquel entonces disponíamos de Netscape Communicator 4.5 e Internet Explorer 4.0 [1]) implementaron la versión 1.1 del protocolo HTTP, según la cual el navegador debe enviar el nombre del dominio al que se están conectando en la cabecera Host, usada por los servidores web para discriminar que a VirtualHost deben enviar la petición.
Desgraciadamente, las webs seguras nunca han podido beneficiarse de esta mejora por la forma en que funciona el protocolo HTTPS (HTTP sobre SSL). La cabecera Host que permite al servidor discernir el host virtual al que va dirigida la petición del usuario está a nivel HTTP, debajo del cual tenemos SSL y su negociación. En esa negociación es cuando el servidor envía al cliente su certificado X.509 y el navegador comprueba la validez del certificado comparando el campo CN del certificado con el nombre del dominio al que se está conectando. Por tanto, el servidor debe enviar el certificado correcto, y por tanto del host virtual correcto, al navegador antes que este le pueda indicar mediante la cabecera Host el dominio al que se quiere conectar. La única solución es usar VirtualHosts basados en dirección IP.
Sin embargo, si los diferentes dominios que tenemos en el servidor son subdominios que comparten el second-level domain, o SLD, podemos conseguir que todos ellos dispongan de web segura usando una única dirección IP. Por ejemplo www.example.com y mail.example.com tienen el SLD example.com en común.
Para conseguirlo, también es imprescindible el uso de un Wildcard SSL Certificate (he decidido no traducirlo porque ninguna de las tradicciones que se me han ocurrido me acababa de gustar), que es un certificado SSL en el que el nombre del dominio contiene un comodín. Por ejemplo: *.example.com. De esta manera, indicamos al navegador que este certificado es válido para cualquier subdominio de example.com. No voy a explicar como se generan los certificados SSL ya que buscando un poco por google se encuentran multitud de páginas al respecto. Si le vamos a dar un uso personal, podéis crear un self signed certificate. La única diferencia con los tutoriales que podáis encontrar es que como dominio introduciremos “*.<dominio>” (Por ejemplo: *.example.com).
Configuración del servidor web de Apache
Voy a suponer que en nuestro servidor tenemos configurado un servidor web Apache2 con varios host virtuales (www.example.com, mail.example.com y un servidor WebDAV para el Subversion en svn.example.com), y que el servidor sólo dispone de una dirección IP.
Lo primero que hay que hacer es poner el servidor web a escuchar en el puerto 443 (puerto estándar de HTTPS) y configurar un host virtual sobre ese puerto:
Listen 443
<VirtualHost *:443>
ServerName *.example.com
ErrorLog /var/log/apache2/https-error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog /var/log/apache2/https-access.log combined
ServerSignature Off
SSLEngine On
SSLCertificateFile /path/to/certs/example.com.pem
SSLCertificateKeyFile /path/to/private-keys/example.com.key
</VirtualHost> |
Con esto ya tenemos la capa SSL configurada. El certificado indicado en el parámetro SSLCertificateFile
es el Widlcard SSL Certificate que hemos creado anteriormente. A partir de aquí, el “truco” está en el uso creativo del mod_proxy, que conseguiremos añadiendo la siguiente configuración dentro del hosts virtual que acabamos de crear:
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
# Proxy requests to ourselves preserving the Host header
ProxyPass / http://localhost/
ProxyPassReverse / http://localhost/
ProxyPreserveHost on |
Con esta configuración estamos reenviando todas las peticiones que nos lleguen a este host virtual por HTTPS hacia el propio servidor, pero cambiando el protocolo de HTTPS a HTTP. Es importante destacar la última línea, que le indica al servidor web que debe mantener la cabecera Host al hacerse la petición a sí mismo, gracias a la cual no tendrá ningún problema a la hora de determinar que página quiere ver el usuario.
Para que las aplicaciones web puedan distinguir si la petición que les llega a entrado por HTTP o HTTPS, configuraremos el host virtual para que añada la cabecera X_FORWARDED_PROTO
a las peticiones que se hace a sí mismo:
# Add the X_FORWARDED_PROTO=https to allow applications to identify
# proxyed https connections
RequestHeader set X_FORWARDED_PROTO https |
Ya sólo queda un detalle, necesario únicamente si vamos a usar esta configuración para proteger un WebDAV. El problema de esta configuración con WebDAV viene a la hora de intentar hacer un ‘svn mv
‘, por ejemplo. Este comando indica el destino en la cabecera HTTP Destination
. Como el cliente se esta conectando a https://svn.example.com/repo/etc, la cabecera tendrá ese valor. Pero el host virtual que tiene la configuración de WebDAV sólo está configurado para HTTP por lo que espera que la cabecera Destination
empiece por http://svn… Para solucionar la inconsistencia entre la cabecera esperada y la recibida echaremos mano del mod_rewrite:
# Workarrounf for WebDAV Destination header
RewriteEngine on
RewriteCond %{HTTP:Destination} ^https://(.*)$
RewriteRule . - [env=DESTINATION:http://%1,PT]
RequestHeader set Destination %{DESTINATION}e env=DESTINATION |
Resumen de la configuración
Una vez explicadas las diferentes partes de la configuración, os pongo toda la configuración tal cual se debe escribir en los ficheros de configuración del servidor web para que no haya problemas de “¿y esto donde va?, ¿dentro o fuera del virtualhost?” :
Listen 443
<VirtualHost *:443>
ServerName *.example.com
ErrorLog /var/log/apache2/https-error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog /var/log/apache2/https-access.log combined
ServerSignature Off
SSLEngine On
SSLCertificateFile /path/to/certs/example.com.pem
SSLCertificateKeyFile /path/to/private-keys/example.com.key
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
# Workarrounf for WebDAV Destination header
RewriteEngine on
RewriteCond %{HTTP:Destination} ^https://(.*)$
RewriteRule . - [env=DESTINATION:http://%1,PT]
RequestHeader set Destination %{DESTINATION}e env=DESTINATION
# Add the X_FORWARDED_PROTO=https to allow applications to identify
# proxyed https connections
RequestHeader set X_FORWARDED_PROTO https
# Proxy requests to ourselves preserving the Host header
ProxyPass / http://localhost/
ProxyPassReverse / http://localhost/
ProxyPreserveHost on
</VirtualHost> |
Conclusiones
Personalmente veo está configuración como una solución válida para servidores personales en los que se suelen usar self signed certificates o de CAcert. Para pequeñas empresas (con unos pocos dominios) puede ser una solución, pero los Widlcard SSL Certificates de autoridades certificadoras comerciales (cuyos certificados raíz vienen en los navegadores) no son baratos. Dependiendo del hosting creo que saldría más barato contratar IPs adicionales para el servidor y certificados individuales que no usar el Widlcard SSL Certificate en un servidor con una sóla IP.
En empresas medianas o grandes, puede justificarse el uso de Widlcard SSL Certificates si tienen un gran número de subdominios que quieren asegurar ya que puede suponer un ahorro, pero no veo el motivo de tenerlo todo sobre una sóla dirección IP. Por tanto, no veo que la configuración aquí explicada sea aplicable en este tipo de empresas.
A mi, esta solución me está funcionando muy bien y de momento no he encontrado ningún problema.
Notas adicionales
[1] Para los nostálgicos, ¿os acordáis de la guerra de navegadores?