← todos os artigos

Autorização por rota no Linkerd: mTLS ligado não é tráfego autorizado

0

Injetar o proxy do Linkerd num Deployment te dá mTLS automático entre todos os pods do mesh. Isso costuma ser interpretado como “já está seguro” — mas mTLS resolve só uma pergunta: quem está falando comigo é quem diz que é. Não resolve a pergunta seguinte, que é esse alguém tem permissão pra falar comigo nessa rota. Essa segunda pergunta é trabalho de outro conjunto de CRDs: Server, MeshTLSAuthentication e AuthorizationPolicy.

O padrão abaixo é o que a própria documentação do projeto recomenda para restringir acesso — não é relato de “rodei isso em produção”, é a forma oficial de compor essas três CRDs (mais HTTPRoute, quando a granularidade precisa ser por caminho, não só por porta). Testei o schema direto no chart linkerd-crds do repositório do projeto pra garantir que os campos abaixo batem com a versão atual — sem inventar enum ou flag que eu não vi na fonte.

As três peças

Server (policy.linkerd.io/v1beta3) declara uma porta de um Deployment como alvo de política. O campo que muda o jogo é accessPolicy, que hoje tem deny como valor default: se o tráfego não casar com nenhuma regra de AuthorizationPolicy, ele é recusado — não fica liberado por omissão.

apiVersion: policy.linkerd.io/v1beta3
kind: Server
metadata:
  name: billing-api
  namespace: billing
spec:
  podSelector:
    matchLabels:
      app: billing-api
  port: http
  # sem regra correspondente, a requisição é negada
  accessPolicy: deny

MeshTLSAuthentication (policy.linkerd.io/v1alpha1) descreve quem está autorizado, usando a identidade mTLS do proxy de origem — não IP, não header, a identidade criptográfica que o Linkerd já verificou no handshake. Você referencia por string de identidade completa ou por ServiceAccount:

apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
  name: checkout-identity
  namespace: billing
spec:
  identityRefs:
    - kind: ServiceAccount
      name: checkout
      namespace: storefront

AuthorizationPolicy (policy.linkerd.io/v1alpha1) é quem liga as duas pontas: um targetRef (o que está protegido) e um ou mais requiredAuthenticationRefs (quem pode acessar). Se houver mais de uma referência de autenticação, todas precisam ser satisfeitas — não é “qualquer uma serve”.

apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
  name: billing-api-checkout-only
  namespace: billing
spec:
  targetRef:
    kind: Server
    name: billing-api
  requiredAuthenticationRefs:
    - kind: MeshTLSAuthentication
      name: checkout-identity

Com essas três peças, só o pod rodando com a ServiceAccount checkout no namespace storefront consegue falar com billing-api, em qualquer rota daquela porta. Qualquer outro pod do mesh — mesmo com mTLS válido, mesmo no mesmo namespace — recebe conexão recusada.

Granularidade por rota, não só por porta

O ponto acima protege a porta inteira. Na prática, é comum precisar de granularidade menor: checkout pode chamar POST /orders, mas /admin/refund só pode ser chamado por um serviço interno de operações. Pra isso, o targetRef do AuthorizationPolicy não precisa apontar pro Server — pode apontar pra um HTTPRoute.

O Linkerd aceita tanto o HTTPRoute próprio (policy.linkerd.io) quanto o HTTPRoute padrão da Gateway API (gateway.networking.k8s.io). Se você já usa Gateway API para roteamento (como a maioria dos clusters recentes), reaproveita o mesmo recurso como alvo de autorização — não precisa manter dois objetos de rota:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: billing-admin-route
  namespace: billing
spec:
  parentRefs:
    - name: billing-api
      kind: Server
      group: policy.linkerd.io
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /admin
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
  name: billing-admin-restricted
  namespace: billing
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: billing-admin-route
  requiredAuthenticationRefs:
    - kind: MeshTLSAuthentication
      name: ops-identity

Com isso, /admin/* fica restrito à identidade ops-identity, enquanto o resto das rotas de billing-api segue liberado pra quem satisfizer o AuthorizationPolicy de nível de Server. As duas políticas convivem: a mais específica (rota) governa o que ela cobre, a mais geral (porta) cobre o restante.

O que isso não substitui

AuthorizationPolicy opera em L7, com identidade criptográfica — é um controle diferente (e complementar) de NetworkPolicy, que opera em L3/L4 por IP/CIDR e não depende do Linkerd estar rodando. Um ataque que comprometa o nó ou contorne o proxy sidecar não é coberto pela política de autorização do mesh. Continue usando NetworkPolicy como camada de isolamento de rede; o AuthorizationPolicy entra por cima, como controle de identidade de serviço para serviço, inclusive por rota.

Também vale registrar: a mudança de default para accessPolicy: deny é comportamento atual do Server v1beta3 (a versão storage: true no CRD hoje). Se você tem Server de uma versão anterior do cluster com outro default, confira o accessPolicy explicitamente antes de assumir que “sem AuthorizationPolicy” significa “bloqueado” — não assuma, leia o manifesto aplicado.

Quando aplicar esse padrão

Faz sentido priorizar isso em: serviços com endpoint administrativo/destrutivo que não deveria estar acessível por qualquer coisa autenticada no mesh; ambientes multi-tenant onde “estar no cluster” não deveria implicar “poder chamar qualquer serviço”; e qualquer rota que hoje só está protegida por “ninguém mais sabe que ela existe” — segurança por obscuridade que uma varredura de service discovery derruba em minutos.

Não é o primeiro passo se o cluster ainda não tem mTLS habilitado de forma consistente (a injeção do proxy precisa estar em todo pod relevante) nem se você ainda não mapeou quem-chama-quem — aplicar accessPolicy: deny sem esse mapa quebra tráfego legítimo. Comece com Server + AuthorizationPolicy nos serviços de maior risco, valide em um ambiente de homologação observando erros de conexão recusada, e só depois expanda pra granularidade de rota.

Fontes