8 min de leitura

A canonicalização de host SSH pode mudar o alvo aprovado?

Veja como a canonicalização de host SSH reescreve alvos, relê a configuração e muda o que uma aprovação precisa registrar.

A canonicalização de host SSH pode mudar o alvo aprovado?

Uma aprovação que diz ssh build não autoriza necessariamente a máquina que recebe a conexão TCP. O OpenSSH pode expandir esse nome curto, seguir um CNAME permitido, analisar a configuração uma segunda vez, escolher outro usuário ou porta e resolver o nome resultante para um entre vários endereços. Se um gateway de agentes aprova apenas o texto enviado pelo agente, a aprovação pode descrever um alvo enquanto o cliente SSH usa outro.

Isso não é um argumento contra a canonicalização. Nomes curtos são úteis, nomes canônicos facilitam a administração da configuração e as chaves de host continuam autenticando servidores. É um argumento para colocar a fronteira da aprovação depois que o OpenSSH calculou o destino efetivo e antes de abrir um socket. A pessoa que aprova deve ver o nome solicitado, o nome final e o endereço, junto de qualquer mudança importante causada pela configuração.

Um comando SSH carrega quatro identidades diferentes

Um alvo SSH não é uma única string. Tratá-lo assim cria a falha discutida neste artigo. Um caminho de aprovação sério mantém quatro identidades separadas:

  • O nome solicitado é o argumento fornecido pelo agente, como build.
  • O nome de host efetivo é o valor usado pelo OpenSSH depois da substituição de Hostname e da canonicalização, como build.ops.example.
  • O endereço do par é o endereço IP escolhido quando o cliente abre o socket.
  • A identidade autenticada é a chave ou o certificado de host aceito para aquela conexão.

Esses valores respondem a perguntas diferentes. O nome solicitado registra a intenção. O nome efetivo controla as correspondências posteriores de configuração e, em geral, a consulta da chave de host. O endereço diz para onde foram os pacotes. A chave de host diz qual servidor provou possuir uma chave privada. A aprovação precisa dos três primeiros antes da conexão, e o resultado da auditoria deve acrescentar o quarto depois da troca de chaves.

O próprio OpenSSH expõe essa diferença em ssh_config. O token %n significa o nome de host remoto original da linha de comando, enquanto %h significa o nome remoto após o processamento da configuração. %k é outro valor: HostKeyAlias se estiver definido ou, caso contrário, o nome original da linha de comando. Esses tokens existem porque o OpenSSH não pode fingir com segurança que todas as etapas usam o mesmo nome.

A distinção também explica por que uma verificação bem-sucedida da chave de host não corrige uma autorização vaga. A autenticação pode provar que o endpoint possui uma chave esperada. Ela não pode provar que a pessoa queria autorizar aquele endpoint quando o cartão mostrava apenas build. A autorização pergunta se a ação pode chegar ao destino. A autenticação pergunta se o servidor que respondeu corresponde a um registro de identidade. As duas verificações importam e acontecem em momentos diferentes.

A canonicalização pode reescrever o nome antes da conexão

CanonicalizeHostname controla se o OpenSSH faz uma reescrita explícita. O padrão é no, deixando as consultas para o resolvedor do sistema. Com yes, o OpenSSH tenta os sufixos de CanonicalDomains para conexões diretas. Com always, ele também canonicaliza destinos alcançados por ProxyCommand ou ProxyJump.

Suponha que o agente solicite esta ação:

host: build
user: deploy
command: /usr/local/bin/release status

O cliente pode ter esta configuração:

Host *
    CanonicalizeHostname yes
    CanonicalDomains ops.example
    CanonicalizeFallbackLocal no
    CanonicalizeMaxDots 1
    CanonicalizePermittedCNAMEs *.ops.example:*.hosts.example

O OpenSSH tenta primeiro build.ops.example. Se o DNS devolver um CNAME permitido para build-07.hosts.example, esse destino pode virar o nome canônico. A solicitação ainda diz build, mas o cliente já não se conecta com esse nome. Um cartão que mostra somente a solicitação esconde a transformação relevante.

Os limites merecem atenção. CanonicalizeMaxDots tem padrão 1, então um nome com um ponto ainda pode passar pela canonicalização, não apenas um rótulo sem pontos. CanonicalizeFallbackLocal tem padrão yes; se os domínios canônicos configurados não produzirem resultado, o OpenSSH pode passar o nome original ao resolvedor e às regras de pesquisa do sistema. O valor no torna a falha explícita. Para um gateway de aprovação, isso costuma ser melhor porque a lista de pesquisa local pode variar conforme a rede e o momento.

O acompanhamento de CNAME é limitado separadamente. CanonicalizePermittedCNAMEs tem padrão none, e suas regras combinam padrões de origem permitidos com padrões de destino. Esse padrão faz sentido. Uma regra ampla como *:* transforma o alias DNS em uma etapa de reescrita sem limites. Uma regra estreita documenta a transição de namespace realmente esperada, por exemplo dos aliases de serviço em ops.example para os hosts em hosts.example.

O manual do OpenSSH descreve esses controles com precisão, mas uma camada de política não pode apenas copiar os valores para um cartão. Ela deve executar o mesmo cálculo com a mesma configuração do cliente. Reimplementar a pesquisa de sufixos e as regras de CNAME no serviço de aprovação costuma criar divergências à medida que o cliente e a configuração local mudam.

A segunda leitura muda mais do que o DNS

Quando a canonicalização está ativa, o OpenSSH processa novamente a configuração com o novo nome de destino. Essa segunda leitura pode ativar blocos Host e Match que não correspondiam ao alias original. Uma checagem prévia apenas de DNS pode, portanto, perder outras mudanças do destino.

Considere um nome canônico que corresponde a este bloco:

Match canonical host *.prod.ops.example
    User release
    Port 2222
    IdentityFile ~/.ssh/prod_release
    ProxyJump bastion.ops.example

Uma solicitação para deploy pode primeiro virar deploy.prod.ops.example e depois selecionar outro usuário remoto, porta, arquivo de identidade e host de salto. O OpenSSH usa o primeiro valor obtido para a maioria das diretivas, então a aplicação dessas opções depende do que os blocos anteriores já definiram e de sua ordem. Não é possível deduzir a configuração final lendo apenas o bloco correspondente.

A condição canonical corresponde somente durante a nova análise após a canonicalização. A condição final pede uma leitura final mesmo com a canonicalização desativada. Quando ela está ativa, canonical e final correspondem na mesma passagem. host compara o alvo após substituição por Hostname ou canonicalização, enquanto originalhost compara o valor da linha de comando. Esses detalhes tornam uma configuração aparentemente simples bastante dependente de estado.

O padrão perigoso é aprovar host=deploy, user=agent, port=22, entregar ssh deploy a um cliente comum e supor que os campos continuam verdadeiros. Se a configuração ainda pode alterar User, Port, ProxyJump, RemoteCommand, encaminhamentos ou escolha de identidade, a aprovação cobriu uma proposta, não a ação executada.

Um projeto mais seguro congela a configuração efetiva importante após a última passagem. No mínimo, vincule a aprovação ao host efetivo, endereço escolhido, porta, usuário remoto, rota do proxy, comando remoto, encaminhamentos, alias da chave e fonte de identidade. Se qualquer valor mudar antes da conexão, descarte a aprovação e peça outra. Não preserve silenciosamente a aprovação durante uma segunda avaliação.

A origem da configuração faz parte do alvo

O destino final depende dos arquivos, das opções de linha de comando, do ambiente e da rede local avaliados pelo OpenSSH. Aprovar sem registrar essas entradas deixa um caminho fácil para mudar o significado autorizado. O manual define a ordem normal como opções da linha de comando, arquivo do usuário e arquivo do sistema. Para a maioria das diretivas, vale o primeiro valor encontrado.

Um agente capaz de acrescentar opções SSH arbitrárias pode contornar um padrão cuidadoso. -F seleciona outro arquivo, enquanto -o pode definir diretamente Hostname, ProxyCommand, ProxyJump, Port, User, HostKeyAlias ou encaminhamentos. O gateway deve analisar uma ação SSH em campos tipados e rejeitar opções não suportadas, em vez de aceitar uma string opaca. Se opções brutas forem necessárias, classifique todas as que afetam o destino e inclua o valor normalizado na aprovação.

Os arquivos também trazem entradas transitivas. Include aceita vários caminhos, curingas, tokens e variáveis de ambiente, e processa as correspondências em ordem lexical. Adicionar um arquivo a uma pasta incluída pode mudar qual valor aparece primeiro sem alterar o arquivo principal. Registre o resumo do conteúdo e o proprietário de cada arquivo carregado, não só o caminho do primeiro. Recuse arquivos graváveis pelo agente ou pela pasta de trabalho de um repositório não confiável.

Match exec não é apenas uma comparação condicional: o OpenSSH executa o comando indicado no shell do usuário e considera o retorno zero uma correspondência. Match localnetwork pode variar a configuração conforme os endereços das interfaces locais ativas. O próprio manual avisa que uma rede observada localmente não é confiável para configuração sensível quando vem de DHCP. Assim, a mesma solicitação ssh build pode produzir outro alvo depois que uma VPN conecta, um script muda de resultado ou surge um arquivo incluído.

O executor da aprovação deve estabelecer um conjunto fechado de entradas antes da avaliação. Use um binário fixo, uma raiz de configuração controlada, um ambiente limpo, proprietários conhecidos, uma família de endereços explícita e uma política declarada para configuração de usuário e sistema. Calcule um resumo desse conjunto e coloque-o no ticket de execução. Ele não precisa dominar o cartão, mas deve entrar na auditoria e ser conferido novamente no uso.

Congelar a configuração não exige proibir opções úteis por host. Exige decidir quem pode escrevê-las. Um alias administrado pelo operador que liga build a um host canônico de produção pode ser seguro quando o cartão mostra esse mapeamento. Um alias criado em um checkout controlado pelo agente faz parte da solicitação não confiável, mesmo que a sintaxe pareça configuração comum do OpenSSH.

Essa checagem de origem também fecha uma lacuna frequente nos testes. As equipes costumam testar com um arquivo temporário limpo e depois executar com toda a configuração do desenvolvedor e os padrões do sistema. O teste prova o arquivo temporário, não a ação real. Capture as entradas exatas uma vez e leve-as pela aprovação até a conexão.

Trate a versão do OpenSSH como entrada. Padrões e diretivas aceitas mudam, e uma frota pode conter várias versões mesmo lendo o mesmo arquivo compartilhado. Registre o caminho e a versão no ticket e repita os testes de compatibilidade antes de uma atualização chegar ao tráfego de agentes. Um analisador escrito para uma versão deve recusar saída desconhecida, não presumir que um campo novo ou renomeado é inofensivo.

A origem também decide se uma aprovação pode ser reutilizada. Isso só se sustenta quando solicitação, configuração completa, resultado da resolução e estado do conector permanecem idênticos por um período curto. Um apelido igual não prova nada disso. Uma nova aprovação concisa é melhor que uma permissão duradoura cujo alvo efetivo pode mudar entre execuções do agente sem ser percebido.

As respostas DNS podem mudar depois do cartão

Registre cada chamada SSH
O diário Activity registra cada chamada SSH individual feita pelo gateway.

Mesmo que o nome efetivo continue igual, o endereço pode mudar entre aprovação e conexão. Rotação DNS, visões separadas, mudança de VPN, caminhos de pesquisa e atualizações normais podem produzir outra resposta. Surge uma diferença clássica entre checagem e uso quando o serviço resolve e mostra um endereço, mas o processo SSH resolve novamente mais tarde.

A correção não é declarar o DNS não confiável e ignorar o endereço. É vincular a execução ao resultado exato visto pela pessoa. Resolva uma vez no executor confiável, escolha um endereço segundo as regras da família do cliente, mostre-o e passe esse endereço de socket para a conexão sem nova consulta. Mantenha o nome canônico para usos de identidade aplicáveis, consulta de chave, certificados e logs, mas não deixe o transporte escolher outro endereço em silêncio.

Vários registros A ou AAAA exigem uma regra explícita. Mostrar um conjunto e permitir ao cliente tentar um membro pode ser razoável, mas a aprovação deve dizer que cobre o conjunto e a auditoria deve registrar qual funcionou. Se o conjunto mudar, a aprovação antiga não deve incluir o novo endereço. Para hosts de alto risco, aprovar um endereço escolhido é mais fácil de entender.

Um proxy muda onde a resolução local acontece. Com ProxyJump, o cliente geralmente pede à conexão de salto que leve o tráfego ao host e porta finais. ProxyCommand pode implementar quase qualquer transporte. O endereço visível localmente pode pertencer ao proxy, enquanto o nome final é resolvido em outro lugar. A aprovação precisa das duas etapas: o endpoint concreto do proxy alcançado localmente e o valor final enviado por ele. Tratar o proxy como alvo perde o destino; ignorá-lo perde a rota da rede.

Uma chave válida não valida o nome solicitado

A verificação estrita da chave protege uma fronteira diferente da aprovação do alvo. Ela deve continuar ativa, mas não sabe se o resultado da canonicalização estava na intenção humana. Chaves compartilhadas, certificados com vários principais e um HostKeyAlias explícito podem criar uma conexão criptograficamente válida sob um nome diferente do exibido.

A arquitetura SSH do RFC 4251 descreve um banco local que associa o nome digitado pelo usuário à chave de host. O OpenSSH acrescenta nomes dirigidos por configuração sobre esse modelo. HostKeyAlias manda usar um alias no lugar do nome real ao procurar ou salvar chaves e validar certificados. Isso ajuda com túneis e vários servidores no mesmo endereço, mas cria outro nome que a auditoria deve guardar.

O RFC 4255, que define SSHFP, é especialmente relevante. Ele alerta sobre nomes não qualificados e caminhos de pesquisa DNS injetados e recomenda consultar primeiro o banco local de chaves nessa situação. Também diz que um registro SSHFP não deve ser confiável sem autenticação por DNSSEC. O conselho trata de autenticar o servidor, mas reforça o ponto maior: expandir um nome muda o contexto de segurança, e a resposta do resolvedor não prova identidade.

O RFC 4462 faz uma advertência mais direta para GSS-API. Uma implementação não deve usar DNS inseguro para montar o nome alvo, pois um invasor pode alterar um alias ou mapeamento e se passar pelo servidor. Mesmo com autenticação comum por chave pública, a lição vale. Uma reescrita desprotegida não deve decidir em silêncio a identidade que um controle afirma ter aprovado.

A verificação da chave deve ligar o resultado ao registro prévio. Guarde a impressão digital apresentada, o nome ou alias da consulta, a correspondência de principal do certificado e o resultado estrito. Se o cliente pedir para aceitar uma chave nova, isso é outra decisão de segurança. Um processo autônomo não deve aceitá-la automaticamente só porque alguém aprovou antes o comando de shell.

A checagem prévia precisa das mesmas entradas

Separe sessões de chamadas individuais
Os diários Sessions e Activity preservam separadamente a execução e cada ação.

Uma boa checagem reproduz a configuração final e observa o caminho sem executar o comando remoto. ssh -G imprime a configuração depois dos blocos Host e Match. ssh -vvv acrescenta diagnósticos de resolução, conexão e chave; o manual define três opções -v como o nível máximo.

Execute esta sequência em ambiente controlado, substituindo build pelo token solicitado exato:

ssh -G build | awk '
  $1 == "hostname" || $1 == "user" || $1 == "port" ||
  $1 == "proxyjump" || $1 == "proxycommand" ||
  $1 == "hostkeyalias" || $1 == "canonicalizehostname" {
    print
  }
'
ssh -vvv -o BatchMode=yes -o SessionType=none \
  -o ConnectTimeout=5 build 2>&1

O primeiro comando produz uma saída deste formato:

user deploy
hostname build-07.hosts.example
port 22
canonicalizehostname true
proxyjump none

A execução detalhada emite linhas cuja redação varia por versão, mas identifica o host expandido, endereço e porta escolhidos, passagens de configuração e chave oferecida. Capture fatos estruturados no executor, em vez de tratar texto de depuração como API estável. Os comandos são ótimos para investigação manual; a produção deve obter os mesmos fatos da implementação que abre o socket.

Há duas armadilhas. Primeiro, ssh -G mostra a configuração avaliada, mas não prova qual endereço uma conexão posterior alcançará. Segundo, a sondagem detalhada pode abrir a conexão e trocar chaves mesmo com SessionType=none; execute-a apenas quando ela própria estiver autorizada. BatchMode=yes remove prompts interativos, mas não transforma uma conexão em cálculo local.

Para uma investigação reproduzível, isole todas as entradas. Forneça a configuração esperada com -F, considere como a compilação trata o arquivo do sistema, fixe variáveis usadas por Include ou tokens e registre a versão. Registre também se um socket de controle pode reutilizar conexão multiplexada. Uma checagem com DNS e configuração novos vale pouco se a execução se conecta a uma sessão mestre antiga.

Não faça uma análise para aprovação e outra para execução. A arquitetura preferida usa um executor que carrega, canonicaliza, resolve, pausa com um registro candidato imutável e continua a mesma máquina de estados após a aprovação. Se a biblioteca não puder pausar com segurança, crie um ticket assinado ou autenticado com todos os campos importantes e faça o conector recusar qualquer diferença.

O registro de aprovação deve mostrar a transformação

Um bom cartão torna a mudança visível sem exigir um log de depuração. Coloque o alvo solicitado ao lado do efetivo e do endereço. Se nada mudou, diga de forma breve. Se a canonicalização ou um bloco Match mudou um campo importante, marque o valor antigo e o novo.

Um registro prático pode usar esta forma:

{
  "requested": {
    "host": "build",
    "user": "deploy",
    "command": "/usr/local/bin/release status"
  },
  "effective": {
    "host": "build-07.hosts.example",
    "address": "192.0.2.44",
    "port": 22,
    "user": "deploy",
    "proxy": null,
    "host_key_alias": null
  },
  "resolution": {
    "canonicalized": true,
    "source": "build.ops.example",
    "permitted_cname": true
  },
  "binding": "sha256:REDACTED"
}

O vínculo deve cobrir uma codificação determinística dos campos solicitados e efetivos, o comando remoto, encaminhamentos, identidade da configuração, endereço ou conjunto aprovado e uma validade curta. O conector recalcula tudo imediatamente antes de abrir o socket. Se DNS, configuração, comando, usuário, porta ou proxy diferirem, ele para.

Não retire um campo do cartão só porque ele está no vínculo. Pessoas autorizam o que conseguem ver. Mostre build -> build-07.hosts.example (192.0.2.44) como transformação legível e depois usuário, porta, proxy e comando. Deixe impressões digitais e origem em detalhes expansíveis, salvo quando uma identidade nova ou alterada exigir atenção.

O caso difícil é um nome que resolve para um conjunto grande ou volátil. Não aprove um conceito ilimitado como qualquer endereço atual desse nome. Escolha um candidato, aprove um conjunto publicado e limitado com expiração, ou exija uma identidade estável mais forte, como um principal de certificado apoiado por uma CA confiável. O modelo deve ser declarado, não adivinhado pelo conector.

Proxies e reutilização exigem vínculos separados

Execute o gateway no seu Mac
Um aplicativo assinado executa o cofre e o caminho SSH no mesmo processo.

A canonicalização se comporta de forma diferente com proxies. CanonicalizeHostname yes vale só para conexões sem ProxyCommand nem ProxyJump; always inclui conexões por proxy. Uma palavra pode mudar o nome final e ativar a segunda leitura. O sistema deve registrar o valor real, sem presumir que a opção está ligada ou desligada em toda parte.

Cada host de salto é uma conexão SSH com nome solicitado, nome efetivo, endereço, usuário, porta e chave autenticada próprios. O destino final também preserva sua identidade pelo túnel. Aprovar só o rótulo final perde um salto inesperado ou comprometido. Aprovar só o servidor de salto perde para onde ele encaminha o fluxo.

A multiplexação cria um contorno menos óbvio. Se já existe uma sessão ControlMaster correspondente, uma nova chamada pode reutilizá-la em vez de criar a conexão prevista. O mestre pode ter resolvido DNS antes, usado uma configuração antiga e autenticado uma chave antes da aprovação atual. Desative a reutilização para ações controladas ou vincule a aprovação à identidade imutável da sessão mestre e confira antes de abrir um canal.

A mesma regra vale para tentativas seguintes. Se um endereço falhar, o segundo continua autorizado apenas se constava no conjunto mostrado. Um proxy alternativo, outra porta ou um nome recém-canonicalizado não são detalhes de transporte. Eles mudam o objeto da autorização e exigem nova decisão.

A auditoria deve preservar intenção e resultado

Uma entrada útil permite reconstruir toda a transformação sem consultar DNS novamente. Guarde a solicitação original, campos efetivos, nome canônico, cadeia DNS, endereços candidatos, endereço escolhido, saltos, impressão digital aceita, principal do certificado e comando ou subsistema. Registre separadamente os horários da resolução e da conexão para mostrar o intervalo.

O Sallyport encaminha ações SSH por seu auxiliar sp-ssh enquanto as chaves permanecem no cofre criptografado, portanto o agente nunca recebe a chave privada. Seus diários Activity e Sessions vêm de um único log criptografado e encadeado por hash, permitindo que aprovação e execução preservem o alvo solicitado e o observado, em vez de apenas uma string parecida com comando.

Guardar dados não basta. Defina invariantes testáveis: o endereço executado pertencia ao conjunto aprovado, host e porta correspondiam ao ticket, a identidade aceita foi registrada e toda nova tentativa ou salto tinha autorização. Forneça ao teste CanonicalizeHostname, um CNAME permitido, um bloco Match canonical e duas respostas DNS. Mude um fato entre checagem e conexão e confirme que a execução para.

A falha a evitar é comum: o agente pede build, uma pessoa reconhece o apelido e aprova, e um cliente em outra rede o expande para outro domínio. A sessão pode continuar criptografada e o servidor pode apresentar uma chave válida para o próprio nome. A aprovação continua errada. Reúna nome solicitado, nome final e endereço escolhido na mesma decisão e exija que o conector prove que usou exatamente esses valores.

FAQ

O que a canonicalização de host SSH faz?

Ela permite que o OpenSSH reescreva um host usando sufixos configurados e regras CNAME permitidas. Quando ativa, também relê a configuração com o nome resultante.

CanonicalizeHostname vem ativado por padrão?

Não. O OpenSSH documenta CanonicalizeHostname no como padrão, então a reescrita explícita fica desligada salvo configuração contrária. O resolvedor do sistema ainda pode aplicar suas regras em uma busca comum.

Qual é a diferença entre %n e %h no ssh_config?

%n é o token original da linha de comando. %h é o host remoto depois de Hostname e da canonicalização, portanto aprovação e auditoria não devem tratá-los como iguais.

A canonicalização SSH pode seguir um CNAME?

Sim, mas CanonicalizePermittedCNAMEs precisa permitir a transição entre o domínio de origem e o de destino. O padrão é none, e um curinga amplo remove uma fronteira importante.

ssh -G mostra o endereço IP final?

Não. ssh -G mostra a configuração avaliada, incluindo hostname, mas não prova qual endereço uma conexão posterior alcançará. O executor que abre o socket deve resolver e vincular o resultado.

Uma chave de host válida torna a canonicalização segura?

Uma chave válida autentica o servidor segundo as regras do cliente. Ela não prova que a pessoa queria autorizar o nome reescrito ou o endereço selecionado.

Uma aprovação SSH deve mostrar nome ou endereço IP?

Mostre o nome solicitado, o nome efetivo e o endereço escolhido. Os nomes explicam intenção e consulta de identidade, enquanto o endereço registra o endpoint real.

Como aprovar vários endereços DNS?

Aprove um endereço escolhido ou mostre um conjunto limitado que o conector possa tentar. Registre o endereço usado e rejeite qualquer um fora do conjunto aprovado.

ProxyJump muda a canonicalização do host?

Pode mudar. CanonicalizeHostname yes ignora destinos por proxy, enquanto always os inclui, e cada host de salto adiciona uma identidade a vincular.

Um ControlMaster existente pode contornar as verificações?

Ele pode invalidar uma checagem nova ao reutilizar uma conexão criada com DNS ou configuração anteriores. Desative a multiplexação ou vincule e confira a identidade da conexão mestre.

Sallyport

O Sallyport executa chamadas de API e comandos SSH pelo seu agente de IA. As chaves ficam em um cofre local no seu Mac; você aprova cada execução e toda ação vai para um registro selado.

© 2026 Sallyport · Código aberto sob Apache-2.0 · Oleg Sotnikov