Passo a passo: configurar mTLS entre microserviços em Docker Compose usando PKI interna

Redes & Segurança · Por · Publicado em · Atualizado em · 8 min de leitura

Monitor exibindo um IDE com código-fonte e painel de controle de versão (Git) visível
Tela de desenvolvimento com código e histórico Git, apropriada para tutorial técnico Foto: Daniil Komov / Pexels

Este passo a passo mostra como configurar mTLS entre microserviços em Docker Compose usando uma PKI interna. Você vai gerar uma CA local e certificados com OpenSSL, configurar um serviço com Nginx para exigir certificado de cliente e validar a comunicação segura entre containers, sem expor portas públicas.

Pré-requisitos e objetivos do laboratório

Você precisa de Docker e Docker Compose instalados e OpenSSL disponível no host. Vamos criar uma CA local, emitir certificados para dois serviços e exigir autenticação mútua entre eles.

Se ainda não tem Docker/Compose no servidor, veja o guia de instalação antes de seguir.

O objetivo é ter service-a chamando service-b via HTTPS interno, com verificação de certificado de cliente obrigatória.

  • Docker e Docker Compose funcionando no host
  • OpenSSL instalado para gerar chaves e certificados
  • Editor de texto para criar arquivos de configuração
  • Conhecimento básico de redes Docker e volumes
Recurso Uso no tutorial
CA interna Assinar os certificados de servidor e cliente
Nginx Encerrar TLS e exigir certificado de cliente
curl Testar requisições com mTLS
Docker Compose Orquestrar serviços e volumes de certificados

Arquitetura: mTLS entre containers no mesmo network

No Docker Compose, cada serviço é resolvido pelo próprio nome DNS no network padrão. Vamos explorar isso emitindo certificados com DNS SAN igual ao nome do serviço.

O service-b rodará Nginx em 8443, com certificado de servidor assinado pela nossa CA. Ele exigirá um certificado de cliente confiável para aceitar conexões.

O service-a terá um certificado de cliente assinado pela mesma CA e usará curl para se autenticar no service-b.

Se você planeja usar proxy reverso em produção, veja um comparativo prático de opções.

  • SAN do servidor: DNS:service-b
  • SAN do cliente: DNS:service-a
  • Rede: default do Compose, sem portas públicas
  • Autenticação mútua: ambos apresentam certificados confiáveis

1) Criar a PKI interna (CA, extensões e chaves)

Vamos criar a estrutura de diretórios, uma CA raiz local e os arquivos de extensão para emitir certificados de servidor e cliente com SAN apropriado. Não versione chaves privadas no Git; mantenha-as fora do controle de versão e com permissões restritas.

Arquivo/dir Finalidade
pki/ca/ca.key.pem Chave privada da CA (secreta)
pki/ca/ca.cert.pem Certificado público da CA (compartilhável)
pki/private/*.key.pem Chaves privadas dos serviços
pki/certs/*.cert.pem Certificados assinados para serviços
pki/ext/*.ext Extensões: SAN e usos de chave
#!/usr/bin/env bash
set -euo pipefail

mkdir -p pki/{ca,csr,certs,private,ext}

# 1) CA raiz local
openssl genrsa -out pki/ca/ca.key.pem 4096
openssl req -x509 -new -key pki/ca/ca.key.pem -days 3650 -sha256 \
  -subj "/CN=Dev Local Root CA" -out pki/ca/ca.cert.pem

# 2) Arquivos de extensão (SAN + usos)
cat > pki/ext/service-b.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=critical, digitalSignature, keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=@alt_names
[alt_names]
DNS.1=service-b
EOF

cat > pki/ext/service-a.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=critical, digitalSignature, keyEncipherment
extendedKeyUsage=clientAuth
subjectAltName=@alt_names
[alt_names]
DNS.1=service-a
EOF

# 3) Certificado de servidor (service-b)
openssl req -new -newkey rsa:2048 -nodes \
  -keyout pki/private/service-b.key.pem \
  -subj "/CN=service-b" -out pki/csr/service-b.csr.pem
openssl x509 -req -in pki/csr/service-b.csr.pem \
  -CA pki/ca/ca.cert.pem -CAkey pki/ca/ca.key.pem -CAcreateserial \
  -out pki/certs/service-b.cert.pem -days 825 -sha256 \
  -extfile pki/ext/service-b.ext

# 4) Certificado de cliente (service-a)
openssl req -new -newkey rsa:2048 -nodes \
  -keyout pki/private/service-a.key.pem \
  -subj "/CN=service-a" -out pki/csr/service-a.csr.pem
openssl x509 -req -in pki/csr/service-a.csr.pem \
  -CA pki/ca/ca.cert.pem -CAkey pki/ca/ca.key.pem -CAcreateserial \
  -out pki/certs/service-a.cert.pem -days 825 -sha256 \
  -extfile pki/ext/service-a.ext

chmod 600 pki/private/*.key.pem

echo "PKI pronta em ./pki"

2) Configurar o Nginx do service-b para exigir mTLS

O Nginx atuará como servidor HTTPS no service-b, validando o certificado de cliente com a CA local. Para o laboratório, serviremos uma resposta simples. Em produção, você apontaria para um upstream interno.

Se prefere proxies prontos para containers, compare opções de roteamento HTTPS.

events {}
http {
  server {
    listen 8443 ssl;

    ssl_certificate     /etc/nginx/certs/service-b.cert.pem;
    ssl_certificate_key /etc/nginx/certs/service-b.key.pem;

    ssl_client_certificate /etc/nginx/certs/ca.cert.pem;
    ssl_verify_client on;  # exige certificado de cliente

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    location / {
      default_type text/plain;
      return 200 "Hello from service-b. Client DN: $ssl_client_s_dn\n";
    }
  }
}

3) Declarar os serviços no Docker Compose e montar os certificados

Agora criaremos o docker-compose.yml com dois serviços. O service-b roda Nginx com os certificados montados em volume somente leitura. O service-a mantém um container com curl e os seus artefatos de cliente. Nenhuma porta será publicada externamente.

Mantenha chaves privadas como volumes com permissões restritas.

version: "3.8"
services:
  service-b:
    image: nginx:alpine
    volumes:
      - ./nginx/service-b.conf:/etc/nginx/nginx.conf:ro
      - ./pki/certs/service-b.cert.pem:/etc/nginx/certs/service-b.cert.pem:ro
      - ./pki/private/service-b.key.pem:/etc/nginx/certs/service-b.key.pem:ro
      - ./pki/ca/ca.cert.pem:/etc/nginx/certs/ca.cert.pem:ro
    expose:
      - "8443"

  service-a:
    image: curlimages/curl:8.9.0
    command: ["sleep", "infinity"]
    volumes:
      - ./pki/certs/service-a.cert.pem:/certs/service-a.cert.pem:ro
      - ./pki/private/service-a.key.pem:/certs/service-a.key.pem:ro
      - ./pki/ca/ca.cert.pem:/certs/ca.cert.pem:ro

4) Subir o ambiente e validar a configuração básica

Crie o diretório de configuração do Nginx, salve o arquivo e suba os containers. Em seguida, confirme se o service-b está de pé e acessível pela rede interna do Compose.

Se precisar de um ambiente de monitoramento depois, você pode integrar métricas facilmente com Prometheus e Grafana.

# Salve o arquivo acima como nginx/service-b.conf
mkdir -p nginx

# Suba os serviços
docker compose up -d

# Verifique status
docker compose ps

# Opcional: inspecione logs do Nginx
docker compose logs -f service-b

5) Testes de mTLS: sucesso e falhas esperadas

Vamos testar chamadas a partir do service-a. Primeiro, sem certificado de cliente: a conexão deve falhar. Depois, com os artefatos corretos: a resposta deve incluir o DN do cliente. Certifique-se de usar o SAN correto no nome de host: service-b.

# 1) Sem certificado de cliente: deve falhar com 400/495
docker compose exec service-a sh -lc 'curl -sv --cacert /certs/ca.cert.pem https://service-b:8443/'

# 2) Com mTLS: deve retornar 200 e DN do cliente
docker compose exec service-a sh -lc \
  'curl -sS --cert /certs/service-a.cert.pem --key /certs/service-a.key.pem \
  --cacert /certs/ca.cert.pem https://service-b:8443/'

Boas práticas para produção e rotação de chaves

Evite usar a mesma CA para desenvolvimento e produção. Prefira uma cadeia com raiz offline e intermediária ativa.

Implemente rotação periódica de certificados com sobreposição de validade e implantação gradual.

Armazene chaves em cofres de segredo. Em Compose, use volumes com permissões mínimas. Nunca exponha portas TLS internas sem necessidade.

Se sua malha crescer, considere gateways ou sidecars dedicados a TLS.

  • Cadeia: raiz offline + intermediária online
  • Certificados curtos com rotação automatizada
  • Mapeamentos de volume somente leitura
  • Audite quem monta cada chave no container

Solução de problemas comuns

Erro de autoridade desconhecida: o cliente não está usando a CA correta. Monte o ca.cert.pem e passe via --cacert.

Nome inválido no certificado: o SAN não bate com o host service-b. Reemita com DNS:service-b.

Data inválida: hora do host ou container fora de sincronia. Ajuste NTP.

Permissão negada para chave: verifique chmod 600 no host e montagem ro no container.

Conexão cai sem logs: confirme o network do Compose e nome do serviço.

  • x509: certificate signed by unknown authority
  • SSL: no required SSL certificate sent
  • certificate verify failed: IP address mismatch
  • permission denied: /etc/nginx/certs/*.key.pem

Perguntas frequentes

O que é mTLS e quando devo usar entre microserviços?

mTLS autentica os dois lados da conexão TLS: servidor e cliente. Use quando serviços internos precisam garantir identidade forte e reduzir riscos de tráfego lateral malicioso. É útil em ambientes multi-tenant, dados sensíveis e redes compartilhadas.

Como escolher o SAN correto dos certificados no Docker Compose?

Use o nome DNS que os containers resolvem no network do Compose, normalmente o nome do serviço, como service-b. Se usar aliases, inclua todos os DNS relevantes no SAN para evitar falhas de verificação.

Posso usar Traefik ou Caddy no lugar do Nginx?

Sim. Traefik e Caddy têm boas integrações com Docker e suporte a TLS. A exigência de certificado de cliente também é possível. Avalie qual se adapta ao seu fluxo e recursos. Veja comparativos práticos para decidir.

mTLS interno substitui certificados públicos como Let’s Encrypt?

Não. Let’s Encrypt é ideal para expor serviços na internet com confiança pública. mTLS com PKI interna atende comunicação privada entre serviços. Você pode usar ambos: público para borda e PKI interna para tráfego leste-oeste.

Como rotacionar certificados sem parar serviços?

Gere novos certificados antes de expirar, monte-os em paths fixos e recarregue o processo (ex.: reload no Nginx). Mantenha a CA estável. Se trocar a CA, implemente período de coexistência confiando antiga e nova simultaneamente.

Quer adaptar este mTLS para seu ambiente?

Se você precisa levar essa abordagem para produção, com rotação automatizada e observabilidade, podemos conversar sobre o seu contexto e prioridades.

Falar sobre mTLS em produção

Veja também