Guias de Segurança

Menor privilégio para chaves SSH: mantenha as chaves de root de produção fora de ambientes efêmeros que podem ser comprometidos

Uma chave SSH de root cadastrada a partir de um pod de GPU ou runner de CI dá root em produção se vazar. Use um usuário sem root e uma chave restrita a comando.

Publicado 2026-06-11 Atualizado 2026-10-06 14 min de leitura
Nesta página

Para: qualquer pessoa que alcance produção por SSH a partir de um ambiente efêmero — um pod de nuvem com GPU, um runner de CI, uma VM descartável. Nenhum passo de ataque — só tornar as chaves de menor privilégio para que um vazamento cause menos dano. Para o básico de separar chaves, veja o checklist de base.

A visão deste site: trate ambientes efêmeros como não confiáveis

Computação descartável costuma pular o fortalecimento — é um lugar que pode ser comprometido. Coloque ali uma chave de root de produção e o comprometimento desse ambiente vira o comprometimento da produção. Este site usa uma chave distinta por host, restringe cada uma a um propósito e remove chaves assim que ficam sem uso (→ chaves separadas para minimizar o dano). Tratar uma chave não como "uma conveniência", mas como "o ativo mais crítico, que dá acesso a todos os servidores se vazar" é, no fim, a abordagem mais segura.

✗ chave de root em um ambiente efêmero

ambiente comprometido → root em produção com aquela chave → alcança arquivos, segredos e outros serviços — tudo.

○ chave sem root restrita a comando

mesmo que a mesma chave vaze, ela só consegue rodar o único comando especificado. Sem shell, sem encaminhamento — o dano fica contido a uma operação.

Mantenha pequeno desde o início o que uma chave pode fazer. Isso muda o dano quando ela vaza.

Por que uma chave de root em um ambiente efêmero é perigosa

Ambientes efêmeros tendem a pular o fortalecimento ("vamos apagá-lo logo"), o que os torna um alvo fácil e um ponto de passagem conveniente para atacar a produção. Uma chave de root de produção ali torna o dano o maior possível.

root→tudo
uma chave vazada dá controle total da produção
descartável
ambientes efêmeros pulam o fortalecimento
reutilizada
um vazamento expõe todo host
menor priv.
limite uma chave a uma operação e o dano também fica limitado

Digamos que você suba um pod para processamento e, para trabalhar em produção a partir dele, cadastre uma chave no authorized_keys do root. Prático — mas se esse pod for comprometido, o atacante entra em produção como root com aquela chave. Quer o ataque comece com um RCE ou com um vazamento de chave, o resultado final é o mesmo: produção tomada com uma chave roubada (relacionado: uma chave roubada via RCE e usada em cobrança fraudulenta).

Três passos para chaves de menor privilégio

Mude de "conceder poder total porque é conveniente" para "limitar o que ela pode fazer ao mínimo".

1

Remova a chave de root do ambiente efêmero

Apague chaves que você não usa mais do ~/.ssh/authorized_keys de produção. Se ociosas, removê-las não tem impacto. Primeiro, não deixe chaves sem uso no lugar.
2

Mesmo que precise de novo, não volte para root

Ao recadastrar para trabalho em produção, crie um usuário dedicado sem root e cadastre a chave nele. Mesmo que vaze, o alcance fica limitado aos privilégios daquele usuário.
3

Restrinja a chave a uma única operação

Adicione command="..." e restrict à linha da chave no authorized_keys. Mesmo um login bem-sucedido só consegue rodar o único comando especificado, com encaminhamento de porta e shell desativados. Um vazamento fica contido àquela única operação.
# Register a key restricted to ONE operation in a non-root user's authorized_keys.
# Logging in with it can run only the given command; forwarding and PTY are off.
command="/usr/local/bin/deploy-pull",restrict ssh-ed25519 AAAA...rest-of-public-key deploy@ci

restrict desativa encaminhamento, encaminhamento de agente, PTY, X11 e mais de uma só vez; command="..." fixa a ação executável a uma só coisa. O movimento básico é uma chave restrita a um único propósito — só puxar um deploy, só rodar um script específico — criada por caso de uso.

Um cenário comum (perigoso)

  • cadastrar uma chave no root de produção a partir de um pod/CI
  • deixar chaves já cumpridas no lugar
  • reutilizar uma chave em muitos hosts
  • espalhar chaves que concedem um shell completo

O cenário de menor privilégio

  • alcançar produção apenas como um usuário sem root
  • chaves limitadas a uma operação (restritas a comando)
  • separar chaves por host e propósito
  • remover chaves do authorized_keys no momento em que estão sem uso

Trate uma chave reutilizada como seu ativo mais crítico

Pense em uma chave como "uma conveniência" e você a reutiliza e esquece de apagá-la. Em vez disso, trate-a como o ativo mais crítico, que dá acesso a todos os servidores se vazar. Separe por host e propósito, mantenha as chaves de produção fora de ambientes efêmeros e sempre remova-as após o uso. Só isso já impede que um vazamento se espalhe para tudo.

As principais restrições que você pode pôr em uma linha de chave

No OpenSSH, opções escritas no início de uma linha de chave no authorized_keys reduzem o que aquela chave pode fazer. As mais comuns:

OpçãoO que restringeQuando usar
command="..."Logar com a chave roda só o comando especificado; qualquer comando enviado pelo cliente é ignoradoChaves que devem fazer um único trabalho fixo, como puxar um deploy ou fazer um backup
restrictDesativa de uma vez encaminhamento de porta, de agente e de X11, alocação de PTY e a execução de ~/.ssh/rc (OpenSSH 7.2+)Quase toda chave de automação; reative só o que precisar, por exemplo pty
from="..."Além da chave, o nome de host ou o endereço IP do cliente precisa constar na listaCI ou servidores de trabalho com IP de origem fixo
expiry-time="..."A chave deixa de ser aceita depois da data/hora indicada (OpenSSH 7.7+)Chaves que devem funcionar só para um trabalho temporário
PermitRootLogin (no sshd_config)Se o login direto como root é permitido; forced-commands-only permite root só para chaves com command=no, a menos que você realmente precise de acesso direto como root

As notas de versão do OpenSSH descrevem o restrict como incluindo "todas as restrições de chave atuais e futuras". Em vez de listar no-pty, no-port-forwarding e outros um a um, adicione restrict e reative só o que precisar, para que nada escape quando surgirem recursos novos.

# Also narrowing the source and the lifetime (the IP is a documentation address)
from="192.0.2.10",expiry-time="20261231",command="/usr/local/bin/deploy-pull",restrict ssh-ed25519 AAAA...rest-of-public-key deploy@ci-2026-10

Se você gerencia acesso com prazo em muitas chaves, certificados do OpenSSH são outra opção (assine com ssh-keygen -s, defina a validade com -V e restrinja com force-command ou source-address). Para um cenário pequeno, as opções na linha da chave bastam para começar.

Como verificar o seu próprio ambiente

Estas verificações só leem as configurações e os logs de servidores que você administra. Ao mudar configurações, mantenha uma sessão SSH aberta, teste uma nova conexão a partir de outro terminal e só feche a sessão antiga quando souber que não ficou trancado do lado de fora.

1. Liste as chaves cadastradas

# Show the fingerprint and comment of each key registered for root and each user
sudo ssh-keygen -lf /root/.ssh/authorized_keys
sudo ssh-keygen -lf /home/deploy/.ssh/authorized_keys

Cada chave imprime uma linha como 256 SHA256:... deploy@ci (ED25519). Use o comentário (a parte deploy@ci no fim) para separar chaves de origem desconhecida, chaves de ambientes efêmeros que você já encerrou e chaves cadastradas no root. O nome de arquivo antigo authorized_keys2 também é lido por padrão; então, se ele existir, verifique-o da mesma forma.

2. Verifique a configuração que o sshd realmente usa

# Check the syntax, then print the settings actually in effect
sudo sshd -t
sudo sshd -T | grep -iE 'permitrootlogin|authorizedkeysfile|pubkeyauthentication|passwordauthentication'

O sshd -T imprime os valores efetivos depois de ler os arquivos de configuração. Se permitrootlogin estiver como yes ou prohibit-password, qualquer pessoa com uma chave pode logar diretamente como root. Se authorizedkeysfile apontar para outro lugar que não o padrão, inclua esse local no passo 1.

3. Veja quais chaves são de fato usadas

Depois de um login por chave bem-sucedido, o sshd normalmente registra uma linha como Accepted publickey for deploy from 192.0.2.10 port 50022 ssh2: ED25519 SHA256:.... Leia-a em /var/log/auth.log no Debian e no Ubuntu, em /var/log/secure em sistemas da família Red Hat, ou com journalctl -u ssh em hosts com systemd (-u sshd em sistemas da família Red Hat).

Comparar essas impressões digitais com a lista do passo 1 mostra quais chaves ficam sem uso. O relatório do NIST sobre gestão de chaves SSH (NISTIR 7966) sugere acompanhar os logs por cerca de 6 a 12 meses antes de remover chaves sem uso, para não remover por engano chaves usadas raramente. Chaves que você sabe que já foram encerradas, como a de um ambiente efêmero, podem sair na hora.

4. Verifique o lado efêmero

  • Há uma chave privada que alcança produção guardada nos segredos do CI ou no ~/.ssh de um pod de GPU?
  • Você se conecta ao ambiente efêmero com ssh -A (encaminhamento de agente) a caminho da produção?
  • Uma mesma chave é compartilhada entre vários sistemas de CI ou servidores?

Erros comuns e como corrigi-los

  • Apagar a chave privada no lado efêmero e parar aí: se a chave privada já foi copiada, ela continua funcionando enquanto a chave pública ficar no servidor. Sempre remova a linha correspondente do authorized_keys de produção.
  • Rodar o comando do cliente (a variável de ambiente SSH_ORIGINAL_COMMAND) como está dentro do script chamado por command=: isso equivale a não ter restrição. Use o valor só quando ele corresponder exatamente a uma de um conjunto fixo de opções.
  • Trocar para um usuário sem root mas liberar sudo sem restrição: se esse usuário pode rodar qualquer coisa com sudo sem senha, ele é na prática root. Se o sudo for necessário, limite-o a um comando no sudoers.
  • Entrar em um ambiente efêmero com ssh -A: qualquer pessoa com privilégios suficientes ali pode usar as chaves carregadas no seu agente local por meio da conexão encaminhada (o manual do OpenSSH alerta sobre isso). Se precisar passar por um host, use ssh -J (conexão via jump host).
  • Deixar chaves sem comentário: numa revisão você não consegue saber de onde vieram. Adicione o propósito e a data ao criá-las, por exemplo ssh-keygen -C "deploy@ci-2026-10".
  • Compartilhar uma chave entre várias pessoas ou ambientes: o NISTIR 7966 também pede que se proíba compartilhar chaves privadas ou copiá-las para outros sistemas. Você não consegue rastrear quem a usou onde, e um vazamento obriga a substituí-la em todo lugar.

Um exemplo real: chaves SSH estavam entre o que uma ferramenta de CI comprometida coletou

Em março de 2026, apareceu uma versão maliciosa do scanner de vulnerabilidades de código aberto Trivy, e as tags dele no GitHub Actions foram reescritas. Segundo a desenvolvedora, Aqua Security, o código malicioso coletava credenciais dos runners de CI e as enviava para fora, e entre os alvos estavam chaves privadas em ~/.ssh, authorized_keys, known_hosts e chaves de host SSH. A Aqua pede que quem possa ter rodado uma versão afetada trate como exposto todo segredo que aquele pipeline conseguia ler, chaves SSH incluídas, e o rotacione imediatamente (detalhes: o comprometimento da cadeia de suprimentos do Trivy (março de 2026), em inglês).

No caso Codecov de 2021, um script executado em CI também foi alterado na origem e, por cerca de dois meses, segredos dos ambientes de CI de clientes foram enviados para fora (→ o ataque à cadeia de suprimentos da Codecov).

Os dois mostram que chaves dentro do CI podem ser levadas mesmo quando o seu próprio código está correto. Se uma chave levada estiver cadastrada no root de produção, a produção é entregue nesse momento. Com um usuário dedicado sem root, command= mais restrict e a origem restrita com from=, a mesma chave vazada só consegue fazer o único trabalho que você definiu, e conexões de qualquer outro lugar são recusadas.

O que este site faz por conta própria

Este site usa uma chave dedicada por servidor e nunca reutiliza chaves. Os deploys chegam em um usuário sem root, feito para esse fim, e produção é unidirecional — "ela recebe, não sai para buscar" (→ produção só recebe). Ela não deixa chaves permanentes de computação efêmera para dentro da produção; conecta-se só quando necessário, com menor privilégio. O motivo é exatamente o deste artigo: manter pequeno com antecedência o que cada chave pode fazer, para que um comprometimento não consiga se espalhar para tudo.

Leia a seguir

Fontes

  • Manual do OpenBSD, "sshd(8)", AUTHORIZED_KEYS FILE FORMAT (command, restrict, from, expiry-time, SSH_ORIGINAL_COMMAND): man.openbsd.org
  • Manual do OpenBSD, "sshd_config(5)", PermitRootLogin e AuthorizedKeysFile: man.openbsd.org
  • Manual do OpenBSD, "ssh(1)", -A (alerta sobre encaminhamento de agente) e -J: man.openbsd.org
  • Manual do OpenBSD, "ssh-keygen(1)", -l, -C e opções de certificado: man.openbsd.org
  • Notas de versão do OpenSSH 7.2 (inclusão do restrict): openssh.org
  • Notas de versão do OpenSSH 7.7 (inclusão do expiry-time): openssh.org
  • NIST, "NISTIR 7966: Security of Interactive and Automated Access Management Using Secure Shell (SSH)" (outubro de 2015): csrc.nist.gov
  • Wikibooks, "OpenSSH/Logging and Troubleshooting" (impressões digitais de chave nas linhas de login bem-sucedido): en.wikibooks.org
  • Aqua Security, "Trivy Supply Chain Attack: What You Need to Know" (2026): aquasec.com
  • Codecov, "Bash Uploader Security Update" (2021): about.codecov.io

FAQ

QPor que colocar uma chave de root em um ambiente efêmero é perigoso?
A

Ambientes efêmeros como pods de nuvem com GPU ou runners de CI são descartáveis, então o fortalecimento costuma ser pulado e eles podem ser comprometidos. Coloque ali uma chave de root para produção e, no momento em que esse ambiente é comprometido, o atacante ganha root em produção junto com a chave. Trate ambientes efêmeros como não confiáveis e mantenha as chaves de root de produção fora deles.

QE se eu ainda precisar alcançar produção a partir de um ambiente efêmero?
A

Não volte para root. Crie um usuário dedicado sem root e cadastre uma chave que limite o que ela pode fazer a uma única operação. No authorized_keys, uma chave restrita a comando, com a flag restrict, faz com que até um login bem-sucedido só consiga rodar aquele um comando. Um vazamento fica então contido àquela única operação.

QQual o princípio-chave para a gestão de chaves?
A

Trate uma chave reutilizada como seu ativo mais crítico e nunca construa um cenário em que um vazamento expõe tudo. Separe chaves por host e por propósito, e remova chaves do authorized_keys quando não forem mais necessárias. Isso impede que um único vazamento se espalhe para tudo.

QComo verifico quais chaves SSH estão cadastradas em um servidor?
A

Rode ssh-keygen -lf no ~/.ssh/authorized_keys de cada usuário para listar todas as chaves cadastradas com a impressão digital (SHA256:...) e o comentário. Verifique todas as contas, inclusive root, e separe chaves cuja origem você não sabe explicar e chaves de ambientes efêmeros que você não usa mais. Para ver quais chaves são de fato usadas, compare essas impressões digitais com as linhas de login bem-sucedido que o sshd grava no log.

QQual valor usar em PermitRootLogin?
A

O padrão do OpenSSH é prohibit-password: root não pode logar com senha, mas qualquer pessoa com uma chave pode logar diretamente como root. Se você não precisa de login direto como root, defina no e trabalhe a partir de um usuário sem root. Se algo como um backup realmente precisa de uma chave de root, forced-commands-only permite root só para chaves que têm a opção command="...".

QComo fazer deploy por SSH a partir de CI ou GitHub Actions com segurança?
A

Runners de CI são o exemplo típico de ambiente efêmero, e há casos reais em que uma ferramenta usada em CI foi comprometida e segredos, incluindo chaves SSH, foram coletados. Cadastre a chave de deploy em um usuário dedicado sem root e limite-a à única ação de pull/deploy com command="..." e restrict. Se o IP de origem é fixo, restrinja com from="...". Use uma chave separada por pipeline e ambiente e, quando um comprometimento for anunciado, remova primeiro as chaves antigas e só depois cadastre as novas.