Seus microsserviços compartilham uma VPC, então confiam uns nos outros. Essa confiança é autoridade ambiente: a permissão para invocar um serviço é concedida não por prova criptográfica, mas pela topologia de rede. Um atacante que viola o perímetro herda tudo isso.

Serviços absolutamente podem se autenticar sem autoridade ambiente. A pergunta mais difícil é por que sua plataforma faz isso parecer uma traição.

Como a autoridade ambiente se parece na produção

A maior parte da “autenticação” entre serviços é na verdade apenas segmentação de rede. Os serviços vivem em uma sub-rede privada, atrás de um API gateway, ou dentro de um cluster Kubernetes com um service mesh. A suposição é simples: se a requisição vem de um IP interno, ela é legítima.

Esse modelo colapsa quando o perímetro colapsa. Um pod comprometido, movimentação lateral após vazamento de credenciais, ou uma injeção na cadeia de suprimentos coloca o atacante dentro da zona de confiança. Nesse ponto, todo serviço é alcançável e a maioria não pedirá prova de identidade. O atacante não precisa roubar mais credenciais. Ele simplesmente herda a autoridade ambiente da rede.

A autenticação real entre serviços significa que cada chamador prova quem ele é, e cada chamado verifica essa prova. A rede é apenas um transporte. Ela não deveria ser uma credencial.

Como funciona a autenticação baseada em capabilities

Existem duas abordagens práticas: identidade criptográfica (todo serviço tem um nome comprovável) e tokens de capability (toda requisição carrega uma autorização infalsificável e escopada).

Identidade criptográfica com mTLS

No mutual TLS, tanto o cliente quanto o servidor apresentam certificados X.509. O servidor verifica o certificado do cliente antes de processar a requisição. O cliente verifica o certificado do servidor antes de enviar dados. Nenhum lado confia no outro por causa do endereço IP.

O desafio de infraestrutura é a distribuição de certificados. SPIFFE e SPIRE resolvem isso dando a toda workload uma identidade criptográfica derivada de suas propriedades, não de sua localização. Um pod executando o payments-api recebe um SPIFFE ID como spiffe://prod.example.com/payments-api. O certificado é de curta duração e rotacionado automaticamente.

Aqui está como isso se parece em Go:

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "log"
    "net/http"
    "os"
)

// loadClientTLS returns a config that verifies the server AND presents a client cert.
func loadClientTLS(caCert, clientCert, clientKey []byte) *tls.Config {
    pool := x509.NewCertPool()
    pool.AppendCertsFromPEM(caCert)

    cert, err := tls.X509KeyPair(clientCert, clientKey)
    if err != nil {
        log.Fatalf("failed to load client cert: %v", err)
    }

    return &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      pool,
        // Do not skip verification based on IP or DNS name alone.
        InsecureSkipVerify: false,
    }
}

func main() {
    caCert, _ := os.ReadFile("ca.crt")
    clientCert, _ := os.ReadFile("service.crt")
    clientKey, _ := os.ReadFile("service.key")

    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: loadClientTLS(caCert, clientCert, clientKey),
        },
    }

    resp, err := client.Get("https://orders-api.internal:8443/orders")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(resp.Status)
}

A linha crítica é InsecureSkipVerify: false. Sem ela, você volta para a confiança ambiente. Com ela, o cliente se recusa a conectar a menos que o servidor apresente um certificado válido assinado pela CA. O servidor faz o mesmo para o cliente.

Tokens de capability para autorização escopada

Certificados provam identidade. Capabilities provam autorização. Em muitos sistemas, você quer ambos: prove quem você é, então prove que tem permissão para fazer essa coisa específica.

Um token de capability vincula uma identidade, uma action e um recurso em um package infalsificável. O serviço o emite. O serviço o verifica. Não há um armazenamento central de sessões para consultar.

import hmac
import hashlib
import secrets
import time

SECRET = secrets.token_bytes(32)

def mint_service_capability(
    caller_id: str,
    service: str,
    action: str,
    resource: str,
    ttl_seconds: int = 300
) -> str:
    """Mint a time-bound capability for a specific service action."""
    expires = int(time.time()) + ttl_seconds
    payload = f"{caller_id}:{service}:{action}:{resource}:{expires}"
    sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:24]
    return f"{payload}:{sig}"

def verify_service_capability(
    token: str,
    expected_service: str,
    expected_action: str,
    expected_resource: str
) -> bool:
    """Verify a capability token is unexpired and correctly scoped."""
    try:
        caller_id, svc, action, resource, expires, sig = token.rsplit(":", 5)
    except ValueError:
        return False

    if int(expires) < time.time():
        return False
    if svc != expected_service or action != expected_action or resource != expected_resource:
        return False

    payload = f"{caller_id}:{svc}:{action}:{resource}:{expires}"
    expected_sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:24]
    return hmac.compare_digest(sig, expected_sig)

# Service A requests a capability to call Service B.
token = mint_service_capability("payments-api", "orders-api", "read", "order-123")

# Service B verifies the capability before handling the request.
if not verify_service_capability(token, "orders-api", "read", "order-123"):
    raise PermissionError("Invalid or expired capability")

O token é autocontido e de curta duração. O Serviço B não precisa chamar um servidor de autorização. Ele apenas verifica o HMAC e a expiração. O escopo é explícito: este token só é válido para ler order-123 do orders-api.

Por que isso é mais difícil do que deveria

A tecnologia existe. A dificuldade é operacional.

O bootstrap de confiança é o primeiro problema. Se todo serviço precisa de um certificado, algo precisa emitir esses certificados. Esse algo, a autoridade certificadora ou o servidor SPIRE, se torna um novo alvo. Você não eliminou a confiança. Você a concentrou.

Há também o problema de revogação. Se um serviço é comprometido, como você invalida sua identidade? Certificados mTLS podem ser de curta duração, o que ajuda, mas revogar um certificado específico ainda requer distribuir uma lista de revogação de certificados ou usar OCSP. Tokens de capability expiram rapidamente, o que limita o dano, mas uma chave de assinatura roubada permite que um atacante emita tokens ilimitados.

A performance é outra preocupação. Handshakes TLS adicionam latência. Em um service mesh com sidecars, o overhead geralmente é aceitável (alguns milissegundos), mas em caminhos de alto throughput, esses milissegundos se acumulam. Tokens de capability evitam uma viagem de ida e volta a um servidor de autenticação, mas a verificação de HMAC não é gratuita em escala.

Onde a autoridade ambiente ainda faz sentido

Nem toda chamada interna precisa de prova criptográfica. Um cron job que roda dentro do mesmo container que o cliente do banco de dados não precisa de mTLS para falar com localhost. Health checks entre um load balancer e um node não precisam de tokens de capability.

O objetivo não é zero autoridade ambiente. O objetivo é saber onde sua autoridade ambiente reside e se o raio de explosão é aceitável. Uma VPC compartilhada com 200 serviços e sem autenticação interna é um raio de explosão grande e invisível. Um socket Unix local com permissão de arquivo 0600 é um raio de explosão pequeno e explícito.

Como começar a remover a autoridade ambiente das chamadas de serviço

Você não precisa de um service mesh no primeiro dia. Comece com os serviços que tocam dados sensíveis ou estão em fronteiras de confiança.

Primeiro, identifique seus agents privilegiados. Quais serviços detêm privilégios que outros não têm? Quais APIs internas seriam perigosas se chamadas pelo serviço errado? Esses são seus candidatos.

Segundo, adicione mTLS na camada de API. Se você está usando um reverse proxy como Envoy ou NGINX, você pode terminar mTLS lá sem mudar código de aplicação. O proxy passa um header com a identidade do cliente verificada para o upstream. O upstream verifica esse header.

Terceiro, escopeie seus tokens. Se você já usa JWTs para autenticação de serviços, pare de colocar role: service no payload. Coloque allowed_services: ["orders-api"] e allowed_resources: ["order:*"]. Torne a autorização tão específica quanto a operação.

Perguntas frequentes sobre autenticação de serviços sem autoridade ambiente

mTLS é suficiente, ou eu preciso de capabilities também?

mTLS prova identidade. Capabilities provam autorização. Eles resolvem problemas diferentes. Um serviço com um certificado válido ainda não deveria ser permitido deletar dados arbitrários. Use mTLS para segurança de transporte e identidade, capabilities para controle de acesso.

E quanto a service meshes como Istio ou Linkerd?

Service meshes automatizam mTLS e identidade entre pods. Eles são uma forma prática de remover autoridade ambiente sem reescrever cada serviço. O mesh gerencia rotação de certificados, atestado de identidade e criptografia de tráfego. A contrapartida é complexidade e um novo plano de controle para proteger.

Isso funciona para funções serverless?

Sim, mas é mais difícil. Funções serverless são efêmeras, então rotação de certificados e atestado de identidade precisam acontecer no momento da invocação. Provedores de nuvem oferecem workload identity para isso (AWS IAM Roles for Service Accounts, GCP Workload Identity). Esses são capability-like: a função recebe um token escopado para o que tem permissão de fazer, não uma credencial genérica.

Qual é a diferença entre uma service account e uma capability?

Uma service account é uma identidade que carrega autoridade ambiente por padrão. Se o Serviço A roda como payments-sa, ele geralmente pode fazer qualquer coisa que payments-sa tem permissão de fazer. Uma capability é escopada para uma action específica em um recurso específico. Mesmo que a capability seja roubada, ela não pode ser replayada contra um alvo diferente.

Comece com o perímetro que você já tem

Sua VPC nunca foi um limite de segurança. Era um limite de rede que fingíamos ser um limite de segurança. Remover a autoridade ambiente significa tratar toda chamada de serviço como se cruzasse esse limite. Identidade criptográfica e capabilities escopadas são as ferramentas. A parte difícil é admitir que o modelo antigo era conveniência disfarçada de segurança.

Escolha uma API interna. Adicione verificação de certificado de cliente. Observe o que quebra. Na maioria das vezes, o que quebra é uma suposição que você não sabia que seu código estava fazendo.