# Uma chave de host SSH revogada ainda deixa o agente entrar?

Uma chave de host SSH revogada deve impedir o agente antes da autenticação do usuário, qualquer que seja o nome ou endereço usado. Se o teste tenta apenas o hostname conhecido uma vez, ele prova bem menos do que parece. A configuração SSH pode mapear vários nomes para o mesmo servidor, uma porta não padrão muda o token de busca em known-hosts, e um cliente multiplexado pode abrir outro canal sem repetir a verificação da chave de host.

Trate a revogação como propriedade da identidade criptográfica e teste todos os caminhos capazes de apresentar ou reutilizar essa identidade. Isso exige uma lista de revogação de chaves, estado limpo do cliente, variantes explícitas de conexão e um procedimento separado para conexões já existentes. Um aviso de chave alterada ajuda, mas não é o mesmo controle.

## A revogação deve acompanhar a chave, não o hostname

A chave de host autentica o servidor durante a troca de chaves SSH. A RFC 4253 descreve o cliente recebendo a chave pública do servidor, verificando se ela pertence ao destino pretendido e validando a assinatura do servidor sobre a troca. A revogação pertence a essa verificação: quando o servidor apresenta uma chave pública proibida, o cliente deve recusá-la antes de considerar uma chave de usuário, senha ou comando remoto.

O OpenSSH oferece dois mecanismos parecidos, mas com alcances diferentes. Uma linha iniciada por `@revoked` em um arquivo known-hosts combina um padrão de host com uma chave revogada. Ela funciona quando o nome de busca da conexão corresponde à linha. Um alias ou outra forma de endereço pode não corresponder ao padrão, mesmo que o servidor apresente a mesma chave. O manual de `sshd` do OpenSSH diz que uma entrada revogada correspondente nunca deve ser aceita, mas a condição de correspondência faz diferença.

A opção de cliente `RevokedHostKeys` é mais adequada para revogação durante um incidente. Ela aponta para um arquivo de texto com chaves públicas ou para uma lista de revogação de chaves do OpenSSH (KRL). O OpenSSH compara a identidade apresentada com esse arquivo, independentemente de o usuário digitar `build-test`, `build-test.example` ou um endereço IP literal. Uso entradas `@revoked` para manutenção de known-hosts de um destino específico; uso `RevokedHostKeys` quando uma identidade criptográfica precisa morrer em todos os lugares.

Não confunda nenhum dos mecanismos com `StrictHostKeyChecking=yes`. A verificação estrita recusa hosts desconhecidos e chaves alteradas, impedindo confiança silenciosa no primeiro uso. Ela não declara revogada uma chave que continuaria confiável. Um teste que apaga a entrada known-host antiga e comemora uma falha de host desconhecido testou uma base de confiança vazia, não a revogação.

## Monte um host de teste que você possa quebrar

Use um servidor SSH descartável com porta, chave de host, conta de usuário e diretório de cliente dedicados. Não ensaie revogação em um host de homologação compartilhado. Você precisa substituir chaves, parar processos e fechar sockets de controle sem se preocupar com quem mais depende deles.

A estrutura a seguir mantém as evidências em um diretório. O caminho exato de `sshd` e o modelo de privilégios variam por sistema operacional, então execute o daemon no contêiner, na VM ou no fixture que sua equipe já usa para testes de integração SSH.

```bash
set -eu
work="$PWD/ssh-revocation-fixture"
mkdir -p "$work/client" "$work/server"
chmod 700 "$work/client"

ssh-keygen -q -t ed25519 -N '' \
  -C revoked-host-test -f "$work/server/ssh_host_ed25519_key"
ssh-keygen -q -t ed25519 -N '' \
  -C agent-test-user -f "$work/client/id_ed25519"

ssh-keygen -lf "$work/server/ssh_host_ed25519_key.pub"
```

O último comando imprime uma linha com este formato:

```text
256 SHA256:<base64-fingerprint> revoked-host-test (ED25519)
```

Registre essa impressão digital no log do teste. Nunca copie uma impressão de um chamado e suponha que o arquivo do fixture contém a mesma chave. Calcule-a a partir do arquivo de chave pública realmente carregado pelo servidor de teste.

Configure o daemon para usar somente essa chave de host. Preencha o `AuthorizedKeysFile` com a chave pública do cliente, desative senhas e vincule o serviço a um endereço de loopback ou isolado. Um fixture mínimo também deve ter `PidFile` explícito e log detalhado. A meta é determinismo: um listener, uma identidade de host e um caminho de autenticação.

Antes de adicionar a revogação, faça uma conexão estrita funcionar e execute um comando sentinela inofensivo, como `printf BASELINE_OK`. Obtenha a chave de host em uma etapa autenticada de provisionamento, não com um `ssh-keyscan` sem verificação sobre o mesmo caminho de rede que você quer proteger. `ssh-keyscan` coleta chaves sem provar quem as forneceu.

## Coloque a identidade revogada em uma KRL

Uma KRL torna o teste ligado à chave, não à grafia do destino. Gere-a diretamente com a chave pública carregada pelo fixture e consulte-a antes de qualquer tentativa pela rede:

```bash
work="$PWD/ssh-revocation-fixture"
ssh-keygen -k -f "$work/client/revoked-hosts.krl" \
  "$work/server/ssh_host_ed25519_key.pub"

if ssh-keygen -Q -f "$work/client/revoked-hosts.krl" \
  "$work/server/ssh_host_ed25519_key.pub"; then
  printf '%s\n' 'FAIL: fixture host key is not revoked'
  exit 1
else
  printf '%s\n' 'OK: fixture host key is revoked'
fi
```

O status de saída invertido confunde muita gente. O manual de `ssh-keygen` especifica que `-Q` retorna valor diferente de zero se alguma chave consultada estiver revogada ou se ocorrer erro; zero significa que nenhuma chave foi revogada. Não descarte stderr e diferencie no log uma KRL ilegível de uma correspondência válida de revogação.

Agora associe o arquivo à configuração do cliente no escopo mais amplo usado pelo agente:

```sshconfig
Host *
    BatchMode yes
    StrictHostKeyChecking yes
    UserKnownHostsFile ./ssh-revocation-fixture/client/known_hosts
    RevokedHostKeys ./ssh-revocation-fixture/client/revoked-hosts.krl
    ConnectTimeout 5
    ConnectionAttempts 1
```

O OpenSSH falha de forma fechada quando o arquivo `RevokedHostKeys` não existe ou não pode ser lido: ele recusa autenticação de host para todos os destinos cobertos pela configuração. Isso é mais seguro que ignorar o arquivo, mas pode fazer uma implantação quebrada parecer um teste de revogação bem-sucedido. A consulta prévia prova que o arquivo pode ser lido e contém a chave desejada.

Um arquivo de texto simples com uma chave pública por linha também funciona. A complexidade da KRL compensa quando há muitas chaves ou certificados de host, pois ela pode revogar chaves simples, números de série, IDs de certificado ou chaves assinadas por uma CA. Use o formato mais simples que seus processos de distribuição e inspeção suportam e teste o formato que realmente será implantado.

## Um marcador por hostname exige um teste adversarial

Mantenha um caso propositalmente fraco no fixture para mostrar por que a KRL é necessária. Crie uma segunda configuração de cliente sem `RevokedHostKeys`, dependente apenas desta entrada known-hosts:

```text
@revoked revoked-lab ssh-ed25519 AAAA...fixture-public-key...
```

Conecte como `revoked-lab` e confirme que o OpenSSH recusa a entrada correspondente. Depois, conecte ao mesmo listener como `127.0.0.1`, com uma entrada known-host confiável separada para `[127.0.0.1]:2222`. Se essa forma funcionar, o fixture reproduziu a lacuna: a chave não mudou, mas o nome de busca deixou de corresponder ao marcador revogado. Mantenha a demonstração isolada da produção porque seu objetivo é provar que um controle é insuficiente.

Não cole o valor abreviado `AAAA...` do exemplo. Construa o marcador usando o arquivo público real para manter exatos o algoritmo e os dados base64. Um comando seguro do fixture pode ler os campos de algoritmo e chave e acrescentar o marcador e o padrão de host. Verifique o registro com `ssh-keygen -F revoked-lab -f known_hosts`; repita a busca para o endereço literal e mostre que o registro não existe ali.

Essa falha explica por que adicionar todos os aliases conhecidos a uma linha `@revoked` é frágil. O inventário muda sempre que alguém inclui um nome curto, registro DNS, entrada local de hosts, porta não padrão, alias de túnel ou `HostKeyAlias`. Nomes known-host com hash tornam a análise manual mais difícil, embora `ssh-keygen -F` ainda consiga procurá-los. Um curinga amplia a correspondência, mas só revoga a chave quando o destino está dentro do padrão e também dificulta revisar o alcance desejado.

O caso da KRL deve usar o mesmo servidor, chave de usuário, caminho de rede e registros known-host confiáveis. Mude apenas o mecanismo de revogação. Com `RevokedHostKeys` ativo, tanto `revoked-lab` quanto `127.0.0.1` devem falhar depois de apresentar a chave do fixture. O experimento pareado transforma um alerta abstrato sobre aliases em uma falha que revisores podem ver e reproduzir.

Teste também a precedência. O OpenSSH mantém o primeiro valor obtido para uma opção, então uma configuração específica anterior pode anular o padrão posterior pretendido. Execute `ssh -G` para cada destino e compare byte a byte o caminho `revokedhostkeys` resolvido. Uma revisão que apenas encontra a palavra `RevokedHostKeys` em algum arquivo não prova que o agente a usa.

A configuração de sistema e a de usuário criam outra divisão. Um engenheiro pode ter `/etc/ssh/ssh_config` apontando para a KRL da organização, enquanto o agente inicia com `ssh -F private-config`, que manda o OpenSSH usar outro arquivo. Também pode ocorrer de uma conta limpa de CI passar no teste, mas não representar um runner de produção que injeta opções na linha de comando. Capture a invocação completa no limite de ação do agente e torne o caminho de revogação explícito ali.

Permissões fazem parte da aceitação. Segundo o manual do OpenSSH, uma falha de leitura de `RevokedHostKeys` recusa a autenticação de todos os hosts. Verifique o arquivo como o usuário do sistema operacional que executa o agente antes da conexão e consulte a chave desejada. Isso separa três resultados que parecem apenas um comando SSH com falha: correspondência válida de revogação, arquivo ausente ou ilegível e KRL malformada.

A distribuição também tem uma questão temporal. Se várias máquinas podem iniciar o agente, publicar a KRL em apenas uma não completa a revogação. Registre um resumo do conteúdo e exija que cada runner apresente esse resumo antes de aceitar novo trabalho SSH. Faça a substituição de modo atômico para que nenhum leitor veja um arquivo parcial. Após a distribuição, execute o teste negativo em cada imagem de cliente ou classe de configuração distinta, não só na máquina que produziu a KRL.

A reversão precisa ter significado verificável. Remover uma impressão da KRL porque um chamado foi fechado pode restaurar a confiança enquanto a chave privada comprometida ainda existe. Prefira substituir a identidade do servidor, distribuir o novo registro de confiança e manter a identidade antiga revogada. Se a política permitir remover a revogação depois, exija evidência de que a chave antiga não pode voltar e preserve um caso de regressão que a apresente.

O registro de aceitação de uma identidade revogada deve conter impressão digital, algoritmo de chave pública, resumo da KRL, formas de destino testadas, configuração efetiva de cada uma e resultado de uma nova troca. Adicione separadamente o resultado da sessão multiplexada, pois ele responde outra pergunta. Assim, revisores conseguem distinguir correspondência de identidade, cobertura de caminhos, configuração, distribuição e limpeza de transporte.

## Uma conexão nova deve falhar antes da sentinela

Force o primeiro caso negativo a criar um transporte novo. Desative o compartilhamento na linha de comando mesmo que a configuração SSH do usuário o habilite, mantenha o cliente não interativo e inclua no comando remoto um marcador inconfundível caso seja executado.

```bash
work="$PWD/ssh-revocation-fixture"
config="$work/client/config"
log="$work/client/direct.stderr"

set +e
ssh -F "$config" \
  -o ControlMaster=no -o ControlPath=none \
  -i "$work/client/id_ed25519" \
  -p 2222 testuser@127.0.0.1 \
  'printf REVOKED_KEY_EXECUTED' \
  >"$work/client/direct.stdout" 2>"$log"
rc=$?
set -e

if [ "$rc" -eq 0 ]; then
  printf '%s\n' 'FAIL: SSH accepted a revoked host identity'
  exit 1
fi
if grep -q REVOKED_KEY_EXECUTED "$work/client/direct.stdout"; then
  printf '%s\n' 'FAIL: remote sentinel ran'
  exit 1
fi
printf 'PASS rc=%s\n' "$rc"
```

Valide o resultado, não uma mensagem exata em inglês. Versões do OpenSSH e sistemas operacionais podem escrever o diagnóstico de outro modo. Quando um caso falhar, preserve a saída detalhada de uma segunda execução com `-vv` e procure a impressão apresentada junto do diagnóstico de revogação. Timeout TCP, porta recusada, host desconhecido, chave de usuário ausente ou conta negada também retornam valor diferente de zero, mas nenhum prova que a revogação impediu a conexão.

O log do servidor fornece a outra metade da validação. O cliente deve se desconectar durante a troca de chaves, antes que o servidor registre autenticação de usuário bem-sucedida e antes de iniciar uma sessão. Se o fixture não separa essas fases nas evidências, ele é opaco demais para este teste.

Execute um controle com uma KRL vazia e legível ou outra chave de servidor não revogada. A conexão deve alcançar `BASELINE_OK`. Um teste negativo sem controle positivo costuma passar porque DNS, roteamento, permissões ou a conta do fixture já estavam quebrados.

## Cada alias e forma de endereço precisa de um caso

Uma KRL deve recusar a chave entre nomes, mas a resolução da configuração ainda pode conduzir uma forma ao redor da opção de revogação. Teste a configuração efetiva e o resultado de rede de cada forma de destino que o agente consegue produzir.

Comece com uma pequena matriz ligada ao fixture:

1. O alias configurado, como `revoked-lab`, cujo `HostName` aponta para o endereço de teste.
2. O hostname completo e qualquer nome curto aceito pelas regras locais de resolução.
3. O endereço IPv4 literal e, se o listener tiver, o endereço IPv6 literal.
4. O token known-host de uma porta não padrão, geralmente escrito como `[host]:port` nas ferramentas.
5. Um alias que defina `HostKeyAlias`, pois a opção substitui o nome real na busca de chaves e na validação de certificados.

Mantenha as variantes em um arquivo de dados ou função, em vez de duplicar um bloco shell. Para cada nome visível, inspecione `ssh -G destination` e salve `hostname`, `port`, `hostkeyalias`, `userknownhostsfile`, `revokedhostkeys`, `proxycommand`, `proxyjump`, `controlmaster` e `controlpath`. `ssh -G` expande a configuração sem conectar, detectando uma seção `Host` que troca silenciosamente o caminho da KRL.

`Hostname` e `HostKeyAlias` têm funções diferentes. `Hostname` seleciona o destino de rede. `HostKeyAlias` seleciona o nome usado pelo OpenSSH para ler ou salvar chaves e validar certificados. Nenhum deve enfraquecer uma verificação global de `RevokedHostKeys`, mas ambos podem mudar qual registro known-host confiável será considerado. Por isso, uma linha `@revoked` limitada ao hostname é um controle ruim de incidente.

A canonicalização merece um caso quando está ativa. `CanonicalizeHostname yes` pode acrescentar domínios configurados e faz o OpenSSH reprocessar a configuração com o destino reescrito. Uma seção posterior pode escolher outro arquivo known-hosts ou omitir o arquivo de revogação. Teste a entrada curta e o nome canônico resultante, confirmando que ambas as configurações apontam para a mesma KRL.

Não trate `CheckHostIP=yes` como cobertura de aliases. O manual diz que ele também verifica o IP de destino em known-hosts e não está disponível com comando proxy. Pode detectar uma associação alterada, mas não substitui um arquivo de revogação centrado na chave. Caminhos por proxy e salto ainda precisam de teste completo do destino final, com confiança separada para cada host de salto.

## Conexões em cache delimitam a resposta a incidentes

Um mestre SSH multiplexado ativo já autenticou o servidor. Abrir outra sessão pelo socket de controle não cria nova conexão TCP nem repete a troca de chave, portanto uma KRL recém-instalada não pode rejeitar a chave retroativamente. Chamar isso de desvio da revogação confunde o modelo. É o reaproveitamento de um transporte autenticado que ainda existe.

Comprove esse comportamento no fixture em vez de presumir que uma reinicialização limpa tudo. Primeiro estabeleça um mestre enquanto a chave é permitida. Mantenha-o ativo com `ControlPersist`, adicione a chave à KRL e mostre que `ssh -O check` ainda encontra o mestre. Um comando enviado pelo socket pode ser executado. Registre isso como controle esperado antes da correção, não como resultado aprovado de revogação.

Depois execute a resposta: impeça o agente de abrir trabalho novo, encerre os mestres relacionados e revogue ou pare a sessão do agente que os possui. Para um socket dedicado do fixture, o comando local tem este formato:

```bash
socket="$PWD/ssh-revocation-fixture/client/cm-testuser-127.0.0.1-2222"
ssh -S "$socket" -O exit testuser@127.0.0.1
```

Depois que o socket desaparecer, repita a conexão com `ControlMaster=no` e `ControlPath=none`; ela deve falhar pela chave revogada. Teste também a configuração normal de multiplexação do agente após a limpeza. Modos oportunistas como `ControlMaster auto` recorrem a uma conexão nova se nenhum mestre estiver ouvindo, e essa tentativa deve atingir a KRL.

Um arquivo de socket obsoleto não é uma conexão autenticada. O OpenSSH tenta o socket, descobre que não há mestre e pode recorrer a uma conexão normal. Mantenha esse caso porque falhas deixam arquivos antigos. O resultado seguro continua sendo falha de revogação durante a nova troca.

Revogar uma identidade durante um incidente ativo exige dois controles: recusar futuras trocas de chave e encerrar transportes autenticados antes da revogação. Um teste que cobre apenas um deles deixa para o agente uma rota nova ou uma rota antiga que nunca fechou.

## Um servidor pode apresentar várias identidades

Servidores costumam carregar várias chaves de host ou um certificado junto com sua chave subjacente. Revogar uma impressão não torna necessariamente o destino inacessível. Durante a negociação, cliente e servidor escolhem um algoritmo compatível; outra chave confiável e não revogada pode autenticar o mesmo servidor.

Decida o que a declaração do incidente significa. Se uma chave privada vazou, o cliente precisa recusá-la, enquanto outra chave protegida de forma independente pode continuar aceita após revisão. Se a máquina perdeu integridade, revogue todas as identidades que ela pode apresentar, incluindo certificados, e remova a confiança até reconstruí-la. Escrever "host revogado" em um chamado sem listar identidades deixa a decisão para a negociação de algoritmos.

Faça o inventário pelo daemon e pelos registros de confiança, não por uma única varredura. Teste cada algoritmo ativo restringindo `HostKeyAlgorithms` ao algoritmo em exame. A chave revogada deve falhar. Cada identidade que deve sobreviver precisa de seu próprio caso positivo e impressão documentada.

Certificados de host adicionam outra distinção. Você pode revogar o certificado como objeto público, revogar um número de série ou ID em uma KRL, ou revogar a CA que autoriza uma classe de hosts. As escolhas têm alcances diferentes. O manual de `ssh-keygen` documenta diretivas KRL para números, IDs, chaves públicas e impressões; consulte a KRL concluída com o arquivo de certificado exato apresentado pelo servidor.

`UpdateHostKeys` também merece atenção. O OpenSSH pode aprender chaves adicionais após autenticar um host com uma chave simples já confiável. Isso ajuda em rotações planejadas, mas alternativas aprendidas continuam candidatas se a decisão de revogação não as incluir. Extraia os registros do cliente com `ssh-keygen -F` para cada nome e forma `[name]:port`, comparando cada chave com o alcance do incidente.

## Execute o teste pelo caminho real do agente

Um teste no terminal da conta de um engenheiro não prova que um agente autônomo usa o mesmo binário SSH, configuração, diretório pessoal, resolvedor, proxy ou socket. A suíte final deve invocar o limite exato de ação do agente e iniciar com o mesmo ambiente recebido em produção.

Faça a ferramenta retornar evidência estruturada por variante: rótulo de destino, hostname e porta resolvidos, impressão apresentada, resumo da KRL, estado do compartilhamento, código de saída, presença da sentinela e fase em que o servidor fechou a tentativa. Não inclua segredos. A validação útil é simples: a identidade revogada apareceu, o cliente a reconheceu e não houve autenticação de usuário nem comando depois.

Isolar o ambiente revela dependências acidentais. Defina explicitamente a configuração SSH, arquivo known-hosts, KRL, arquivo de identidade e control path. Limpe ou substitua `SSH_AUTH_SOCK` se o agente não deve usar chaves alheias. Limite o tempo para CI não travar. Preserve stderr e logs do servidor em falhas; o relatório aprovado pode guardar hashes e linhas decisivas.

Se os agentes emitem SSH pelo Sallyport, use esse caminho no teste negativo: o helper sem estado `sp-ssh` executa ações SSH, enquanto os diários Sessions e Activity registram a execução e cada chamada. Verifique a cadeia offline com `sp audit verify` se o teste também cobre integridade das evidências; a recusa da chave ainda deve vir da configuração de confiança SSH exercitada pela ação real.

Não enfraqueça o cliente para facilitar a automação. `StrictHostKeyChecking=no`, um destino known-hosts vazio ou enviar diagnóstico para `/dev/null` transforma um teste de segurança em teste de conectividade. `BatchMode=yes` remove perguntas; não substitui material de confiança ausente.

## O teste deve falhar por apenas um motivo

Coloque a matriz em CI, mas mantenha o fixture pequeno para diagnóstico. Execute um controle positivo com identidade permitida, verifique a KRL, inicie o listener e rode as variantes de conexão nova. Teste multiplexação em outra etapa, pois a preparação e o resultado esperado diferem. Por fim, pare o listener e confirme que não ficou mestre nem socket do fixture.

Rejeite a build se algum destino alcançar a sentinela, se alguma configuração efetiva não tiver o arquivo exigido ou se o servidor oferecer identidade não classificada. Rejeite também execução inconclusiva. Timeout ou KRL ilegível indicam teste quebrado, ainda que o SSH retorne valor diferente de zero.

Mantenha a impressão esperada e o resumo KRL no relatório. Quando uma rotação mudar a identidade do fixture, a revisão deve mostrar os dois valores mudando juntos. Essa pequena dificuldade evita a falha comum em que o servidor ganha nova chave e o teste continua revogando um arquivo público abandonado.

Execute a suíte com um processo de agente incapaz de exibir perguntas. Diálogo de aprovação, solicitação de senha ou confirmação de host pode deixar o trabalho esperando até um timeout genérico esconder a causa. `BatchMode=yes` deve fazer esses caminhos falharem imediatamente, enquanto o controle positivo prova que o fixture dispensa interação. Coloque um limite duro no trabalho externo, mas nunca o conte como evidência de revogação.

Conserve um rastreamento detalhado e higienizado para cada versão suportada. Ele oferece referência da ordem de expansão da configuração, reutilização, troca de chaves, seleção e recusa. Quando uma atualização mudar o comportamento ou o texto do OpenSSH, compare o novo rastreamento e só ajuste as validações após confirmar a mesma impressão e fase. Testes de segurança apodrecem quando toda falha nova é tratada como simples mudança de saída.

Preserve o resultado incômodo do mestre ainda ativo. Ele lembra que distribuir uma KRL não revoga uma sessão. Feche os transportes existentes, prove que a próxima tentativa realiza a troca de chaves e veja o cliente recusar a impressão exata marcada. Só então você demonstrou que aliases, cache e grafia do endereço não devolvem acesso ao agente.
