Tutorial

Cómo proteger una aplicación de Node.js en contenedor con Nginx, Lets Encrypt y Docker Compose

Node.jsSecurityDockerLet's EncryptUbuntu 18.04

Introducción

Existen varias formas de mejorar la flexibilidad y la seguridad de su aplicación de Node.js. Utilzar un proxy inverso como Nginx le permite cargar solicitudes de equilibrio, almacenar en caché contenido estático e implementar seguridad en la capa de transporte (TLS). Habilitar HTTPS cifrado en su servidor garantiza que la comunicación hacia y desde su aplicación permanezca protegida.

La implementación de un proxy inverso con TLS/SSL en contenedores implica un conjunto de procedimientos diferente del que se emplea trabajar directamente en un sistema operativo host. Por ejemplo, si obtiene certificados de ​​​Let’s Encrypt​​​ ​para una aplicación que se ejecuta en un servidor, instalaría el software requerido directamente en su host. Los contenedores le permiten adoptar un enfoque diferente. Utilizando Docker Compose, puede crear contenedores para su aplicación, su servidor web y el cliente de Certbot que le permitirá obtener sus certificados. Siguiendo estos pasos, puede aprovechar la modularidad y portabilidad de un flujo de trabajo en contenedor.

A través de este tutorial, implementará una aplicación Node.js con un proxy inverso de Nginx utilizando Docker Compose. Obtendrá certificados TLS/SSL para el dominio asociado con su aplicación y garantizará que reciba una alta calificación de seguridad de SSL Labs. Por último, configurará una tarea cron para renovar sus certificados de modo que su dominio permanezca seguro.

Requisitos previos

Para seguir este tutorial, necesitará lo siguiente:

  • Un servidor de Ubuntu 18.04, un usuario no root con privilegios sudo y un firewall activo. Para obtener información sobre cómo configurarlos, consulte esta guía de configuración inicial de servidores.
  • Docker y Docker Compose instalados en su servidor. Para obtener orientación sobre la instalación de Docker, siga los pasos 1 y 2 de Cómo instalar y usar Docker en Ubuntu 18.04. Para acceder a orientación relacionada con la instalación de Compose, siga el paso 1 de Cómo instalar Docker Compose en Ubuntu 18.04.
  • Un nombre de dominio registrado. En este tutorial, se utilizará example.com en todo momento. Puede obtener un ejemplar gratis en Freenom o utilizar el registrador de dominios que desee.
  • Los dos registros DNS que se indican a continuación se configuraron para su servidor. Puede seguir esta introducción al DNS de DigitalOcean para obtener información sobre cómo agregarlos a una cuenta de DigitalOcean, si usa una:

    • Un registro A con example.com​​​ orientado a la dirección IP pública de su servidor.
    • Un registro A con example.com​​​ ​​orientado a la dirección IP pública de su servidor.

Paso 1: Clonar y probar la aplicación de Node

Como primer paso, clonaremos el repositorio con el código de aplicación de Node, que incluye el Dockerfile que utilizaremos para crear nuestra imagen de aplicación con Compose. Primero, podemos probar la aplicación compilándola y ejecutándola con el comando docker run, sin un proxy inverso o SSL.

En el directorio principal de su usuario no root, clone el repositorio nodejs-image-demo de la cuenta de GitHub de la comunidad de DigitalOcean. Este repositorio incluye el código de la configuración descrita en Cómo crear una aplicación de Node.js con Docker.

Clone el repositorio en un directorio llamado node_project:

  • git clone https://github.com/do-community/nodejs-image-demo.git node_project

Pase al directorio node_project:

  • cd node_project

En este directorio, hay un Dockerfile que contiene instrucciones para crear una aplicación de Node usando la imagen Docker node:10 y el contenido de su directorio de proyecto actual. Puede consultar el contenido de Dockerfile escribiendo lo siguiente:

  • cat Dockerfile
Output
FROM node:10-alpine RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app WORKDIR /home/node/app COPY package*.json ./ USER node RUN npm install COPY --chown=node:node . . EXPOSE 8080 CMD [ "node", "app.js" ]

Estas instrucciones crean una imagen de Node copiando el código del proyecto del directorio actual al contenedor e instalando dependencias con npm install. También aprovechan el almacenamiento en caché y la disposición en capas de imágenes de Docker al separar la copia de package.json y package-lock.json, que contienen las dependencias listadas del proyecto, de la copia del resto del código de aplicación. Por último, las instrucciones especifican que el contenedor se ejecutará como usuario del nodo no root con los permisos apropiados establecidos en el código de aplicación y los directorios de node_modules.

Para obtener más información sobre prácticas recomendadas de Dockerfile y Node, consulte el análisis completo en el paso 3 de Cómo crear una aplicación Node.js con Docker.

Para probar la aplicación sin SSL, puede crear y etiquetar la imagen usando docker build y el indicador -t. Daremos a la imagen el nombre node-demo, pero puede elegir cualquier otro nombre:

  • docker build -t node-demo .

Una vez que el proceso de compilación esté completo, podrá enumerar sus imágenes con docker images:

  • docker images

Visualizará el siguiente resultado, que confirma la compilación de la imagen de la aplicación:

Output
REPOSITORY TAG IMAGE ID CREATED SIZE node-demo latest 23961524051d 7 seconds ago 73MB node 10-alpine 8a752d5af4ce 3 weeks ago 70.7MB

A continuación, cree el contenedor con docker run. Incluiremos tres marcas con este comando:

  • -p: edita el puerto en el contenedor y lo asigna a un puerto en nuestro host. Usaremos el puerto 80 en el host, pero puede modificarlo como considere necesario si tiene otro proceso en ejecución en ese puerto. Para obtener más información sobre cómo funciona, consulte esta discusión en la documentación de Docker sobre enlaces de puerto.
  • -d: ejecuta el contenedor en segundo plano.
  • --name: permite darle al contenedor un nombre fácil de recordar.

Ejecute el siguiente comando para compilar el contenedor:

  • docker run --name node-demo -p 80:8080 -d node-demo

Inspeccione sus contenedores en ejecución con docker ps:

  • docker ps

Verá un resultado que confirmará que el contenedor de su aplicación se encuentra en ejecución:

Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4133b72391da node-demo "node app.js" 17 seconds ago Up 16 seconds 0.0.0.0:80->8080/tcp node-demo

Ahora podrá visitar su dominio para probar su configuración: http://example.com. Recuerde sustituir example.com por su propio nombre de dominio. En su aplicación se mostrará la siguiente página de inicio:

Página de destino de la aplicación

Ahora que probó la aplicación, puede detener el contenedor y quitar las imágenes. Utilice docker ps de nuevo para obtener su CONTAINER ID:

  • docker ps
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4133b72391da node-demo "node app.js" 17 seconds ago Up 16 seconds 0.0.0.0:80->8080/tcp node-demo

Detenga el contenedor con docker stop. Asegúrese de sustituir el CONTAINER ID que se enumera aquí por su propio CONTAINER ID de la aplicación:

  • docker stop 4133b72391da

Ahora podrá quitar el contenedor detenido y todas las imágenes, incluidas las que no se utilicen y las pendientes, con docker system prune y el indicador -a:

  • docker system prune -a

Escriba y cuando se le indique, en la salida, para confirmar que desea eliminar el contenedor detenido y las imágenes. Tenga en cuenta que esto también eliminará la memoria caché de su compilación.

Una vez que se pruebe la imagen de su aplicación, puede continuar creando el resto de su configuración con Docker Compose.

Paso 2: Definir la configuración del servidor web

Una vez implementada nuestra aplicación Dockerfile, podemos crear un archivo de configuración para ejecutar nuestro contenedor de Nginx. Comenzaremos con una configuración mínima que incluirá nuestro nombre de dominio, root de documentos, información de proxy y un bloque de ubicación para dirigir las solicitudes de Certbot al directorio .well-known, donde creará un archivo temporal para validar que el DNS de nuestro dominio se resuelve en nuestro servidor.

Primero, cree un directorio en el directorio de proyecto actual para el archivo de configuración:

  • mkdir nginx-conf

Abra el archivo con nano o su editor favorito:

  • nano nginx-conf/nginx.conf

Añada el siguiente bloque de servidor a las solicitudes de los usuarios de proxy en su contenedor de aplicación de Node, y para dirigir solicitudes de Certbot al directorio .well-known. Asegúrese de sustituir example.com por su propio nombre de dominio:

~/node_project/nginx-conf/nginx.conf
server {
        listen 80;
        listen [::]:80;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name example.com www.example.com;

        location / {
                proxy_pass http://nodejs:8080;
        }

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }
}

Este bloque de servidor nos permitirá iniciar el contenedor de Nginx como proxy inverso, lo cual transmitirá solicitudes a nuestro contenedor de aplicación de Node. También nos permitirá usar el complemento webroot de Certbot para obtener certificados para nuestro dominio. Este complemento depende del método de validación HTTP-01, que utiliza una solicitud HTTP para probar que Certbot puede acceder a los recursos de un servidor que responda a un nombre de dominio determinado.

Una vez que haya concluido de editar, guarde y cierre el archivo. Para obtener más información sobre los algoritmos bloques de servidor y ubicación de Nginx, consulte el artículo Información sobre algoritmos de selección de bloques de servidores y ubicación de Nginx.

Una vez configurados los detalles del servidor web, podremos crear nuestro archivo docker-compose.yml que nos permitirá crear nuestros servicios de aplicación y el contenedor de Certbot que utilizaremos para obtener nuestros certificados.

Paso 3: Crear el archivo de Docker Compose

El archivo docker-compose.yml definirá nuestros servicios, incluidos la aplicación y el servidor web de Node. Especificará detalles, como volúmenes nombrados, que serán esenciales para compartir credenciales SSL entre contenedores, así como información de redes y puertos. También nos permitirá especificar comandos puntuales que se ejecutarán cuando se creen nuestros contenedores. Este archivo es el recurso central que definirá la manera en que nuestros servicios funcionarán juntos.

Abra el archivo en su directorio actual:

  • nano docker-compose.yml

Primero, defina el servicio de aplicación:

~/node_project/docker-compose.yml
version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped

La definición de servicio nodejs incluye lo siguiente:

  • build: define las opciones de configuración, incluido el context y dockerfile, que se aplicarán cuando Compose cree la imagen de la aplicación. Si desea utilizar una imagen existente de un registro como Docker Hub, podría utilizar la instrucción de imagen como alternativa, con información sobre su nombre de usuario, repositorio y etiqueta de imagen.
  • context: define el contexto de compilación para la compilación de la imagen de la aplicación. En este caso, es el directorio de proyectos actual.
  • dockerfile: especifica el Dockerfile que Compose usará para la compilación, el Dockerfile que examinó en el paso 1.
  • image y container_name: aplican nombres a la imagen y al contenedor.
  • restart: define la política de reinicio. El valor predeterminado es no, pero configuramos el contenedor para reiniciarse a menos que se detenga.

Tenga en cuenta que no incluiremos los montajes “bind” con este servicio, ya que nuestra configuración no se centra en el desarrollo sino en la implementación. Para obtener más información, consulte la documentación de Docker sobre montajes bind y volúmenes.

Para habilitar la comunicación entre los contenedores de la aplicación y del servidor web, también añadiremos una red de puente llamada app-network debajo de la definición de reinicio:

~/node_project/docker-compose.yml
services:
  nodejs:
...
    networks:
      - app-network

Una red de puente definida por el usuario como esta permite la comunicación entre contenedores en el mismo host de demonio de Docker. Esto agiliza el tráfico y la comunicación dentro de su aplicación, ya que abre todos los puertos entre contenedores en la misma red de puente y, al mismo tiempo, no expone ningún puerto al mundo exterior. Por lo tanto, puede ser selectivo a la hora de abrir solo los puertos que necesita para exponer sus servicios de frontend.

A continuación, defina el servicio webserver:

~/node_project/docker-compose.yml
...
 webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

Algunos de los ajustes que definimos para el servicio nodejs siguen siendo los mismos, pero también realizamos los siguientes cambios:

También especificamos los siguientes montajes “bind” y volúmenes con nombre:

  • web-root:/var/www/html: agregará los activos estáticos de nuestro sitio, copiados a un volumen llamado web-root, al directorio /var/www/html del contenedor.
  • ./nginx-conf:/etc/nginx/conf.d: vinculará mediante montaje “bind” el directorio de configuración de Nginx en el host con el directorio pertinente en el contenedor, lo cual garantizará que cualquier cambio que realicemos en los archivos del host se reflejarán en el contenedor.
  • certbot-etc:/etc/letsencrypt: montará los certificados y las claves pertinentes de Let’s Encrypt para nuestro dominio en el directorio apropiado del contenedor.
  • certbot-var:/var/lib/letsencrypt: monta el directorio de trabajo predeterminado de Let’s Encrypt en el directorio correspondiente del contenedor.

A continuación, agregue las opciones de configuración para el contenedor de certbot. Asegúrese de sustituir la información de dominio y correo electrónico por su propio nombre de dominio y su correo electrónico de contacto:

~/node_project/docker-compose.yml
...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com  -d www.example.com

Esta definición indica a Compose que obtenga la imagen de certbot/certbot de Docker Hub. También utiliza volúmenes con nombre para compartir recursos con el contenedor de Nginx, incluidos los certificados de dominio y la clave en certbot-etc, el directorio de trabajo de Let’s Encrypt en certbot-var y el código de aplicación en web-root.

Una vez más, usamos depends_on para especificar que el contenedor de certbot debe iniciarse una vez que el servicio webserver esté en ejecución.

También incluimos una opción de command que especifica el comando que se ejecutará cuando se inicie el contenedor. Incluye el subcomando certonly con las siguientes opciones:

  • --webroot: indica a Cerbot que utilice el complemento webroot para colocar archivos en la carpeta webroot para la autenticación.
  • --webroot-path: especifica la ruta del directorio webroot.
  • --email: su correo electrónico preferido para el registro y la recuperación.
  • --agree-tos: especifica que acepta el Acuerdo de suscripción de ACME.
  • --no-eff-email: indica a Certbot que usted no desea compartir su correo electrónico con la Electronic Frontier Foundation (EFF). Puede omitirlo si lo prefiere.
  • --staging: indica a Certbot que desea utilizar el entorno de configuración de Let’s Encrypt para obtener certificados de prueba. Utilizar esta opción le permite probar sus opciones de configuración y evitar posibles límites vinculados a solicitudes de dominio. Para obtener más información sobre estos límites, consulte la documentación sobre los límites de tasas de Let’s Encrypt.
  • -d: le permite especificar los nombres de dominio que desee aplicar a su solicitud. En este caso, incluimos example.com y www.example.com. Asegúrese de sustituirlos por sus propias preferencias de dominio.

Como paso final, agregue las definiciones de volumen y red. Asegúrese de sustituir aquí el nombre de usuario por su propio usuario no root:

~/node_project/docker-compose.yml
...
volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge

Nuestros volúmenes con nombre incluyen nuestros volúmenes de certificados de Certbot y directorios de trabajo, y el volumen de los activos estáticos de nuestro sitio, web-root. En la mayoría de los casos, el controlador predeterminado de volúmenes de Docker es el controlador local, que en Linux acepta opciones similares al comando mount. Gracias a esto, podemos especificar una lista de opciones de controladores con driver_opts que montan el directorio views en el host, el cual contiene los activos estáticos de nuestra aplicación, en el volumen en tiempo de ejecución. El contenido del directorio puede, entonces, compartirse entre contenedores. Para obtener más información sobre el contenido del directorio de views, consulte el paso 2 de Cómo crear una aplicación de Node.js con Docker.

El archivo docker-compose.yml tendrá este aspecto al terminar:

~/node_project/docker-compose.yml
version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    networks:
      - app-network

  webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com  -d www.example.com

volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge  

Una vez configuradas las definiciones de servicio, estará listo para iniciar los contenedores y probar las solicitudes de su certificado.

Paso 4: Obtener certificados y credenciales SSL

Podemos iniciar nuestros contenedores con docker-compose up, que creará y ejecutará nuestros contenedores y servicios en el orden que especificamos. Si las solicitudes de nuestros dominios tienen éxito, veremos el estado de salida correcto en nuestro resultado y los certificados correctos montados en la carpeta /etc/letsencrypt/live del contenedor webserver.

Cree los servicios con docker-compose up y el indicador -d, que ejecutarán los contenedores nodejs y webserver en segundo plano:

  • docker-compose up -d

Verá un resultado que confirmará la creación de sus servicios:

Output
Creating nodejs ... done Creating webserver ... done Creating certbot ... done

Mediante docker-compose ps, compruebe el estado de sus servicios:

  • docker-compose ps

Si todo se realizó correctamente, el estado de sus servicios de nodejs y webserver debería ser Up y el contenedor de certbot se habrá cerrado con un mensaje de estado de 0.

Output
Name Command State Ports ------------------------------------------------------------------------ certbot certbot certonly --webroot ... Exit 0 nodejs node app.js Up 8080/tcp webserver nginx -g daemon off; Up 0.0.0.0:80->80/tcp

Si ve algo diferente de Up en la columna State para los servicios nodejs y webserver, o un estado de salida distinto de 0 para el contenedor de certbot, asegúrese de verificar los registros de servicio con el comando docker-compose logs:

  • docker-compose logs service_name

Ahora podrá verificar que sus credenciales se hayan montado en el contenedor webserver con docker-compose exec:

  • docker-compose exec webserver ls -la /etc/letsencrypt/live

Si su solicitud fue correcta, verá un resultado como este:

Output
total 16 drwx------ 3 root root 4096 Dec 23 16:48 . drwxr-xr-x 9 root root 4096 Dec 23 16:48 .. -rw-r--r-- 1 root root 740 Dec 23 16:48 README drwxr-xr-x 2 root root 4096 Dec 23 16:48 example.com

Ahora que sabe que su solicitud será correcta, puede editar la definición de servicio de certbot para eliminar el marcador --staging.

Abra docker-compose.yml:

  • nano docker-compose.yml

Encuentre la sección del archivo con la definición de servicio de certbot y sustituya el indicador --staging en la opción command por el indicador --force-renewal, el cual indicará a Certbot que usted desea solicitar un nuevo certificado con los mismos dominios que un certificado existente. Ahora, la definición de servicio de certbot debería tener este aspecto:

~/node_project/docker-compose.yml
...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com
...

Podrá ejecutar docker-compose up para recrear el contenedor de certbot y sus volúmenes pertinentes. También incluiremos la opción --no-deps para indicar a Compose que puede omitir el inicio del servicio webserver, dado que ya está en ejecución:

  • docker-compose up --force-recreate --no-deps certbot

Verá un resultado que indicará que su solicitud de certificado fue exitosa:

Output
certbot | IMPORTANT NOTES: certbot | - Congratulations! Your certificate and chain have been saved at: certbot | /etc/letsencrypt/live/example.com/fullchain.pem certbot | Your key file has been saved at: certbot | /etc/letsencrypt/live/example.com/privkey.pem certbot | Your cert will expire on 2019-03-26. To obtain a new or tweaked certbot | version of this certificate in the future, simply run certbot certbot | again. To non-interactively renew *all* of your certificates, run certbot | "certbot renew" certbot | - Your account credentials have been saved in your Certbot certbot | configuration directory at /etc/letsencrypt. You should make a certbot | secure backup of this folder now. This configuration directory will certbot | also contain certificates and private keys obtained by Certbot so certbot | making regular backups of this folder is ideal. certbot | - If you like Certbot, please consider supporting our work by: certbot | certbot | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate certbot | Donating to EFF: https://eff.org/donate-le certbot | certbot exited with code 0

Una vez implementados sus certificados, podrá modificar su configuración de Nginx para incluir SSL.

Paso 5: Modificar la configuración del servidor web y la definición del servicio

Habilitar SSL en nuestra configuración de Nginx implicará agregar un redireccionamiento de HTTP a HTTPS y especificar las ubicaciones de nuestros certificados y nuestras claves SSL. También implicará especificar nuestro grupo Diffie-Hellman, que utilizaremos para confidencialidad directa perfecta.

Debido a que va a recrear el servicio webserver para incluir estas adiciones, puede detenerlo ahora:

  • docker-compose stop webserver

A continuación, cree un directorio en el directorio de su proyecto actual para su clave Diffie-Hellman:

  • mkdir dhparam

Genere su clave con el comando openssl:

  • sudo openssl dhparam -out /home/sammy/node_project/dhparam/dhparam-2048.pem 2048

Tomará algunos momentos generar la clave.

Para agregar la información pertinente de Diffie-Hellman y SSL a su configuración de Nginx, primero elimine el archivo de configuración de Nginx que creó anteriomente:

  • rm nginx-conf/nginx.conf

Abra otra versión del archivo:

  • nano nginx-conf/nginx.conf

Añada el siguiente código al archivo para redireccionar HTTP a HTTP y para agregar credenciales, protocolos y encabezados de seguridad SSL. Recuerde sustituir example.com por su propio dominio:

~/node_project/nginx-conf/nginx.conf

server {
        listen 80;
        listen [::]:80;
        server_name example.com www.example.com;

        location ~ /.well-known/acme-challenge {
          allow all;
          root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com www.example.com;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        ssl_buffer_size 8k;

        ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
        ssl_prefer_server_ciphers on;

        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

        ssl_ecdh_curve secp384r1;
        ssl_session_tickets off;

        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8;

        location / {
                try_files $uri @nodejs;
        }

        location @nodejs {
                proxy_pass http://nodejs:8080;
                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header Referrer-Policy "no-referrer-when-downgrade" always;
                add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
                # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
                # enable strict transport security only if you understand the implications
        }

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
}

El bloque de servidor HTTP especifica el webroot para solicitudes de renovación de Certbot al directorio .well-known/acme-challenge. También incluye una directiva de reescritura que dirige las solicitudes HTTP al directorio root hacia HTTPS.

El bloque de servidor HTTPS habilita ssl y http2. Para obtener más información sobre la iteración de HTTP/2 en protocolos HTTP y los beneficios que puede tener para el rendimiento del sitio web, consulte la introducción a Cómo configurar Nginx con soporte HTTP/2 en Ubuntu 18.04. Este bloque también incluye varias opciones para garantizar que usted utilice los protocolos y los cifrados SSL más actualizados y que el engrapado OSCP esté activado. El grapado OCSP le permite ofrecer una respuesta con registro de tiempo de su autoridad de certificación durante el protocolo de enlace TLS inicial, lo que puede acelerar el proceso de autenticación.

El bloque también especifica sus credenciales y ubicaciones de claves SSL y Diffie-Hellman.

Por último, movimos la información de pase de proxy a este bloque, incluido un bloque de ubicación con una directiva try_files, que dirige solicitudes a nuestro contenedor de aplicación de Node.js con alias y un bloque de ubicación para ese alias, que incluye encabezados de seguridad que nos permitirán obtener calificaciones de A en aspectos como los laboratorios SSL y sitios de prueba de servidores de encabezados de seguridad. Entre estos encabezado se incluyen X-frame-Options, X-Frame-Options, Referer Policy, Content-Security-Policy y X-XSS-Protection. El encabezado HTTP de Strict Transport Security (HSTS) no se incluye: habilite esto solo si comprende las implicaciones y evaluó su funcionalidad “preload”.

Una vez que haya finalice la edición, guarde y cierre el archivo.

Antes de recrear el servicio webserver, deberá realizar algunas adiciones a la definición de servicio de su archivo docker-compose.yml, incluida la información de puerto pertinente para HTTPS y una definición de volumen de Diffie-Hellman.

Abra el archivo:

  • nano docker-compose.yml

En la definición del servicio webserver, agregue la siguiente asignación de puerto y el volumen llamado dhparam:

~/node_project/docker-compose.yml
...
 webserver:
    image: nginx:latest
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - dhparam:/etc/ssl/certs
    depends_on:
      - nodejs
    networks:
      - app-network

A continuación, agregue el volumen dhparam a sus definiciones de volúmenes:

~/node_project/docker-compose.yml
...
volumes:
  ...
  dhparam:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/dhparam/
      o: bind

De manera similar al volumen web-root, el volumen dhparam monta en el contenedor webserver la clave Diffie-Hellman almacenada en el host.

Guarde y cierre el archivo cuando haya terminado de editar.

Recree el servicio webserver:

  • docker-compose up -d --force-recreate --no-deps webserver

Compruebe sus servicios con docker-compose ps:

  • docker-compose ps

Debería ver un resultado que indique que sus nodejs y sus servicios webserver se encuentran en ejecución:

Output
Name Command State Ports ---------------------------------------------------------------------------------------------- certbot certbot certonly --webroot ... Exit 0 nodejs node app.js Up 8080/tcp webserver nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp

Por último, puede visitar su dominio para asegurarse de que todo funcione como se espera. Visite con su navegador https://example.com y asegúrese de sustituir example.com por su propio nombre de dominio. Visualizará la siguiente página de destino:

Página de destino de la aplicación

También debería ver el icono del candado en el indicador de seguridad de su navegador. Si lo desea, puede visitar la página de destino de SSL Labs Server Test o la página de inicio de prueba de servidores de encabezados de seguridad. Las opciones de configuración que incluimos deberían hacer que la calificación de su sitio sea *A *en ambos casos.

Paso 6: Renovar certificados

Los certificados de Let’s Encrypt son válidos durante 90 días, por lo que le convendrá configurar un proceso de renovación automática para asegurarse de que no caduquen. Una forma de hacerlo es crear un trabajo con la utilidad de programación de cron. En este caso, programaremos una tarea cron utilizando una secuencia de comandos que renovará nuestros certificados y volverá a cargar nuestra configuración de Nginx.

Abra una secuencia de comandos llamada ssl_renew.sh en el directorio de su proyecto:

  • nano ssl_renew.sh

Agregue el siguiente código a la secuencia de comandos para renovar sus certificados y volver a cargar la configuración de su servidor web:

~/node_project/ssl_renew.sh
#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/
$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Esta secuencia de comandos primero asigna el binario docker-compose a una variable llamada COMPOSE y especifica la opción --no-ansi, que ejecutará comandos docker-compose sin los caracteres de control ANSI. Luego hace lo mismo con el binario docker. Luego, cambia el posicionamiento al directorio ~/wordpress project y ejecuta los siguientes comandos docker-compose:

  • docker-compose run: iniciará un contenedor certbot y anulará el command proporcionado en nuestra definición de servicio certbot. En lugar de usar el subcomando de certonly, usaremos aquí el subcomando renew que renovará certificados que caducarán pronto. En este caso, incluimos la opción --dry-run para probar nuestra secuencia de comandos.
  • docker-compose kill: enviará una señal de SIGHUP al contenedor webserver para volver a cargar la configuración de Nginx. Si desea obtener más información sobre el uso de este proceso para volver a cargar su configuración de Nginx, consulte este post del blog de Docker sobre la implementación de la imagen oficial de Nginx con Docker.

Luego ejecuta docker system prune para eliminar todos los contenedores y las imágenes que no se utilizan.

Cierre el archivo cuando finalice la edición. Haga que sea ejecutable:

  • chmod +x ssl_renew.sh

A continuación, abra su archivo root crontab para ejecutar la secuencia de comandos de renovación en un intervalo especificado:

  • sudo crontab -e

Si es la primera vez que edita este archivo, se le solicitará elegir un editor:

crontab
no crontab for root - using an empty one
Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest
  3. /usr/bin/vim.basic
  4. /usr/bin/vim.tiny
Choose 1-4 [2]:
...

Al final del archivo, añada la siguiente línea:

crontab
...
*/5 * * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

Esto fijará un intervalo de tarea de cinco minutos, de modo que puede probar si su solicitud de renovación ha funcionado como estaba previsto. También creamos un archivo de registro, cron.log para registrar el resultado pertinente de la tarea.

Una vez que transcurran cinco minutos, revise ​​​​​cron.log​​​​​​ para comprobar si la solicitud de renovación se realizó con éxito o no:

  • tail -f /var/log/cron.log

Debería ver un resultado que confirme el éxito de la renovación:

Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates below have not been saved.) Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/example.com/fullchain.pem (success) ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates above have not been saved.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Killing webserver ... done

Ahora podrá modificar el archivo de crontab para establecer un intervalo diario. Para ejecutar la secuencia de comandos cada día al mediodía, por ejemplo, se debería modificar la última línea del archivo de modo que tenga el siguiente aspecto:

crontab
...
0 12 * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

También le convendrá eliminar la opción --dry-run de su secuencia de comandos ssl_renew.sh:

~/node_project/ssl_renew.sh
#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Su tarea cron controlará que sus certificados de Let´s Encrypt no caduquen al renovarlos cuando reúnan las condiciones. También puede configurar la rotación de registros con la utilidad Logrotate para rotar y comprimir sus archivos de registro.

Conclusión

Usó contenedores para configurar y ejecutar una aplicación de Node con un proxy inverso de Nginx. También protegió certificados SSL para el dominio de su aplicación y configuró una tarea cron para renovar estos certificados cuando sea necesario.

Si le interesa obtener más información sobre los complementos de Let´s Encrypt, consulte nuestros artículos sobre el uso del complemento de Nginx o el complemento independiente.

También puede obtener más información sobre Docker Compose consultando los siguientes recursos:

La documentación de Compose es también un excelente recurso para aprender más sobre aplicaciones en varios contenedores.

Creative Commons License