Certificados TLS sob controle com cert-manager e Prometheus

Ou: como quebrar seu site em produção sem fazer nada.

Daniel Martins
Jusbrasil Tech
8 min readAug 31, 2019

--

Vivemos no tempo no qual as pessoas entenderam que a Internet não é um lugar totalmente seguro, até mesmo perigoso, e por isso é preciso tomar certos cuidados para se proteger e ter um nível mínimo de privacidade e segurança.

Como uma parte significativa da interação dos usuários com a Internet se dá através de um navegador, os mesmos acabam sendo um vetor importante para propagação de melhorias rumo a uma Internet mais segura; mesmo que a princípio muitas dessas melhorias sejam um inconveniente para a maior parte dos usuários, de modo geral elas têm sido planejadas e executadas gradualmente com mínimo impacto aos usuários.

Interface do Chrome mostrando para o usuário que HTTP é inseguro.
O primeiro passo para uma Internet mais segura são pessoas conscientes dos riscos que correm.

Uma dessas mudanças em particular é fazer o usuário entender que HTTP é inseguro. Os navegadores têm feito um trabalho excelente em termos de user experience e aos poucos mesmo usuários leigos estão deixando de achar que o “íconezinho do 🔒 só precisa estar lá quando se acessa o site do banco.”

Para a mudança ser completa, no entanto, é preciso fechar a outra ponta: a de incentivar os sites a implementarem HTTPS. Isso tem sido feito de várias formas, a mais notória delas sendo piorar a posição de sites sem suporte a HTTPS no ranqueamento dos motores de busca; como uma das principais fontes de tráfego de boa parte dos sites são justamente os motores de busca, implementar HTTPS acaba sendo algo necessário para manter o tráfego orgânico saudável.

HTTPS no Mundo Real — O Ciclo do Mal

Pior que ver isso num site de terceiros é ver isso no seu próprio site.

A seguinte situação é mais comum do que a maioria das empresas gosta de admitir, e existe uma boa chance de algo parecido já ter acontecido na sua (nota: história totalmente fictícia!)

O responsável pelo SEO entra na sala desesperado dizendo que o tráfego está caindo e que já fizeram de tudo. "Só falta o HTTPS," diz.

Para solucionar o problema o mais rápido possível — afinal isso está afetando diretamente a conversão do produto e consequentemente a receita — a empresa compra um certificado TLS (geralmente com um ano de duração), instala as chaves no servidor e pronto: de zero a HTTPS em poucas horas.

Incêndio apagado. Todos respiram aliviados.

O tempo passa e logo ninguém mais se lembra do tal certificado, até que um belo dia — exatamente um ano depois — as métricas de visitas caem para zero. Você tenta acessar o produto e se depara com a famosa tela de erro do browser. Pânico.

Com o site fora do ar, o jeito é, novamente, resolver o problema do jeito mais rápido possível, e, novamente, o certificado é renovado manualmente.

E o ciclo se repete.

Existe um jeito melhor de fazer isso e vou mostrar como fizemos aqui no Jusbrasil.

Nossa Stack

Aqui no Jus rodamos quase tudo no Kubernetes do Google Cloud. O tráfego é balanceado entre Pods do nginx, que por sua vez roteiam as requests para os serviços corretos dependendo do hostname e/ou URI. Bem padrão.

Para cobrir todo o range de serviços da nossa plataforma com HTTPS, foram criados alguns certificados manualmente ao longo do tempo; uns foram obtidos via GoDaddy; outros via Let's Encrypt com certbot.

Automatizando a Emissão de Certificados

O Let's Encrypt é uma ONG cuja missão é democratizar o acesso a certificados que, até então, por serem caríssimos, eram acessíveis apenas a empresas. Com Let's Encrypt, gente como você e eu podemos ter nossos sites pessoais com suporte a HTTPS sem pagar nada por isso. Eu particularmente acho isso incrível e, sem isso, jamais seria possível sonharmos com uma Internet mais segura para todos!

Mas voltando ao assunto. O grande lance do Let’s Encrypt é que os certificados emitidos por eles são de curta duração (3 meses), o que de certo modo serve para desincentivar a criação manual desses certificados, afinal, ninguém quer ter que parar a cada três meses para recriar os certificados. Em contrapartida, para possibilitar a emissão e renovação de certificados de forma 100% automatizada, o Let's Encrypt suporta o protocolo ACMEv2, que conta com uma infinidade de clientes suportados — como certbot e lego — permitindo que certificados sejam criados e renovados com o mínimo de fricção.

Protocolo ACMEv2 com challenge DNS-01, para a criação de certificados wildcard (i.e. *.domain.com).

No caso do Jus, como falei antes, até então os certificados eram todos gerados manualmente e não existia nenhum monitoramento para avisar com antecedência quando cada certificado iria expirar; ou seja, tínhamos em mãos uma bomba prestes a explodir!

Preparando o Terreno

Até então, havia um script para simplificar a geração desses certificados. A cada três meses alguém tinha que parar e rodar o script para regerar novos certificados e copiar as chaves para os locais corretos.

Para complicar um pouco as coisas, temos mais de um cluster de nginx, e em cada cluster, os certificados eram "instalados" de formas diferentes; em um cluster, as chaves do certificado eram passadas para o contêiner do Docker via variáveis de ambiente; em outro, as chaves eram copiadas diretamente para a imagem do Docker durante o processo de build.

Como ambos os clusters rodam no Kubernetes, uma possibilidade seria expor esses certificados para os contêineres de forma mais padronizada, via Secrets montados no sistema de arquivos como Volumes. Inclusive, esse padrão é tão comum no Kubernetes que existe um sub-tipo de Secret somente para definir chaves TLS.

apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
name: my-tls-cert
data:
tls.crt: ...
tls.key: ...

Como um dos certificados iria vencer dentro de poucos dias, optamos por gerar os certificados manualmente uma última vez e ajustar os nginx para receber os certificados através de Volumes.

apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: cert
mountPath: /etc/nginx/certs
readOnly: true

volumes:
- name: cert
secret:
secretName: my-tls-cert

Com isso, bastou alterar as configurações do nginx para ler as chaves do diretório correto.

server {
listen 443 ssl;
server_name ...;

ssl_certificate /etc/nginx/certs/tls.crt;
ssl_certificate_key /etc/nginx/certs/tls.key;


...
}

A Solução Definitiva

O cert-manager tem se tornado a solução de-facto para criação e renovação de certificados em clusters de Kubernetes pela sua facilidade de uso e grande número de integrações.

Uma vez instalado, basta usar a cli do Kubernetes — o kubectl — para gerenciar os certificados. Não entrarei em muitos detalhes aqui já que a documentação do cert-manager faz um excelente trabalho em explicar como a ferramenta funciona, mas basicamente o que fizemos foi configurar um ClusterIssuer que aponta para o endpoint ACMEv2 do Let's Encrypt e especifica qual é o provider de DNS que o cert-manager pode usar para completar o desafio.

apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ...
privateKeySecretRef:
name: letsencrypt-production
dns01:
providers:
- name: cf-dns
cloudflare:
email: ...
apiKeySecretRef:
name: cloudflare-api-key
key: api-key

Em seguida, foi só criar os objetos Certificate e assistir a magia acontecer.

apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: wildcard
spec:
secretName: cert-wildcard
issuerRef:
name: letsencrypt-production
kind: ClusterIssuer
commonName: '*.jusbrasil.com.br'
dnsNames:
- '*.jusbrasil.com.br'
acme:
config:
- dns01:
provider: cf-dns
domains:
- '*.jusbrasil.com.br'

Com isso configurado, bastou conferir status do certificado com kubectl describe certs. Em alguns minutos, o certificado estava disponível para uso, sendo só uma questão de alterar os nginx para ler o certificado do novo Secret, o cert-wildcard.

Monitorando Certificados com Prometheus

Com esse setup, o cert-manager renovará os certificados automaticamente quando faltar 30 dias ou menos para o vencimento dos mesmos.

No entanto, como problemas acontecem, não é possível garantir que não acontecerá nada nesse meio tempo que quebre o cert-manager, por isso é necessário ter algum tipo de monitoração para avisar quando um certificado está próximo de vencer para que possamos tomar as providências necessárias — como, por exemplo, olhar os logs do cert-manager e entender o porquê ele não conseguiu renovar os certificados.

No Jus, boa parte da monitoração existente foi feita em cima do Prometheus, que é uma das ferramentas open source mais usadas pra monitorar clusters Kubernetes. O próprio cert-manager expõe algumas métricas com relação aos certificados gerados por ele, incluindo a data de expiração.

Query PromQL para retornar quantos meses faltam para cada certificado emitido via cert-manager expirar.

Como ainda temos outros certificados válidos gerados através de outros meios, somente olhar as métricas do cert-manager (ainda) não é uma opção.

Uma solução para isso é utilizar o blackbox-exporter, uma ferramenta que suporta uma infinidade de protocolos para monitorações caixa-preta. Nesse caso específico, configuramos o blackbox-exporter para usar o módulo TCP e, sempre que o Prometheus tenta coletar as métricas do blackbox-exporter, o mesmo cria uma conexão TLS na porta 443 (que é a porta padrão usada por servidores HTTPS) com os endpoints especificados e retorna o resultado para o Prometheus na forma de métricas.

# Configuração do blackbox-exportermodules:
tls_connect:
prober: tcp
timeout: 5s
tcp:
tls: true
# Configuração do Prometheus- job_name: tls-blackbox
scrape_interval: 1m
metrics_path: /probe
params:
module: [tls_connect]
static_configs:
- targets:
- some.hostname.com:443
- other.hostname.com:443
- ...
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:80

O fluxo parece complicado, mas não é.

O blackbox-exporter conecta nos endpoints a cada pedido de métricas (scrape) do Prometheus.

Com isso, passamos a ter métricas do tempo de expiração de todos os certificados que precisamos monitorar independente de como os mesmos foram gerados.

Tempo de expiração dos certificados usados nos nossos domínios, em meses.

Para encerrar, o que falta agora é criar um alerta no Prometheus para avisar caso algum certificado esteja perto de expirar.

groups:
- name: tls.rules
rules:
- alert: TLSCertExpiringSoon
expr: probe_ssl_earliest_cert_expiry{job="tls-blackbox"} - time() < 86400 * 20
for: 1h
labels:
severity: high
annotations:
summary: TLS certificate for {{ $labels.instance }} is due in less than 20 days

Conclusão

Automatizar a criação e renovação de certificados era uma tarefa extremamente complexa, mas ferramentas como cert-manager e serviços como Let's Encrypt tornaram o processo muito mais simples.

Monitorar a expiração dos certificados com o Prometheus também foi um processo bem descomplicado, o que mostra a versatilidade desta ferramenta.

Gostou do artigo? Bate palminhas!! 👏🏻👏🏻👏🏻
Gostaria de trabalhar no Jusbrasil? https://sobre.jusbrasil.com.br/carreiras
Acompanhe nossas contribuições em: https://twitter.com/JusbrasilTech

--

--