Let's Encrypt: Um Tutorial Razoavelmente Decente de Encriptação SSL para uma Configuração django, gunicorn, nginx e Ubuntu

publicado originalmente em inglês no dia 2017-02-28

Introdução

Encriptação subitamente tornou-se o assunto do momento, agora que o Google conseguiu finalmente quebrar o algoritmo SHA-1 (em inglês). Claro, nada disso vai salvar você da NSA, então é razoável questionar o quão necessária ela realmente é. Certamente parece uma daquelas causas techie que todo nerd decidiu subitamente começar a apoiar, como a iniciativa One Laptop Per Child, ou, pior ainda, a de que todo mundo é capaz e deveria aprender a programar. Jeff Atwood explica perfeitamente o que eu penso a respeito (em inglês).

Como um bom autoproclamado geek, decidi ceder. Digo, é uma coisa legal de se ter, e eu mereço coisas legais. Além disso, é sempre divertido aprender coisas novas. Afinal, se há algo que eu procuro nesta vida, é uma oportunidade de sair do tédio insuportavelmente doloroso (em inglês) que é a minha vida. Não faço ideia de como consegui leitores, mas, a não ser que o Google Analytics esteja pregando uma peça elaborada na minha pessoa, existem aparentemente cinco pessoas lendo a vergonha alheia estas linhas todos os dias. 

Esta publicação será monótona, porém. Prometi a mim mesmo que faria um passo-a-passo detalhado de como consegui aquele fofo cadeado verde próximo ao meu nome na barra de endereços do seu navegador, depois de ter batalhado contra documentações tenebrosamente ruins e recursos incompletos por alguns dias. Aqui vamos nós!

Configuração 

Todos os meus sites têm configurações similares, uma vez que não sou nenhum administrador de sistemas, e tenho um certo prazer em automatizar coisas. Todos são servidores virtuais (VPS) fornecidos pelo Digital Ocean, que, além de ser bastante competente no que faz, foi gentil o suficiente de me ceder 50 dólares (na verdade, 100 dólares, porque eu trapaceei) para experimentar o serviço. Se você for um estudante com 13 anos de idade ou mais, pode conseguir o mesmo através do GitHub Academic Pack. Caso contrário, bem, ainda pode espremer alguns dólares deles usando meu link de afiliado.

Eu sempre escolho a última versão 32-bit LTS do Ubuntu, o que, no caso, é a 16.04. Por questão de praticidade, meus servidores virtuais sempre rodam a mesma dupla gunicorn e nginx. De agora em diante, tenha em mente que este passo-a-passo foi concebido com estas quatro dependências em mente. Se você estiver usando outra configuração, é mais provável que você encontre outro tutorial melhor pela internet.

Também vou assumir que você já tem um domínio adequadamente configurado. Se você conseguiu o Github Academic Pack, então deve ter recebido um gratuitamente, cortesia do Namecheap. Este passo é necessário, já que certificados para endereços IP numéricos foram proibidos há muitos anos.  

Primeiros passos

Eu usei o Let's Encrypt para conseguir um certificado SSL válido. É fácil e rápido, uma vez que você finalmente ultrapassa a cachoeira de erros que despencarão a sua frente, com pouca ou nenhuma informação disponível para analisá-los. Se você estiver com sorte, este passo-a-passo ajudar-lhe-á a superar ou evitar a maioria deles. 

Os certificados do Let's Encrypt são gratuitos e compatíveis com todos os sistemas operacionais e navegadores a que eu escolhi dar suporte. Para instalá-lo, insira:

sudo apt-get update
sudo apt-get install letsencrypt

Antes que você faça qualquer outra coisa, encontre o arquivo settings.py e adicione as seguintes linhas:

sudo nano settings.py
# URL that handles encryption files
ENCRYPT_URL = '/.well-known/'
ENCRYPT_ROOT = os.path.join(STATIC_ROOT, '.well-known')

Agora, encontre o arquivo urls.py e adicione isto:

sudo nano urls.py
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # ... the rest of your URLconf goes here ...
] + static(settings.ENCRYPT_URL, document_root=settings.ENCRYPT_ROOT)

Em seguida, encontre o arquivo de configuração do nginx para o seu site. Deve ser algo como:

nano /etc/nginx/sites-available/yourwebsite.com

Abaixo da linha server_name yourwebsite.com;, adicione o seguinte para fazer o nginx autorizar o Let's Encrypt a encontrar os seus certificados:

location ~ /.well-known { allow all; }

Rode sudo nginx -t para ter certeza de que você não cometeu nenhum engano. Você deve obter o seguinte como resposta:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok 
nginx: configuration file /etc/nginx/nginx.conf test is successful

Se você receber alguma mensagem diferente, reze para o São Google ajudar a encontrar a solução. Não se preocupe muito, o mais provável é que tenha sido apenas algum erro de digitação. Não chegamos à parte realmente complicada ainda.

Reinicie o nginx com o comando sudo systemctl restart nginx. Espere alguns segundos, depois entre com os comandos abaixo para ensinar ao firewall a permitir o tráfego HTTP (porta 80) e HTTPS (porta 443):

ufw allow 80
ufw allow 443
ufw allow 'Nginx-Full'

Você deve receber uma destas duas mensagens: Allow rule ou Skipped rule, qualquer uma das duas é um resultado positivo. Se você chegou até aqui, está pronto para fazer o trabalho da NSA um pouquinho mais difícil. Hora de começar a encriptação propriamente dita.

Conseguindo os certificados

Você precisa decidir onde quer guardar os arquivos de encriptação. Onde quer que seja, precisa ser na mesma pasta que você informou ao django. Eu coloquei a minha na pasta static, porque é, ao mesmo tempo, mais fácil para o nginx servir e para o django encontrar. Execute o comando:

sudo letsencrypt certonly -a webroot --webroot-path=/path/to/website/files -d www.yourwebsite.com -d yourwebsite.com

Uma tela estranha aparecerá, pedindo seu endereço de email. Após, pedirá que você aceite os termos de uso, então pressione Agree. Este passo deve levar alguns minutos, controle sua ansiedade.

This is also the point where most errors should happen. It's impossible to make a guide covering all of them, but, luckily, they are very straight forward. You should be able to handle most of them by yourself, but, if you don't, it's time to go back to the Church of Google. 

Here's a tip: if you are getting 404 errors, go back to your nginx configuration, and to Django settings/urls, and double check them. They must agree with each other, and they must coincide with the actual directory where /.well-known/ is located. 

Another one: 403 errors are either firewall misconfiguration (run ufw status to check it) or a badly configured nginx. Go back there and double check them.

Once you figure everything out and finally get a success message, run:

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

This command attempts to renew your certificate. You should get a message telling you it's too early, and maybe another one telling you need to input your email address. Ignore it, this is a known bug.

The next step will take several minutes to complete, so, if you need a lunch break or some sleep, type it and leave it:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

This will generate a random Diffie-Hellman number that will be used to provide you with a secret key. Rumour has it that the NSA has broken into the 1024 bits long keys, so 2048 bits long might be its next target. I have picked 4096 bits long just for peace of mind. Don't trust me on this, though, it's probably inocuous. Let's be honest: if they really wanted to, NSA could crack this blog in a matter of seconds. The only reason for me to do this is the shiny padlock badge next to my name.

Once that command finishes, your website should have properly encrypted communications. It is still not enough, however. We have some patching up to do now.

Concluding

Create the following file and add those two lines in it:

sudo nano /etc/nginx/snippets/ssl-www.yourwebsite.com.conf
ssl_certificate /etc/letsencrypt/live/www.yourwebsite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.yourwebsite.com/privkey.pem;

Make sure those two files actually exist. You might need to fix their names on the file above.

If you are sure they exist, in that file path, then create this new file:

sudo nano /etc/nginx/snippets/ssl-params.conf

And paste this configuration that I have shamelessly copied from somewhere else:

# from https://cipherli.st/
# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Disable preloading HSTS for now.  You can use the commented out header line that includes
# the "preload" directive if you understand the implications.
#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

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

That is a basic security configuration that should be enough for most websites. I won't even pretend I understand what most of those code lines mean, nor should you. Just know that, unlike you and me, the professionals that typed that code are actually competent. The earlier you accept it as a fact, the earlier you can move on.

We are going to make changes to your nginx configuration (again). These might break nginx for good, so common sense tells us to backup the file:

sudo cp /etc/nginx/sites-available/yourwebsite /etc/nginx/sites-available/yourwebsite.bak

Now that the certificates have been successfully emitted, it is suggested that you avoid using unencrypted traffic altogether. Of course, your users, being even more stupid than you, have no idea of how to accomplish that, so you need to strip away wrong choices from them. We are going to edit this file:

sudo nano /etc/nginx/sites-available/yourwebsite

To effectively disable the port 80 (regular HTTP). Every request, from now on, should be handled only through the port 443 (HTTPS, with S as in Secure). Edit the existent entries in there with these lines:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name yourwebsite.com www.yourwebsite.com;
    return 301 https://$server_name$request_uri;
}

server {

    # SSL configuration
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    include snippets/ssl-example.com.conf;
    include snippets/ssl-params.conf;

# other stuff
}

As usual, run sudo nginx -t right after to make sure you didn't fuck it up. You should get this message:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If you received a complaint about default_server, this means that you need to get rid of the default nginx configuration file:

sudo mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak

Maybe get rid wasn't an adequate term, you just renamed it so nginx couldn't find it anymore. Let this phylosophical conclusion sink in: you don't need to get rid of your problems, you just need to be able to never find them again.

Once you recover from this egregious attempt at smart humor, restart nginx: 

sudo systemctl restart nginx

Wait a full minute, then open your favorite web browser and check this website: https://www.ssllabs.com/ssltest/analyze.html?d=yourwebsite.com. Make sure you replaced the placeholder with your actual website address. This is a test of your SSL implementation, it should return you an A+ after a few minutes, be patient. 

After the result, your website is fully encrypted and ready to go. In order to safely stay this way, let's set a cron job (geeky slang for a scheduled task) that will automatically renew your certificate. Let's Encrypt suggests two jobs as a safety measure:

sudo nano /etc/cron.monthly/renew1.sh
sudo nano /etc/cron.monthly/renew2.sh

In both files, copy and paste this same script, and let Ubuntu do its magic:

sudo letsencrypt renew
sudo systemctl reload nginx

Finally, as an additional layer of security, let's also teach Django how to behave under HTTPS. No more bash scripts, however, this time we'll do it the Python way. First, make sure your virtual environment is active. From the same directory where you found your settings.py and urls.py files:

source ../venv/bin/activate

A (venv) or similar must have shown up in the terminal. If so, run:

pip install django-sslify

This installation takes a couple minutes. Once it's done, open your settings.py file:

sudo nano settings.py

Look for the MIDDLEWARE_CLASSES variable. Once you find it, type in there, right in the first line:

MIDDLEWARE_CLASSES = ( 'sslify.middleware.SSLifyMiddleware',
# ... other stuff ...
)

That's it. You have done it. It is a pain in the ass in the first time, but it gets remarkably easier the next time. I just wish there was a tutorial with this setup when I was deploying my first website. But I guess I can't complain that much. After all, it has provided me with the opportunity to share my knowledge with whomever is stupid enough to pay attention to me, and also has kept this wheel spinning. This is the third blog post I have written in 48 hours, which is something I would have never thought possible. 

Last thoughts

I'm not going to lie: maintaining systems is boring as fuck. I love coding, though, and setting up the adequate environment for my apps to perform is a requisite, unfortunately. I do miss my starting days, spending nights between heavy metal songs and liters of coffee, coding useless Facebook apps on Heroku that never saw the light of day, because deployment was never worrisome. 

Heroku apps can get expensive quite fast, though. In the end, the perks of having cheap scalability and full infrastructure control far outweigh the disadvantages of having extra responsibilities. VPSs do make the job a lot more manageable, however. I gotta say that, if I couldn't preconfigure and build my environments using a foolproof web GUI and then repeatedly clone and adapt them in loco to suit my needs, I'd still be curralled on Heroku, fighting to fit into those measly 10K database rows the free plan would allow me. One of these days, I'll honor my promises and debunk that stupid myth of IaaS and PaaS are two different services that can't be compared

At this point, I actually have a bash script that does most of the typing for me, so it doesn't bother me as much. I'm still reliant on the Django/gunicorn/nginx/Ubuntu stack, I must confess, but that's a ludicrously easy setup to reproduce elsewhere. I might try it the next time I find myself drowning in the never-ending boredom. Until then, I hope the workflow I have just delineated here remains useful for a long time, and helps someone else overcome the fear of deploying their own apps to the cloud. Encryption itself does not mean much, and this process is quite annoying to deal with, but it has to be done only once, and it enables web applications that require logins and passwords, effectively unlocking another set of features for developers.

Or whatever else I need to say to convince myself that the job was done and I deserve some sleep. 

Etiqueta: tools 

 

Comentários

Não há comentários no momento.

Novo Comentário