ssh alguns comandos através de (uma quantidade variável de) lúpulo intermediário

0

(Espero ter pesquisado os sites corretamente para evitar duplicatas: não encontrei uma pergunta / necessidade semelhante, e a maioria das perguntas semelhantes apenas passa por um salto):

a necessidade : Eu preciso de ideias para criar uma maneira de fazer remotamente (qualquer) comandos através de múltiplos saltos. O principal problema é que o número de saltos intermediários é variável! Então eu preciso 1) descrever a "topologia" dos servidores, e então 2) ter uma função? / Script que possa executar alguns comandos em todos eles, usando os saltos intermediários conforme necessário para chegar ao destino final.

exemplo de topologia : (mas isso também varia)

server A communicates with server B directly.
servers C and D are "behind" B.
server E is behing C
etc.... (many servers, many indirect ways to reach some of them, some have multiple ways)

Then from A, you can execute "some command" on E with:
    ssh foo@B ssh bar@C ssh baz@E "some command"
 or you can build intermediary tunnels connecting A to **C** and then:
    ssh -P 1234 baz@E "some command"

Eu tenho alguns "main hop" que podem atingir muitos servidores diretamente

Eu quero ser capaz de fazer um comando (potencialmente complexo) em todos os 5 servidores, através de um comando não muito complexo, permitindo-me (em um script) fazer algo como

do_on_all_servers "for dir in /something* ; do cd \"\$dir\" && something using $thisLOCALvariable ; done"
# with an easy way to say "from X, ssh to specificuser@Y using this ssh command and this username" to describe each intermediary hops
# dir is a remote variable, thisLOCALvariable is at the initiating script level, and the amount of "\" needs to be adjusted if going through multiple hops...

Restrição adicional : nenhuma ferramenta GNU, nenhuma "nc", apenas simples (muito) antiga ssh & awk & ferramentas antigas semelhantes, para ter um caminho portátil. A portabilidade deve ser alta (o uso de opções e ferramentas bacanas "> 1990?" Deve ser evitado). E eu preferiria não ter que fazer isso copiando um script sobre o destino final & ssh via múltiplos saltos que script, mas isso pode ser ok se você acha que é mais simples / melhor.

Eu vou atualizar (ou dar uma resposta (provavelmente ruim) abaixo) quando eu aparecer com algo inteligente o suficiente ... agora eu estou usando variáveis para concatenar o ssh ... mas isso não ajuda o problema "quantos" - "eu - preciso", que é secundário, mas que eu adoraria ser capaz de resolver / evitar elegantemente.

Eu agradeço todas as dicas sobre como (corretamente) fazer isso, enquanto eu também tento fazer isso do meu jeito.

Minha idéia inclui: stringing variables contendo um "shh user @ host" juntos. Ou talvez usando esse link muito simples, o que se eu posso fazê-lo corretamente, posso fazer scp, etc usando o próprio porta local para chegar ao destino final "diretamente" (mas não tenho certeza de que esse caminho está disponível em todos os saltos).

    
por Olivier Dulac 14.01.2016 / 11:16

2 respostas

1

Você pode gravar / usar uma quebra automática de função de shell ssh que gere um arquivo de configuração personalizado que usa ProxyCommands aninhado para alcançar seu host final. A função chamaria ssh com -F apontando para o arquivo de configuração temporário gerado.

Algo parecido com isto:

sssh() {
  TMPF=$(mktemp);
  echo -n "$1" |
  awk '
    BEGIN {RS=" "}
    { split($0,a,":"); u=a[1]; p=a[3]; h=a[2]
      print "\nHost "h"\n\tUser "u"\n\tPort "p;
      if (hop) print "\tProxyCommand ssh -q -W %h:%p "hop;
      hop=h }
    END {print "\nHost *\n\tControlMaster auto\n\tControlPath /tmp/%r@%h:%p"}
  ' > $TMPF;   TMPA=($1); TMPAA=(${TMPA[-1]//:/ }); TMPH=${TMPAA[1]}; shift;
  ssh -F $TMPF $TMPH $@;
  rm $TMPF; }

Quando correr assim:

sssh "user1:so.me.ho.st:1234 user2:router.internal:2345 user3:device.internal:3456" do_complex_command

geraria um arquivo tmp como este:

Host so.me.ho.st
    User user1
    Port 1234

Host router.internal
    User user2
    Port 2345
    ProxyCommand ssh -q -W %h:%p so.me.ho.st

Host device.internal
    User user3
    Port 3456
    ProxyCommand ssh -q -W %h:%p router.internal

Host *
    ControlMaster auto
    ControlPath /tmp/%r@%h:%p

e finalmente execute:

ssh -F /tmp/tmp.IUVSRrer45 device.internal do_complex_command

Isso executará do_complex_command no último host "mais interno". Se você precisa que seu comando seja executado em todos os intermediários, você terá que adaptar um pouco a função.

    
por 01.08.2016 / 21:33
0

Eu encontrei uma solução bastante portátil (não precisa de nenhuma edição nos arquivos de configuração ssh, e é facilmente contida no próprio script de chamada, e é bem fácil de usar): apenas junte os sshs, e o último chama o shell (ksh ou bash funcionam bem para os unixes "um pouco mais antigos"):

echo "for remotedir in /some/path/*/ ; do \
      cd \"\$remotedir\" && \
      something using $thisLOCALvariable \
      ; done" | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash"
# note: the 'obvious' approach: 
#     ssh u1@h1 ssh u2@h2 "cmd1 ; cmd2; ..."
#  does NOT work : it would execute "cmd2; ..." on h1, not on h2 !!!
#  only cmd1 would be executed on h2 ... BE CAREFUL ! (at least on a not-too-old cygwin)

Esse exemplo mostra que podemos misturar variáveis locais e remotas ... e a necessidade de "escapar" das remotas, bem como de aspas duplas, etc ... alguns cuidados precisam ser tomados para garantir que façamos o que pretendíamos fazer.

Mas é muito útil, pois há sempre o mesmo nível de cotação (apenas 1!), mesmo se agruparmos 3 ou mais aninhamento ssh, o que é bastante útil ... É semelhante ao que $(cmd) traz em comparação com a antiga abordagem ' (backtick): simplifica a escrita sem ter que levar em conta o nível de aninhamento.

caveat : ter isso como parâmetros para um script (como eu perguntei na pergunta: on_all_servers "for dir in /something* ; do cd \"\$dir\" && something using $thisLOCALvariable ; done" ) é um pouco mais difícil de descobrir, como o shell de chamada interpreta primeiro o "\ "e aspas ... então, por enquanto, eu uso a versão de linha de comando (ou uma construção semelhante dentro de um script, mas não um script que obtenha a linha de comando de seus argumentos) ...

Eu ainda deixo a questão aberta, esperando que alguém obtenha uma solução ainda melhor (ou uma melhoria desta, ie: permitir que um comando similar seja passado como parâmetro para um "script de chamada")

Editar : Eu também descobri como fazer extração remota de alcatrão usando este método! Não é trivial, como stdin precisa ser dado ao shell remoto, uma vez para chegar ao diretório apropriado, então o tar local tar - precisa ser encaminhado para esse tar remoto no local adequado: aqui vai:

# the easy one, nothing special needed: from remote to local:
echo "cd /remote/path && tar cf - *" | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash" | ( cd /local/path && tar xvf - )

# and the hard one: the other way around: needs a trick:
( cd /local/path && tar cf - * ) | { { echo "cd /remote/path/ && tar xvf -" ; cat ;} | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash" ;}

#once again: this trick allows you to have 1, 2, or any level of ssh.
# PLEASE NOTE: I just run bash, and feed it the command, to avoid
#     the command to be split between the remote hose and the first hop!
#  ex of the way the 'obvious' way fails:
#       ssh user1@host1 ssh user2@host2 "hostname ; hostname;"
#    will return not twice the name "host2", but "host2" then "host1" !! 
#    which means the first part of the command was executed on the last hop,
#    but then the rest of it was executed on the first hop!!! DANGEROUS!
#    that is why I choose instead to use that way:
#       echo "hostname; hostname" | ssh user1@host1 ssh user2@host2 "bash"
#    this one properly executes the full (complex) command on host2
    
por 22.09.2016 / 19:29