Let's Encrypt Ghost

Aunque el título esté en inglés no os asustéis, simplemente he utilizado el nombre del proyecto sobre el que vamos a hablar hoy para titular esta entrada, el resto del post será en castellano.

Desde que configuré por primera vez Ghost he querido encriptar todo el tráfico del blog bajo un certificado seguro, además google se está poniendo un poco pesado respecto a la reputación en las búsquedas. Afortunadamente hace un tiempo que tenemos un proyecto que proporciona certificados seguros gratuitamente, Let's Encrypt. Teniendo el blog montado sobre un servidor web Nginx la configuración es más entretenida que con un Apache.

Pero antes de entrar en materia vamos a poner un poco de orden en la configuración de Nginx ya que desde que lo configuré todo no la he tocado y me he dado cuenta de que podemos mejorarla.

Lo primero que vamos a hacer es sacar la configuración del blog a un archivo aparte y lo situaremos en su lugar natural /etc/nginx/sites-available, para luego activarla mediante un enlace simbólico a /etc/nginx/sites-enable. Según vimos en los posts anteriores referentes al montaje del blog toda nuestra configuración recae en el fichero /etc/nginx/nginx.conf. Lo que vamos a hacer es sacar la entrada referente a server a un archivo nuevo llamado /etc/nginx/sites-available/ghost:

upstream ghost_upstream {  
        server                          127.0.0.1:2368;
        keepalive                       64;
}

server {  
        listen                          80;
        server_name                     libreadmin.es www.libreadmin.es;

        location / {
                proxy_redirect          off;
                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_set_header        Host                    $http_host;
                proxy_set_header        X-NginX-Proxy           true;
                proxy_set_header        Connection              "";
                proxy_http_version      1.1;
                proxy_cache             one;
                proxy_cache_key         ghost$request_uri$scheme;
                proxy_pass              http://ghost_upstream;
        }

        access_log              /var/log/nginx/access-libreadmin-es.log;
        error_log               /var/log/nginx/error-libreadmin-es.log;
}

Si comparáis con la configuración anterior, aparte del hecho de que he extraído las declaraciones de server y upstream también he limpiado algunas partes heredadas de versiones anteriores de Ghost.
Ni que decir tiene que he eliminado estas partes del fichero original /etc/nginx/nginx.conf.

Ahora pasamos a la parte que nos ha traído hasta aquí Let's Encrypt. En este enlace del proyecto podréis aprender sobre el funcionamiento de estos certificados, aunque como está en inglés os lo resumiré: tendremos un agente en nuestro servidor que se encargará de verificar la autenticidad del dominio contra el servidor principal del proyecto y nosotros nos encargaremos de que este procedimiento se realice periódicamente.

Como decía antes, si tuviéramos un servidor web Apache sobre una máquina con Ubuntu 16.04, este agente lo configuraría todo automáticamente, pero como no es nuestro caso tendremos que mancharnos un poco las manos.

La obtención de un certificado de este tipo está explicado en la propia página del proyecto, de nuevo, en un perfecto inglés, pero aquí estamos para explicarlo detenidamente.

En un principio diferencian entre servidores a los que tenemos acceso vía shell y a los que no. Para los segundos, que no es nuestro caso, deberíamos comprobar si nuestro proveedor de hosting soporta Let's Encrypt, aunque si no es así también podremos instalarnos el agente en nuestro ordenador, generar el certificado y aplicarlo, siempre que soporte la subida de certificados externos. Como nosotros tenemos acceso completo no cubriremos el proceso para la otra opción.

Hay muchos agentes que implementan el protocolo ACME, sí lo sé, cualquiera que haya crecido viendo los dibujos del coyote y el correcaminos esbozará una sonrisa al leer el término. Este protocolo, llamado así por las iniciales de lo que representa, Automatic Certificate Management Environment, sirve precisamente para que los agentes negocien la generación de los certificados seguros con el servidor oficial. Como decía, hay multitud de implementaciones de este protocolo aunque el agente recomendado por el propio proyecto es CertBot.

En su página podremos seleccionar el sistema operativo que estamos utilizando y nuestro servidor web con lo que obtendremos las instrucciones precisas para encriptar el tráfico de nuestra página.

Si elegís, como es mi caso, la combinación Ubuntu Server 16.04 y Nginx la página os mostrará las instrucciones, en este caso concreto los pasos automáticos coinciden con los avanzados ya que no existe un método completamente automatizado. El primer paso de estas instrucciones pasan por instalar el paquete oficial del proyecto:

# apt install letsencrypt

Veréis en la lista unos cuantos paquetes referentes al lenguaje Python ya que se ha elegido oficialmente este lenguaje para la implementación del protocolo ACME. Según nos explican en la misma página el plugin elegido para descargar el certificado se llama webroot y parece ser un método que funciona con casi todos los servidores web, en futuras versiones de Ubuntu espero que el sistema se automatice completamente.

Antes de lanzar el comando necesario para descargarnos el certificado debemos permitir que nuestro robot pueda publicar en el servidor web cierta información que intercambiará con su nave nodriza. Para ello añadimos el siguiente apartado al archivo de configuración /etc/nginx/sites-available/ghost, dentro de la configuración server:

[...]
    location ~ ^/.well-known {
        root /var/www/ghost;
    }
[...]

Y después de reiniciar el servicio de nginx, el siguiente paso es invocar a nuestro robot:

# letsencrypt certonly --webroot -w /var/www/ -d libreadmin.es -d www.libreadmin.es

La sintaxis como véis es muy simple, le decimos el directorio de la página web y el dominio para el que queremos el certificado. Tras ejecutarlo nos preguntará un correo para las notificaciones y nos pedirá que aceptemos las condiciones de la licencia. La dirección de correo es importante ya que a través de éste nos notificarán todo lo relacionado con nuestro certificado.

Los archivos descargados por el robot estarán en el directorio /etc/letsencrypt/keys. Ahora ya estamos en disposición de encriptar nuestro blog, también podemos optar por encriptar únicamente la parte de administración de ghost aunque como ya dije al principio, los buscadores se están poniendo serios con la encriptación por lo que vamos a proteger todo el site. Dividiremos nuestro archivo de configuración /etc/nginx/sites-available/ghost en dos apartados server. La parte que responderá en el puerto 80 quedaría como sigue:

server {  
        listen                          80;
        server_name                     libreadmin.es www.libreadmin.es;

        location ~ /.well-known {
                root                    /var/www;
        }

        location / {
                return                  301 https://www.$server_name$request_uri;
        }

        access_log                      /var/log/nginx/access-libreadmin-es.log;
        error_log                       /var/log/nginx/error-libreadmin-es.log;
}

Aquí podemos ver cómo conservamos la parte de logs para poder diferenciar las peticiones que nos lleguen directamente a la parte cifrada aunque todavía tengo que estudiar si realmente merece la pena. Sólamente haremos una redirección a la parte cifrada para todo lo que nos llegue por el puerto 80. Ahora vamos a ver cómo quedaría la parte correspondiente a las peticiones que nos vengan por la parte con certificado:

server {  
        listen                          443 ssl;
        server_name                     libreadmin.es www.libreadmin.es;

        location ~ /.well-known {
                root /var/www;
        }

        location / {
                proxy_redirect          off;
                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_set_header        Host                    $http_host;
                proxy_set_header        X-NginX-Proxy           true;
                proxy_set_header        Connection              "";
                proxy_http_version      1.1;
                proxy_cache             one;
                proxy_cache_key         ghost$request_uri$scheme;
                proxy_pass              http://ghost_upstream;
        }

        ssl                             on;
        ssl_certificate                 /etc/letsencrypt/live/libreadmin.es/fullchain.pem;
        ssl_certificate_key             /etc/letsencrypt/live/libreadmin.es/privkey.pem;
        ssl_prefer_server_ciphers       on;
        ssl_protocols                   TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers                     ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;

        access_log              /var/log/nginx/access-libreadmin-es-ssl.log;
        error_log               /var/log/nginx/error-libreadmin-es-ssl.log;
}

Es una configuración clásica de encriptación. Después de reiniciar nuestro servicio de nginx podremos ver cómo se cifra todo nuestro tráfico. Ahora bien, estos certificados caducan en noventa días por lo que hay que establecer una renovación periódica. Lo más fácil es crear una entrada en el cron cada sesenta días, por tener un margen de actuación en caso de que no funcione este procedimiento, en mi caso realizo un par de operaciones ya que el certificado requiere un reinicio del servidor web:

# For letsencrypt renovations
0 0     1 1,3,5,7,9,11 *        /usr/bin/letsencrypt renew --agree-tos ; /bin/systemctl restart nginx.service  

Para testear una renovación sin una ejecución real podemos ejecutar lo siguiente:

# letsencrypt renew --dry-run --agree-tos

Si en la salida véis un aviso como éste:

WARNING:letsencrypt.client:Registering without email!  

no os preocupéis porque es normal, ya comprobaréis como en la primera renovación la cosa funciona sin problemas.

Para la canción de este mes he elegido algo un poco más oscuro, KoRn, el tema Here To Stay de su disco Untouchables, si los pilláis de gira no dudéis en verlos, yo incluso le toqué las rastas al cantante en un Festimad.

Y poco más que decir, espero que disfrutéis de vuestros certificados digitales gratuitos, ya iba siendo hora.