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
Para seguir este tutorial, será necessário:
- Um servidor Ubuntu 18.04, um usuário não raiz com privilégios
sudo
e um firewall ativo. Para saber como configurar isso, consulte este guia de configuração inicial do servidor. - O Docker e o Docker Compose instalados no seu servidor. Como orientação na instalação do Docker, siga os Passos 1 e 2 de Como instalar e usar o Docker no Ubuntu 18.04. Como orientação na instalação do Compose, siga o Passo 1 de Como instalar o Docker Compose no Ubuntu 18.04.
- Um nome de domínio registrado. Este tutorial usará o example.com do início ao fim. Você pode obter um de graça no Freenom, ou usar o registrador de domínios da 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 DigitalOcean para mais detalhes sobre como adicioná-los a uma conta DigitalOcean, caso seja o que está usando:
- Um registro A com
example.com
apontando para o endereço IP público do seu servidor. - Um registro A com
example.com
apontando para o endereço IP público do seu servidor.
- Um registro A com
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 DigitalOcean. 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 node_project
Vá para o diretório node_project
:
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:
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" ]
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 a flag -t
. Vamos nomear a imagem node-demo
, mas você pode dar a ela o nome que quiser:
- docker build -t node-demo .
Assim que o processo de construção for concluído, você pode listar suas imagens com o docker images
:
Você verá o seguinte resultado, confirmando a compilação da imagem do aplicativo:
Output
REPOSITORY TAG IMAGE ID CREATED SIZE
node-demo 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 flags com este comando:
-p
: publica a porta no contêiner e a mapeia para uma porta no nosso host. Usaremos a porta80
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 node-demo -p 80:8080 -d node-demo
Verifique seus contêineres em execução com o docker ps
:
Você verá o resultado que confirma que o seu contêiner do aplicativo está funcionando:
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
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
:
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
Pare o contêiner com o docker stop
. Certifique-se de substituir o CONTAINER ID
listado aqui pelo CONTAINER ID
do seu aplicativo:
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 a flag -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:
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:
~/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 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:
Primeiro, defina o serviço de aplicativo:
~/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 ocontext
edockerfile
, 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çãoimage
, 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:
~/node_project/docker-compose.yml
services:
nodejs:
...
networks:
- app-network
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
:
~/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
Algumas das configurações que definimos para o serviço nodejs
permanecem as mesmas, mas também fizemos as seguintes alterações:
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 chamadoweb-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:
~/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 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.--agree-tos
: especifica que você concorda com o Acordo de Subscrição do ACME.--no-eff-email
: diz ao Certbot que você não deseja compartilhar seu e-mail com a Fundação Electronic Frontier (EFF). Sinta-se à vontade para omitir isso se preferir.--staging
: diz ao Certbot que você gostaria de 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 taxas do Let’s Encrypt.-d
: permite que você especifique nomes de domínio que gostaria de aplicar ao seu pedido. Neste caso, incluímos oexample.com
ewww.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:
~/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
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:
~/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
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 a flag -d
, que executarão os contêineres nodejs
e webserver
em segundo plano:
Você verá um resultado confirmando que seus serviços foram criados:
Output
Creating nodejs ... done
Creating webserver ... done
Creating certbot ... done
Com o uso do docker-compose ps
, verifique o status dos seus serviços:
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
:
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
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 service_name
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:
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
Agora que você sabe que seu pedido será bem sucedido, edite a definição do serviço certbot
para remover a flag --staging.
Abra o docker-compose.yml
:
Encontre a seção do arquivo com a definição de serviço do certbot
e substitua a flag --staging
na opção command
pela flag --force-renewal
, que 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:
~/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
...
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á um resultado indicando que seu pedido de certificado foi bem sucedido:
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
Com seus certificados no lugar, você pode seguir em frente para modificar sua configuração 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:
Gere sua chave com o comando openssl
:
- sudo openssl dhparam -out /home/sammy/node_project/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:
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. Lembre-se de substituir o example.com
pelo seu próprio domínio:
~/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;
}
O bloco de servidor HTTP especifica o webroot para pedidos de renovação do Certbot no diretório .well-knowb/acme-challenge
. Isso também inclui uma diretriz de reescrita, que direciona pedidos HTTP ao diretório root 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:
Na definição do serviço webserver
, adicione o seguinte mapeamento de portas e o volume nomeado 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
Em seguida, adicione o volume dhparam
às suas definições de volumes
:
~/node_project/docker-compose.yml
...
volumes:
...
dhparam:
driver: local
driver_opts:
type: none
device: /home/sammy/node_project/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
:
Você deve ver um resultado indicando que seus serviços nodejs
e webserver
estão funcionando:
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 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:
Adicione o seguinte código ao script para renovar seus certificados e recarregar a configuração do seu servidor Web:
~/node_project/ssl_renew.sh
#!/bin/bash
/usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml run certbot renew --dry-run
&& /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml kill -s SIGHUP webserver
Além de especificar o local do nosso binário docker-compose
, também especificamos o local do nosso arquivo docker-compose.yml
para que os comandos docker-compose
sejam executados. Neste caso, estamos usando o docker-compose run
para iniciar um contêiner do certbot
e sobrepor o command
fornecido na nossa definição de serviço com outro: o subcomando renew
, que renova certificados que estão próximos de expirar. Incluímos a opção --dry-run
aqui para testar nosso script.
O script usa o docker-compose kill
para enviar um sinal SIGHUP
para o contêiner webserver
de forma a recarregar a configuração do Nginx. Para obter mais informações sobre o uso deste processo para recarregar sua configuração do Nginx, consulte este post do blog do Docker sobre a implantação da imagem oficial do Nginx com o Docker.
Feche o arquivo quando terminar a edição. Torne-o executável:
Em seguida, abra seu arquivo root crontab
para executar o script de renovação em um intervalo especificado:
Se essa é a primeira vez que você edita esse arquivo, será solicitado que escolha um 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]:
...
No final do arquivo, adicione a seguinte linha:
crontab
...
*/5 * * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1
Isso definirá o intervalo de trabalho para a cada cinco minutos, para que você possa testar se seu pedido de renovação funcionou como previsto ou não. 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 ou não:
- tail -f /var/log/cron.log
Um resultado confirmando uma renovação bem-sucedida deve aparecer:
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
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 se pareça com isto:
crontab
...
0 12 * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1
Você também vai querer remover a opção --dry-run
do seu script ssl_renew.sh
:
~/node_project/ssl_renew.sh
#!/bin/bash
/usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml run certbot renew
&& /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml kill -s SIGHUP webserver
Seu trabalho cron
irá garantir que seus certificados do Let’s Encrypt não expirem, através da renovação deles quando forem elegíveis. Você também pode configurar uma rotação de registro com o utilitário Logrotate para rotacionar 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.