TLS, HTTPS e mTLS em Zig: Terminação, Certificados e Conexões Seguras em Produção

Servir HTTP em texto plano serve para desenvolvimento local e quase nada mais. Em produção, todo tráfego que cruza a rede — entre o navegador e seu serviço, entre dois microsserviços, entre um worker e um broker — precisa de TLS. Sem ele, qualquer hop intermediário consegue ler, modificar ou regravar a conversa. O engraçado é que a maior parte da confusão em torno de TLS não vem do algoritmo em si, e sim de quem termina a conexão, quem valida o quê, e como os certificados entram e saem do processo.

Em Zig, você raramente vai reimplementar OpenSSL. A biblioteca padrão já expõe primitivas de TLS tanto para servidor quanto para cliente, e o ecossistema converge para os mesmos padrões do resto do mercado. Este guia é sobre como pensar TLS em Zig na prática: terminação na borda, terminação no processo, mTLS entre serviços, certificados e rotação. Ele complementa os guias de servidor HTTP em Zig para produção, rede com sockets TCP e UDP, configuração segura com segredos e env e observabilidade em Zig. A mentalidade é a mesma: falha explícita, comportamento previsível e nada de mágica de framework.

Onde o TLS termina

A primeira decisão de projeto não é “como”, é “onde”. TLS tem custo de CPU, custo de configuração e custo operacional. O lugar onde ele termina define boa parte da sua superfície de ataque.

TopologiaOnde terminaQuando usar
Edge / reverse proxyNginx, Caddy, Traefik, balanceador de nuvemServiço exposto à internet, automação de Let’s Encrypt
No processo (in-process)A própria aplicação ZigBackend isolado, mTLS interno, restrição de rede
Service meshSidecar (Linkerd, Istio)Muitos serviços, política centralizada
Sem TLSRede privada confiável e curtaApenas dev/local, nunca produção

A regra prática: se o tráfego sai de uma rede que você controla, ele precisa de TLS. Se ele entra por um navegador, termina em um proxy de borda com redirecionamento HTTP → HTTPS, HSTS e uma AC pública (Let’s Encrypt, ZeroSSL). Se ele corre entre dois pods do mesmo cluster, mTLS com uma AC interna (Step, Smallstep, cert-manager + uma CA privada) resolve.

O armadilha clássica é a topologia “TLS na borda, HTTP puro da borda até o pod” sem proteção adicional. Em clusters bem desenhados isso é aceitável porque o plano de dados é confiável. Em redes compartilhadas, ele abre espaço para interceptação lateral. mTLS ponta a ponta elimina a ambiguidade.

Certificados como arquivos, não como abstração

Um certificado X.509 é um arquivo PEM com três partes que importam: a chave privada, a folha assinada e a cadeia intermediária. Em produção, trate todos como segredos. Carregue do disco ou de um cofre; nunca embuta no binário; nunca registre em log.

Convenção útil, alinhada ao guia de configuração segura com segredos e env:

/etc/zigapp/tls/
  fullchain.pem   # folha + intermediários
  privkey.pem     # 0600, dono do serviço
  ca.pem          # CA usada para validar mTLS do cliente

A aplicação lê esses caminhos do ambiente (TLS_CERT, TLS_KEY, TLS_CLIENT_CA) e falha rápido na inicialização se algum estiver ausente ou ilegível. Falhar cedo é melhor do que descobrir o problema no meio da noite com uma conexão pendurada.

SNI e ClientHello: por que o nome importa

O Server Name Indication (SNI) é o que permite hospedar múltiplos domínios no mesmo IP com certificados diferentes. O cliente envia o nome do host no ClientHello, antes do handshake completar, e o servidor escolhe o certificado correto a partir dali.

Em Zig, ao construir um cliente TLS, passe sempre o nome do host que você está conectando. Não apenas o IP. Sem o nome, o servidor pode devolver o certificado errado e a validação falha — ou pior, devolve o certificado default e você fica sem perceber.

const std = @import("std");
const tls = std.crypto.tls;

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    var ctx = try tls.Client.init(allocator, .{
        .host = "ziglang.com.br",
        .ca = .{ .bundle = try std.fs.cwd().readFileAlloc(
            allocator,
            "/etc/ssl/certs/ca-certificates.crt",
            1 << 20,
        ) },
    });
    defer ctx.deinit(allocator);

    // A partir daqui ctx encapsula o estado do cliente TLS.
    // O nome em .host vira SNI no ClientHello e é comparado com o CN/SAN do certificado.
}

O detalhe que quebra produção: o nome do host precisa bater com o Subject Alternative Name (SAN) do certificado, não com o Common Name. Navegadores modernos ignoram o Common Name. O mesmo vale para bibliotecas atualizadas.

mTLS entre serviços

Mutual TLS (mTLS) é TLS com uma volta extra: o servidor também exige um certificado do cliente. É o padrão de ouro para comunicação serviço-a-serviço porque dispensa tokens de longa duração em redes internas — a identidade está atada ao certificado, que tem data de expiração curta.

O fluxo:

  1. O servidor carrega TLS_CERT + TLS_KEY como em qualquer HTTPS.
  2. O servidor também carrega TLS_CLIENT_CA e configura o handshake para exigir e validar o certificado do cliente.
  3. O cliente carrega seu próprio certificado cliente + chave.
  4. O cliente valida o certificado do servidor (cadeia pública).
  5. O servidor valida o certificado do cliente (cadeia privada interna).
  6. Os dois lados extraem a identidade do SAN do cliente para autorização.

A regra de ouro: certificados de cliente curtos. Um par válido por 24 horas, renovado por um processo automático (Step-CA, SPIFFE, cert-manager), é dramaticamente mais seguro do que um token JWT de 30 dias. Se um certificado vaza, a janela útil é curta.

Rotação sem reiniciar

A parte que mais assusta operação é a rotação. Você renovou o certificado no disco; como a aplicação passa a usá-lo sem cair?

Há três caminhos comuns:

  1. Reload por sinal. A aplicação captura SIGHUP, recarrega os PEMs do disco e troca o estado TLS ativo de forma atômica. Conexões novas usam o novo certificado; conexões antigas terminam com o antigo.
  2. Reload por watchers. Um watcher (inotify no Linux, kqueue no BSD) detecta a mudança no arquivo e dispara o mesmo caminho.
  3. Reload por deploy. Reimplanta a aplicação com o novo certificado embutido no volume. Simples, mas força janela de indisponibilidade.

O caminho 1 é o equilíbrio típico. A implementação Zig mantém um ponteiro atômico para a configuração TLS corrente; o handler de SIGHUP carrega a nova, faz o swap e libera a antiga quando a última conexão fecha. O padrão é o mesmo usado para recarregar rate limiting e cache TTL sem reiniciar.

Armadilhas de produção

Validação desligada. Em dev é tentador setar insecure_skip_verify. Em produção, isso transforma TLS em teatro. Nunca suba com validação desligada.

Cipher suites desatualizadas. TLS 1.2 ainda é aceitável, mas TLS 1.3 é o default moderno. Restrinja explicitamente aos ciphers recomendados (TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256). Desligue tudo abaixo de TLS 1.2.

Pinning mal feito. Pin de certificado inteiro quebra na primeira rotação. Pin de chave pública (SPKI) sobrevive enquanto a chave privada não rotaciona.

Sem OCSP stapling. Clientes que validam OCSP adicionam uma viagem extra por handshake. Servidores que fazem stapling entregam a resposta OCSP junto com o certificado, economizando um round-trip.

Log de segredos. Nunca logue a chave privada. Logue o fingerprint (SHA-256) para correlação, nunca o conteúdo.

Timeout de handshake. Handshake TLS não tem timeout natural; um cliente malicioso pode segurar o socket. Envolva o socket com um deadline e aborte o handshake se passar de poucos segundos.

Verificação

Antes de subir, verifique com ferramentas que não são a sua:

  • curl -v https://seu.servico/ para validar cadeia, SNI e protocolo negociado.
  • openssl s_client -connect host:443 -servername host -showcerts para inspecionar o certificado devolvido.
  • testssl.sh ou SSL Labs para auditoria externa de configuração.
  • step ca certificate para validar mTLS ponta a ponta.

A regra geral, que também vale para circuit breaker e retry e para JWT em APIs Zig: segurança que você não consegue medir a partir de fora, você não tem.

Conclusão

TLS em Zig segue o mesmo princípio do resto do ecossistema da linguagem: poucas camadas, falha explícita e controle total do que está acontecendo. A biblioteca padrão dá o que precisa; o que falta é disciplina operacional — onde termina a conexão, como os certificados entram e saem, como roda a rotação. Quem trata TLS como infraestrutura e não como detalhe, dorme melhor. Para padrões mais amplos de rede e estratégia, vale também o material sobre serviços Go em produção na rede Diego, que cobre o mesmo modelo mental de terminação e rotação aplicado a outra stack.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.