O que o force_ssl realmente faz

Prévia grátis 5 min de leitura

Esta aplicação já roda em HTTPS, e três linhas do config/environments/production.rb são a razão:

# Assume all access to the app is happening through a SSL-terminating reverse proxy.
config.assume_ssl = true

# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true

# Skip http-to-https redirect for the default health check endpoint.
config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }

A maioria das pessoas lê isso, concorda e segue em frente. Elas merecem uma leitura mais atenta, porque entre as três elas decidem se as suas pessoas usuárias estão protegidas e se os seus deploys funcionam.

O modelo de terminação

O processo Rails quase nunca fala TLS. Algo na frente dele, como o kamal-proxy, um ingress controller ou um balanceador de nuvem, aceita a conexão criptografada, decifra e encaminha HTTP puro por uma rede privada.

flowchart LR
  B["Navegador"] -->|"HTTPS :443"| P["kamal-proxy / ingress / balanceador"]
  P -->|"HTTP :3000, rede privada"| R["Rails / Puma"]
  P -.->|"X-Forwarded-Proto: https"| R
  C[("certificado + chave privada")] --- P

Isso se chama terminação TLS, e explica a primeira linha de configuração. Do ponto de vista do Puma, toda requisição chegou em HTTP puro. O Rails concluiria que a conexão é insegura, se recusaria a marcar cookies como seguros e, com force_ssl ligado, redirecionaria para HTTPS para sempre.

assume_ssl = true diz ao Rails para tratar toda requisição como segura. Como alternativa, o proxy define X-Forwarded-Proto: https e o Rails confia nisso. Qualquer um funciona. Não ter nenhum dos dois é o que falha, e o sintoma é um laço infinito de redirecionamento que só acontece em produção.

O assume_ssl vem com uma ressalva: é uma suposição incondicional. Se uma requisição consegue chegar ao Puma sem passar pelo proxy, por um health check ao IP do pod ou uma porta de container exposta sem querer, o Rails vai tratar aquela requisição em texto puro como segura. A mitigação é entediante e eficaz: a porta do app nunca é alcançável de fora da rede privada.

O que o force_ssl liga

force_ssl = true são três comportamentos, não um:

Primeiro, o redirecionamento. Requisições HTTP recebem um 301 para a URL https://. Isso protege quem digitou o domínio sem esquema, mas só depois de uma requisição desprotegida já ter acontecido.

Segundo, o Strict-Transport-Security. Um cabeçalho de resposta manda o navegador usar HTTPS para este host, e os subdomínios dele, por um período, sem perguntar. Isso fecha a brecha acima: depois da primeira visita, o navegador já sobe para HTTPS antes de mandar qualquer coisa.

Strict-Transport-Security: max-age=63072000; includeSubDomains

Terceiro, cookies seguros. Cookies de sessão e outros são marcados como secure, então o navegador não os envia em HTTP puro. Sem isso, uma única requisição rebaixada vaza o cookie de sessão, e o redirecionamento não ajuda, porque o cookie sai junto com a requisição que é redirecionada.

Dos três, o marcador de cookie é o que mais importa e o menos discutido.

A exclusão do health check

config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }

Um balanceador ou uma probe do Kubernetes consulta /up em HTTP puro na rede interna. Se essa requisição for redirecionada, a probe recebe um 301, e uma probe que espera 200 lê um 301 como falha. A plataforma conclui que o pod não está saudável e reinicia uma aplicação funcional em laço. O gerador do Rails 8 traz essa linha, comentada, por esse motivo.

Nesta aplicação a linha é uma rede de segurança, e não o que mantém o /up vivo. O Rails coloca o ActionDispatch::AssumeSSL na pilha de middlewares antes do ActionDispatch::SSL, então, com assume_ssl ligado, toda requisição já conta como HTTPS e nada é redirecionado. A exclusão passa a importar no dia em que o assume_ssl for desligado e o proxy consultar em HTTP puro.

A exclusão cobre mais que o redirecionamento. Uma requisição excluída que conta como HTTPS continua recebendo o cabeçalho HSTS, mas os cookies dela não são marcados como secure.

O que este curso cobre

As três linhas acima pressupõem que existe um certificado válido. Este curso é sobre de onde ele vem e como ele continua válido sem ninguém lembrar de renovar.

  1. O que o force_ssl liga (esta aula).
  2. Certificados e a cadeia de confiança.
  3. ACME e Let's Encrypt.
  4. Automatizando a renovação.
  5. Terminando TLS, e o que o Rails enxerga.
  6. Wildcards, múltiplos domínios e os erros de sempre.
  7. Juntando tudo: HTTPS para esta aplicação.

Pontos-chave

  • O TLS é terminado por um proxy; o Rails recebe HTTP puro por uma rede privada.
  • O assume_ssl impede o Rails de concluir que a conexão é insegura. Sem ele ou um X-Forwarded-Proto confiável, você tem um laço de redirecionamento.
  • O force_ssl faz três coisas: redirecionar, HSTS e cookies seguros. O marcador de cookie é o que evita o vazamento de sessão.
  • Excluir /up do redirecionamento evita que um health check em HTTP leia um 301 como falha; com assume_ssl ligado, nada é redirecionado, para começar.
Aula 1 de 7 · grátis

Continue com a aula 2

Certificados e a cadeia de confiança

O que um certificado contém, por que o servidor envia o intermediário e como diagnosticar os erros comuns.

Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.

Digite para buscar...