O blog da AWS

Autenticação de dispositivo com mTLS no Amazon CloudFront

Por André Botelho, Solutions Architect na AWS; Eduardo Rodrigues, Principal Solutions Architect na AWS e Edgar Costa Filho, Senior Solutions Architect na AWS.

Em serviços em que segurança é pilar de negócio, a pergunta que abre qualquer discussão é: como eu tenho certeza de que quem fala com a minha API é o meu app, naquele dispositivo, e não um script ou um emulador? Este artigo responde com mTLS de dispositivo terminado no Amazon CloudFront.
A resposta passa por três camadas que se reforçam mutuamente: device binding (vincular uma identidade criptográfica ao aparelho), chave protegida por hardware (Keystore no Android, Secure Enclave/Keychain no iOS) e platform attestation (Play Integrity / App Attest) para provar que o ambiente não foi adulterado.
O que faltava era um lugar para terminar essa autenticação mútua sem expor a origin. Esse gap fechou com um recurso recente: o Amazon CloudFront agora suporta mutual TLS (mTLS) no viewer, a autenticação mútua entre o dispositivo do usuário e a borda da AWS (anúncio oficial).
Neste artigo iremos abordar, de ponta a ponta, uma arquitetura real na AWS que combina essas três ideias. O código Terraform e os scripts de teste estão no final: clone, suba o laboratório e use a seção “Da demonstração para produção” como checklist antes de qualquer uso real.

Visão geral da solução

A plataforma proposta tem as seguintes características:

A figura a seguir resume a arquitetura demonstrada neste artigo:

Figura 1 — Arquitetura mTLS no Amazon CloudFront.

Figura 1: Arquitetura de enrollment e data plane com mTLS terminado no Amazon CloudFront.

Por que mTLS de dispositivo?

No Transport Layer Security (TLS) normal, só o servidor prova quem é; o cliente fica anônimo na camada de transporte e a identidade é empurrada para cima (token, cookie, API key). No mTLS os dois lados apresentam certificado e se autenticam já no handshake.
A sacada para mobile: se cada dispositivo tem um certificado próprio, a API passa a aceitar conexões só de dispositivos que você conhece. Quem não tem certificado válido não abre a conexão: é rejeitado no handshake, antes de qualquer processamento na camada de aplicação. Isso tende a reduzir o tráfego de bots, emuladores e ferramentas de interceptação.
Mas o certificado sozinho prova apenas posse de uma chave. Ele não garante que o app é o original nem que o aparelho não está comprometido. Por isso ele anda com mais duas peças: a chave protegida por hardware e a attestation.

Enrollment com Keystore/Keychain e respaldo de hardware

Enrollment é quando o dispositivo nasce para a plataforma: gera uma identidade, envia um Certificate Signing Request (CSR), a solicitação de assinatura de certificado, e recebe o certificado que vai usar dali em diante.
O ponto central é onde a chave privada vive. A boa prática é gerar o par de chaves dentro do enclave de segurança do aparelho.

A chave privada não sai do hardware, nem o próprio app a extrai. Você pede “assine isto” e o hardware assina internamente. É o que dá força ao device binding: mesmo com o dispositivo comprometido, reduzimos a chance de exposição inadvertida da chave.

Importante. Não embuta um certificado/chave fixos no binário. Se todos os dispositivos compartilham a mesma chave, o binding não vale nada. A chave é por dispositivo, gerada no enclave.

A figura a seguir acompanha esse fluxo do começo ao fim: repare que a chave privada nunca deixa o enclave e que o CSR só sai do dispositivo acompanhado do token de attestation.

Figura 2 — Fluxo de enrollment com chave em hardware e attestation.

Figura 2: Fluxo de enrollment — o app gera o par de chaves no enclave, coleta a attestation, envia o CSR e recebe o certificado emitido pela AWS Private CA.

Repare no que vem primeiro: no enrollment o device ainda não tem certificado, então essa rota não pode exigir mTLS. Ela é protegida por attestation. E, justamente por não ter mTLS, é a maior superfície de ataque da solução: é a rota que emite as credenciais de identidade.

Importante: Para produção, o enrollment precisa de mais do que attestation. Trate-o como um endpoint de emissão de credenciais, não como uma API comum: exija autenticação forte do usuário (amarrando o device a uma identidade já autenticada, no exemplo deste artigo o user_id chega no corpo sem nenhuma prova de identidade, apenas para fins de demonstração), coloque rate limiting e AWS WAF à frente e imponha um limite de dispositivos por usuário com idempotência.

Attestation: avaliando se o app é genuíno

A chave em hardware prova “este aparelho tem a chave”. A attestation indica algo complementar: um veredito do fornecedor da plataforma de que “este é o meu app, não modificado, num aparelho/ambiente íntegro”, um sinal de confiança, não uma prova criptográfica. Falta uma terceira prova, que nenhuma das duas entrega por si: que a chave daquele CSR é a que está no hardware. Sem essa amarração, “a chave nunca sai do dispositivo” é afirmação do cliente, não fato verificado pelo servidor. Para estreitar essa lacuna, exija que a attestation esteja vinculada àquele CSR, e valide esse vínculo no servidor antes de emitir o certificado.

No enrollment, o backend valida a attestation antes de emitir o certificado (na demonstração deste artigo, um stub; veja a nota mais adiante). E depois? A attestation é uma foto do momento: o dispositivo pode ser rooteado ou o app adulterado semanas depois. Por isso re-atestamos periodicamente. Detalhe importante: a attestation não é feita a cada request (é cara, tem limites de uso e depende de um serviço externo).
A divisão de trabalho:

  • Certificado mTLS = credencial contínua, barata, verificável na borda em cada conexão.
  • Attestation = checagem de postura, cara e externa, feita pontualmente (enrollment, renovação e eventos de risco).

Importante: Na demonstração apresentada neste artigo, não fazemos a checagem de attestation nas APIs oficiais da Apple/Google. O Lambda de enrollment aceita qualquer token não-vazio (stub), apenas para demonstrar a arquitetura.

Terminando o mTLS no CloudFront

Com o viewer mTLS, o CloudFront valida o certificado do device contra um trust store (o bundle da sua autoridade certificadora, a CA que emite os certificados de device) já no handshake, na borda. Por que terminar no CloudFront e não mais adiante, já dentro do seu perímetro?

  • Corte na borda: quem não tem certificado válido é rejeitado no ponto de presença mais próximo, antes de entrar no seu perímetro.
  • Escala e ataques distribuídos de negação de serviço (DDoS): o CloudFront é dimensionado para validar certificado em escala e absorver picos de volume.
  • Mecânica do TLS: o handshake do device termina no CloudFront. Do CloudFront para a origem é outra conexão, quem está atrás (ALB/Lambda) não vê o handshake do device. A identidade é repassada à origem como headers (ex.: CloudFront-Viewer-Cert-Serial-Number).

Vale um cuidado prático com esse header: o valor de CloudFront-Viewer-Cert-Serial-Number chega como hexadecimal separado por dois-pontos (ex.: 4a:3f:5c:92:...). Por isso a origem, a borda e o CloudFront KeyValueStore (KVS) precisam normalizar o serial para uma forma canônica (hex minúsculo, sem :) antes de comparar. Se os três lados não normalizarem da mesma maneira, a revogação por serial “não corta” mesmo com todo o resto correto, é uma das causas mais comuns de falso negativo neste desenho.
Um ponto que confunde: o viewer mTLS é configurado por distribuição, não por path. Como o /enroll não pode exigir certificado (o device ainda não tem um), no nosso exemplo, separamos em duas distribuições apontando para o mesmo ALB interno (via VPC origin, recurso que permite ao CloudFront alcançar origens em VPCs privadas):

  • distribuição de enrollment → sem mTLS, protegida por attestation.
  • distribuição de API (data plane) → mTLS Required: CloudFront KeyValueStore (KVS), que faz o corte, mais Online Certificate Status Protocol (OCSP), opcional e habilitado neste laboratório.

Ainda no nível da distribuição, um requisito fácil de esbarrar: o viewer mTLS exige HTTP/2 e não é suportado em HTTP/3 (pré-requisitos do recurso). O HTTP/2 é o padrão do CloudFront, então isso só vira problema se você habilitar HTTP/3 na distribuição de API.
No Terraform de exemplo, as duas distribuições usam o certificado default do CloudFront (cloudfront_default_certificate = true) e não declaram aliases: os endpoints são os domínios *.cloudfront.net de cada distribuição, sem domínio customizado. É intencional: ligar o viewer mTLS na distribuição com certificado default dispensa Amazon Route 53 e ACM e deixa o laboratório mais simples de subir. Se você quiser domínios próprios (enroll.<domínio> / api.<domínio>), basta adicionar aliases + certificado ACM + registros Amazon Route 53; é opcional. Por isso os scripts de teste leem os domínios via terraform output, em vez de assumir nomes fixos.

Ciclo de vida do certificado: validade e recert

A validade do certificado é um trade-off, não um “quanto menor, melhor”. Uma validade curta reduz a janela de um certificado exposto e a dependência de revogação. Mas ela cobra um preço em disponibilidade: quanto mais curto o certificado, mais o acesso do dispositivo depende de a renovação funcionar de forma confiável. Se o recert falha, por rede móvel instável, indisponibilidade do backend ou erro no cliente, o dispositivo não passa mais no handshake e o usuário fica sem acesso. Em muitas aplicações, esse impacto de negócio é mais grave do que o risco que a validade curta pretende mitigar.
A validade certa, então, é a que equilibra janela de exposição × tolerância a indisponibilidade, e pressupõe uma renovação robusta (ver “Boas práticas do recert”). Neste laboratório usamos certificados de 7 dias, emitidos pela AWS Private CA em modo short-lived (custo por certificado menor, pensado para rotação frequente, validade de até 7 dias). É um valor didático: em produção, calibre-o ao seu apetite de risco e à confiabilidade do seu pipeline de renovação. No exemplo, essa CA short-lived é uma CA raiz que emite os certificados de device diretamente. Simples para o laboratório, mas com implicações de hierarquia de Public Key Infrastructure (PKI) que discutiremos na seção “Da demonstração para produção”.
A renovação (recert) é silenciosa e acontece antes de expirar (~50–70% da vida). O truque central: a chave em hardware permanece a mesma, renova-se só o certificado para aquela chave.

Figura 3 — Renovação silenciosa do certificado (recert).

Figura 3: Recert por rollover (Renovação) — reusando a mesma chave de hardware, o Lambda emite um novo certificado, atualiza o serial no registry e revoga o antigo.

Boas práticas do recert

Como a validade curta torna o acesso dependente da renovação, o recert deixa de ser um detalhe e vira um caminho crítico de disponibilidade; por isso os itens a seguir merecem atenção.

  • Grace: emita o novo antes de o antigo expirar; considere clock skew.
  • Idempotência/retry: rede de celular falha; não emita vários certs por tentativa.
  • Fallback: se o certificado expirou, o device não passa no handshake, então volta para /enroll (sem mTLS, com attestation), e esse /enroll deve ser idempotente em relação ao device_id, ou seja, reenrollment do mesmo dispositivo substitui a identidade anterior em vez de consumir uma nova vaga no limite de dispositivos por usuário. Para que essa idempotência seja segura, o device_id não pode valer como credencial, o /enroll resolve o par (usuário autenticado, device_id) e só substitui a identidade quando aquele device_id já pertence àquele usuário. Um device_id que não seja dele é tratado como dispositivo novo, nunca como substituição, quem autoriza a troca é a autenticação forte do usuário (a nota de produção da seção de Enrollment), e o device_id apenas desambigua qual dos dispositivos daquele usuário está renascendo.
  • Re-attestation periódica: anexe um token de attestation atualizado no corpo do /renew de tempos em tempos, não precisa de rota própria.

Revogação: cortar um dispositivo na hora

Um certificado válido pode precisar morrer antes da hora: aparelho roubado, chave comprometida, fraude. Combinamos OCSP + KVS na borda, com o DynamoDB como reforço na origem.
Antes de detalhar as camadas, um esclarecimento que evita uma leitura equivocada da documentação: a doc de modos de CA diz que certificados short-lived podem ser implantados sem mecanismo de revogação, o que é diferente de dizer que não suportam um. Não há proibição. Neste exemplo a CA short-lived tem ocsp_configuration { enabled = true }, o que embute a URL do responder na extensão Authority Information Access (AIA) dos certificados de device, e o CloudFront usa essa URL na validação da borda. Short-lived e OCSP coexistem: a validade curta reduz a dependência de revogação, não a exclui.

a) OCSP na borda (autoritativo da CA, opcional). O CloudFront valida via OCSP, consultando o responder da sua CA (a URL vem no próprio certificado, na extensão AIA), e faz cache da resposta por ~30 minutos para reduzir latência e proteger contra indisponibilidade do responder (anúncio do OCSP, documentação de revogação de certificado).
Habilitar OCSP tem dois custos que pesam em produção. Primeiro, ele entra no caminho crítico do handshake. Quando o CloudFront não consegue determinar o status de revogação (responder inalcançável, resposta Unknown ou Error), o comportamento default é negar a conexão (comportamento em falha do OCSP). Ou seja, a disponibilidade do responder da CA passa a afetar a conexão de todos os dispositivos. O cache de ~30 min ameniza, mas não elimina: um soft-fail só existe se você o implementar na Connection Function via connection.clientCertificate.revocationStatus, e a função do exemplo ignora esse campo, herdando o hard-fail. Segundo, o OCSP trafega sobre HTTP não criptografado, entregando à CA metadados sobre qual certificado é consultado e quando, podendo ser sensível em cenários financeiros / mobile. Por esses dois motivos, o OCSP é opcional neste desenho: quem viabiliza o corte, quando o serial está normalizado corretamente nas três pontas, é o KVS + DynamoDB, e ligar o OCSP é uma decisão de negócio (disponibilidade × privacidade × garantia autoritativa da CA). Opcional na arquitetura, mas ligado no laboratório: o Terraform deste artigo habilita o OCSP por padrão para que você possa exercitar a validação na borda de ponta a ponta.

Se optar por OCSP, ele não precisa ser o do AWS Certificate Manager (ACM): é compatível com qualquer emissor que forneça a URL do responder OCSP no certificado. Aqui, no exemplo, usamos os dois juntos (OCSP + KVS).

b) KVS na borda (corte no próximo handshake, sob seu controle). Uma Connection Function (função executada durante o handshake TLS no CloudFront) associada ao handshake consulta um CloudFront KeyValueStore (KVS) com os números de série revogados; se o serial está lá, o handshake é recusado ali mesmo, sem dependência externa. É o complemento do OCSP: autoridade da CA (com cache) + corte sob seu controle, em segundos, valendo do próximo handshake em diante.

c) DynamoDB na origem (defense in depth). Mesmo passando na borda, confere-se o status do device no DynamoDB, cobre a janela de propagação e estados que a borda não representa (suspenso, em revisão, step-up).

Importante: Neste exemplo essa verificação é feita por um Lambda, a cada request, apenas para demonstrar o conceito. Em um cenário produtivo, seguindo a boa prática de autorizar antes de encaminhar (não deixar a requisição inválida nem chegar ao serviço), você tende a mover essa decisão para uma camada de autorização no gateway/mesh, por exemplo o Amazon API Gateway com um Lambda authorizer, Envoy/Istio via ext_authz, ou um gateway de mercado como o Kong (plugin de autorização com Open Policy Agent, o OPA). Também é comum cachear a decisão por poucos segundos ou codificar o estado numa credencial de vida curta (exemplo: um JSON Web Token (JWT) assinado, validado de forma stateless), reduzindo a consulta a cada requisição.

A figura a seguir mostra o fan-out da revogação: repare que um único evento alimenta as três camadas, e que cada uma corta em um ponto diferente do caminho.

Figura 4 — Revogação de dispositivo em camadas.

Figura 4: Revogação — o Lambda faz o fan-out para DynamoDB (origem), CloudFront KVS (borda) e AWS Private CA (OCSP), cortando o dispositivo no próximo handshake e no próximo request.

Como os certificados são de vida curta, a denylist do KVS se mantém pequena: um serial revogado só é útil até a data em que o certificado expiraria de qualquer forma. Vale dimensionar, porém. Um key value store tem limite de 5 MB e o KVS não expira chaves sozinho: com o serial canônico como chave, isso acomoda dezenas de milhares de revogações simultâneas, folgado para a maioria dos cenários, mas em produção convém uma rotina agendada removendo os seriais já expirados.

Fechando a seção com as três camadas lado a lado: elas cortam em tempos bem diferentes, e é essa diferença que você calibra no seu modelo de risco. O corte mais rápido vem do KVS (segundos, na borda, no próximo handshake) e do DynamoDB (na origem, no próximo request). O OCSP é a camada autoritativa da CA, mas a mais lenta: um RevokeCertificate pode levar até 60 minutos para propagar no responder da AWS Private CA (API Reference), somados a até 30 minutos de cache na borda do CloudFront (cache de resposta OCSP), o que dá, no pior caso, cerca de 90 minutos entre o comando de revogação e o corte efetivo pelo OCSP.

Camada Onde corta Latência típica
CloudFront KVS (Connection Function) borda, no handshake segundos (propagação do KVS)
DynamoDB (status) origem, no request imediato, no próximo request
OCSP (AWS Private CA) borda, no handshake até ~60 min de propagação no responder da CA + até ~30 min de cache na borda

Uma ressalva sobre o alcance da revogação no KVS: ela vale para novos handshakes. Uma conexão TLS já estabelecida antes do evento não é derrubada, porque o certificado do device é verificado no handshake, não a cada request. Quem cobre essa janela é a camada de origem: o próximo request dentro daquela conexão ainda passa pela checagem de status no DynamoDB. Se o seu modelo de risco exige encerrar sessões em curso, isso é responsabilidade da aplicação (por exemplo, invalidar a sessão no backend e forçar reconexão).

A arquitetura de exemplo na AWS

Veja a Figura 1. Componentes:

  • Amazon CloudFront: duas distribuições (enrollment sem mTLS; API com mTLS Required), trust store, KeyValueStore e Connection Function.
  • Amazon S3: bundle da CA (trust store).
  • Application Load Balancer interno: alcançado pelo CloudFront via VPC origin; roteia por path.
  • AWS Lambda: Enroll, Ping e Recert (targets do ALB) e Revoke (invocação). Cada função tem sua própria role IAM, com o mínimo que ela precisa e apontando para os ARNs exatos da tabela, da CA e do KVS: o Enroll emite certificado e não revoga, o Ping só lê o registry, e só o Recert e o Revoke escrevem no KVS.
  • AWS Private CA: emite os certs de device (short-lived + OCSP).
  • Amazon DynamoDB: device registry (índice por serial do cert).

Os dois planos:

  • Enrollment (sem mTLS): o device gera a chave, atesta, manda o CSR; o Lambda autentica o usuário, valida a attestation e a Private CA emite o certificado.
  • Data plane (mTLS Required): o device apresenta o cert; o CloudFront valida na borda (trust store + OCSP + KVS) e repassa a identidade como headers ao ALB → Lambda Ping, que confere o status no DynamoDB.

Enrollment — sequência

O diagrama a seguir detalha a ordem das chamadas: repare que a emissão do certificado só acontece depois da autenticação do usuário e da validação da attestation.

Figura 5 — Sequência de enrollment (sem mTLS).

Figura 5: Sequência de enrollment — na distribuição sem mTLS, o Lambda autentica o usuário, valida a attestation e a AWS Private CA emite o certificado do dispositivo.

O diagrama separa dois controles distintos no Lambda de enroll: a autenticação do usuário e a validação de attestation. Um prova quem está pedindo o certificado; o outro prova em que app/aparelho. No exemplo deste artigo o primeiro não existe e o segundo é um stub, ambos precisam ser implementados de verdade em produção.

Data plane — sequência

O diagrama a seguir segue uma requisição autenticada: repare que toda a validação de borda (trust store, OCSP e KVS) acontece antes de qualquer chamada à origem.

Figura 6 — Sequência do data plane (mTLS Required).

Figura 6: Data plane — o CloudFront valida o certificado na borda (trust store + OCSP + KVS) e repassa a identidade em headers ao ALB e ao Lambda Ping, que confere o status do dispositivo no DynamoDB.

Pré-requisitos

Para acompanhar este walkthrough, você precisa de:

  • uma conta AWS com permissão para criar Amazon CloudFront, Application Load Balancer, AWS Private CA, AWS Lambda, Amazon DynamoDB e Amazon S3.
  • Terraform ≥ 1.6 e AWS CLI v2 instalados.
  • Python 3 (usado no build do awscrt empacotado nos Lambdas).
  • familiaridade básica com TLS/mTLS, certificados X.509 e Terraform.

Como implantar

Todo o código deste walkthrough, Terraform, Lambdas e scripts de teste, está no repositório deste artigo. Clone-o e execute os passos a seguir.

cd terraform

# 1) Empacota os Lambdas Revoke e Recert com awscrt.
bash scripts/build-lambdas.sh

# 2) Inicializa o Terraform e baixa os providers.
terraform init

# 3) Cria a infraestrutura.
terraform apply

Ao final do apply, confirme que os outputs enroll_distribution_domain e api_distribution_domain foram exibidos, eles indicam que as duas distribuições CloudFront foram criadas com sucesso.
O trust store, a Connection Function e o viewer mTLS required são gerenciados pelo Terraform. O OCSP é habilitado no fim do apply por um terraform_data + local-exec (scripts/enable-ocsp.sh), porque o toggle de OCSP fica no trust store e ainda não é exposto pelo provider Terraform. Como visto na seção de revogação, o OCSP é opcional na arquitetura; o laboratório o habilita por padrão apenas para demonstrar a validação na borda.

Testando

Os scripts em test-scripts/ simulam, a partir da linha de comando, o que um app mobile e seu backend fariam no fluxo real. É importante entender essa correspondência: no mundo real, a geração de chaves, a proteção por hardware e a attestation acontecem dentro do dispositivo (Secure Enclave/StrongBox) e das APIs das plataformas (Play Integrity/App Attest). Aqui, para você exercitar a arquitetura sem precisar de um app publicado nem de aparelhos físicos, essas partes são representadas com OpenSSL e tokens de exemplo. Os detalhes de cada script estão em test-scripts/README.md.

Primeiro exporte os domínios das duas distribuições (nada é hardcoded):

cd ../test-scripts
export ENROLL_HOST=$(cd ../terraform && terraform output -raw enroll_distribution_domain)
export API_HOST=$(cd ../terraform && terraform output -raw api_distribution_domain)

1. enroll.sh — simula o registro inicial do dispositivo. Representa o app “nascendo” na plataforma: gera o par de chaves e o CSR (no lugar do enclave), coleta a attestation (aqui, o token mock) e chama POST /enroll na distribuição sem mTLS. O Lambda valida a attestation, a AWS Private CA emite o certificado do dispositivo e o registry (DynamoDB) o grava como active.

bash enroll.sh "$ENROLL_HOST" user-123 android device
# -> HTTP 201; salva device.crt (assinado pela AWS Private CA) e device.key.
#    Imprime o device_id (guarde) e o cert_serial.

2. test-mtls.sh — simula uma chamada autenticada do app. Mostra o mTLS na prática contra GET /api/ping: sem certificado, o CloudFront corta a conexão no handshake (o curl recebe connection reset); com o certificado do device, o handshake passa na borda e a origem responde HTTP 200.

bash test-mtls.sh "$API_HOST" device
# -> sem cert: connection reset (mTLS Required cortando na borda)
# -> com cert: HTTP 200

3. recert.sh — simula a renovação silenciosa (rollover). Representa o app renovando o certificado antes de expirar, reusando a mesma chave de hardware. Gera um novo CSR com a device.key existente e chama POST /api/device/cert:renew autenticado com o certificado atual (mTLS). O Lambda emite o novo certificado, atualiza o registry e revoga o antigo (KVS + Private CA). O script se auto-verifica: certificado novo responde 200 e o antigo é rejeitado.

bash recert.sh "$API_HOST" device
# -> novo cert: HTTP 200; cert antigo: 403 (origem) ou connection reset (borda)

Repare que a auto-verificação confirma o corte do cert antigo em poucos segundos — isso funciona porque o corte vem do KVS (borda) e do DynamoDB (origem), não do OCSP, cuja propagação pode chegar a ~90 minutos no pior caso. É a evidência prática da tabela de latências da seção de revogação.

4. revoke-device.sh — simula um evento administrativo/antifraude. Representa o backend cortando um dispositivo (roubo, fraude, logout forçado). Invoca o Lambda revoke, que faz o fan-out para as três pontas: status=revoked no DynamoDB, serial no KVS (corte imediato na borda) e RevokeCertificate na Private CA (alimenta o OCSP). Use o device_id impresso no enrollment (ou --serial <cert_serial>).

bash revoke-device.sh <DEVICE_ID>

5. Confirme o corte rodando o teste de mTLS de novo. Agora o certificado do device está revogado, então a conexão “com certificado” também falha, prova visual da revogação funcionando na borda:

bash test-mtls.sh "$API_HOST" device
# -> com cert: connection reset (KVS) ou HTTP 403 (DynamoDB na origem)

O sinal mais importante em todos os testes é o handshake: é nele que o dispositivo não autenticado (ou revogado) é cortado, na borda, antes de qualquer lógica de aplicação.

Da demonstração para produção

O padrão de borda apresentado aqui é adequado para produção depois de endereçadas as decisões da tabela a seguir (comportamento do OCSP em falha, hierarquia de CA), mas o código de exemplo é um laboratório: várias peças foram simuladas para você conseguir subir tudo sem um app publicado nem aparelhos físicos. A tabela a seguir consolida, em um só lugar, o que separa o exemplo didático de uma implantação enterprise. Use como checklist de hardening, e confira também os pré-requisitos e limitações do viewer mTLS.

Área Como está na demo O que fazer em produção
Attestation Stub que aceita qualquer token não-vazio e não valida a procedência da chave do CSR Validar veredito real de Play Integrity (Google) / App Attest (Apple) antes de emitir o cert
Auth no enrollment user_id livre no corpo, sem autenticação Exigir usuário já autenticado (OpenID Connect ou sessão); amarrar o cert à identidade; rate limiting + AWS WAF; limite de dispositivos/usuário
Validade do cert / robustez da renovação 7 dias, valor didático; renovação sem monitoração de falhas Calibrar a validade ao apetite de risco e à tolerância a indisponibilidade (validade curta transfere risco para o recert); renovação com grace amplo, retry/backoff e fallback para /enroll; alarmes sobre taxa de falha de renovação (uma alta de falhas indica risco de lockout em massa)
Hierarquia de CA CA raiz short-lived, online, emitindo folhas direto (em conformidade com a doc, mas concentra risco) Root offline (general-purpose) → CA subordinada emissora short-lived; trust store ancorado no root. Com OCSP ligado, a subordinada também precisa de URL de OCSP no AIA
Hop CloudFront → origem HTTP:80 na rede privada Re-encriptar (HTTPS) e restringir a origem ao security group (SG) do VPC origin
Autorização na origem Lambda consulta DynamoDB a cada request Autorizar antes de encaminhar (API Gateway + Lambda authorizer, Envoy/Istio ext_authz, Kong/OPA); cachear a decisão por segundos ou usar credencial curta stateless (JWT)
Revogação na CA (OCSP) Opcional no exemplo: o corte já vem de KVS + DynamoDB, então o RevokeCertificate é best-effort e, se falhar, a resposta traz pca: skipped sem abortar o corte Se você depende do OCSP, torne essa falha observável: log estruturado, alarme e retry/DLQ para reconciliar CA × registry
Falha do OCSP Hard-fail default (status indeterminado ⇒ conexão negada) Decidir explicitamente entre hard-fail e soft-fail na Connection Function (via revocationStatus), pesando disponibilidade × garantia de revogação
Privacidade / OCSP OCSP habilitado (HTTP, expõe metadados à CA) Avaliar exposição de metadados; considerar que KVS + registry cobrem a maior parte dos casos, o que permite manter o OCSP desligado conforme o perfil de risco
Observabilidade Logs básicos de Lambda CloudTrail de IssueCertificate/RevokeCertificate, métricas de handshake/negações, alarmes de fraude, auditoria do registry
Domínios/TLS Certificado default do CloudFront Domínios próprios via aliases + ACM + Amazon Route 53 (opcional)
Capacidade da denylist (KVS) Seriais são adicionados e nunca removidos Limite de 5 MB por key value store; rotina agendada para remover seriais de certificados já expirados

Custos e limpeza

O exemplo cria recursos cobráveis (AWS Private CA em short-lived mode, duas distribuições CloudFront, ALB, DynamoDB, S3, KVS, Lambda). Para evitar cobranças futuras, apague os recursos ao terminar o laboratório:

cd terraform
terraform destroy

Consulte os preços atuais de AWS Private CA, Amazon CloudFront, Elastic Load Balancing, Amazon DynamoDB, Amazon S3 e AWS Lambda.

Conclusão

Neste artigo, mostramos como mover a autenticação do dispositivo para a borda com viewer mTLS no CloudFront, cortando tráfego não autenticado antes de ele entrar no seu perímetro. Este laboratório demonstra esse padrão de borda e um ciclo de vida bem resolvido (validade curta, recert por rollover, revogação por OCSP + KVS + registry); para completar as três camadas do início do artigo, some a isso uma chave em hardware real e uma attestation validada de verdade (ver “Da demonstração para produção”), usando serviços gerenciados e infraestrutura como código.
O código completo (Terraform + Lambdas + scripts de teste) acompanha este artigo. Experimente a arquitetura em uma conta de desenvolvimento e adapte os parâmetros de validade e de re-attestation ao perfil de risco e à tolerância a indisponibilidade da sua aplicação. Faça o apply, rode os scripts de test-scripts/ e observe o corte no handshake acontecendo na borda.

Referências

Sobre os autores

André Botelho André Botelho é Solutions Architect na AWS, com mais de 10 anos de experiência em tecnologia e seis no mercado financeiro. Graduado em Sistemas de Informação, é especialista em streaming de dados e Amazon MSK, atende clientes da indústria financeira no Brasil. Antes da AWS, atuou como coordenador de cloud e observabilidade em uma fintech por cinco anos, liderando iniciativas de modernização e resiliência de infraestrutura.
Eduardo Rodrigues Eduardo Rodrigues é Principal Solutions Architect na AWS, liderando estratégias de arquitetura em nuvem para as maiores instituições financeiras do Brasil e impulsionando iniciativas de transformação digital em escala regional.
Com foco em excelência técnica e inovação, Eduardo arquiteta soluções de missão crítica para clientes de banking e serviços financeiros de primeira linha, oferecendo orientação estratégica em adoção, modernização e inovação em nuvem. Sua expertise abrange o design de arquiteturas escaláveis, seguras e em conformidade com os rigorosos requisitos do setor financeiro.
Edgar Costa Filho Edgar Costa Filho é Arquiteto de Soluções Sênior com foco em Fundações e Containers, incluindo expertise na integração do Amazon EKS com ferramentas como Crossplane, Terraform e GitOps. Em seu papel, Edgar se dedica a ajudar os clientes a alcançar seus objetivos de negócios implementando as melhores práticas em design e gestão de infraestrutura em nuvem.