<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="pt-BR"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://thinkcloudnative.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://thinkcloudnative.com/" rel="alternate" type="text/html" hreflang="pt-BR" /><updated>2026-10-02T01:08:21+00:00</updated><id>https://thinkcloudnative.com/feed.xml</id><title type="html">Think Cloud Native</title><subtitle>Artigos e sugestões sobre soluções CNCF. Meu dia a dia com essas ferramentas, compartilhado toda semana.</subtitle><author><name>Bezaleel Silva</name></author><entry xml:lang="pt-BR"><title type="html">Karmada virou Graduated no CNCF — e o v1.19 muda um default de scheduling</title><link href="https://thinkcloudnative.com/articles/karmada-graduado-cncf/" rel="alternate" type="text/html" title="Karmada virou Graduated no CNCF — e o v1.19 muda um default de scheduling" /><published>2026-10-01T00:00:00+00:00</published><updated>2026-10-01T00:00:00+00:00</updated><id>https://thinkcloudnative.com/articles/karmada-graduado-cncf</id><content type="html" xml:base="https://thinkcloudnative.com/articles/karmada-graduado-cncf/"><![CDATA[<p>Em 8 de setembro de 2026 o CNCF anunciou o Karmada como seu mais novo projeto <strong>Graduated</strong> — o mesmo nível de maturidade de Kubernetes, Prometheus, Envoy e Istio. Pra chegar lá o projeto passou por auditoria de segurança independente, formalizou um steering committee e sustentou o CII Best Practices Badge por tempo suficiente pra provar que não depende de uma única empresa ou mantenedor.</p>

<p>Se você nunca usou Karmada, o resumo é: ele resolve orquestração multi-cluster e multi-cloud sem pedir que você reescreva manifests. Você aplica o YAML normal contra a <code class="language-plaintext highlighter-rouge">karmada-apiserver</code> e associa uma <code class="language-plaintext highlighter-rouge">PropagationPolicy</code> (ou <code class="language-plaintext highlighter-rouge">ClusterPropagationPolicy</code>, pra recursos cluster-scoped) dizendo em quais clusters membro aquilo deve rodar — com regras de failover e override por cluster. A comunicação com os clusters membro pode ser em modo <strong>push</strong> (o control plane do Karmada acessa a API dos membros diretamente, bom pra clusters na mesma rede) ou <strong>pull</strong> (um <code class="language-plaintext highlighter-rouge">karmada-agent</code> roda dentro do cluster membro e busca instruções no control plane, necessário quando o cluster está atrás de firewall ou é edge).</p>

<p>Desde que entrou no Sandbox em 2021 e passou pra Incubating em dezembro de 2023, o projeto cresceu pra mais de 1.200 contribuidores de 292 organizações, com adotantes documentados como Bloomberg e Wellhub, além de players de cloud, telecom e IA na Ásia.</p>

<p>A graduação coincidiu com o release do v1.19.0, publicado em 31 de agosto de 2026. E é o release — não o selo — que muda comportamento de quem já roda Karmada ou está avaliando adotar. Quatro pontos merecem atenção antes de atualizar.</p>

<h2 id="fix-rotação-de-credencial-parava-de-sincronizar-em-silêncio">Fix: rotação de credencial parava de sincronizar em silêncio</h2>

<p>Em modo push, o Karmada mantém um watch aberto contra a API de cada cluster membro pra reagir a mudanças de status. Antes do v1.19, se o bearer token usado nesse watch fosse rotacionado — o que acontece por padrão em vários setups de service account com token de vida curta —, a conexão não era renovada automaticamente. O watch continuava “vivo” na aparência, mas parava de receber eventos novos. Sem crash, sem log óbvio de erro recorrente: só um cluster que para de refletir status atualizado até alguém reiniciar o componente manualmente.</p>

<p>O v1.19 implementa uma camada de transporte que renova o token no informer automaticamente quando ele rotaciona, sem exigir restart. Se você roda push mode com tokens de curta duração — comum em integrações com OIDC ou em clusters gerenciados que forçam rotação —, esse fix sozinho já justifica a atualização, porque o sintoma anterior é exatamente do tipo que passa despercebido até virar incidente.</p>

<h2 id="default-novo-scheduling-por-prioridade-vira-beta-e-liga-sozinho">Default novo: scheduling por prioridade vira Beta e liga sozinho</h2>

<p>O feature gate <code class="language-plaintext highlighter-rouge">PriorityBasedScheduling</code> foi promovido a Beta e <strong>vem habilitado por padrão</strong> no v1.19. Na prática, isso faz o scheduler do Karmada respeitar <code class="language-plaintext highlighter-rouge">spec.schedulePriority</code> nas suas <code class="language-plaintext highlighter-rouge">PropagationPolicy</code> e agendar workloads de prioridade mais alta primeiro quando há disputa por capacidade entre clusters.</p>

<p>Se você nunca configurou <code class="language-plaintext highlighter-rouge">schedulePriority</code>, o efeito prático deve ser neutro — sem prioridade declarada, não há reordenação pra aplicar. Mas se algum manifesto seu já tem esse campo preenchido (por exemplo, copiado de um exemplo da documentação sem intenção de usar o recurso), o comportamento de scheduling muda no upgrade sem você ter pedido. Vale grepar suas policies por <code class="language-plaintext highlighter-rouge">schedulePriority</code> antes de atualizar. Pra manter o comportamento anterior de qualquer forma, dá pra desligar com <code class="language-plaintext highlighter-rouge">--feature-gates=PriorityBasedScheduling=false</code> no <code class="language-plaintext highlighter-rouge">karmada-scheduler</code>.</p>

<h2 id="breaking-change-dois-valores-de-purgemode-saíram-do-ar">Breaking change: dois valores de PurgeMode saíram do ar</h2>

<p><code class="language-plaintext highlighter-rouge">PurgeMode</code> é o campo que controla o que acontece com a instância antiga de uma aplicação quando ela migra de cluster por failover — <code class="language-plaintext highlighter-rouge">Directly</code> evicta na hora (útil quando duas instâncias rodando ao mesmo tempo não pode acontecer), <code class="language-plaintext highlighter-rouge">Gracefully</code> espera a aplicação ficar saudável no cluster novo antes de tirar a antiga do ar.</p>

<p>Os valores antigos <code class="language-plaintext highlighter-rouge">Immediately</code> e <code class="language-plaintext highlighter-rouge">Graciously</code> — que já estavam deprecados — foram <strong>removidos</strong> no v1.19, não só descontinuados. Se alguma <code class="language-plaintext highlighter-rouge">PropagationPolicy</code> sua com regra de failover ainda usa esses nomes, ela vai falhar validação depois do upgrade do control plane. É find-and-replace simples (<code class="language-plaintext highlighter-rouge">Immediately</code> → <code class="language-plaintext highlighter-rouge">Directly</code>, <code class="language-plaintext highlighter-rouge">Graciously</code> → <code class="language-plaintext highlighter-rouge">Gracefully</code>), mas precisa ser feito antes, não depois de descobrir em produção.</p>

<h2 id="memória-mesma-carga-menos-ram">Memória: mesma carga, menos RAM</h2>

<p>Karmada estripou <code class="language-plaintext highlighter-rouge">managedFields</code> dos caches dos informers dinâmicos e separou melhor a responsabilidade entre o execution controller (mudanças nos clusters membro) e o work-status controller (coleta de status), eliminando reconciliação duplicada. O resultado reportado pelo projeto: pico de memória do <code class="language-plaintext highlighter-rouge">karmada-controller-manager</code> caindo de 5 GB pra 3.4 GB num teste distribuindo 20.000 Deployments. Se você dimensionou o control plane pro consumo do v1.18, vale reavaliar o limite depois de migrar — não porque vai faltar memória, mas porque provavelmente sobra headroom que estava sendo desperdiçado.</p>

<h2 id="quem-deveria-se-importar-com-isso">Quem deveria se importar com isso</h2>

<p>Se você roda um cluster Kubernetes só, isso não muda nada pra você hoje. Karmada resolve um problema específico: aplicação distribuída em múltiplos clusters — por região, por cloud, por ambiente de produção replicado — com uma política declarativa central em vez de pipeline de CI/CD duplicando manifests por cluster. Se esse é o seu cenário e você está hoje resolvendo isso com script, Terraform aplicando N vezes, ou ArgoCD ApplicationSet puro sem lógica de failover, vale colocar Karmada na lista de avaliação — a chancela Graduated é, no mínimo, sinal de que o projeto não vai sumir nem mudar de API de forma descontrolada.</p>

<h2 id="antes-de-atualizar-pra-v119">Antes de atualizar pra v1.19</h2>

<ul>
  <li>Se você roda push mode: nenhuma ação necessária além de atualizar — o fix de rotação de credencial é automático.</li>
  <li>Grep suas <code class="language-plaintext highlighter-rouge">PropagationPolicy</code> e <code class="language-plaintext highlighter-rouge">ClusterPropagationPolicy</code> por <code class="language-plaintext highlighter-rouge">schedulePriority</code>; se existir e não for intencional, decida se aceita o novo comportamento de scheduling ou desliga o feature gate.</li>
  <li>Grep as mesmas policies por <code class="language-plaintext highlighter-rouge">purgeMode: Immediately</code> ou <code class="language-plaintext highlighter-rouge">purgeMode: Graciously</code> e substitua antes do upgrade do control plane, não depois.</li>
  <li>Se você fixa limite de memória pro <code class="language-plaintext highlighter-rouge">karmada-controller-manager</code> via <code class="language-plaintext highlighter-rouge">resources.limits</code>, revalidar depois da migração — o número mudou pra baixo no benchmark do projeto, mas seu workload real pode variar.</li>
</ul>

<h2 id="fontes">Fontes</h2>

<ul>
  <li><a href="https://www.cncf.io/announcements/2026/09/07/cloud-native-computing-foundation-announces-karmada-graduation/">Cloud Native Computing Foundation Announces Karmada Graduation — CNCF</a></li>
  <li><a href="https://github.com/karmada-io/karmada/releases/tag/v1.19.0">Karmada v1.19.0 — release notes (GitHub)</a></li>
  <li><a href="https://github.com/karmada-io/karmada/blob/master/docs/CHANGELOG/CHANGELOG-1.19.md">CHANGELOG-1.19.md — karmada-io/karmada</a></li>
  <li><a href="https://karmada.io/docs/administrator/upgrading/v1.18-v1.19/">Guia de upgrade v1.18 → v1.19 — karmada.io</a></li>
  <li><a href="https://www.infoq.com/news/2026/09/karmada-kubernetes-cncf/">Kubernetes Multi-Cluster Project Karmada Reaches CNCF Graduation — InfoQ</a></li>
</ul>]]></content><author><name>Bezaleel Silva</name></author><summary type="html"><![CDATA[Karmada alcançou o topo da maturidade do CNCF em 8 de setembro, junto com o v1.19.0. O selo importa menos do que quatro mudanças do release: um bug de credencial corrigido, um feature gate que liga sozinho, um breaking change em failover e um ganho de memória.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thinkcloudnative.com/assets/social/karmada-graduado-cncf.png" /><media:content medium="image" url="https://thinkcloudnative.com/assets/social/karmada-graduado-cncf.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt-BR"><title type="html">Autorização por rota no Linkerd: mTLS ligado não é tráfego autorizado</title><link href="https://thinkcloudnative.com/articles/linkerd-autorizacao-por-rota/" rel="alternate" type="text/html" title="Autorização por rota no Linkerd: mTLS ligado não é tráfego autorizado" /><published>2026-09-26T00:00:00+00:00</published><updated>2026-09-26T00:00:00+00:00</updated><id>https://thinkcloudnative.com/articles/linkerd-autorizacao-por-rota</id><content type="html" xml:base="https://thinkcloudnative.com/articles/linkerd-autorizacao-por-rota/"><![CDATA[<p>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: <em>quem está falando comigo é quem diz que é</em>. Não resolve a pergunta seguinte, que é <em>esse alguém tem permissão pra falar comigo nessa rota</em>. Essa segunda pergunta é trabalho de outro conjunto de CRDs: <code class="language-plaintext highlighter-rouge">Server</code>, <code class="language-plaintext highlighter-rouge">MeshTLSAuthentication</code> e <code class="language-plaintext highlighter-rouge">AuthorizationPolicy</code>.</p>

<p>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 <code class="language-plaintext highlighter-rouge">HTTPRoute</code>, quando a granularidade precisa ser por caminho, não só por porta). Testei o schema direto no chart <code class="language-plaintext highlighter-rouge">linkerd-crds</code> 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.</p>

<h2 id="as-três-peças">As três peças</h2>

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

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">policy.linkerd.io/v1beta3</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Server</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">billing-api</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">billing</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">podSelector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">billing-api</span>
  <span class="na">port</span><span class="pi">:</span> <span class="s">http</span>
  <span class="c1"># sem regra correspondente, a requisição é negada</span>
  <span class="na">accessPolicy</span><span class="pi">:</span> <span class="s">deny</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">MeshTLSAuthentication</code></strong> (<code class="language-plaintext highlighter-rouge">policy.linkerd.io/v1alpha1</code>) descreve <em>quem</em> 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 <code class="language-plaintext highlighter-rouge">ServiceAccount</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">policy.linkerd.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">MeshTLSAuthentication</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">checkout-identity</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">billing</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">identityRefs</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">ServiceAccount</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">checkout</span>
      <span class="na">namespace</span><span class="pi">:</span> <span class="s">storefront</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">AuthorizationPolicy</code></strong> (<code class="language-plaintext highlighter-rouge">policy.linkerd.io/v1alpha1</code>) é quem liga as duas pontas: um <code class="language-plaintext highlighter-rouge">targetRef</code> (o que está protegido) e um ou mais <code class="language-plaintext highlighter-rouge">requiredAuthenticationRefs</code> (quem pode acessar). Se houver mais de uma referência de autenticação, <strong>todas</strong> precisam ser satisfeitas — não é “qualquer uma serve”.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">policy.linkerd.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">AuthorizationPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">billing-api-checkout-only</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">billing</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">targetRef</span><span class="pi">:</span>
    <span class="na">kind</span><span class="pi">:</span> <span class="s">Server</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">billing-api</span>
  <span class="na">requiredAuthenticationRefs</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">MeshTLSAuthentication</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">checkout-identity</span>
</code></pre></div></div>

<p>Com essas três peças, só o pod rodando com a <code class="language-plaintext highlighter-rouge">ServiceAccount checkout</code> no namespace <code class="language-plaintext highlighter-rouge">storefront</code> consegue falar com <code class="language-plaintext highlighter-rouge">billing-api</code>, em qualquer rota daquela porta. Qualquer outro pod do mesh — mesmo com mTLS válido, mesmo no mesmo namespace — recebe conexão recusada.</p>

<h2 id="granularidade-por-rota-não-só-por-porta">Granularidade por rota, não só por porta</h2>

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

<p>O Linkerd aceita tanto o <code class="language-plaintext highlighter-rouge">HTTPRoute</code> próprio (<code class="language-plaintext highlighter-rouge">policy.linkerd.io</code>) quanto o <code class="language-plaintext highlighter-rouge">HTTPRoute</code> padrão da Gateway API (<code class="language-plaintext highlighter-rouge">gateway.networking.k8s.io</code>). 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:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">gateway.networking.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">HTTPRoute</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">billing-admin-route</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">billing</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">parentRefs</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">billing-api</span>
      <span class="na">kind</span><span class="pi">:</span> <span class="s">Server</span>
      <span class="na">group</span><span class="pi">:</span> <span class="s">policy.linkerd.io</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">matches</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span>
            <span class="na">type</span><span class="pi">:</span> <span class="s">PathPrefix</span>
            <span class="na">value</span><span class="pi">:</span> <span class="s">/admin</span>
</code></pre></div></div>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">policy.linkerd.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">AuthorizationPolicy</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">billing-admin-restricted</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">billing</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">targetRef</span><span class="pi">:</span>
    <span class="na">group</span><span class="pi">:</span> <span class="s">gateway.networking.k8s.io</span>
    <span class="na">kind</span><span class="pi">:</span> <span class="s">HTTPRoute</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">billing-admin-route</span>
  <span class="na">requiredAuthenticationRefs</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">MeshTLSAuthentication</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">ops-identity</span>
</code></pre></div></div>

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

<h2 id="o-que-isso-não-substitui">O que isso não substitui</h2>

<p><code class="language-plaintext highlighter-rouge">AuthorizationPolicy</code> opera em L7, com identidade criptográfica — é um controle diferente (e complementar) de <code class="language-plaintext highlighter-rouge">NetworkPolicy</code>, 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 <code class="language-plaintext highlighter-rouge">NetworkPolicy</code> como camada de isolamento de rede; o <code class="language-plaintext highlighter-rouge">AuthorizationPolicy</code> entra por cima, como controle de identidade de serviço para serviço, inclusive por rota.</p>

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

<h2 id="quando-aplicar-esse-padrão">Quando aplicar esse padrão</h2>

<p>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.</p>

<p>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 <code class="language-plaintext highlighter-rouge">accessPolicy: deny</code> sem esse mapa quebra tráfego legítimo. Comece com <code class="language-plaintext highlighter-rouge">Server</code> + <code class="language-plaintext highlighter-rouge">AuthorizationPolicy</code> 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.</p>

<h2 id="fontes">Fontes</h2>

<ul>
  <li><a href="https://github.com/linkerd/linkerd2/blob/main/charts/linkerd-crds/templates/policy/authorization-policy.yaml">linkerd/linkerd2 — CRD <code class="language-plaintext highlighter-rouge">AuthorizationPolicy</code> (<code class="language-plaintext highlighter-rouge">charts/linkerd-crds/templates/policy/authorization-policy.yaml</code>)</a></li>
  <li><a href="https://github.com/linkerd/linkerd2/blob/main/charts/linkerd-crds/templates/policy/server.yaml">linkerd/linkerd2 — CRD <code class="language-plaintext highlighter-rouge">Server</code> (<code class="language-plaintext highlighter-rouge">charts/linkerd-crds/templates/policy/server.yaml</code>)</a></li>
  <li><a href="https://github.com/linkerd/linkerd2/blob/main/charts/linkerd-crds/templates/policy/meshtls-authentication.yaml">linkerd/linkerd2 — CRD <code class="language-plaintext highlighter-rouge">MeshTLSAuthentication</code> (<code class="language-plaintext highlighter-rouge">charts/linkerd-crds/templates/policy/meshtls-authentication.yaml</code>)</a></li>
  <li><a href="https://github.com/linkerd/linkerd2/blob/main/charts/linkerd-crds/templates/policy/httproute.yaml">linkerd/linkerd2 — CRD <code class="language-plaintext highlighter-rouge">HTTPRoute</code> própria do Linkerd (<code class="language-plaintext highlighter-rouge">charts/linkerd-crds/templates/policy/httproute.yaml</code>)</a></li>
  <li><a href="https://github.com/linkerd/linkerd2/blob/main/CHANGES.md">linkerd/linkerd2 — CHANGES.md, suporte a HTTPRoute da Gateway API como alvo de política</a></li>
  <li><a href="https://gateway-api.sigs.k8s.io/api-types/httproute/">Gateway API — especificação HTTPRoute</a></li>
</ul>]]></content><author><name>Bezaleel Silva</name></author><summary type="html"><![CDATA[Injetar o Linkerd te dá mTLS entre pods, mas não te dá controle de quem pode chamar o quê. AuthorizationPolicy + MeshTLSAuthentication fazem esse trabalho — inclusive por rota HTTP específica.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thinkcloudnative.com/assets/social/linkerd-autorizacao-por-rota.png" /><media:content medium="image" url="https://thinkcloudnative.com/assets/social/linkerd-autorizacao-por-rota.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt-BR"><title type="html">cert-manager 1.21.2: por que resposta de ACME não devia ir parar no Events do seu cluster</title><link href="https://thinkcloudnative.com/articles/cert-manager-eventos-vazamento-acme/" rel="alternate" type="text/html" title="cert-manager 1.21.2: por que resposta de ACME não devia ir parar no Events do seu cluster" /><published>2026-09-23T00:00:00+00:00</published><updated>2026-09-23T00:00:00+00:00</updated><id>https://thinkcloudnative.com/articles/cert-manager-eventos-vazamento-acme</id><content type="html" xml:base="https://thinkcloudnative.com/articles/cert-manager-eventos-vazamento-acme/"><![CDATA[<p>Em 11 de setembro de 2026 o cert-manager lançou a v1.21.2, e em 16 de setembro a v1.20.4 (patch pra quem ainda está na linha 1.20). Nenhuma das duas vem com CVE ou advisory formal — mas a mudança central da 1.21.2 é o tipo de fix que vale entender mesmo sem selo de CVE, porque mexe com uma suposição que muita gente faz sobre RBAC no Kubernetes sem perceber: “Events são inofensivos, só Secrets precisa de cuidado”.</p>

<h2 id="o-que-mudou">O que mudou</h2>

<p>A partir da 1.21.2, o comportamento dos issuers ACME e Vault muda assim:</p>

<ul>
  <li><strong>Corpo de resposta HTTP não vai mais pro <code class="language-plaintext highlighter-rouge">status</code> do <code class="language-plaintext highlighter-rouge">Issuer</code>/<code class="language-plaintext highlighter-rouge">ClusterIssuer</code> nem pros Events do cluster.</strong> Antes, quando o servidor ACME (ou o Vault, no caso do Vault issuer) respondia algo fora do esperado, o cert-manager copiava esse corpo de resposta pra dentro da condição de status do recurso e pra um Event — objetos que ficam visíveis via <code class="language-plaintext highlighter-rouge">kubectl describe</code> ou <code class="language-plaintext highlighter-rouge">kubectl get events</code> pra qualquer principal com permissão de leitura no namespace, que costuma ser bem mais gente do que quem pode ler um <code class="language-plaintext highlighter-rouge">Secret</code>.</li>
  <li><strong>Só o “problem document” do ACME (RFC 7807) é exposto, e com tamanho limitado.</strong> É o formato estruturado de erro que o protocolo ACME já prevê — não é resposta bruta do servidor. Qualquer outra coisa vira só o código de status HTTP no status do recurso; o corpo completo fica exclusivamente no log do controller.</li>
  <li><strong>Resposta do servidor ACME agora tem teto de 16 MiB.</strong> Sem esse limite, um servidor ACME malicioso ou comprometido (ou um MITM em rede sem TLS pinning correto) podia devolver uma resposta arbitrariamente grande, que o cert-manager tentava processar inteira — vetor de negação de serviço por exaustão de memória/CPU no controller.</li>
  <li><strong>No Vault issuer, o mesmo tratamento</strong>: respostas HTTP que não vêm do próprio Vault não populam mais status/Events com o corpo, e mensagens de erro do Vault são truncadas antes de ser armazenadas.</li>
</ul>

<p>Junto veio uma leva de correções sem relação direta com isso — panic no webhook de validação quando o <code class="language-plaintext highlighter-rouge">AdmissionReview</code> vem sem campos opcionais, race condition no self-check do HTTP-01, panic no controller de issuing quando um <code class="language-plaintext highlighter-rouge">CertificateRequest</code> tem timestamp de falha mas não tem condição <code class="language-plaintext highlighter-rouge">Ready</code>, e um bug de agenda cron pontual (29 de fevereiro cruzando século não-bissexto — específico, mas reflete o tipo de teste de borda que o projeto vem fazendo). A 1.20.4, por sua vez, é patch de dependências: Go atualizado pra 1.26.6 (fixes em <code class="language-plaintext highlighter-rouge">crypto/tls</code>, <code class="language-plaintext highlighter-rouge">encoding/asn1</code>, <code class="language-plaintext highlighter-rouge">net/http</code>), <code class="language-plaintext highlighter-rouge">golang.org/x/crypto</code> e <code class="language-plaintext highlighter-rouge">grpc</code> bumped. As notas de release citam três achados em <code class="language-plaintext highlighter-rouge">golang.org/x/crypto</code> que continuam sem correção na linha 1.20 — o próprio changelog registra que eles não afetam a operação do cert-manager, então não é motivo pra travar o upgrade.</p>

<h2 id="por-que-isso-importa-mesmo-sem-cve">Por que isso importa mesmo sem CVE</h2>

<p>O ponto não é “vazamento de segredo direto” — nenhum dos dois issuers coloca token de API ou chave privada na resposta HTTP em condições normais. O ponto é a classe de risco: <strong>dado não confiável, vindo de um endpoint externo (ACME ou Vault), sendo refletido sem sanitização num objeto do Kubernetes com modelo de RBAC mais permissivo do que o dado provavelmente merecia.</strong></p>

<p>Dois cenários concretos que essa mudança fecha:</p>

<ol>
  <li><strong>Exposição de informação além do necessário.</strong> Times que têm RBAC read-only em Events (comum — é um objeto “de observabilidade”, não “de segredo”) passam a enxergar qualquer coisa que o servidor ACME/Vault decidiu devolver, incluindo detalhes de infraestrutura interna do provedor ou do seu próprio ambiente que apareciam nas mensagens de erro. Não é uma falha de autorização — é dado sensível indo parar num objeto pensado pra ser mais aberto.</li>
  <li><strong>Superfície de injeção em pipelines que consomem Events/status como texto.</strong> Se você tem automação — bot de Slack, exportador de Events pra um SIEM, dashboard que renderiza <code class="language-plaintext highlighter-rouge">status.conditions[].message</code> — que trata esse campo como texto confiável, um corpo de resposta controlado por um endpoint comprometido virava um jeito de injetar conteúdo arbitrário nesse pipeline.</li>
</ol>

<p>Vale registrar o contraste com o que <strong>é</strong> tratado como vulnerabilidade formal no projeto: em junho de 2026 saiu a <a href="https://github.com/cert-manager/cert-manager/security/advisories/GHSA-8rvj-mm4h-c258">GHSA-8rvj-mm4h-c258</a>, severidade alta, sobre a <code class="language-plaintext highlighter-rouge">ClusterRole</code> padrão de <code class="language-plaintext highlighter-rouge">edit</code> permitir que qualquer usuário de namespace crie recursos <code class="language-plaintext highlighter-rouge">Challenge</code>/<code class="language-plaintext highlighter-rouge">Order</code> diretamente e explore credenciais DNS de um <code class="language-plaintext highlighter-rouge">ClusterIssuer</code> sem passar pela política do Issuer. São dois problemas diferentes — um é bypass de política via RBAC padrão permissivo demais, o outro é dado não confiável indo pra objeto errado — mas os dois apontam pro mesmo ponto cego: no fluxo ACME do cert-manager, o que “parece” read-only ou inofensivo (<code class="language-plaintext highlighter-rouge">Challenge</code>, <code class="language-plaintext highlighter-rouge">Order</code>, <code class="language-plaintext highlighter-rouge">Event</code>, <code class="language-plaintext highlighter-rouge">status</code>) às vezes carrega mais poder ou mais dado do que a intuição sugere.</p>

<h2 id="o-que-fazer">O que fazer</h2>

<ul>
  <li><strong>Atualize.</strong> Se você está na linha 1.21, vá pra 1.21.2. Se está preso na 1.20 (LTS-like, ainda recebendo patch), vá pra 1.20.4. Nenhuma das duas traz breaking change documentado — é upgrade de rotina, sem motivo pra esperar janela especial.</li>
  <li><strong>Se você tem automação lendo <code class="language-plaintext highlighter-rouge">status.conditions</code> ou Events do cert-manager como fonte de diagnóstico</strong>, ajuste pra também consultar o log do controller depois do upgrade — o corpo completo do erro não estará mais no status pra requisições que não são “problem document” ACME.</li>
  <li><strong>Revise quem tem <code class="language-plaintext highlighter-rouge">get</code>/<code class="language-plaintext highlighter-rouge">list</code> em Events e em <code class="language-plaintext highlighter-rouge">Issuer</code>/<code class="language-plaintext highlighter-rouge">ClusterIssuer</code> no seu cluster</strong> e compare com quem tem acesso a <code class="language-plaintext highlighter-rouge">Secret</code>. Se a resposta for “praticamente todo mundo tem Events, poucos tem Secret”, vale entender que isso é intencional no design do Kubernetes — Events são efêmeros e de baixa confidencialidade por padrão — mas só funciona se nada sensível for escrito lá, que era exatamente a falha que essa versão corrige.</li>
  <li><strong>Se você usa a <code class="language-plaintext highlighter-rouge">ClusterRole</code> <code class="language-plaintext highlighter-rouge">edit</code> padrão e ClusterIssuers com credenciais DNS</strong>, revisite a GHSA-8rvj-mm4h-c258 separadamente — é advisory de junho, já tem tempo, mas continua relevante pra quem não aplicou a mitigação.</li>
</ul>

<p>Fora essas duas frentes, o resto do release é manutenção: menos superfície de bug em cenários de borda, dependências atualizadas. Não é um patch que pede reunião de emergência — mas é o tipo de upgrade que vale entrar na próxima janela normal, não na próxima vez que sobrar tempo.</p>

<h2 id="fontes">Fontes</h2>

<ul>
  <li><a href="https://github.com/cert-manager/cert-manager/releases/tag/v1.21.2">cert-manager v1.21.2 — release notes</a></li>
  <li><a href="https://github.com/cert-manager/cert-manager/releases/tag/v1.20.4">cert-manager v1.20.4 — release notes</a></li>
  <li><a href="https://github.com/cert-manager/cert-manager/pull/9222">PR #9222 — fix(acme): bound ACME response body size to prevent unbounded-body DoS</a></li>
  <li><a href="https://github.com/cert-manager/cert-manager/security/advisories/GHSA-8rvj-mm4h-c258">GHSA-8rvj-mm4h-c258 — Direct ACME Challenge resources can bypass Issuer DNS01 solver policy</a></li>
  <li><a href="https://github.com/cert-manager/cert-manager/security/advisories">cert-manager — Security Advisories</a></li>
  <li><a href="https://github.com/cert-manager/cert-manager/releases">cert-manager — Releases</a></li>
</ul>]]></content><author><name>Bezaleel Silva</name></author><summary type="html"><![CDATA[Até o cert-manager 1.21.1, o corpo da resposta do seu servidor ACME ou Vault podia ir parar no status do Issuer e nos Events do cluster — objetos que muita gente com RBAC restrito consegue ler. A 1.21.2 corta isso e também limita o tamanho da resposta contra DoS.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thinkcloudnative.com/assets/social/cert-manager-eventos-vazamento-acme.png" /><media:content medium="image" url="https://thinkcloudnative.com/assets/social/cert-manager-eventos-vazamento-acme.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt-BR"><title type="html">Cilium 1.20: o Gateway API virou camada de tráfego completa — com duas pegadinhas pra testar antes</title><link href="https://thinkcloudnative.com/articles/cilium-1-20-gateway-api/" rel="alternate" type="text/html" title="Cilium 1.20: o Gateway API virou camada de tráfego completa — com duas pegadinhas pra testar antes" /><published>2026-09-17T00:00:00+00:00</published><updated>2026-09-17T00:00:00+00:00</updated><id>https://thinkcloudnative.com/articles/cilium-1-20-gateway-api</id><content type="html" xml:base="https://thinkcloudnative.com/articles/cilium-1-20-gateway-api/"><![CDATA[<p>Em 14 de setembro de 2026 o Cilium lançou a versão 1.20, o segundo release maior do ano — mais de 2.660 commits, 1.100+ contribuidores. O destaque não é uma feature isolada: é o Gateway API do projeto saltando de v1.4 pra v1.6.1 numa tacada só, o que muda de vez o que dá pra fazer com um <code class="language-plaintext highlighter-rouge">Gateway</code> do Cilium sem sair do padrão upstream do Kubernetes.</p>

<p>Se você já usa Cilium como CNI e tem Gateway API habilitado (ou está cogitando substituir um Ingress Controller por ele), o release traz capacidade nova real. Mas duas delas têm ressalvas que vale testar antes de generalizar em produção — não é só “atualiza e usa”.</p>

<h2 id="o-que-o-salto-de-versão-do-gateway-api-trouxe">O que o salto de versão do Gateway API trouxe</h2>

<p>Quatro capacidades novas, todas vindas do upstream do Gateway API e agora implementadas no Cilium:</p>

<ul>
  <li><strong>ListenerSets</strong> — permitem que um time de aplicação anexe e gerencie os próprios listeners num <code class="language-plaintext highlighter-rouge">Gateway</code> compartilhado, sem precisar que o time de plataforma edite o <code class="language-plaintext highlighter-rouge">Gateway</code> principal a cada novo listener. Separa “quem é dono do Gateway” de “quem precisa expor uma porta nele”.</li>
  <li><strong>TCPRoute e UDPRoute</strong> — tráfego que não é HTTP (banco de dados, DNS, servidor de jogo, qualquer coisa em TCP/UDP puro) passa a caber no mesmo modelo de roteamento do Gateway API, em vez de precisar de um <code class="language-plaintext highlighter-rouge">Service</code> do tipo <code class="language-plaintext highlighter-rouge">LoadBalancer</code> à parte.</li>
  <li><strong>ExternalAuth</strong> — filtro de <code class="language-plaintext highlighter-rouge">HTTPRoute</code> que delega a decisão de autenticação/autorização a um serviço externo via protocolo <code class="language-plaintext highlighter-rouge">ext_authz</code> do Envoy (gRPC ou HTTP), antes da requisição chegar na aplicação. É a implementação do GEP-1494 do Gateway API.</li>
  <li><strong>CORS nativo e mais códigos de redirect</strong> (303, 307, 308) — resolve casos que antes exigiam <code class="language-plaintext highlighter-rouge">EnvoyExtraConfig</code> ou anotação específica do Cilium.</li>
</ul>

<p>As três primeiras CRDs (<code class="language-plaintext highlighter-rouge">TCPRoute</code>, <code class="language-plaintext highlighter-rouge">UDPRoute</code>, <code class="language-plaintext highlighter-rouge">ListenerSet</code>) são opcionais: se você não instalar os CRDs correspondentes, o Cilium simplesmente desabilita o suporte àquela feature, sem quebrar o resto. Isso facilita adoção incremental — mas também significa que “atualizei o Cilium” não é o mesmo que “já tenho TCPRoute disponível”.</p>

<h2 id="pegadinha-1-tcprouteudproute-não-combina-com-host-network">Pegadinha 1: TCPRoute/UDPRoute não combina com host network</h2>

<p>O tráfego de <code class="language-plaintext highlighter-rouge">TCPRoute</code> e <code class="language-plaintext highlighter-rouge">UDPRoute</code> não passa pelo Envoy — ele vai direto pro <code class="language-plaintext highlighter-rouge">Service</code> que o Gateway gera, que normalmente expõe a porta do listener como <code class="language-plaintext highlighter-rouge">LoadBalancer</code>. Se o seu Gateway roda em modo host network, esse <code class="language-plaintext highlighter-rouge">Service</code> vira <code class="language-plaintext highlighter-rouge">NodePort</code> em vez de <code class="language-plaintext highlighter-rouge">LoadBalancer</code>, e a porta deixa de ser a que você configurou no listener — passa a ser uma porta aleatória da faixa de NodePort do cluster.</p>

<p>Na prática: se você depende de host network (comum em bare metal sem <code class="language-plaintext highlighter-rouge">LoadBalancer</code> de nuvem) e quer expor um Postgres ou um DNS via <code class="language-plaintext highlighter-rouge">TCPRoute</code>, a porta que chega no cliente não é a que está no manifesto. Vale conferir esse detalhe antes de apontar qualquer coisa em produção pra uma porta fixa.</p>

<h2 id="pegadinha-2-externalauth-com-wireguard-cross-node--reportado-não-confirmado-por-advisory">Pegadinha 2: ExternalAuth com WireGuard cross-node — reportado, não confirmado por advisory</h2>

<p>O <code class="language-plaintext highlighter-rouge">ExternalAuth</code> já apareceu em pré-release (v1.20.0-pre.3) e um operador documentou, em relato público (não é advisory oficial do projeto Cilium), um comportamento específico: em cluster com modo túnel + criptografia WireGuard habilitada, a sub-requisição <code class="language-plaintext highlighter-rouge">ext_authz</code> do Envoy pro backend de autenticação era descartada silenciosamente sempre que cruzava nó — sem log de política bloqueada, sem erro visível. A causa relatada: o tráfego de <code class="language-plaintext highlighter-rouge">ext_authz</code> sai do Envoy com identidade <code class="language-plaintext highlighter-rouge">host</code>/<code class="language-plaintext highlighter-rouge">remote-node</code> (<code class="language-plaintext highlighter-rouge">encryptkey=0</code>, sem criptografia), e não consegue ser entregue a um pod que espera tráfego criptografado (<code class="language-plaintext highlighter-rouge">encryptkey=255</code>) — um padrão parecido com o do CVE-2024-28250.</p>

<p>O mesmo relato registra que, ao subir para 1.20.1, o bloqueio nesse cenário específico deixou de ocorrer — mas o próprio autor deixou a validação completa do datapath como pendente. Ou seja: não é uma falha confirmada e corrigida via security advisory formal do Cilium, é um relato de campo. Dá pra tratar como sinal, não como veredito.</p>

<p>Se seu cluster combina os três ingredientes — <code class="language-plaintext highlighter-rouge">ExternalAuth</code>, modo túnel e WireGuard node-to-node —, vale rodar o cenário em staging com nós físicos separados (o problema só aparece cruzando nó) antes de confiar nisso pra autenticação em produção. Sem essa combinação específica, a feature não tem nenhum caveat documentado.</p>

<h2 id="ipv6-no-eni-ipam-beta-e-migração-de-ipam-sem-rebuild">IPv6 no ENI IPAM (Beta) e migração de IPAM sem rebuild</h2>

<p>Cilium 1.20 adiciona suporte a IPv6 no IPAM de ENI da AWS, em Beta. E separadamente — mas no mesmo tema de gestão de IP — chega um caminho de migração de <code class="language-plaintext highlighter-rouge">cluster-pool</code> pra <code class="language-plaintext highlighter-rouge">multi-pool</code> IPAM sem precisar reconstruir o cluster. Se você já cogitou migrar de IPAM mas parou no “vai exigir recriar tudo”, esse é o motivo pra reavaliar.</p>

<h2 id="o-que-fica-pra-trás-mutual-authentication-beta">O que fica pra trás: Mutual Authentication (Beta)</h2>

<p>O suporte a Mutual Authentication (Beta) — o mecanismo mTLS mais antigo do Cilium — foi marcado como deprecado nesta versão, com remoção prevista pra uma versão futura (a nota de release não fixa qual). A recomendação do próprio changelog é migrar para o Ztunnel Transparent Encryption (também Beta). Se você tem Mutual Authentication configurado hoje, não é urgente, mas é hora de colocar a migração no radar antes que vire remoção sem aviso maior.</p>

<h2 id="outros-pontos-de-atenção-no-upgrade">Outros pontos de atenção no upgrade</h2>

<ul>
  <li><strong>Dependência de Kubernetes v1.36</strong> e Envoy v1.37.x — confira a matriz de compatibilidade do seu cluster gerenciado antes de agendar o upgrade.</li>
  <li><strong>Caminho de upgrade e rollback testado é só entre versões minor consecutivas.</strong> Pular de 1.18 direto pra 1.20 não é o caminho validado — suba um minor de cada vez.</li>
  <li><strong>Binário do CNI caiu de ~77 MB pra 16 MB</strong> — bom pra quem tem <code class="language-plaintext highlighter-rouge">initContainer</code> com timeout apertado ou registry lento.</li>
  <li>Além de Mutual Authentication, o changelog pede atenção redobrada pra quem usa extensões Go do Envoy, política Kafka-aware, a API <code class="language-plaintext highlighter-rouge">cilium.io/v2alpha1</code> de <code class="language-plaintext highlighter-rouge">CiliumNodeConfig</code>, integração com libnetwork e configuração de CNI customizada — nenhum desses tem detalhe público além do aviso genérico “confira o guia de upgrade” no próprio release.</li>
</ul>

<h2 id="o-que-fazer">O que fazer</h2>

<p>Se você já roda Gateway API no Cilium: o upgrade em si é direto, mas trate <code class="language-plaintext highlighter-rouge">TCPRoute</code>/<code class="language-plaintext highlighter-rouge">UDPRoute</code> e <code class="language-plaintext highlighter-rouge">ExternalAuth</code> como features novas a testar isoladamente, não como parte automática do “upgrade e segue”. Antes de generalizar:</p>

<ol>
  <li>Se for expor tráfego não-HTTP via <code class="language-plaintext highlighter-rouge">TCPRoute</code>/<code class="language-plaintext highlighter-rouge">UDPRoute</code> <strong>e</strong> o Gateway roda em host network — confirme a porta real que chega no cliente antes de apontar produção.</li>
  <li>Se for adotar <code class="language-plaintext highlighter-rouge">ExternalAuth</code> <strong>e</strong> o cluster usa modo túnel + WireGuard node-to-node — valide em staging com nós físicos distintos antes de depender disso pra autorização.</li>
  <li>Se você usa Mutual Authentication hoje — comece a avaliar o Ztunnel Transparent Encryption com calma, sem esperar o aviso de remoção.</li>
  <li>Confirme a versão do Kubernetes do seu cluster contra a matriz de compatibilidade do 1.20 antes de agendar a janela.</li>
</ol>

<p>Fora dessas combinações específicas, o resto do release — ListenerSets pra delegar listener sem soltar o Gateway principal, CORS nativo, binário menor — é ganho direto sem contrapartida conhecida até agora.</p>

<h2 id="fontes">Fontes</h2>

<ul>
  <li><a href="https://github.com/cilium/cilium/releases/tag/v1.20.0">Cilium v1.20.0 — release notes</a></li>
  <li><a href="https://github.com/cilium/cilium/releases/tag/v1.20.0-pre.3">Cilium v1.20.0-pre.3 — introdução do ExternalAuth filter</a></li>
  <li><a href="https://github.com/cilium/cilium/issues/45704">cilium/cilium issue #45704 — Gateway API HTTPRouteExternalAuth filter (GEP-1494)</a></li>
  <li><a href="https://github.com/devantler-tech/platform/issues/2284">devantler-tech/platform issue #2284 — relato de ExternalAuth ext_authz black-holed cross-node sob WireGuard</a></li>
  <li><a href="https://github.com/advisories/GHSA-v6q2-4qr3-5cw6">GHSA-v6q2-4qr3-5cw6 — CVE-2024-28250 (padrão de falha citado no relato acima)</a></li>
  <li><a href="https://www.cncf.io/blog/2026/09/14/cilium-1-20-gateway-api-externalauth-tcproute-udproute-eni-ipam-for-ipv6-and-more/">CNCF Blog — Cilium 1.20: Gateway API ExternalAuth, TCPRoute/UDPRoute, ENI IPAM for IPv6, and more</a></li>
</ul>]]></content><author><name>Bezaleel Silva</name></author><summary type="html"><![CDATA[Cilium 1.20 salta de Gateway API v1.4 pra v1.6.1 de uma vez: ListenerSets, TCPRoute/UDPRoute e ExternalAuth chegam juntos. Duas combinações específicas merecem teste em staging antes de ir pra produção.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thinkcloudnative.com/assets/social/cilium-1-20-gateway-api.png" /><media:content medium="image" url="https://thinkcloudnative.com/assets/social/cilium-1-20-gateway-api.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt-BR"><title type="html">Kyverno corrigiu 6 falhas de segurança de uma vez — uma delas dá cluster-admin a qualquer tenant</title><link href="https://thinkcloudnative.com/articles/kyverno-seis-falhas-de-seguranca/" rel="alternate" type="text/html" title="Kyverno corrigiu 6 falhas de segurança de uma vez — uma delas dá cluster-admin a qualquer tenant" /><published>2026-09-11T00:00:00+00:00</published><updated>2026-09-11T00:00:00+00:00</updated><id>https://thinkcloudnative.com/articles/kyverno-seis-falhas-de-seguranca</id><content type="html" xml:base="https://thinkcloudnative.com/articles/kyverno-seis-falhas-de-seguranca/"><![CDATA[<p>Em 10 de setembro de 2026, o projeto Kyverno lançou o v1.19.1 corrigindo seis advisories de segurança de uma vez — a maior leva desde que o projeto virou incubating na CNCF. Uma delas tem CVSS 9.9 (crítica) e permite que um tenant com permissão apenas para criar <code class="language-plaintext highlighter-rouge">Policy</code> no próprio namespace escale até cluster-admin. As outras cinco são bypasses de isolamento entre namespaces, de SSRF e de verificação de assinatura de imagem.</p>

<p>Se você usa Kyverno só pra bloquear <code class="language-plaintext highlighter-rouge">latest</code> como tag de imagem, o impacto prático é baixo. Mas se você delega a criação de políticas pra times de plataforma ou tenants — o caso de uso mais comum de um policy engine multi-tenant — vale ler com atenção, porque o requisito de exploração em quase todas é justamente esse: alguém que só devia poder escrever política no próprio namespace.</p>

<h2 id="a-crítica-cluster-admin-a-partir-de-uma-policy-no-seu-próprio-namespace">A crítica: cluster-admin a partir de uma Policy no seu próprio namespace</h2>

<p><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-5qq8-67g6-4h2w">GHSA-5qq8-67g6-4h2w</a> (CVSS 9.9) nasce de uma discrepância boba de aparência, grave na prática: o Kyverno valida o <code class="language-plaintext highlighter-rouge">urlPath</code> de um <code class="language-plaintext highlighter-rouge">apiCall</code> com <code class="language-plaintext highlighter-rouge">path.Clean()</code> — que normaliza sequências <code class="language-plaintext highlighter-rouge">..</code> literais — mas executa a requisição com o path bruto, sem decodificar. Uma sequência <code class="language-plaintext highlighter-rouge">%2e%2e</code> (<code class="language-plaintext highlighter-rouge">..</code> percent-encoded) passa pela validação como se estivesse dentro do namespace do tenant, e só é decodificada de volta pra <code class="language-plaintext highlighter-rouge">..</code> quando o client-go do Kubernetes monta a requisição real — aí sim resolvendo pra fora do namespace.</p>

<p>Com isso, um tenant com permissão de criar <code class="language-plaintext highlighter-rouge">Policy</code> (e de criar workloads, no papel de editor) tem dois caminhos documentados até cluster-admin:</p>

<ul>
  <li><strong>Via <code class="language-plaintext highlighter-rouge">MutatingWebhookConfiguration</code></strong>: registra um webhook mutante cluster-wide, intercepta criação de pods no <code class="language-plaintext highlighter-rouge">kube-system</code>, sequestra a ServiceAccount do <code class="language-plaintext highlighter-rouge">clusterrole-aggregation-controller</code> e injeta um sidecar que dá permissão total à ClusterRole <code class="language-plaintext highlighter-rouge">system:basic-user</code>.</li>
  <li><strong>Via <code class="language-plaintext highlighter-rouge">PolicyException</code></strong>: cria um <code class="language-plaintext highlighter-rouge">PolicyException</code> dentro do namespace <code class="language-plaintext highlighter-rouge">kyverno</code> — que o tenant não deveria conseguir acessar diretamente — desativando políticas enforcing só pro próprio namespace e admitindo workloads que estavam bloqueados.</li>
</ul>

<p>Não existe CVE atribuído ainda, só o GHSA. O achado é de Artem Cherezov.</p>

<h2 id="as-duas-de-isolamento-entre-namespaces-mesmo-bug-dois-lugares">As duas de isolamento entre namespaces (mesmo bug, dois lugares)</h2>

<p><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-c5qq-7g2q-cpqp">GHSA-c5qq-7g2q-cpqp</a> (CVSS 7.7) é a mesma classe de bug da crítica acima, só que aplicada à leitura de recursos em vez de escalada de privilégio: um path como <code class="language-plaintext highlighter-rouge">/api/v1/namespaces/atacante-ns/%2e%2e/vitima-ns/configmaps/config</code> passa pela validação lexical (<code class="language-plaintext highlighter-rouge">path.Clean()</code> não decodifica percent-encoding) mas é resolvido de verdade pelo <code class="language-plaintext highlighter-rouge">url.Parse()</code> na hora de montar a chamada, que decodifica <code class="language-plaintext highlighter-rouge">%2e%2e</code> em <code class="language-plaintext highlighter-rouge">..</code> e atravessa pro namespace vizinho — com a ServiceAccount do controller do Kyverno, não a do tenant. Em instalação default, isso já dá leitura de ConfigMaps de qualquer namespace; se a ServiceAccount do controller tiver permissão sobre Secrets, o alcance é maior.</p>

<p><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-59v6-2x73-wfg4">GHSA-59v6-2x73-wfg4</a> (CVSS 7.7) é um isolamento quebrado por omissão, não por bug de parsing: a biblioteca CEL <code class="language-plaintext highlighter-rouge">globalcontext.Lib</code> — ao contrário de <code class="language-plaintext highlighter-rouge">resource.Lib</code> e <code class="language-plaintext highlighter-rouge">http.Lib</code> — nunca recebeu parâmetro de namespace no registro. Um <code class="language-plaintext highlighter-rouge">NamespacedValidatingPolicy</code> (ou as variantes Mutating/Deleting/Generating/ImageValidating) escrita por um tenant pode chamar <code class="language-plaintext highlighter-rouge">globalContext.get("&lt;entry&gt;", "")</code> e ler o conteúdo completo de um <code class="language-plaintext highlighter-rouge">GlobalContextEntry</code> cluster-scoped, mesmo de dados vindos de outros namespaces. Só afeta quem já usa <code class="language-plaintext highlighter-rouge">GlobalContextEntry</code> cruzando namespaces — cluster sem isso não é afetado.</p>

<h2 id="ssrf-e-vazamento-de-token-via-o-executor-antigo-de-apicall">SSRF e vazamento de token via o executor antigo de <code class="language-plaintext highlighter-rouge">apiCall</code></h2>

<p><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-q825-p383-r9v5">GHSA-q825-p383-r9v5</a> (CVSS 7.6) mostra o risco de proteção parcial: em abril de 2026 o Kyverno adicionou blocklist de SSRF e controle de token pro caminho novo baseado em CEL (<code class="language-plaintext highlighter-rouge">pkg/cel/compiler/http.go</code>). O executor legado (<code class="language-plaintext highlighter-rouge">pkg/engine/apicall/executor.go</code>), usado por <code class="language-plaintext highlighter-rouge">ClusterPolicy</code>/<code class="language-plaintext highlighter-rouge">Policy</code> clássicas e por <code class="language-plaintext highlighter-rouge">GlobalContextEntry</code>, nunca recebeu o mesmo tratamento. Ele monta a requisição com a URL bruta definida pelo autor da política, sem qualquer filtro, e anexa <code class="language-plaintext highlighter-rouge">Authorization: Bearer &lt;token&gt;</code> da ServiceAccount sempre que o autor não define header próprio. Resultado: quem tem permissão de criar política pode apontar <code class="language-plaintext highlighter-rouge">apiCall.service.url</code> pro metadata endpoint da cloud (<code class="language-plaintext highlighter-rouge">169.254.169.254</code>), loopback ou serviço interno, e o Kyverno faz a requisição com o próprio token embutido — sem confirmar se o destino é legítimo.</p>

<h2 id="bypass-de-verificação-de-assinatura-de-imagem-via-policyexception">Bypass de verificação de assinatura de imagem via <code class="language-plaintext highlighter-rouge">PolicyException</code></h2>

<p><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-5cjf-wwfg-pj4c">GHSA-5cjf-wwfg-pj4c</a> (CVSS 7.7) atinge quem usa <code class="language-plaintext highlighter-rouge">ImageValidatingPolicy</code> com Notary ou Cosign pra checar assinatura de imagem antes do deploy. O <code class="language-plaintext highlighter-rouge">PolicyException</code> deveria isentar só as imagens listadas em <code class="language-plaintext highlighter-rouge">spec.images</code> — é assim que <code class="language-plaintext highlighter-rouge">ValidatingPolicy</code>, <code class="language-plaintext highlighter-rouge">MutatingPolicy</code> e <code class="language-plaintext highlighter-rouge">GeneratingPolicy</code> já se comportam. Só que no compilador do <code class="language-plaintext highlighter-rouge">ImageValidatingPolicy</code> (<code class="language-plaintext highlighter-rouge">pkg/image/verification/evaluator/compiler.go</code>), apenas os <code class="language-plaintext highlighter-rouge">matchConditions</code> da exceção são compilados; os campos <code class="language-plaintext highlighter-rouge">images</code> e <code class="language-plaintext highlighter-rouge">allowedValues</code> nunca são processados. Na prática, qualquer <code class="language-plaintext highlighter-rouge">PolicyException</code> que dê match na política desliga a verificação de assinatura inteira — não só pras imagens que deveriam estar isentas. Quem tem permissão de criar <code class="language-plaintext highlighter-rouge">PolicyException</code> desativa verificação de supply chain cluster-wide, mesmo sem essa intenção.</p>

<h2 id="o-que-fazer">O que fazer</h2>

<p>Todas as seis (mais duas CVEs de dependência — <code class="language-plaintext highlighter-rouge">CVE-2026-39821</code> e <code class="language-plaintext highlighter-rouge">CVE-2026-56853</code>, resolvidas com bump de Go 1.26.6 e <code class="language-plaintext highlighter-rouge">x/net</code>) estão corrigidas no v1.19.1. Não há mitigação parcial documentada pra nenhuma delas — atualizar é o caminho.</p>

<p>Priorize pela combinação que te afeta:</p>

<ul>
  <li><strong>Multi-tenant com criação de Policy delegada</strong> (times de plataforma que dão namespace + permissão de <code class="language-plaintext highlighter-rouge">Policy</code> pra squads): a crítica e as duas de isolamento são motivo pra não esperar a janela de manutenção normal. É exatamente esse modelo de uso que os três bugs exploram.</li>
  <li><strong>Usa <code class="language-plaintext highlighter-rouge">apiCall</code> em <code class="language-plaintext highlighter-rouge">ClusterPolicy</code>/<code class="language-plaintext highlighter-rouge">Policy</code> clássica ou <code class="language-plaintext highlighter-rouge">GlobalContextEntry</code></strong> apontando pra serviço externo: o bypass de SSRF é seu — confira se o executor legado ainda está em uso antes de assumir que a blocklist de abril te protege.</li>
  <li><strong>Usa <code class="language-plaintext highlighter-rouge">ImageValidatingPolicy</code> com Notary/Cosign e permite <code class="language-plaintext highlighter-rouge">PolicyException</code></strong>: valide se alguma exceção existente hoje está isentando mais imagens do que deveria — o comportamento errado já pode estar em produção silenciosamente.</li>
  <li><strong>Usa <code class="language-plaintext highlighter-rouge">GlobalContextEntry</code> compartilhado entre namespaces</strong>: revise quem tem permissão de escrever <code class="language-plaintext highlighter-rouge">NamespacedValidatingPolicy</code> antes de assumir isolamento entre tenants.</li>
</ul>

<p>O padrão que conecta quatro das seis falhas é o mesmo: validação de path feita numa representação (limpa, decodificada) e execução feita em outra (bruta, ou decodificada de novo mais adiante). Vale como lição pra qualquer controller seu que valida uma string e delega a interpretação real dela pra uma lib de terceiro — <code class="language-plaintext highlighter-rouge">path.Clean()</code> e <code class="language-plaintext highlighter-rouge">url.Parse()</code> não concordam sobre o que <code class="language-plaintext highlighter-rouge">%2e%2e</code> significa, e essa divergência é exatamente onde bypass de autorização mora.</p>

<h2 id="fontes">Fontes</h2>

<ul>
  <li><a href="https://github.com/kyverno/kyverno/releases/tag/v1.19.1">Kyverno v1.19.1 — release notes</a></li>
  <li><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-5qq8-67g6-4h2w">GHSA-5qq8-67g6-4h2w — Privilege escalation to cluster admin via Policy apiCall urlPath (CVSS 9.9)</a></li>
  <li><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-c5qq-7g2q-cpqp">GHSA-c5qq-7g2q-cpqp — Namespace isolation bypass via percent-encoded path segments</a></li>
  <li><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-59v6-2x73-wfg4">GHSA-59v6-2x73-wfg4 — globalcontext.Lib CEL library namespace isolation bypass</a></li>
  <li><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-q825-p383-r9v5">GHSA-q825-p383-r9v5 — Legacy apiCall service executor bypasses SSRF and token controls</a></li>
  <li><a href="https://github.com/kyverno/kyverno/security/advisories/GHSA-5cjf-wwfg-pj4c">GHSA-5cjf-wwfg-pj4c — ImageValidatingPolicy exceptions ignore PolicyException specifications</a></li>
  <li><a href="https://github.com/kyverno/kyverno/security/advisories">Kyverno — Security Advisories (lista completa)</a></li>
  <li><a href="https://github.com/kyverno/kyverno/blob/main/README.md">Kyverno — README (status CNCF Incubating)</a></li>
</ul>]]></content><author><name>Bezaleel Silva</name></author><summary type="html"><![CDATA[Em 10 de setembro o Kyverno lançou o v1.19.1 cobrindo 6 advisories, incluindo uma CVSS 9.9 que deixa um tenant de namespace virar cluster-admin. Se você delega criação de Policy pra times, isso te afeta direto.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thinkcloudnative.com/assets/social/kyverno-seis-falhas-de-seguranca.png" /><media:content medium="image" url="https://thinkcloudnative.com/assets/social/kyverno-seis-falhas-de-seguranca.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt-BR"><title type="html">Desligar um ambiente inteiro por horário com o cron scaler do KEDA</title><link href="https://thinkcloudnative.com/articles/keda-cron-scaler/" rel="alternate" type="text/html" title="Desligar um ambiente inteiro por horário com o cron scaler do KEDA" /><published>2026-09-10T00:00:00+00:00</published><updated>2026-09-10T00:00:00+00:00</updated><id>https://thinkcloudnative.com/articles/keda-cron-scaler</id><content type="html" xml:base="https://thinkcloudnative.com/articles/keda-cron-scaler/"><![CDATA[<p>O cenário é comum: você tem um cluster de homologação (ou dev, ou staging) que só é usado em horário comercial, de segunda a sexta. Fora disso — noite, madrugada, fim de semana — ele fica ligado consumindo nó, memória e, na nuvem, dinheiro de verdade. Ninguém desliga porque desligar “na mão” é chato e ligar de volta esquecido é pior.</p>

<p>O jeito mais frágil de resolver isso é um <code class="language-plaintext highlighter-rouge">CronJob</code> rodando <code class="language-plaintext highlighter-rouge">kubectl scale</code>. Ele não tem estado (se falhar, o ambiente fica no limbo), some no primeiro <code class="language-plaintext highlighter-rouge">kubectl apply</code> do Argo CD, e você precisa manter duas listas de deployments em sincronia. Dá pra fazer melhor com o <a href="https://keda.sh/docs/2.20/scalers/cron/">cron scaler do KEDA</a>.</p>

<h2 id="e-o-hpa-nativo-não-dá-pra-usar">E o HPA nativo, não dá pra usar?</h2>

<p>Desde o Kubernetes v1.37 — Beta, feature gate <code class="language-plaintext highlighter-rouge">HPAScaleToZero</code> ligado por padrão —, o <code class="language-plaintext highlighter-rouge">HorizontalPodAutoscaler</code> nativo já aceita <code class="language-plaintext highlighter-rouge">minReplicas: 0</code>. Duas ressalvas tiram ele da jogada pra esse caso:</p>

<ul>
  <li><strong>Só funciona com métrica object ou external</strong> (fila, lag de consumer, request rate via um adapter tipo Prometheus Adapter). CPU e memória continuam sem suporte a zero — sem pod rodando não tem o que medir.</li>
  <li><strong>Não existe conceito de horário.</strong> O HPA é puramente reativo a métrica; não tem <code class="language-plaintext highlighter-rouge">start</code>/<code class="language-plaintext highlighter-rouge">end</code> embutido. Pra desligar “das 20h às 7h” você precisaria expor uma métrica externa que varie sozinha com o relógio — na prática, reimplementar um cron como métrica.</li>
</ul>

<p>O KEDA não compete com o HPA, aliás: o <code class="language-plaintext highlighter-rouge">ScaledObject</code> cria um <code class="language-plaintext highlighter-rouge">HorizontalPodAutoscaler</code> por baixo dos panos (é por isso que ter os dois no mesmo alvo dá conflito — mais adiante). O que ele soma são os <em>triggers</em> — mais de 60, incluindo o <code class="language-plaintext highlighter-rouge">cron</code> — que o HPA sozinho não tem. Pra “desligar fora do expediente”, o caminho direto continua sendo o cron scaler.</p>

<h2 id="o-modelo-a-janela-é-quando-está-ligado">O modelo: a janela é quando está ligado</h2>

<p>O ponto que confunde: o cron scaler não “dispara” nada num horário. Ele define uma <strong>janela <code class="language-plaintext highlighter-rouge">[start, end)</code></strong> e, enquanto o relógio está dentro dela, mantém o deployment num número fixo de réplicas. Fora da janela, ele solta a restrição.</p>

<p>Para “desligar fora do expediente”, a janela é o <strong>expediente</strong>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">keda.sh/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ScaledObject</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">api-pedidos</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">homolog</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">scaleTargetRef</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">api-pedidos</span>
  <span class="na">minReplicaCount</span><span class="pi">:</span> <span class="m">0</span>        <span class="c1"># fora da janela -&gt; zero</span>
  <span class="na">cooldownPeriod</span><span class="pi">:</span> <span class="m">300</span>
  <span class="na">triggers</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">cron</span>
      <span class="na">metadata</span><span class="pi">:</span>
        <span class="na">timezone</span><span class="pi">:</span> <span class="s">America/Sao_Paulo</span>
        <span class="na">start</span><span class="pi">:</span> <span class="s">0 7 * * 1-5</span>   <span class="c1"># 07:00, seg a sex</span>
        <span class="na">end</span><span class="pi">:</span> <span class="s">0 20 * * 1-5</span>    <span class="c1"># 20:00, seg a sex</span>
        <span class="na">desiredReplicas</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span> <span class="c1"># capacidade dentro do expediente</span>
</code></pre></div></div>

<p>Lê como: “das 7h às 20h, de segunda a sexta, no horário de São Paulo, quero 2 réplicas; fora disso, zero”. O <code class="language-plaintext highlighter-rouge">dia-da-semana</code> (<code class="language-plaintext highlighter-rouge">1-5</code>) já resolve o fim de semana — ele nunca entra na janela, então fica desligado sábado e domingo.</p>

<p>Alguns detalhes dos campos:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">timezone</code></strong> é obrigatório e usa a IANA Time Zone Database. Como você declara o fuso, o horário de verão é tratado sozinho.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">start</code> / <code class="language-plaintext highlighter-rouge">end</code></strong> são cron <strong>Linux de 5 campos</strong> (<code class="language-plaintext highlighter-rouge">minuto hora dia-do-mês mês dia-da-semana</code>), sem segundos. Os dois não podem resolver para o mesmo instante.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">desiredReplicas</code></strong> é um inteiro <strong>como string</strong> (<code class="language-plaintext highlighter-rouge">"2"</code>, com aspas).</li>
  <li><strong><code class="language-plaintext highlighter-rouge">minReplicaCount: 0</code></strong> é o que permite a ida a zero. Se você quiser deixar 1 réplica de pé fora do horário (pra health check, uma demo eventual), use <code class="language-plaintext highlighter-rouge">minReplicaCount: 1</code>.</li>
</ul>

<h2 id="o-detalhe-que-ninguém-conta-o-argo-cd-vai-reclamar">O detalhe que ninguém conta: o Argo CD vai reclamar</h2>

<p>Se o <code class="language-plaintext highlighter-rouge">replicas</code> dos seus deployments está versionado no Git e você usa Argo CD (ou Flux), o KEDA vai alterar <code class="language-plaintext highlighter-rouge">spec.replicas</code> o tempo todo — e isso vira <strong>drift permanente</strong>: o Argo CD vê “Git diz 2, cluster diz 0”, marca como <code class="language-plaintext highlighter-rouge">OutOfSync</code> e, no pior caso, sincroniza de volta pra 2 no meio da madrugada.</p>

<p>Duas formas de resolver, escolha uma:</p>

<ol>
  <li><strong>Tire <code class="language-plaintext highlighter-rouge">replicas</code> do manifesto.</strong> Se o campo não existe no Git, o KEDA passa a ser o dono e não há o que divergir. É a opção mais limpa quando o KEDA cuida do scaling daquele workload de ponta a ponta.</li>
  <li><strong>Ignore o campo no Argo CD.</strong> Na <code class="language-plaintext highlighter-rouge">Application</code>:</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spec</span><span class="pi">:</span>
  <span class="na">ignoreDifferences</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">group</span><span class="pi">:</span> <span class="s">apps</span>
      <span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
      <span class="na">jqPathExpressions</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">.spec.replicas</span>
</code></pre></div></div>

<p>Sem isso, você não tem uma rotina de desligamento — tem uma guerra de sincronização.</p>

<h2 id="e-os-n-serviços">E os N serviços?</h2>

<p>Um <code class="language-plaintext highlighter-rouge">ScaledObject</code> aponta pra <strong>um</strong> <code class="language-plaintext highlighter-rouge">scaleTargetRef</code>. Não existe “escale tudo que tem o label X” nativo. Então você tem um <code class="language-plaintext highlighter-rouge">ScaledObject</code> por deployment que participa do desligamento. Na prática isso é um <code class="language-plaintext highlighter-rouge">for</code> no seu Kustomize ou um range no seu Helm chart:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">{{</span><span class="nv">- range .Values.desligarForaDoExpediente</span> <span class="pi">}}</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">keda.sh/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ScaledObject</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">.</span> <span class="pi">}}</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">$.Release.Namespace</span> <span class="pi">}}</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">scaleTargetRef</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">.</span> <span class="pi">}}</span>
  <span class="na">minReplicaCount</span><span class="pi">:</span> <span class="m">0</span>
  <span class="na">cooldownPeriod</span><span class="pi">:</span> <span class="m">300</span>
  <span class="na">triggers</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">cron</span>
      <span class="na">metadata</span><span class="pi">:</span>
        <span class="na">timezone</span><span class="pi">:</span> <span class="s">America/Sao_Paulo</span>
        <span class="na">start</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">$.Values.expediente.start | quote</span> <span class="pi">}}</span>
        <span class="na">end</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">$.Values.expediente.end | quote</span> <span class="pi">}}</span>
        <span class="na">desiredReplicas</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">$.Values.expediente.replicas | quote</span> <span class="pi">}}</span>
<span class="pi">{{</span><span class="nv">- end</span> <span class="pi">}}</span>
</code></pre></div></div>

<p>Uma lista só (<code class="language-plaintext highlighter-rouge">desligarForaDoExpediente: [api-pedidos, api-clientes, frontend, ...]</code>) e a janela definida em um lugar. Serviço novo entra na rotina adicionando uma linha.</p>

<h2 id="o-que-não-desligar">O que <strong>não</strong> desligar</h2>

<p>Escopo isso para <strong>deployments stateless de aplicação</strong>. Fique longe de:</p>

<ul>
  <li><strong>Bancos de dados e qualquer <code class="language-plaintext highlighter-rouge">StatefulSet</code> com dado.</strong> O KEDA até escala StatefulSet, mas levar um Postgres a zero toda noite é pedir corrupção e dor de cabeça de recovery.</li>
  <li><strong>Ingress controller / gateway.</strong> Deixe de pé. Requisições fora do horário vão receber erro ou pagar a latência de scale-from-zero — o que costuma ser aceitável em não-produção, mas é uma decisão consciente.</li>
  <li><strong>Jobs e <code class="language-plaintext highlighter-rouge">CronJob</code>s.</strong> Não são afetados por <code class="language-plaintext highlighter-rouge">ScaledObject</code> (isso é <code class="language-plaintext highlighter-rouge">ScaledJob</code>, outro recurso). Se você tem um batch noturno em homolog, ele continua rodando.</li>
</ul>

<h2 id="ligar-de-volta-sem-susto">Ligar de volta sem susto</h2>

<p>A janela deve <strong>abrir antes</strong> do primeiro uso. Se as pessoas chegam às 8h, comece às 7h ou 7h30: cold start de pod, pool de conexão com o banco, cache frio e warmup de JVM não são instantâneos. <code class="language-plaintext highlighter-rouge">start: 0 7 * * 1-5</code> te dá essa folga.</p>

<p>Na descida, o <code class="language-plaintext highlighter-rouge">cooldownPeriod</code> (padrão 300s) é quanto o KEDA espera depois do <code class="language-plaintext highlighter-rouge">end</code> antes de escalar pra baixo — então o ambiente cai uns 5 minutos depois das 20h, não no minuto exato. Ajuste se quiser corte mais seco.</p>

<p>Precisa manter o ambiente de pé numa noite específica (deploy de emergência, demo pro cliente lá fora)? Anote no <code class="language-plaintext highlighter-rouge">ScaledObject</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">metadata</span><span class="pi">:</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">autoscaling.keda.sh/paused-replicas</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>
</code></pre></div></div>

<p>Isso congela em 2 réplicas e ignora o cron até você remover a anotação. É a válvula de escape sem editar a janela.</p>

<p>Um último cuidado: se o deployment já tem um HPA nativo, <strong>remova</strong>. O KEDA cria o próprio HPA e dois HPAs no mesmo alvo brigam entre si.</p>

<h2 id="quando-isso-não-vale">Quando isso não vale</h2>

<p>Produção com SLA fica de fora — o risco de alguém precisar do serviço às 3h e ele estar frio não compensa a economia. E se o seu workload tem cold start de vários minutos ou depende de uma cadeia grande de serviços subindo em ordem, o custo de religar todo dia pode passar do que você economiza. Nesses casos, ou você aceita 1 réplica mínima, ou o desligamento fica só pro fim de semana.</p>

<p>Para homolog, dev e staging, porém, essa é uma das economias mais fáceis de cloud native: um <code class="language-plaintext highlighter-rouge">ScaledObject</code> por serviço, uma janela, e o ambiente se desliga sozinho todo dia às 20h.</p>

<h2 id="fontes">Fontes</h2>

<ul>
  <li><a href="https://kubernetes.io/blog/2026/09/02/kubernetes-v1-37-hpa-scale-to-zero-beta/">Kubernetes v1.37 — Scale Workloads to Zero with HorizontalPodAutoscaler (Beta)</a></li>
  <li><a href="https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/">Kubernetes — Horizontal Pod Autoscaling (scaling to and from zero)</a></li>
  <li><a href="https://keda.sh/docs/2.20/scalers/cron/">KEDA — Cron scaler (2.20)</a></li>
  <li><a href="https://keda.sh/docs/2.20/reference/scaledobject-spec/">KEDA — ScaledObject spec (<code class="language-plaintext highlighter-rouge">minReplicaCount</code>, <code class="language-plaintext highlighter-rouge">cooldownPeriod</code>, <code class="language-plaintext highlighter-rouge">pollingInterval</code>)</a></li>
  <li><a href="https://keda.sh/docs/2.20/concepts/scaling-deployments/#pause-autoscaling">KEDA — Pausing autoscaling (<code class="language-plaintext highlighter-rouge">paused-replicas</code>)</a></li>
  <li><a href="https://argo-cd.readthedocs.io/en/stable/user-guide/diffing/">Argo CD — Diffing / <code class="language-plaintext highlighter-rouge">ignoreDifferences</code></a></li>
</ul>]]></content><author><name>Bezaleel Silva</name></author><summary type="html"><![CDATA[Homolog e dev ligados 24/7 é dinheiro parado. O cron scaler do KEDA desliga os serviços fora do expediente e religa antes de alguém chegar — sem CronJob de kubectl scale e sem brigar com o Argo CD.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thinkcloudnative.com/assets/social/keda-cron-scaler.png" /><media:content medium="image" url="https://thinkcloudnative.com/assets/social/keda-cron-scaler.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt-BR"><title type="html">3 erros que quebram o Prometheus em produção e como corrigir cada um</title><link href="https://thinkcloudnative.com/articles/3-erros-que-quebram-o-prometheus/" rel="alternate" type="text/html" title="3 erros que quebram o Prometheus em produção e como corrigir cada um" /><published>2026-09-07T00:00:00+00:00</published><updated>2026-09-07T00:00:00+00:00</updated><id>https://thinkcloudnative.com/articles/3-erros-que-quebram-o-prometheus</id><content type="html" xml:base="https://thinkcloudnative.com/articles/3-erros-que-quebram-o-prometheus/"><![CDATA[<p>Prometheus é uma das ferramentas mais sólidas que uso no dia a dia. Mas ele tem uma característica que aprendi da forma difícil: aceita tudo que você joga nele até o momento em que não aceita mais.</p>

<p>Sem aviso. Sem graceful shutdown. Só um OOM e silêncio total no dashboard.</p>

<p>Já vi isso acontecer por três motivos diferentes. Todos evitáveis. Todos silenciosos até o momento errado. Vou contar cada um com o que causou, como detectar antes que vire incidente, e como corrigir.</p>

<h2 id="erro-1-cardinalidade-explosiva-em-labels">Erro 1: cardinalidade explosiva em labels</h2>

<p>O Prometheus armazena cada combinação única de métrica + labels como uma série temporal separada na memória. Isso é o que torna ele poderoso. E é exatamente o que mata ele quando você não presta atenção.</p>

<p>Um <code class="language-plaintext highlighter-rouge">request_id</code> como label parece inofensivo. Mas em produção, com milhares de requisições por segundo, cada ID único vira uma série nova. Em horas, você tem dezenas de milhões de séries ativas. O processo não avisa — ele simplesmente consome toda a RAM disponível e morre.</p>

<p>Labels que explodem cardinalidade quase sempre são os mesmos: <code class="language-plaintext highlighter-rouge">user_id</code>, <code class="language-plaintext highlighter-rouge">request_id</code>, <code class="language-plaintext highlighter-rouge">trace_id</code>, <code class="language-plaintext highlighter-rouge">session_id</code>, caminhos de URL brutos. Se o valor pode ser diferente a cada requisição, não é label — é dado de trace.</p>

<h3 id="como-perceber-que-está-acontecendo">Como perceber que está acontecendo</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Séries ativas no TSDB — saudável: 100k–2M. Acima de 5M: problema. Acima de 10M: urgente.
prometheus_tsdb_head_series

# Quais métricas estão explodindo
topk(10, count by (__name__) ({__name__=~".+"}))

# Taxa de criação de séries novas — churn acelerado é sinal de label problemático
rate(prometheus_tsdb_head_series_created_total[5m])
</code></pre></div></div>

<h3 id="como-corrigir">Como corrigir</h3>

<p>Se você não controla o exporter, dropa o label antes que entre no TSDB:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">metric_relabel_configs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">regex</span><span class="pi">:</span> <span class="s1">'</span><span class="s">(request_id|trace_id|user_id|session_id)'</span>
    <span class="na">action</span><span class="pi">:</span> <span class="s">labeldrop</span>
</code></pre></div></div>

<p>E coloca um alerta pra não ser pego de surpresa de novo:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">CardinalidadeAlta</span>
  <span class="na">expr</span><span class="pi">:</span> <span class="s">prometheus_tsdb_head_series &gt; </span><span class="m">5000000</span>
  <span class="na">for</span><span class="pi">:</span> <span class="s">10m</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">severity</span><span class="pi">:</span> <span class="s">warning</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">$value</span><span class="nv"> </span><span class="s">}}</span><span class="nv"> </span><span class="s">séries</span><span class="nv"> </span><span class="s">ativas</span><span class="nv"> </span><span class="s">—</span><span class="nv"> </span><span class="s">investiga</span><span class="nv"> </span><span class="s">antes</span><span class="nv"> </span><span class="s">de</span><span class="nv"> </span><span class="s">virar</span><span class="nv"> </span><span class="s">OOM"</span>
</code></pre></div></div>

<h2 id="erro-2-retention-sem-planejamento-de-storage">Erro 2: retention sem planejamento de storage</h2>

<p>Esse é mais silencioso. Não mata o Prometheus na hora — mata ele no pior momento possível.</p>

<p>O default é 15 dias de retenção. Na maioria dos setups, ninguém muda. O problema é que o número de séries cresce com o tempo — novos serviços, novos exporters, novos labels — mas o disco fica o mesmo. Em algum ponto, o volume diário de dados supera o que o disco aguenta para 15 dias.</p>

<p>Sem o parâmetro <code class="language-plaintext highlighter-rouge">--storage.tsdb.retention.size</code> configurado, o Prometheus vai consumir todo o disco disponível. Com ele configurado errado, você perde exatamente o histórico que precisaria pra fechar um postmortem.</p>

<p>Já vi time passar horas tentando entender a causa raiz de um incidente sem conseguir — porque o Prometheus tinha deletado os dados de 3 semanas atrás que mostrariam o padrão.</p>

<h3 id="como-acompanhar">Como acompanhar</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Uso atual do TSDB em bytes
prometheus_tsdb_storage_blocks_bytes

# Crescimento diário estimado
rate(prometheus_tsdb_storage_blocks_bytes[24h])
</code></pre></div></div>

<h3 id="como-configurar-direito">Como configurar direito</h3>

<p>O ponto não é aumentar o tempo de retenção. É combinar os dois parâmetros de forma que o disco nunca estoure e você nunca perca dados antes do prazo esperado.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">--storage</span>.tsdb.retention.time<span class="o">=</span>7d
<span class="nt">--storage</span>.tsdb.retention.size<span class="o">=</span>50GB
</code></pre></div></div>

<p>O primeiro limite atingido prevalece. Se o volume cresceu mais do que o esperado e o disco está chegando em 50GB antes dos 7 dias, o Prometheus deleta os blocos mais antigos automaticamente. Você não perde a instância — perde só o histórico mais velho, de forma controlada.</p>

<p>Sem o <code class="language-plaintext highlighter-rouge">retention.size</code>, o Prometheus vai crescer até ocupar todo o disco disponível. Aí não tem deleção controlada: tem crash.</p>

<p>Uma coisa que pouca gente lembra: o WAL (write-ahead log) ocupa espaço além dos blocos compactados. Em instâncias com alta ingestão, ele pode representar 2–3x o tamanho do head block. Monitora separado.</p>

<p>E se você precisa de histórico longo — mais de 30 ou 60 dias — o Prometheus não é a ferramenta certa pra isso. Thanos, Mimir ou VictoriaMetrics existem pra isso. Deixa o Prometheus local com janela curta e delega o histórico longo pra quem foi projetado pra aguentar.</p>

<h2 id="erro-3-scrape-interval-curto-demais-em-targets-pesados">Erro 3: scrape interval curto demais em targets pesados</h2>

<p>Esse erro nasce de uma boa intenção: “mais granularidade = melhor observabilidade”. Em alguns casos é verdade. Em outros, você está criando o problema que quer evitar.</p>

<p>O default do Prometheus é 1 minuto. Muitos times reduzem pra 15s ou 10s. O problema aparece quando você aplica esse interval em exporters pesados. O <code class="language-plaintext highlighter-rouge">kube-state-metrics</code> e o <code class="language-plaintext highlighter-rouge">node-exporter</code> em clusters grandes são os casos mais comuns. Um node-exporter num cluster de 50 nodes pode gerar facilmente 100k samples por scrape. Com interval de 15s, você está ingerindo centenas de milhares de samples por segundo só de infra, antes de qualquer métrica de aplicação.</p>

<p>O sintoma: <code class="language-plaintext highlighter-rouge">scrape_duration_seconds</code> maior que o próprio interval. Quando isso acontece, o Prometheus começa a enfileirar scrapes, atrasa alertas, e passa a consumir mais CPU do que os workloads que ele deveria monitorar.</p>

<h3 id="como-detectar">Como detectar</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Targets onde o scrape está demorando mais que o interval
scrape_duration_seconds &gt; 55  # para interval de 1m

# Volume de samples por job
scrape_samples_scraped

# Scrapes que excederam o limite de samples
increase(prometheus_target_scrapes_exceeded_sample_limit_total[1h])
</code></pre></div></div>

<h3 id="como-corrigir-1">Como corrigir</h3>

<p>Diferencia o interval por job. Não tem por que scrapear métricas de disco a cada 15 segundos.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">scrape_configs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">app-latency'</span>
    <span class="na">scrape_interval</span><span class="pi">:</span> <span class="s">15s</span>
    <span class="na">static_configs</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">app:9090'</span><span class="pi">]</span>

  <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">node-exporter'</span>
    <span class="na">scrape_interval</span><span class="pi">:</span> <span class="s">5m</span>
    <span class="na">static_configs</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">node-exporter:9100'</span><span class="pi">]</span>
</code></pre></div></div>

<p>E coloca <code class="language-plaintext highlighter-rouge">sample_limit</code> como proteção: se um exporter mal configurado começar a gerar samples demais, o scrape falha. O Prometheus continua de pé.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">scrape_configs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">app'</span>
    <span class="na">sample_limit</span><span class="pi">:</span> <span class="m">50000</span>
</code></pre></div></div>

<p>Melhor perder um scrape do que perder a instância inteira.</p>

<h2 id="o-que-os-três-erros-têm-em-comum">O que os três erros têm em comum</h2>

<p>Nenhum deles aparece de uma vez. Todos crescem devagar, por semanas, até o dia em que você precisa do Prometheus funcionando. E ele não está.</p>

<p>A boa notícia é que os três são detectáveis antes de virar incidente, com as queries certas e alertas no lugar certo.</p>

<blockquote>
  <p>Observabilidade que não monitora a si mesma não é observabilidade.</p>
</blockquote>]]></content><author><name>Bezaleel Silva</name></author><summary type="html"><![CDATA[Cardinalidade, retention e scrape interval: três formas silenciosas de derrubar o Prometheus — como detectar cada uma antes do incidente e como corrigir.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thinkcloudnative.com/assets/social/default.png" /><media:content medium="image" url="https://thinkcloudnative.com/assets/social/default.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt-BR"><title type="html">Envoy corrigiu 13 CVEs de uma vez — e algumas são bypass de RBAC, não só crash</title><link href="https://thinkcloudnative.com/articles/envoy-13-cves-de-uma-vez/" rel="alternate" type="text/html" title="Envoy corrigiu 13 CVEs de uma vez — e algumas são bypass de RBAC, não só crash" /><published>2026-09-07T00:00:00+00:00</published><updated>2026-09-07T00:00:00+00:00</updated><id>https://thinkcloudnative.com/articles/envoy-13-cves-de-uma-vez</id><content type="html" xml:base="https://thinkcloudnative.com/articles/envoy-13-cves-de-uma-vez/"><![CDATA[<p>Em 27 de agosto de 2026, o projeto Envoy lançou patch pra quatro branches ativas ao mesmo tempo — v1.39.1, v1.38.4, v1.37.6 e v1.36.10 — cobrindo 13 CVEs. Não é o tipo de release que aparece só como “bug fixes” no changelog: várias das falhas têm impacto direto em autorização e isolamento entre requisições, não apenas estabilidade.</p>

<p>O motivo de importar mesmo se você nunca configurou Envoy diretamente: ele é o data plane por trás de Istio, Envoy Gateway, Contour, Emissary-Ingress, Gloo e vários outros. Se você roda service mesh ou um API gateway construído sobre Envoy, você está exposto ao que está nessas 13 CVEs até atualizar — mesmo que o produto que você usa tenha outro nome na capa.</p>

<h2 id="o-que-de-fato-mudou">O que de fato mudou</h2>

<p>As 13 CVEs se dividem em dois grupos com implicações bem diferentes.</p>

<h3 id="grupo-1-falhas-que-quebram-isolamento-e-autorização">Grupo 1: falhas que quebram isolamento e autorização</h3>

<p>Esse é o grupo que merece atenção prioritária, porque não é “o processo cai” — é “o controle de acesso não funciona como você configurou”:</p>

<ul>
  <li><strong>RBAC com <code class="language-plaintext highlighter-rouge">safe_regex</code> falhando aberto</strong> (GHSA-23xh-2qxr-3xv8, alta severidade): headers HTTP com valores <code class="language-plaintext highlighter-rouge">obs-text</code> (bytes fora do padrão ASCII, mas válidos pela RFC) podiam fazer o matcher de regex do RBAC falhar silenciosamente — e quando falha, a política de autorização não bloqueia a requisição. Se seu RBAC depende de regex em headers, isso é bypass de política.</li>
  <li><strong>Bypass de autenticação via stripping de parâmetro de path</strong> (GHSA-m745-gh6x-349x, moderada) e <strong>canonicalização de path parameter que ignora regras de segurança</strong> (GHSA-77x5-xqjg-hprq, alta): duas variações do mesmo problema de fundo — a normalização de path que o Envoy faz antes de rotear pode divergir da normalização que o backend faz, abrindo brecha pra rotas que deveriam estar protegidas.</li>
  <li><strong>Poisoning de resposta entre usuários via HTTP upgrade em connection pool compartilhado</strong> (GHSA-3vhp-c83q-jqc2, alta): payload enviado antes de um upgrade genérico de HTTP ser aceito podia ser interpretado como requisição HTTP/1 pipeline e vazar pra outra conexão compartilhada no mesmo upstream. Em cenários de multi-tenant isso é vazamento de dados entre requisições de usuários diferentes.</li>
  <li><strong>XSS armazenado na interface admin</strong> (GHSA-pv9h-4fxf-7vrg, alta): <code class="language-plaintext highlighter-rouge">/stats?format=html</code> não sanitizava nomes de métricas antes de converter pra HTML. Se algum componente do seu sistema consegue injetar nome de métrica arbitrário (nem sempre trivial, mas possível em setups com labels dinâmicos) e alguém abre o painel admin, o payload executa no navegador de quem está olhando.</li>
  <li><strong>Null pointer deref no ext_authz com CONNECT</strong> (GHSA-87ph-jqwm-pg6r, alta): requisições sem path (o caso do método CONNECT) causavam crash ao chamar o serviço de ext_authz — isso é DoS pontual, mas se seu authz externo é obrigatório pra decisão de acesso, um crash ali também é falha de segurança, não só de disponibilidade.</li>
</ul>

<h3 id="grupo-2-crashes-e-vazamento-de-memória">Grupo 2: crashes e vazamento de memória</h3>

<p>O restante são bugs de estabilidade sérios, mas sem o componente de “sua política de segurança não estava fazendo o que você achava”:</p>

<ul>
  <li>Use-after-free no HTTP/3 com sequência específica de frames, e outro use-after-free quando o ext_authz via HTTP rejeita uma requisição.</li>
  <li>Crash no HTTP/2 ao receber trailers sem a flag <code class="language-plaintext highlighter-rouge">END_STREAM</code>, e no QUIC ao lidar com endereços IPv6 com escopo em clusters Original Dst.</li>
  <li>Crash na negociação ALPN quando o upstream escolhido é HTTP/3.</li>
  <li>Memory leak em <code class="language-plaintext highlighter-rouge">SSL_get0_peer_certificates()</code> e um mismatch de allocator que quebrava profiling de heap com tcmalloc.</li>
</ul>

<h2 id="o-detalhe-que-muda-comportamento-default">O detalhe que muda comportamento default</h2>

<p>Boa parte dos fixes de segurança vêm atrás de <code class="language-plaintext highlighter-rouge">reloadable_features</code> — flags que existem justamente porque a correção muda comportamento observável, não só corrige um bug isolado. Alguns exemplos:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>envoy.reloadable_features.strip_path_parameters_per_segment
envoy.reloadable_features.strip_dotdot_segments_with_parameters
envoy.reloadable_features.rbac_respect_ignore_path_parameters
envoy.reloadable_features.re2_use_latin1_mode
envoy.reloadable_features.http2_track_size_of_dropped_host_header
envoy.reloadable_features.sanitize_html_stats_names
</code></pre></div></div>

<p>Todas vêm habilitadas por padrão a partir dessas versões — ou seja, o fix já está ativo assim que você atualiza, sem passo extra. A flag existe pra permitir reverter caso o novo comportamento quebre algo inesperado no seu tráfego (por exemplo, se você depende de path com parâmetro não normalizado em alguma rota legada). Mas desabilitar qualquer uma dessas flags volta o comportamento vulnerável — trate reversão como último recurso, não como configuração padrão pós-upgrade.</p>

<h2 id="o-que-fazer">O que fazer</h2>

<p>Antes de tudo, descubra qual versão de Envoy você está rodando de fato. Se você usa Envoy direto, é óbvio. Se você usa Istio, Envoy Gateway, Contour ou Emissary, o Envoy embutido tem seu próprio ciclo — a versão do seu control plane não é a versão do Envoy. Confere no changelog do seu projeto qual patch de Envoy ele já empacotou ou vai empacotar.</p>

<p>Prioriza pela combinação que mais te afeta:</p>
<ul>
  <li>Se você usa RBAC baseado em regex em headers ou expõe roteamento por path parameter em qualquer camada — os itens de bypass de autorização merecem atualização sem esperar a próxima janela de manutenção.</li>
  <li>Se a interface admin do Envoy está acessível (mesmo que só internamente) — o XSS armazenado é motivo suficiente pra não adiar.</li>
  <li>Se você usa ext_authz como decisão obrigatória de acesso — tanto o crash quanto o use-after-free ali têm efeito direto na sua política de autorização ficar de pé.</li>
</ul>

<p>Vale atualizar? Sim, e não é “quando der”. Esse não é um patch de rotina com uma pilha de CVEs de baixa severidade — tem bypass de RBAC e de autenticação por path no meio, categorias de falha que normalmente saem sozinhas com aviso maior, não empacotadas junto com fixes de crash. O fato de terem saído juntas, pras quatro branches ativas ao mesmo tempo, é sinal de que o time levou a sério a superfície combinada.</p>

<h2 id="fontes">Fontes</h2>

<ul>
  <li><a href="https://github.com/envoyproxy/envoy/releases/tag/v1.39.1">Envoy v1.39.1 — release notes</a></li>
  <li><a href="https://raw.githubusercontent.com/envoyproxy/envoy/release/v1.39/changelogs/1.39.1.yaml">Changelog 1.39.1 (raw)</a></li>
  <li><a href="https://github.com/envoyproxy/envoy/security/advisories">Security Advisories — envoyproxy/envoy</a></li>
  <li><a href="https://github.com/envoyproxy/envoy/releases">Releases — envoyproxy/envoy</a></li>
</ul>]]></content><author><name>Bezaleel Silva</name></author><summary type="html"><![CDATA[Em 27 de agosto o time do Envoy lançou patch simultâneo pras quatro branches ativas cobrindo 13 CVEs, incluindo bypass de autorização e XSS armazenado no admin. Se você roda Istio, Envoy Gateway, Contour ou Emissary, você roda Envoy — e isso te afeta mesmo sem saber.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thinkcloudnative.com/assets/social/envoy-13-cves-de-uma-vez.png" /><media:content medium="image" url="https://thinkcloudnative.com/assets/social/envoy-13-cves-de-uma-vez.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>