Introdução

Existem várias maneiras de melhorar a flexibilidade e segurança do seu aplicativo Node.js. O uso de um proxy reverso como o Nginx oferece a você a capacidade de carregar solicitações de balanceamento de carga, conteúdo de cache estático e de* implementar a Segurança em Camada*s de Transporte (TLS). Ao habilitar o HTTPS criptografado no seu servidor, garante-se que a comunicação para o seu aplicativo e vinda dele permaneça segura.

A implementação de um proxy reverso com TLS/SSL em contêineres envolve um conjunto diferente de procedimentos do trabalho em um sistema operacional de host. Por exemplo, se estivesse obtendo certificados do Let's Encrypt para um aplicativo em execução em um servidor, deveria instalar o software necessário diretamente no seu host. Os contêineres permitem que você utilize uma abordagem diferente. Ao usar o Docker Compose, é possível criar contêineres para o seu aplicativo, seu servidor Web e o cliente Certbot que permite você de obter seus certificados. Ao seguir estes passos, você pode aproveitar a modularidade e a portabilidade de um fluxo de trabalho em contêiner.

Neste tutorial, será implantado um aplicativo Node.js com um proxy reverso Nginx usando o Docker Compose. Você receberá certificados TLS/SSL para o domínio associados ao seu aplicativo e garantirá que ele receba uma classificação de segurança elevada do SSL Labs. Por fim, será configurado um trabalho cron para renovar seus certificados para que seu domínio permaneça seguro.

Pré-requisitos

com illustration for: Pré-requisitos

Para seguir este tutorial, será necessário:

  • Um nome de domínio registrado. Este tutorial usará o example.com do início ao fim. Você pode obter um domínio gratuitamente através do Freenom, ou usar o registrador de domínios de sua escolha.
  • Ambos os registros de DNS a seguir serão configurados para o seu servidor. Você pode seguir esta introdução para o DNS da the cloud provider para obter mais detalhes sobre como adicioná-los a uma conta da the cloud provider, caso seja o que estiver usando:
  • Um registro A com <^>example.com<^> apontando para o endereço de IP público do seu servidor.
  • Um registro A com <^>example.com<^> apontando para o endereço IP público do seu servidor.

Passo 1 — Clonando e testando o aplicativo Node

Como primeiro passo, clonaremos o repositório com o código do aplicativo Node, que inclui o Dockerfile que usaremos na construção da nossa imagem de aplicativo com o Compose. Podemos testar primeiro o aplicativo a partir de sua construção e execução com o comando docker run, sem um proxy reverso ou SSL.

No diretório home do seu usuário não raiz, clone o repositório nodejs-image-demo da conta GitHub da Comunidade the cloud provider. Este repositório inclui o código da configuração descrito em Como construir um aplicativo Node.js com o Docker.

Clone o repositório em um diretório chamado <^>node_project<^>:

				
					
git clone https://github.com/do-community/nodejs-image-demo.git &lt;^&gt;node_project&lt;^&gt;

				
			

Vá para o diretório <^>node_project<^>:

				
					
cd  &lt;^&gt;node_project&lt;^&gt;

				
			

Neste diretório, há um Dockerfile que contém instruções para a construção de um aplicativo Node usando a imagem do Docker node:10 e o conteúdo do seu diretório de projeto atual. Você pode olhar o conteúdo do Dockerfile digitando:

				
					
cat Dockerfile

				
			
				
					
[secondary_label Output]

FROM node:10-alpine



RUN mkdir -p /home/node/app/node_modules &amp;&amp; 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" ]

				
			

Essas instruções constroem uma imagem do Node através da cópia do código do projeto do diretório atual para o contêiner e instalação de dependências com o npm install. Elas também se aproveitam do salvamento em cache e disposição em camadas da imagem. Isso é feito pela separação da cópia do package.json e package-lock.json, que contém as dependências listadas do projeto, da cópia do resto do código do aplicativo. Por fim, as instruções especificam que o contêiner será executado como o usuário node não raiz com as permissões apropriadas definidas no código do aplicativo e no diretório node_modules.

Para obter mais informações sobre este Dockerfile e práticas recomendadas da imagem do Node, consulte a discussão completa no Passo 3 de Como construir um aplicativo Node.js com o Docker.

Para testar o aplicativo sem o SSL, construa e identifique a imagem usando o docker build e o sinalizador -t. Vamos nomear a imagem <^>node-demo<^>, mas você pode dar a ela o nome que quiser:

				
					
docker build -t &lt;^&gt;node-demo&lt;^&gt; .

				
			

Assim que o processo de construção for concluído, você pode listar suas imagens com o docker images:

				
					
docker images

				
			

Você verá o seguinte resultado, confirmando a compilação da imagem do aplicativo:

				
					
[secondary_label Output]

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

&lt;^&gt;node-demo&lt;^&gt;           latest              23961524051d        7 seconds ago       73MB

node                10-alpine           8a752d5af4ce        3 weeks ago         70.7MB

				
			

Em seguida, crie o contêiner com o docker run. Vamos incluir três sinalizadores com este comando:

  • -p: publica a porta no contêiner e a mapeia para uma porta no nosso host. Usaremos a porta 80 no host, mas sinta-se a vontade para escolher outra se necessário, caso tenha outro processo em execução naquela porta. Para obter mais informações sobre como isso funciona, veja esta discussão nos documentos do Docker sobre associação de portas.
  • -d: executa o contêiner em segundo plano.
  • --name: permite-nos dar ao contêiner um nome memorável.

Execute o comando a seguir para criar o contêiner:

				
					
docker run --name &lt;^&gt;node-demo&lt;^&gt; -p 80:8080 -d &lt;^&gt;node-demo&lt;^&gt;

				
			

Verifique seus contêineres em execução com o docker ps:

				
					
docker ps

				
			

Você verá o resultado que confirma que o seu contêiner do aplicativo está funcionando:

				
					
[secondary_label Output]

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES

4133b72391da        &lt;^&gt;node-demo&lt;^&gt;           "node app.js"       17 seconds ago      Up 16 seconds       0.0.0.0:80-&gt;8080/tcp   &lt;^&gt;node-demo&lt;^&gt;

				
			

Agora, você pode visitar seu domínio para testar sua configuração: http://<^>example.com<^>. Lembre-se de substituir o <^>example.com<^> pelo seu próprio nome de domínio. Seu aplicativo exibirá a seguinte página de destino:

Agora que você testou o aplicativo, pare o contêiner e remova as imagens. Use o docker ps novamente para obter seu CONTAINER ID:

				
					
docker ps

				
			
				
					
[secondary_label Output]

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES

&lt;^&gt;4133b72391da&lt;^&gt;        &lt;^&gt;node-demo&lt;^&gt;           "node app.js"       17 seconds ago      Up 16 seconds       0.0.0.0:80-&gt;8080/tcp   &lt;^&gt;node-demo&lt;^&gt;

				
			

Pare o contêiner com o docker stop. Certifique-se de substituir o CONTAINER ID listado aqui pelo CONTAINER ID do seu aplicativo:

				
					
docker stop &lt;^&gt;4133b72391da&lt;^&gt;

				
			

Agora, é possível remover o contêiner parado e todas as imagens, incluindo imagens não utilizadas e penduradas, com o docker system prune e o sinalizador -a:

				
					
docker system prune -a

				
			

Digite y quando solicitado na saída para confirmar que você gostaria de remover o contêiner e imagens parados. Fique ciente de que isso também removerá seu cache de construção.

Com sua imagem de aplicativo testada, siga em frente para a construção do resto da sua configuração com o Docker Compose.

Passo 2 — Definindo as configurações do servidor Web

Com nosso aplicativo Dockerfile funcionando, podemos criar um arquivo de configuração para executar nosso contêiner Nginx. Começaremos com uma configuração mínima que incluirá nosso nome de domínio, root do documento, informações de proxy e um bloco de localização para dirigir os pedidos do Certbot ao diretório .well-known. Lá, ele colocará um arquivo temporário para validar que o DNS para nosso domínio resolva para nosso servidor.

Primeiramente, crie um diretório no diretório atual do projeto para o arquivo de configuração:

				
					
mkdir nginx-conf

				
			

Abra o arquivo com o nano ou com o seu editor favorito:

				
					
nano nginx-conf/nginx.conf

				
			

Adicione o seguinte bloco de servidor para servir como proxy para os pedidos de usuário para o contêiner do seu aplicativo Node e redirecionar os pedidos do Certbot ao diretório .well-known. Certifique-se de substituir o <^>example.com<^> pelo seu próprio nome de domínio:

				
					
[label ~/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 &lt;^&gt;example.com&lt;^&gt; www.&lt;^&gt;example.com&lt;^&gt;;



        location / {

                proxy_pass http://nodejs:8080;

        }



        location ~ /.well-known/acme-challenge {

                allow all;

                root /var/www/html;

        }

}

				
			

Este bloco de servidor nos permitirá iniciar o contêiner Nginx como um proxy reverso, que passará pedidos para nosso contêiner do aplicativo Node. Ele também nos permitirá usar o plug-in webroot do Certbot para obter certificados para nosso domínio. Este plug-in depende do método de validação HTTP-01, que usa um pedido HTTP para provar que o Certbot pode acessar recursos de um servidor que responde a um dado nome de domínio.

Assim que terminar a edição, salve e feche o arquivo. Para aprender mais sobre o servidor Nginx e os algoritmos de blocos de localização, consulte este artigo Entendendo o servidor Nginx e os algoritmos de seleção de blocos de localização.

Com os detalhes de configuração do servidor Web funcionando, podemos seguir em frente para a criação do nosso arquivo docker-compose.yml, que nos permitirá criar nossos serviços de aplicativo e o contêiner do Certbot que usaremos para obter nossos certificados.

Passo 3 — Criando o arquivo do Docker Compose

O arquivo docker-compose.yml definirá nossos serviços, incluindo o aplicativo Node e o servidor Web. Ele especificará detalhes como volumes nomeados, que serão críticos para compartilhar credenciais SSL entre contêineres, além de informações de rede e portas. Ele também nos permitirá especificar comandos específicos para serem executados quando nossos contêineres forem criados. Este arquivo é o recurso central que definirá como nossos serviços funcionarão em conjunto.

Abra o arquivo no seu diretório atual:

				
					
nano docker-compose.yml

				
			

Primeiro, defina o serviço de aplicativo:

				
					
[label ~/node_project/docker-compose.yml]

version: '3'



services:

  nodejs:

    build:

      context: .

      dockerfile: Dockerfile

    image: nodejs

    container_name: nodejs

    restart: unless-stopped

				
			

A definição de serviço nodejs inclui o seguinte:

  • build: define as opções de configuração, incluindo o context e dockerfile, que serão aplicadas quando o Compose construir a imagem do aplicativo. Se quisesse usar uma imagem existente de um registro como o Docker Hub, poderia usar como alternativa a instrução image, com informações sobre seu nome de usuário, repositório e tag da imagem.
  • context: define o contexto de compilação para a compilação de imagem do aplicativo. Neste caso, é o diretório atual do projeto.
  • dockerfile: especifica o Dockerfile que o Compose usará para a compilação — o Dockerfile que você olhou no Passo 1.
  • image, container_name: aplicam nomes à imagem e contêiner.
  • restart: define a política de reinício. A padrão é no, mas definimos o contêiner para reiniciar a menos que ele seja interrompido.

Note que não estamos incluindo bind mounts com este serviço, uma vez que nossa configuração está focada na implantação e não no desenvolvimento. Para obter mais informações, consulte a documentação do Docker sobre bind mounts e volumes.

Para habilitar a comunicação entre os contêineres do aplicativo e do servidor Web, adicionaremos também uma rede bridge chamada app-network abaixo da definição de reinicialização:

				
					
[label ~/node_project/docker-compose.yml]

services:

  nodejs:

...

    &lt;^&gt;networks:&lt;^&gt;

      &lt;^&gt;- app-network&lt;^&gt;

				
			

Uma rede bridge definida pelo usuário permite a comunicação entre contêineres no mesmo host daemon do Docker. Isso simplifica o tráfego e a comunicação dentro do seu aplicativo, uma vez que todas as portas entre os contêineres na mesma rede bridge são abertas, ao mesmo tempo em que nenhuma porta é exposta ao mundo exterior. Assim, é possível ser seletivo abrindo apenas as portas que você precisar para expor seus serviços front-end.

Em seguida, defina o serviço webserver:

				
					
[label ~/node_project/docker-compose.yml]

...

 webserver:

    image: nginx:&lt;^&gt;mainline-alpine&lt;^&gt;

    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

				
			

Algumas das configurações que definimos para o serviço nodejs permanecem as mesmas, mas também fizemos as seguintes alterações:

  • ports: expõe a porta 80 para habilitar as opções de configuração que definimos em nossa configuração do Nginx.

Também especificamos os seguintes volumes nomeados e bind mounts:

  • web-root:/var/www/html: adicionará os ativos estáticos do nosso site, copiados para um volume chamado web-root, para o diretório /var/www/html no contêiner.
  • ./nginx-conf:/etc/nginx/conf.d: irá associar a montagem do diretório de configuração Nginx no host ao diretório relevante no contêiner, garantindo que quaisquer alterações que façamos em arquivos no host serão refletidas no contêiner.
  • certbot-etc:/etc/letsencrypt: irá montar os certificados e chaves relevantes do Let's Encrypt do nosso domínio para o diretório apropriado no contêiner.
  • certbot-var:/var/lib/letsencrypt: monta o diretório de trabalho padrão do Let's Encrypt para o diretório apropriado no contêiner.

Em seguida, adicione as opções de configuração para o contêiner certbot. Certifique-se de substituir as informações de domínio e e-mail pelo seu próprio nome de domínio e e-mail:

				
					
[label ~/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 &lt;^&gt;sammy@example.com&lt;^&gt; --agree-tos --no-eff-email --staging -d &lt;^&gt;example.com&lt;^&gt;  -d www.&lt;^&gt;example.com&lt;^&gt;

				
			

Esta definição diz ao Compose para puxar a imagem certbot/certbot do Docker Hub. Ela também usa volumes nomeados para compartilhar recursos com o contêiner do Nginx, incluindo os certificados de domínio e chaves no certbot-etc, o diretório de trabalho do Let's Encrypt no certbot-var e o código do aplicativo no web-root.

Novamente, usamos o depends_on para especificar que o contêiner do certbot deve ser iniciado assim que o serviço webserver estiver funcionando.

Também incluímos uma opção command que especifica o comando a ser executado quando o contêiner for iniciado. Ele inclui o subcomando certonly com as seguintes opções:

  • --webroot: diz ao Certbot para usar o plug-in webroot para colocar arquivos na pasta webroot para autenticação.
  • --webroot-path: especifica o caminho do diretório webroot.
  • --email: seu e-mail escolhido para o registro e recuperação.
  • --no-eff-email: diz ao Certbot que você não deseja compartilhar seu e-mail com a Electronic Frontier Foundation (EFF). Sinta-se à vontade para omitir isso se preferir.
  • --staging: diz ao Certbot que você deseja usar o ambiente de preparo do Let's Encrypt para obter certificados de teste. Usar essa opção permite que você teste suas opções de configuração e evite possíveis limites de solicitação de domínio. Para obter mais informações sobre esses limites, consulte a documentação sobre limites de taxa do Let's Encrypt.
  • -d: permite que você especifique os nomes de domínio que gostaria de aplicar ao seu pedido. Neste caso, incluímos o <^>example.com<^> e www.<^>example.com<^>. Certifique-se de substituí-los pelas suas próprias preferências de domínio.

Como passo final, adicione as definições de volume e rede. Certifique-se de substituir o nome de usuário presente aqui pelo seu usuário não raiz:

				
					
[label ~/node_project/docker-compose.yml]

...

volumes:

  certbot-etc:

  certbot-var:

  web-root:

    driver: local

    driver_opts:

      type: none

      device: /home/&lt;^&gt;sammy&lt;^&gt;/&lt;^&gt;node_project&lt;^&gt;/views/

      o: bind



networks:

  app-network:

    driver: bridge

				
			

Nossos volumes nomeados incluem nosso certificado Certbot e os volumes de diretórios de trabalho, além dos volume para os ativos estáticos do nosso site, o web-root. Na maioria dos casos, o driver padrão para os volumes do Docker é o driver local, que no Linux aceita opções semelhantes ao comando mount. Graças a isso, é possível especificar uma lista de opções de drivers com o driver_opts que montam o diretório views no host, sendo que estes contém os ativos estáticos do nosso aplicativo, além do volume em tempo de execução. O conteúdo do diretório pode ser, então, compartilhado entre contêineres. Para obter mais informações sobre o conteúdo do diretório views, consulte o Passo 2 de Como construir um aplicativo Node.js com o Docker.

O arquivo docker-compose.yml se parecerá com isto quando terminar:

				
					
[label ~/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:&lt;^&gt;mainline-alpine&lt;^&gt;

    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 &lt;^&gt;sammy@example.com&lt;^&gt; --agree-tos --no-eff-email --staging -d &lt;^&gt;example.com&lt;^&gt;  -d www.&lt;^&gt;example.com&lt;^&gt;



volumes:

  certbot-etc:

  certbot-var:

  web-root:

    driver: local

    driver_opts:

      type: none

      device: /home/&lt;^&gt;sammy&lt;^&gt;/&lt;^&gt;node_project&lt;^&gt;/views/

      o: bind



networks:

  app-network:

    driver: bridge  

				
			

Com as definições de serviço instaladas, você está pronto para iniciar os contêineres e testar seus pedidos de certificado.

Passo 4 — Obtendo certificados e credenciais SSL

Podemos iniciar nossos contêineres com o docker-compose up, que criará e executará nossos contêineres e serviços na ordem que especificamos. Se nossos pedidos de domínio forem bem sucedidos, veremos o status correto de saída no nosso resultado e os certificados corretos montados na pasta /etc/letsencrypt/live no contêiner webserver.

Crie os serviços com o docker-compose up e o sinalizador -d, os quais executarão os contêineres nodejs e webserver em segundo plano:

				
					
docker-compose up -d

				
			

Você verá um resultado confirmando que seus serviços foram criados:

				
					
[secondary_label Output]

Creating nodejs ... &lt;^&gt;done&lt;^&gt;

Creating webserver ... &lt;^&gt;done&lt;^&gt;

Creating certbot   ... &lt;^&gt;done&lt;^&gt;

				
			

Com o uso do docker-compose ps, verifique o status dos seus serviços:

				
					
docker-compose ps

				
			

Se tudo ocorreu bem, seus serviços nodejs e webserver devem estar Up e o contêiner certbot terá finalizado com uma mensagem de status 0:

				
					
[secondary_label 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-&gt;80/tcp

				
			

Se você ver qualquer outra coisa além de Up na coluna State para os serviços nodejs e webserver, ou um status de saída que não seja 0 para o contêiner certbot, certifique-se de verificar os registros de serviço com o comando docker-compose logs:

				
					
docker-compose logs &lt;^&gt;service_name&lt;^&gt;

				
			

Agora, é possível verificar se suas credenciais foram instaladas no contêiner webserver com o docker-compose exec:

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

				
			

Se seu pedido foi bem sucedido, você verá um resultado similar a este:

				
					
[secondary_label 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 &lt;^&gt;example.com&lt;^&gt;

				
			

Agora que você sabe que seu pedido será bem sucedido, edite a definição do serviço certbot para remover o sinalizador --staging.

Abra o docker-compose.yml:

				
					
nano docker-compose.yml

				
			

Encontre a seção do arquivo com a definição de serviço do certbot e substitua o sinalizador --staging na opção command pelo sinalizador --force-renewal, o qual dirá ao Certbot que você quer solicitar um novo certificado com os mesmos domínios de um certificado existente. A definição de serviço do certbot deve agora se parecer com isto:

				
					
[label ~/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 &lt;^&gt;sammy@example.com&lt;^&gt; --agree-tos --no-eff-email &lt;^&gt;--force-renewal&lt;^&gt; -d &lt;^&gt;example.com&lt;^&gt; -d www.&lt;^&gt;example.com&lt;^&gt;

...

				
			

Agora, é possível executar o docker-compose up para recriar o contêiner do certbot e seus volumes relevantes. Também vamos incluir a opção --no-deps para dizer ao Compose que ele pode pular a inicialização do serviço webserver, já que ele já está em funcionamento:

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

				
			

Você verá o resultado indicando que o seu pedido de certificado foi bem-sucedido:

				
					
[secondary_label Output]

certbot      | IMPORTANT NOTES:

certbot      |  - Congratulations! Your certificate and chain have been saved at:

certbot      |    /etc/letsencrypt/live/&lt;^&gt;example.com&lt;^&gt;/fullchain.pem

certbot      |    Your key file has been saved at:

certbot      |    /etc/letsencrypt/live/&lt;^&gt;example.com&lt;^&gt;/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

				
			

Com seus certificados instalados, você pode seguir em frente para modificar sua configuração do Nginx para incluir o SSL.

Passo 5 — Modificando as configuração do servidor Web e da definição de serviço

Habilitar o SSL na nossa configuração Nginx envolverá a adição de um redirecionamento do HTTP para o HTTPS e a especificação dos nossos certificados e locais de chave SSL. Isso também envolverá especificar nosso grupo Diffie-Hellman, que usaremos para o Perfect Forward Secrecy.

Como será recriado o serviço webserver para incluir essas adições, você pode interrompê-lo agora:

				
					
docker-compose stop webserver

				
			

Em seguida, crie um diretório no seu diretório atual de projeto para sua chave Diffie-Hellman:

				
					
mkdir dhparam

				
			

Gere sua chave com o comando openssl:

				
					
sudo openssl dhparam -out /home/&lt;^&gt;sammy&lt;^&gt;/&lt;^&gt;node_project&lt;^&gt;/dhparam/dhparam-2048.pem 2048

				
			

A chave será gerada após alguns instantes.

Para adicionar as informações relevantes do Diffie-Hellman e SSL na sua configuração do Nginx, remova primeiro o arquivo de configuração do Nginx que você criou mais cedo:

				
					
rm nginx-conf/nginx.conf

				
			

Abra outra versão do arquivo:

				
					
nano nginx-conf/nginx.conf

				
			

Adicione o seguinte código ao arquivo para redirecionar o HTTP para o HTTPS e adicione credenciais, protocolos e cabeçalhos de segurança do protocolo SSL. Lembre-se de substituir o <^>example.com<^> pelo seu próprio domínio:

				
					
[label ~/node_project/nginx-conf/nginx.conf]



server {

        listen 80;

        listen [::]:80;

        server_name &lt;^&gt;example.com&lt;^&gt; www.&lt;^&gt;example.com&lt;^&gt;;



        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 &lt;^&gt;example.com&lt;^&gt; www.&lt;^&gt;example.com&lt;^&gt;;



        server_tokens off;



        ssl_certificate /etc/letsencrypt/live/&lt;^&gt;example.com&lt;^&gt;/fullchain.pem;

        ssl_certificate_key /etc/letsencrypt/live/&lt;^&gt;example.com&lt;^&gt;/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;

}

				
			

O bloco de servidor HTTP especifica o webroot dos pedidos de renovação do Certbot para o diretório .well-known/acme-challenge. Isso também inclui uma diretriz de reescrita, que direciona os pedidos do HTTP para o diretório raiz para o HTTPS.

O bloco de servidor HTTPS habilita o ssl e o http2. Para ler mais sobre como o HTTP/2 itera nos protocolos HTTP e os benefícios que ele pode ter para o desempenho do site, consulte a introdução de Como configurar o Nginx com suporte HTTP/2 no Ubuntu 18.04. Este bloco também inclui uma série de opções para garantir que você esteja usando os protocolos e criptografias SSL mais atualizados e que o grampeamento OSCP esteja ligado. O grampeamento OSCP permite a oferta de uma resposta com a data marcada da sua autoridade de certificação durante o handshake TLS, o que pode acelerar o processo de autenticação.

O bloco também especifica suas credenciais e locais de chave do SSL e Diffie-Hellman.

Por fim, transferimos as informações de passagem de proxy para este bloco, incluindo um bloco de localização com uma diretriz try_files, direcionando pedidos para nosso contêiner do aplicativo Node.js de alias, e um bloco de localização para aquele alias, que inclui cabeçalhos de segurança que nos permitirão obter classificações A em coisas como os sites de teste de servidor SSL Labs e Security Headers. Estes cabeçalhos incluem o X-Frame-Options, X-Content-Type-Options, Referrer Policy, Content-Security-Policy, e X-XSS-Protection. O cabeçalho HTTP Strict Transport Security (HSTS) é retirado do comentário – habilite isso apenas se você entender as implicações e avaliou sua funcionalidade de "precarregamento".

Assim que terminar a edição, salve e feche o arquivo.

Antes de recriar o serviço webserver, será necessário adicionar algumas coisas na definição de serviço no seu arquivo docker-compose.yml, incluindo informações relevantes de porta para o HTTPS e uma definição de volume do Diffie-Hellman.

Abra o arquivo:

				
					
nano docker-compose.yml

				
			

Na definição do serviço webserver, adicione o seguinte mapeamento de portas e o volume nomeado dhparam:

				
					
[label ~/node_project/docker-compose.yml]

...

 webserver:

    image: nginx:latest

    container_name: webserver

    restart: unless-stopped

    ports:

      - "80:80"

      - &lt;^&gt;"443:443"&lt;^&gt;

    volumes:

      - web-root:/var/www/html

      - ./nginx-conf:/etc/nginx/conf.d

      - certbot-etc:/etc/letsencrypt

      - certbot-var:/var/lib/letsencrypt

      - &lt;^&gt;dhparam:/etc/ssl/certs&lt;^&gt;

    depends_on:

      - nodejs

    networks:

      - app-network

				
			

Em seguida, adicione o volume dhparam às suas definições de volumes:

				
					
[label ~/node_project/docker-compose.yml]

...

volumes:

  ...

  dhparam:

    driver: local

    driver_opts:

      type: none

      device: /home/&lt;^&gt;sammy&lt;^&gt;/&lt;^&gt;node_project&lt;^&gt;/dhparam/

      o: bind

				
			

De maneira similar ao volume web-root, o volume dhparam montará a chave Diffie-Hellman armazenada no host para o contêiner webserver.

Salve e feche o arquivo quando você terminar a edição.

Recrie o serviço webserver:

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

				
			

Verifique seus serviços com o docker-compose ps:

				
					
docker-compose ps

				
			

Você deve ver um resultado indicando que seus serviços nodejs e webserver estão funcionando:

				
					
[secondary_label 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-&gt;443/tcp, 0.0.0.0:80-&gt;80/tcp

				
			

Por fim, visite seu domínio para garantir que tudo está funcionando conforme esperado. Navegue com seu browser até https://<^>example.com<^>, certificando-se de substituir o <^>example.com<^> pelo seu próprio nome de domínio. Você verá a seguinte página de destino:

Você também deve ver o ícone de cadeado no indicador de segurança do seu navegador. Se quiser, navegue até a página de destino do teste de servidor do SSL Labs, ou a página de destino do teste de servidor do Security Headers. As opções de configuração que incluímos devem garantir ao seu site uma classificação A em ambos.

Passo 6 — Renovando certificados

Os certificados do Let's Encrypt são válidos por 90 dias, então você vai querer configurar um processo de renovação automatizado para garantir que eles não expirem. Uma maneira de fazer isso é criando um trabalho com o utilitário de agendamento cron. Neste caso, vamos agendar uma tarefa do cron usando um script que renovará nossos certificados e recarregará nossa configuração do Nginx.

Abra um script chamado ssl_renew.sh no seu diretório de projeto:

				
					
nano ssl_renew.sh

				
			

Adicione o seguinte código ao script para renovar seus certificados e recarregar a configuração do seu servidor Web:

				
					
[label ~/node_project/ssl_renew.sh]

#!/bin/bash



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

DOCKER="/usr/bin/docker"



cd /home/&lt;^&gt;sammy&lt;^&gt;/&lt;^&gt;node_project&lt;^&gt;/

$COMPOSE run certbot renew --dry-run &amp;&amp; $COMPOSE kill -s SIGHUP webserver

$DOCKER system prune -af

				
			

Primeiro, este script atribui o binário docker-compose a uma variável chamada COMPOSE e especifica a opção --no-ansi, a qual executará os comandos do docker-compose sem os caracteres de controle do ANSI. Em seguida, ele faz o mesmo com o binário docker. Por fim, ele muda para o diretório do projeto ~/wordpress e executa os seguintes comandos docker-compose:

  • docker-compose run: iniciará um contêiner certbot e substituirá o comando fornecido em nossa definição de serviço certbot. Em vez de usar o subcomando certonly vamos usar o subcomando renew aqui, o qual renovará os certificados que estão próximos de expirar. Incluímos a opção --dry-run aqui para testar nosso script.

Na sequência, ele executa o docker system prune para remover todos os contêineres e imagens não utilizados.

Feche o arquivo quando terminar a edição. Torne-o executável:

				
					
chmod +x ssl_renew.sh

				
			

Em seguida, abra seu arquivo root crontab para executar o script de renovação em um intervalo especificado:

				
					
sudo crontab -e

				
			

Se esta for a primeira vez que você edita esse arquivo, será solicitado que escolha um editor:

				
					
[label crontab]

no crontab for root - using an empty one

Select an editor.  To change later, run 'select-editor'.

  1. /bin/ed

  2. /bin/nano        &lt;---- easiest

  3. /usr/bin/vim.basic

  4. /usr/bin/vim.tiny

Choose 1-4 [2]:

...

				
			

No final do arquivo, adicione a seguinte linha:

				
					
[label crontab]

...

*/5 * * * * /home/&lt;^&gt;sammy&lt;^&gt;/&lt;^&gt;node_project&lt;^&gt;/ssl_renew.sh &gt;&gt; /var/log/cron.log 2&gt;&amp;1

				
			

Isso definirá intervalos de trabalho de cinco minutos cada, para que você possa testar se o seu pedido de renovação funcionou como o previsto. Também criamos um arquivo de registro, cron.log, para gravar o resultado relevante do trabalho.

Após cinco minutos, verifique o cron.log para ver se o pedido de renovação foi bem-sucedido:

				
					
tail -f /var/log/cron.log

				
			

Um resultado confirmando uma renovação bem-sucedida deve aparecer:

				
					
[secondary_label 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/&lt;^&gt;example.com&lt;^&gt;/fullchain.pem (success)

** DRY RUN: simulating 'certbot renew' close to cert expiry

**          (The test certificates above have not been saved.)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Killing webserver ... &lt;^&gt;done&lt;^&gt;

				
			

Agora, é possível modificar o arquivo crontab para definir um intervalo diário. Para executar o script todos os dias ao meio-dia, por exemplo, você modificaria a última linha do arquivo para que fique com a seguinte aparência:

				
					
[label crontab]

...

0 12 * * * /home/&lt;^&gt;sammy&lt;^&gt;/&lt;^&gt;node_project&lt;^&gt;/ssl_renew.sh &gt;&gt; /var/log/cron.log 2&gt;&amp;1

				
			

Você também vai querer remover a opção --dry-run do seu script ssl_renew.sh:

				
					
[label ~/node_project/ssl_renew.sh]

#!/bin/bash



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

DOCKER="/usr/bin/docker"



cd /home/&lt;^&gt;sammy&lt;^&gt;/&lt;^&gt;node_project&lt;^&gt;/

$COMPOSE run certbot renew &amp;&amp; $COMPOSE kill -s SIGHUP webserver

$DOCKER system prune -af

				
			

Seu trabalho cron irá garantir que seus certificados do Let's Encrypt não expirem, renovando-os quando forem elegíveis para tanto. Você também pode configurar um rodízio de registros com o utilitário Logrotate para rodiziar e comprimir seus arquivos de registro.

Conclusão

Você usou contêineres para configurar e executar um aplicativo Node com um proxy reverso Nginx. Você também utilizou certificados SSL para proteger o domínio do seu aplicativo e configurou um trabalho cron para renovar esses certificados quando necessário.

Se estiver interessado em aprender mais sobre plug-ins do Let's Encrypt, consulte nossos artigos sobre o uso do plug-in Nginx ou do plug-in standalone.

Você também pode aprender mais sobre o Docker Compose consultando os seguintes recursos:

A documentação do Compose também é um ótimo recurso para aprender mais sobre aplicativos multi-contêiner.