Pular para o conteúdo

Denylist de tools

O RootPilot pede leitura privilegiada da sua stack inteira, e para uma parte dela a resposta legítima é não. As operações que leem security group, ACL e regra de firewall descrevem a superfície de ataque da sua infraestrutura; há times que não vão autorizar isso, e não deveriam ter que desligar a AWS inteira — jogando fora custo, compute e RDS junto — para conseguir.

A denylist é como você diz “pode ler minhas métricas e meus logs, mas não os meus security groups”.

Uma denylist que vivesse só no nosso control-plane seria uma configuração nossa, que você teria que confiar que respeitamos. Aplicada na borda, ela é verificável por você: você lê o manifesto do próprio deployment. É o mesmo raciocínio que já rege as suas credenciais (nunca custodiadas fora), a redação de PII (na borda, antes de sair) e o gate de escrita.

Isso tem uma consequência que preferimos dizer na cara: a tela do control-plane é rascunho. Enquanto o artefato não estiver implantado, nada muda — nem a superfície de tools, nem o que o agente consegue ler. Esconder tools com base numa política que você compôs e nunca implantou produziria a sensação de proteção sem a proteção, e um controle de segurança que existe só na nossa interface não é um controle de segurança.

Quatro níveis, resolvidos na união — o mais fino vence sempre que se sobrepõem:

Nível Exemplo Quando usar
Grupo nomeado network-posture, iam O caminho principal. Recorte curado que acompanha o catálogo.
Capability security, audit Quando a categoria inteira está fora de questão.
Connector amplitude Quando a fonte inteira está fora de questão.
Operação network.listSecurityGroups Ajuste fino, quando nenhum recorte acima serve.

Porque capability é grosso demais para o caso que motiva o recurso. A capability network contém a leitura de security group e a de VPC, subnet, load balancer, target group e transit gateway — negá-la inteira derruba junto o mapa de recursos, o blast radius e a topologia de DNS, que provavelmente é o oposto do que você quer.

O grupo network-posture isola exatamente as operações que descrevem postura de rede — regra de firewall, ACL, security group — e deixa o resto de pé.

Você pode escrever uma, e para ajuste fino é o certo. Mas uma lista literal composta em março não cobre a operação de security group que entrou no catálogo em maio, e ninguém relê a denylist a cada release. O artefato guarda intenção, não a lista expandida: o grupo composto em março cobre a operação de maio.

Um JSON que você monta no deployment do runner — ConfigMap, arquivo montado, o que o seu GitOps usar:

{
"version": 1,
"deny": {
"groups": ["network-posture", "iam"],
"capabilities": ["audit"],
"connectors": ["amplitude"],
"ops": ["query.runAthenaQuery"]
}
}

Que ele seja um arquivo, e não um punhado de variáveis de ambiente, é deliberado: um JSON no seu repositório de GitOps é diffável, entra numa pull request e é revisado pelo seu time de segurança. Variável de ambiente enterrada num values.yaml não é revisada por ninguém.

O caminho do arquivo vai em RUNNER_DENY_POLICY_FILE. Para o caso simples, quatro variáveis fazem o mesmo sem artefato — e as duas fontes se somam, se você usar as duas:

Variável Conteúdo
RUNNER_DENY_POLICY_FILE Caminho do artefato JSON
RUNNER_DENIED_GROUPS Grupos nomeados, separados por vírgula
RUNNER_DENIED_CAPABILITIES Capabilities
RUNNER_DENIED_CONNECTORS Connectors
RUNNER_DENIED_OPS Operações

Artefato ilegível, JSON inválido ou chave desconhecida dentro dele derrubam o boot, nunca degradam para “nada negado”: cair para política vazia quando você pediu negação é o pior default possível.

Grupo Cobre
network-posture Security groups (resumo, anexos, os abertos ao mundo) e regras de firewall — AWS e GCP. Não cobre VPC, subnet, load balancer, rota nem DNS: negar postura não pode derrubar o mapa de recursos.
iam Higiene de chave e credencial — access keys (inclusive por sufixo), chaves de service account GCP envelhecidas, API/app keys.
audit-trail A trilha de quem fez o quê — CloudTrail (lookup e as consultas do Lake) e audit logs do GCP. Não cobre inventário de recurso: negar a trilha não pode derrubar a descoberta do que existe.
end-user-activity O indivíduo — busca de usuário e atividade no Amplitude, e os session replays. Não cobre as métricas agregadas de produto (ativos, volume de evento, retenção, conversão): quem nega isto quer proteger a pessoa, não desligar o funil.

A lista acompanha o catálogo: operação nova que descreva postura de rede entra em network-posture sem que você mexa no seu artefato. É o motivo de o grupo existir.

Duas camadas, pela mesma estrutura do gate de escrita — uma no anúncio, outra na execução:

  1. Anúncio. No boot, o runner expande a sua política contra o próprio catálogo e subtrai as operações negadas do conjunto que ele declara servir. O control-plane não expõe ao agente uma tool cuja operação não é servida, então ela simplesmente não existe para o modelo.
  2. Execução. O enforcement.ts — o mesmo ponto que já recusa qualquer operação fora do catálogo — recusa a invocação de uma operação negada, mesmo que ela chegue. Um control-plane com bug, uma versão antiga ou um catálogo dessincronizado não contornam a política.

A segunda camada existe justamente porque a primeira depende de nós. A negação não vale porque nós concordamos em escondê-la; vale porque o processo que roda na sua infra recusa executá-la.

O runner anuncia de volta o que a política de fato negou: quais operações, e por qual regra — se caiu por grupo, por capability, por connector ou nomeada diretamente. É a expansão aplicada, não a intenção que você escreveu.

Isso importa por dois motivos. O primeiro é que é assim que você descobre o que um rótulo expande: o network-posture que você negou vira uma lista concreta de operações, dita pelo processo que as recusa. O segundo é que ela se confronta com o rascunho: o Fleet mostra as duas lado a lado, e uma divergência entre “o que eu compus” e “o que está valendo” é visível — tipicamente um artefato que ainda não foi implantado, ou uma frota com runners em versões diferentes.

É a parte mais importante desta página, e o motivo de a denylist não ser só uma subtração.

Se uma tool simplesmente sumisse, três situações passariam a ter exatamente a mesma cara: o connector não está configurado, o connector está quebrado, e você decidiu proibir. Só na terceira a infraestrutura existe, está saudável, e o vazio é uma decisão. Perguntado sobre exposição de rede, o agente responderia “não encontrei security groups permissivos” — que é falso, e é o pior resultado possível, porque um erro faz alguém investigar e uma resposta tranquilizadora não.

Então a supressão é legível, em três lugares:

  • A sessão sabe da política antes de precisar dela. A política é injetada no contexto da sessão, então o agente responde “isso está desabilitado pela política do seu tenant” em vez de “não encontrei nada”.
  • A resposta carrega o motivo. A fonte negada é reportada como denied_by_policy — que conta como não consegui ler, nunca como não existe. “A política me proibiu” não é prova de ausência.
  • O Fleet distingue as três situações de fora: não conectou · conectou e quebrou · conectou e restringiu.

Uma análise que dependia de uma operação negada roda com o que sobrou e diz o que faltou, em vez de falhar inteira — composta multi-sinal costuma tocar muita coisa além da dimensão negada, e desligá-la faria a capacidade sumir sem explicação.

O que não sai é o veredito derivado. Uma nota de segurança calculada por penalidade ficaria mais alta justamente por não ter conseguido ler a dimensão negada — leitura que não aconteceu melhorando o número —, então a nota não é emitida. Você recebe o que foi observado, marcado como parcial, e nenhuma conclusão que dependa do que a política escondeu.

Parar de aprender é automático: o conhecimento derivado (topologia, mapeamentos) é extraído do resultado das operações, e uma operação negada não produz resultado.

O que foi aprendido antes de a política entrar em vigor continua onde está. Isso é deliberado: o artefato vive num runner descartável, e um erro de digitação num arquivo não pode destruir meses de topologia aprendida em silêncio. A purga é uma ação separada e auditada — o Fleet mostra que existe estado anterior à política e oferece apagá-lo.

Se o artefato citar uma operação que o runner não conhece — renomeada no catálogo, ou composta contra uma versão mais nova —, o runner recusa subir e nomeia a entrada.

Parece severo, e é a escolha certa: uma diretiva de negação que não tem efeito é invisível até virar incidente. Runner é gado, boot que falha é barulhento, e o seu supervisor mostra o erro na hora — enquanto uma política silenciosamente parcial só aparece no dia em que alguém lê o que não deveria.

Uma entrada pode também nomear algo que o catálogo teve e removeu de propósito. A recusa é a mesma — a regra não protege mais nada, e mantê-la é acreditar numa proteção que não existe —, mas a mensagem é outra: ela diz em que versão o alvo saiu e o que usar no lugar, em vez de mandar procurar uma operação renomeada, que aqui seria a causa errada. É o caso da capability profiling e do connector witness, que saíram na versão 0.50.0 do catálogo: se a sua política ainda os cita, veja o Profiler.