7 min de leitura

Comandos SSH forçados podem conter uma conta de serviço de IA?

Comandos SSH forçados limitam contas de serviço controladas por IA a ações nomeadas no servidor, enquanto a aprovação humana permanece no limite da credencial.

Comandos SSH forçados podem conter uma conta de serviço de IA?

Um agente de IA nunca deve receber uma credencial SSH que signifique «faça tudo o que esta conta puder fazer». Isso não é um limite de permissões. É um convite para descobrir uma forma de contorná-lo, geralmente por meio de um argumento que você não esperava, uma conexão encaminhada que se esqueceu de desativar ou um script de implantação que confia demais em quem o chama.

Comandos SSH forçados dão ao servidor remoto a palavra final sobre o que será iniciado depois da autenticação. Eles funcionam bem para contas de implantação e diagnóstico porque substituem uma capacidade vaga, o acesso a um shell remoto, por uma operação nomeada que você controla e pode inspecionar. Eles não substituem a aprovação humana do uso da credencial. Mantenha esses dois controles separados: uma pessoa decide se um agente pode usar a credencial, e o servidor decide qual ação restrita essa credencial pode executar.

Um comando forçado limita a execução, não a autenticação

Um comando forçado instrui o sshd a executar um programa escolhido pelo servidor mesmo quando o cliente solicita um shell ou fornece outro comando. O cliente ainda precisa se autenticar primeiro. Essa distinção parece óbvia até que uma conta de serviço apareça na configuração de um agente e as pessoas comecem a tratar um login bem-sucedido como se fosse uma implantação aprovada.

O OpenSSH oferece esse controle em dois lugares. Você pode anexar command="/path/to/wrapper" a uma chave pública em authorized_keys ou definir ForceCommand em sshd_config para um usuário ou grupo. Nos dois casos, o sshd registra o comando solicitado pelo cliente na variável de ambiente SSH_ORIGINAL_COMMAND e inicia o programa forçado no lugar dele.

O manual do OpenSSH sshd(8) é direto sobre a primeira parte: uma opção command força a execução do comando especificado depois da autenticação. O manual também documenta que o comando original continua disponível para esse programa forçado. É nesse segundo detalhe que muitos projetos frágeis falham. O wrapper recebe uma string de um cliente não confiável. Ele precisa interpretar essa string como uma solicitação, não enviá-la para um shell.

Use a forma por chave quando uma conta tiver várias credenciais cuidadosamente separadas. Uma credencial de release pode iniciar o wrapper de implantação, enquanto uma credencial de operações pode iniciar um wrapper de diagnóstico somente leitura. Assim, a intenção fica visível em authorized_keys, e você pode remover uma credencial sem alterar os outros acessos da conta.

Use ForceCommand quando a própria conta nunca puder oferecer um shell geral, independentemente da forma como ela se autentica. Isso inclui uma senha que você se esqueceu de desativar, uma futura autoridade certificadora ou um administrador que adiciona outra chave pública sem copiar as opções necessárias. Um bloco Match User deploy torna a regra difícil de ignorar durante uma revisão.

Não use nenhuma das duas formas para transformar uma conta de administrador humano em uma conta de automação. Em algum momento, as pessoas precisarão de um shell real para fazer reparos. Dê à automação uma conta Unix separada, uma credencial separada, um wrapper de comandos separado e limites de propriedade compatíveis com a tarefa.

O servidor deve controlar o ponto de entrada da implantação

Uma conta de implantação deve entrar em um único script sob seu controle, não em um interpretador de comandos genérico. O script pode aceitar um vocabulário pequeno de solicitações, mas deve escolher sozinho o caminho do repositório, o diretório de destino, a unidade de serviço e o executável.

Esta entrada em authorized_keys restringe uma única credencial a um wrapper e nega recursos de conexão que não têm lugar em uma conta de implantação:

restrict,command="/usr/local/libexec/release-gate" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... release-agent

A opção restrict é útil porque o OpenSSH a documenta como uma abreviação que desativa o encaminhamento de portas, o encaminhamento do agente, o encaminhamento X11 e a alocação de pseudo-terminal. Seu comportamento exato depende das opções de OpenSSH compatíveis com o servidor, portanto teste-a na versão que você executa. Se o seu ambiente exigir opções explícitas para revisão ou compatibilidade, escreva-as:

command="/usr/local/libexec/release-gate",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... release-agent

O wrapper não deve aceitar um comando de implantação livre. Dê aos chamadores verbos fixos e um valor restrito. Por exemplo, os chamadores podem solicitar um release apenas por uma revisão imutável:

ssh [email protected] "release 9f2a7c6d1e4b8a03"

Um wrapper seguro pode aceitar apenas essa gramática:

#!/bin/sh
set -eu

request=${SSH_ORIGINAL_COMMAND-}
case "$request" in
  "release "[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]* )
    revision=${request#release }
    case "$revision" in
      *" "*|*[!0-9a-f]*)
        echo "invalid revision" >&2
        exit 64
        ;;
    esac
    exec /usr/local/libexec/run-release "$revision"
    ;;
  *)
    echo "unsupported remote request" >&2
    exit 64
    ;;
esac

Este exemplo ainda precisa de uma verificação de comprimento se o formato da sua revisão exigir uma. Um wrapper de produção deve aceitar um ID completo de objeto imutável ou um identificador de release cujo formato você defina. Não aceite um nome de branch como main se o chamador puder movê-lo entre a aprovação e a implantação. Uma branch é um ponteiro. Uma revisão imutável permite que o registro de aprovação, o log de implantação e o artefato resultante se refiram à mesma coisa.

O script run-release deve usar caminhos absolutos e definir seu próprio ambiente. Não dependa de um PATH, diretório de trabalho, localidade, GIT_DIR, GIT_SSH_COMMAND ou LD_PRELOAD fornecido pelo chamador. Um início mínimo seria:

#!/bin/sh
set -eu
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export PATH
unset CDPATH ENV BASH_ENV GIT_DIR GIT_WORK_TREE GIT_SSH_COMMAND
cd /srv/release-repo

revision=$1
/usr/bin/git cat-file -e "$revision^{commit}"
/usr/local/libexec/build-and-activate "$revision"

A conta deve ser proprietária apenas dos arquivos que precisa alterar. Se precisar reiniciar um serviço, conceda um único comando restrito no sudoers, com argumentos fixos, em vez de acesso sem senha a um gerenciador de pacotes ou shell genérico. Uma conta de implantação que pode gravar no próprio wrapper, alterar o próprio authorized_keys ou editar a unidade que executa seu código geralmente consegue recuperar um controle amplo. Verifique esses caminhos, não apenas a configuração do SSH.

SSH_ORIGINAL_COMMAND é entrada, não uma linha de comando

O erro mais comum em comandos forçados é esta linha:

sh -c "$SSH_ORIGINAL_COMMAND"

Essa linha cancela o controle que você acabou de instalar. O cliente pode solicitar release goodrev; curl ... | sh, substituição de comandos, saída redirecionada ou um argumento cuidadosamente entre aspas que chegue a uma ferramenta privilegiada. Um wrapper que chama eval, sh -c, bash -c ou faz uma expansão sem aspas recriou o acesso a um shell remoto com outro nome de arquivo.

Não tente criar um analisador completo de shell. Você não precisa de um. Defina um protocolo pequeno de propósito e rejeite tudo que ficar fora dele. Para uma conta de implantação, uma solicitação pode ser um verbo mais um identificador. Para uma conta de diagnóstico, pode ser uma palavra exata, como health ou version.

Um wrapper de encaminhamento para diagnósticos pode evitar a análise por completo:

#!/bin/sh
set -eu

case "${SSH_ORIGINAL_COMMAND-}" in
  health)
    exec /usr/local/libexec/report-health
    ;;
  queue-depth)
    exec /usr/local/libexec/report-queue-depth
    ;;
  version)
    exec /usr/local/libexec/report-version
    ;;
  "")
    echo "a diagnostic name is required" >&2
    exit 64
    ;;
  *)
    echo "diagnostic is not allowed" >&2
    exit 64
    ;;
esac

Esses scripts de diagnóstico também precisam controlar seus argumentos. report-health deve chamar binários fixos contra sockets locais fixos ou nomes de serviços conhecidos. Ele não deve aceitar um parâmetro de host e executar curl "$host", nem aceitar um filtro de journal e passá-lo para um shell. Um comando somente leitura ainda pode vazar credenciais de banco de dados, topologia interna, valores de ambiente ou dados de clientes.

Muitas pessoas afirmam que um comando shell cuidadosamente entre aspas é suficiente porque o agente que o chama é confiável. Essa afirmação desmorona quando o agente segue instruções hostis de um repositório, confunde um valor com uma instrução ou comete um erro comum. O servidor remoto não consegue saber se uma solicitação perigosa veio de uma ação maliciosa ou de uma chamada de ferramenta entusiasmada demais. Ele vê apenas a entrada. Torne sua decisão determinística.

Se precisar de entradas estruturadas, envie um formato limitado e analise-o com um parser que rejeite campos extras. JSON não é automaticamente mais seguro, porque um wrapper shell ainda pode tratá-lo incorretamente. Uma solicitação pequena, como release <64 lowercase hex characters>, é mais fácil de validar, documentar, testar e auditar do que um bloco JSON com campos opcionais.

O encaminhamento pode contornar o espírito da restrição

Um comando forçado não impede automaticamente que um cliente autenticado use o SSH como túnel. O manual do OpenSSH trata a execução de comandos e o encaminhamento como controles separados. Se você adicionar apenas command="...", um cliente ainda poderá pedir ao sshd que encaminhe uma porta local para um serviço interno, dependendo do restante da configuração do servidor.

Isso importa porque uma conta restrita pode ter acesso de rede que o agente não deveria ter. Um agente que não consegue executar /usr/bin/ps no host remoto ainda pode alcançar uma porta de banco de dados por esse host se o encaminhamento continuar aberto. Nesse caso, a conta deixou de ser uma identidade de implantação e se tornou um ponto de passagem pela rede.

Para uma conta que não precisa de uma sessão interativa, negue todos estes recursos, a menos que consiga explicar por que a conta precisa de algum deles:

  • encaminhamento TCP
  • encaminhamento do agente
  • encaminhamento X11
  • alocação de pseudo-terminal
  • variáveis de ambiente controladas pelo usuário

restrict cobre as quatro primeiras categorias em implantações modernas do OpenSSH. Se a conta realmente precisar de uma exceção, não remova todo o conjunto de restrições. O OpenSSH oferece opções como permitopen="host:port" para limitar o destino do encaminhamento. Trate isso como um projeto de acesso separado e teste tanto os destinos permitidos quanto os negados.

Também inspecione o acesso de saída à rede do wrapper. Um script de implantação que pode buscar URLs arbitrárias, clonar repositórios arbitrários ou enviar dados arbitrários para fora tem um canal amplo, mesmo que o encaminhamento SSH esteja desativado. Fontes de artefatos fixas e revisões fixadas reduzem essa exposição. Regras de firewall ou credenciais específicas do serviço talvez precisem lidar com o restante.

A aprovação deve acontecer antes da abertura da conexão

Coloque a aprovação antes dos comandos forçados
O Sallyport aprova o uso da credencial antes que o comando forçado do servidor decida o que pode ser executado.

Um comando forçado reduz o dano que um uso aprovado do SSH pode causar. Ele não responde se o processo atual do agente deve usar a credencial. Essa decisão pertence ao limite da credencial, antes que o agente crie uma conexão SSH.

Isso é especialmente importante para agentes autônomos de programação. Um repositório pode instruir o agente a executar um comando de implantação. A saída de uma ferramenta pode solicitá-lo. Uma dependência comprometida pode conduzi-lo nessa direção. Se a credencial estiver no ambiente ou no sistema de arquivos do agente, ele poderá usá-la sem que uma pessoa veja o momento do uso.

Mantenha as chaves privadas SSH fora do processo do agente e solicite uma aprovação quando uma nova execução do agente pedir acesso pela primeira vez. Para contas de alto impacto, peça aprovação sempre que a credencial for usada. O comando forçado remoto então coloca um limite rígido na ação autorizada por essa aprovação.

O Sallyport aplica essa separação mantendo as chaves SSH em seu cofre criptografado, autorizando novos processos de agentes por sessão por padrão e executando o SSH por meio do auxiliar sp-ssh, em vez de entregar a chave ao agente.

Não confunda um cartão de aprovação com a autorização do servidor. A aprovação responde: «Este processo pode usar esta credencial agora?» O servidor responde: «O que esta credencial pode fazer depois do login?» Você precisa das duas respostas porque elas falham de maneiras diferentes. A aprovação pode impedir um processo inesperado. Comandos forçados podem impedir que um processo aprovado transforme uma credencial de release em um shell.

Torne a descrição da aprovação útil. Nomeie o ambiente e a ação no rótulo da credencial, como production release ou staging diagnostics. Um rótulo chamado deploy-key-2 obriga o revisor a lembrar do histórico durante uma interrupção. É assim que aprovações rotineiras se transformam em cliques automáticos.

Separe implantação e diagnóstico antes que a lista permitida cresça

Execute SSH sem expor a chave
O auxiliar stateless sp-ssh integrado executa o SSH enquanto a chave privada permanece no Sallyport.

Implantação e diagnóstico parecem semelhantes porque ambos precisam de SSH, mas têm fluxos de dados e modos de falha diferentes. Sempre que possível, coloque-os atrás de contas separadas ou de credenciais com comandos forçados separados.

Uma conta de implantação altera o estado. Ela pode buscar uma revisão fixa, criar um artefato, substituir um diretório de release e reiniciar um serviço. Sua saída deve informar a revisão, o destino, o status de saída e uma mensagem curta de falha. Ela não precisa de acesso arbitrário a logs, inspeção de processos ou consultas ao banco de dados.

Uma conta de diagnóstico lê o estado. Ela pode informar o resultado de um endpoint de saúde, uma contagem limitada de fila, a versão de um serviço ou o final de um log local cuidadosamente filtrado. Ela não deve reiniciar serviços, rotacionar arquivos, consultar todos os processos ou ler caminhos arbitrários. Assim que um wrapper de diagnóstico aceitar um nome de arquivo, unidade, host ou opção de comando fornecido pelo usuário, revise novamente seu modelo de entrada.

Uma conta combinada começa com uma lista aparentemente inocente:

release <revision>
health
logs <service>
restart <service>

Depois alguém precisa de logs api --since, outra pessoa precisa de uma reinicialização de emergência, e o wrapper começa a repassar argumentos para journalctl ou systemctl. Logo, o código contém casos especiais que ninguém consegue explicar. Separe as contas antes que isso aconteça. Credenciais separadas permitem exigir uma aprovação mais rigorosa para mudanças de produção e, ao mesmo tempo, manter um fluxo de diagnóstico de menor risco.

Cada ação deve gerar um registro que informe o que o wrapper aceitou, não apenas a string opaca do comando SSH. Para um release, registre a revisão imutável e o nome do destino. Para um diagnóstico, registre o diagnóstico nomeado e se ele foi bem-sucedido. Mantenha valores secretos fora dos argumentos dos comandos e dos logs. Se uma ação precisar de um segredo, o script remoto deve obtê-lo por seu próprio mecanismo controlado, em vez de aceitá-lo do cliente SSH.

Teste os caminhos de negação a partir de um cliente descartável

Uma conta restrita só está realmente restrita depois que você testa as solicitações que ela precisa rejeitar. Execute estas verificações a partir de uma conta descartável ou de um host de teste antes de confiar na configuração em um ambiente de produção. Os exemplos presumem que a credencial já está instalada no servidor.

ssh [email protected] "release 9f2a7c6d1e4b8a03"
ssh [email protected]
ssh [email protected] "id"
ssh [email protected] "release 9f2a; id"
ssh -N -L 15432:db.internal:5432 [email protected]
ssh -tt [email protected] "health"

O primeiro comando deve chegar apenas ao wrapper de release se a revisão atender às regras. Os três comandos seguintes devem falhar com a mensagem de negação do wrapper e um status de saída diferente de zero. A tentativa de encaminhamento de porta deve falhar antes de estabelecer um listener. A solicitação de terminal deve falhar ou ser executada sem terminal, dependendo de como o cliente informa a alocação negada.

Depois, teste os casos menos óbvios. Tente espaços no início e no fim, tabulações, um comando vazio entre aspas, caracteres de nova linha, um argumento muito longo, espaços em branco Unicode, substituições de comandos, redirecionamentos e argumentos duplicados. Se o shell ou o wrapper normalizar algum deles para uma solicitação aceita, torne a gramática mais rígida.

Verifique as permissões e a propriedade dos arquivos da conta como parte do teste. Um invasor que possa substituir /usr/local/libexec/release-gate não precisa contornar o SSH. Uma conta que possa alterar sua própria fonte de implantação também pode alterar o código executado com mais privilégios. Inspecione toda a cadeia: authorized_keys, configuração do sshd, wrapper, scripts de implantação, definições de serviço, diretórios graváveis e qualquer entrada no sudoers.

Audite a solicitação dos dois lados do limite

Combine contas restritas com aprovação
Comandos forçados limitam a conta no servidor; o Sallyport controla quando um agente pode solicitar sua chave SSH.

Os logs remotos explicam o que o servidor aceitou. Os logs do limite da credencial explicam qual processo local solicitou a capacidade de se conectar. Mantenha os dois, porque nenhum consegue responder à pergunta do outro.

No lado remoto, registre o sucesso da autenticação, a operação aceita pelo wrapper forçado, a revisão imutável ou o nome do diagnóstico, um identificador da solicitação e o status final. Envie esses registros para um local que a conta de serviço não possa reescrever. Não registre o SSH_ORIGINAL_COMMAND bruto se os chamadores puderem colocar material secreto nele, e não permita que seu protocolo aceite material secreto em primeiro lugar.

No lado local, mantenha a identidade da sessão e a solicitação individual de ação SSH. O Sallyport registra execuções de agentes e chamadas individuais em diários separados, dentro de um único log de auditoria criptografado e encadeado por hash. sp audit verify pode verificar essa cadeia offline sem uma chave do cofre.

Uma cadeia de hashes não transforma uma permissão ruim em uma boa. Ela facilita detectar alterações posteriores no histórico registrado. Isso é útil depois de uma implantação malsucedida, de uma aprovação contestada ou de uma solicitação de comando inesperada. Também impõe uma disciplina bem-vinda: defina cedo o vocabulário de ações para que os registros digam algo que uma pessoa consiga entender.

A primeira implementação deve ser sem graça. Crie uma conta Unix dedicada, um wrapper forçado, uma operação com gramática de entrada fixa, encaminhamento desativado e um teste que prove que ssh account@host não oferece um shell. Adicione recursos somente quando conseguir nomear sua entrada, saída, acesso a arquivos, acesso à rede e a pessoa que deve aprová-los.

FAQ

O que é um comando SSH forçado?

Use uma conta SSH cuja entrada em authorized_keys tenha a opção command="...", ou use ForceCommand em sshd_config quando todo método de autenticação dessa conta precisar seguir a mesma regra. O servidor executa seu wrapper em vez do comando enviado pelo cliente e repassa a solicitação original por meio de SSH_ORIGINAL_COMMAND.

Comandos SSH forçados bastam para proteger uma conta de serviço de IA?

Não. Um comando forçado limita o que o servidor SSH inicia depois de uma autenticação bem-sucedida, mas não decide quem pode usar a credencial. Combine-o com um limite de credencial que exija que uma pessoa aprove uma nova execução do agente ou um uso sensível. Depois, mantenha a conta do servidor restrita o suficiente para que essa aprovação tenha uma consequência bem delimitada.

Devo usar ForceCommand ou o comando de authorized_keys?

Em geral, não. Uma chave dedicada em authorized_keys cria uma relação clara entre uma credencial e uma regra de comando forçado, enquanto uma configuração compartilhada da conta pode afetar pessoas e automações ao mesmo tempo. Use ForceCommand quando você quiser deliberadamente que todo caminho de acesso a uma conta restrita passe pelo mesmo wrapper.

Como um comando forçado recebe o comando SSH original?

O OpenSSH armazena o comando solicitado em SSH_ORIGINAL_COMMAND quando executa um comando forçado. Trate esse valor como entrada não confiável: rejeite metacaracteres do shell, rejeite opções que você não projetou e aceite apenas um conjunto pequeno de formatos exatos de comando.

command= em authorized_keys desativa o encaminhamento de portas SSH?

Não. A opção command="..." sozinha não desativa o encaminhamento de portas, o encaminhamento do agente, o encaminhamento X11 nem a alocação de um pseudo-terminal. Adicione restrict quando fizer sentido ou desative cada recurso explicitamente, testando o resultado a partir de um cliente.

Como restringir uma conta SSH apenas a implantações?

Uma conta de implantação deve executar um único script sob seu controle, com caminhos fixos, diretório de trabalho fixo, ambiente restrito e uma lista permitida de destinos de implantação. Não aceite uma branch, um host, um caminho ou um fragmento de shell arbitrário para repassá-lo ao git, rsync, sudo ou a um shell.

O que um wrapper de comando forçado deve rejeitar?

Retorne um status diferente de zero para uma solicitação vazia, um shell, um subcomando desconhecido, argumentos malformados ou uma solicitação com espaços em branco ou metacaracteres inesperados. Registre a rejeição com a conta autenticada e as informações de origem, mas nunca registre segredos passados por variáveis de ambiente ou pelo texto do comando.

Agentes de programação com IA podem usar comandos SSH forçados sem interação?

Comandos forçados funcionam com SSH porque o servidor, e não o processo de IA, decide qual programa será iniciado após a autenticação. Eles não exigem um prompt interativo no host remoto, o que os torna adequados para execuções não interativas de agentes, desde que a aprovação continue no limite da credencial.

Como auditar ações realizadas por uma conta SSH com comando forçado?

Mantenha os logs do wrapper remoto, os logs de implantação e os logs de autenticação SSH. Depois, relacione-os ao registro de uso da credencial. Um diário local de ações com evidências de adulteração é útil porque registra qual processo do agente solicitou o uso da credencial antes que o servidor remoto recebesse a conexão.

Implantação e diagnóstico devem usar contas SSH separadas?

Use uma conta separada quando a tarefa de diagnóstico tiver um objetivo, comandos permitidos ou consequências diferentes dos da implantação. Combinar tudo em um único wrapper costuma ampliar a lista permitida até que ela se transforme em um shell remoto acidental.

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