Passando vários arquivos pelo stdin (sobre ssh)


15

Eu tenho um programa em um host remoto, cuja execução eu preciso automatizar. O comando execute esse programa, na mesma máquina, se parece com isso:

/path/to/program -a file1.txt -b file2.txt

Nesse caso, file1.txte file2.txtsão usados ​​para coisas completamente diferentes dentro do programa, então não posso apenas catjuntá-las. No entanto, no meu caso, o file1.txte file2.txtque eu quero passar para o programa existem apenas no meu dispositivo, não no host em que preciso executar o programa. Eu sei que posso alimentar pelo menos um arquivo através do SSH passando-o stdin:

cat file1.txt | ssh host.name /path/to/program -a /dev/stdin -b file2.txt

mas, como não tenho permissão para armazenar arquivos no host, também preciso de uma maneira de passar file2.txtpor lá. Estou achando que isso pode ser possível através do abuso de variáveis ​​de ambiente e do uso criativo de cate sedjuntos, mas não conheço as ferramentas o suficiente para entender como as usaria para fazer isso. É factível e como?


2
cate sednão é a solução aqui.
Slyx

Provavelmente eu seria capaz de montar, mas, considerando os proxies e as restrições de segurança, não tenho certeza se conseguiria me safar.
Green Cloak Guy

Você tem permissão na máquina remota para montar uma pasta ssh?
Slyx

1
se você pode abrir uma sessão ssh da máquina remota para a local, não há problema no nível da rede para montar uma pasta SSH.
Slyx 03/06/19

Você pode fazer encaminhamentos? Quais sistemas e shells você possui na extremidade local e remota?
mosvy

Respostas:


18

Se os arquivos fornecidos como argumentos para o seu programa forem arquivos de texto e você puder controlar o conteúdo deles (você conhece uma linha que não ocorre dentro deles), poderá usar vários documentos aqui:

{
    echo "cat /dev/fd/3 3<<'EOT' /dev/fd/4 4<<'EOT' /dev/fd/5 5<<'EOT'"
    cat file1
    echo EOT
    cat file2
    echo EOT
    cat file3
    echo EOT
} | ssh user@host sh

Aqui catestá um exemplo de comando que usa nomes de arquivos como argumentos. Pode ser:

echo "/path/to/prog -a /dev/fd/3 3<<'EOT' -b /dev/fd/4 4<<'EOT'

Substitua cada um EOTpor algo que não ocorra em cada um dos arquivos, respectivamente.


1
Essa abordagem é bastante inteligente! Se os arquivos não forem texto, você poderá codificá-los / decodificá-los com algo como base64 ou até um bom código de olu ... Muito bom!
filbranden

3
Observe que várias implementações (a maioria) sh implementam aqui documentos usando arquivos temporários; portanto, pode não funcionar para o OP se eles não tiverem permissão para armazenar arquivos no host remoto.
Stéphane Chazelas

2
dash( /bin/shno debian, ubuntu, etc), busybox sho /bin/shdo FreeBSD e yashnão usa arquivos temporários para os documentos aqui. Na verdade, eu testei com um chroot somente leitura. Obviamente, os /dev/fd/arquivos precisam estar disponíveis - somente em um sistema FreeBSD padrão /dev/fd/0-2, portanto fdescfs, devem ser montados /dev/fd.
mosvy

1
Sou fã dos separadores de informações ASCII , projetados para delimitar campos sem obstrução. U+001Cé "Information Separator Four" (separador de arquivos, FS) e é ideal para este caso, embora arquivos binários possam utilizá-lo por coincidência. Portanto, eu sugiro EOT="$(printf $'\x1c')"em conchas avançadas ou então EOT="$(awk 'BEGIN{printf"%c",28}')"para compatibilidade. Você precisará citá-lo quando o colocar no lugar, como echo "$EOT$EOT$EOT"(triplicou para diminuir as chances de corresponder a um arquivo binário).
Adam Katz

Se você vai usar o tty, por que não usar o ttycomando? A menos que esteja faltando alguma coisa - o que suponho ser possível?
Pryftan

14

Talvez não seja exatamente o que você deseja ... Mas considere enviar um tarball através do cano aberto pelo ssh?

Você disse aquilo:

Não tenho permissão para armazenar arquivos no host.

É possível que você não tenha um diretório inicial gravável ou outro local conveniente para armazenar arquivos a longo prazo, mas eu diria que é improvável que você não tenha nenhum local gravável, mesmo que um temporário tmpfsseja disponibilizado apenas para o seu conexão particular.

Muitos programas (e até rotinas de libc) requerem um gravável /tmp, portanto é muito provável que um esteja disponível para você.

Então você pode usar um script que irá descompactar o tarball em um diretório temporário, executar seu programa e limpar através da conexão ssh.

Algo como:

$ tar cf - file1.txt file2.txt |
  ssh host.name '
      set -e
      tmpdir=$(mktemp -d -t tmp.XXXXXXXXXX)
      cleanup () { rm -rf "$tmpdir"; }
      trap cleanup EXIT
      cd "$tmpdir"
      tar xf -
      /path/to/program -a file1.txt -b file2.txt
  '

Isso pode exigir algum cuidado extra com os caminhos dos arquivos e há alguns casos a serem considerados (teste para eles), mas a abordagem geral deve funcionar.

Se nenhum diretório gravável estiver disponível, uma abordagem possível seria modificar programpara pegar um tarball como entrada única e descompactar seu conteúdo na memória. Por exemplo, se programfor um script Python, o uso do tarfilemódulo embutido poderia facilmente realizar algo assim.


3
Mesmo sem o trabalho de tarballing - eu decidi mais ou menos apenas escrever um arquivo /tmp/e usá-lo diretamente.
Green Cloak Guy

2
A abordagem tarball tem algumas vantagens, pois você usa uma única conexão ssh (não é necessário testar se há êxito / falha) e é provável que várias execuções simultâneas não sejam executadas (use e / ou exclua arquivos em / tmp destinados a execuções separadas .) Mas acho que é o melhor que você obterá do ssh como existe agora. Boa sorte!
filbranden

3
@GreenCloakGuy Então, afinal, você pode armazenar arquivos no servidor. Modifique a pergunta de acordo.
mosvy

1
@mosvy pode , sim, mas não quer . A questão é: em "é possível fazer isso sem armazenar nenhum arquivo", cuja resposta parece ser "talvez, mas não no meu caso"?
Green Cloak Guy

5

Se você pode definir um ouvinte TCP (pode ser uma porta mais alta), poderá usar uma segunda sessão SSH para estabelecer a segunda fonte de entrada nc.

Exemplo:

Existe este script no servidor ( ~/script.bash):

#!/usr/bin/env bash
cat "$1" | tr a 1
nc localhost "$2" | tr '[:lower:]' '[:upper:]'

E existem esses dois arquivos localmente:

$ cat a 
aaa
aaa
aaa
$ cat b
bbb
bbb
bbb

Agora, primeiro inicie a segunda fonte ( $servé o servidor):

ssh "$serv" nc -l -p 2222 -q1 <b &

E execute o comando corretamente:

$ ssh "$serv" ./script.bash - 2222 <a
111
111
111
BBB
BBB
BBB
[1]+  Done                    ssh "$serv" nc -l -p 2222 -q1 < b

2

Foi criado em um comentário que /tmpé gravável, tão simplesmente copiar um dos arquivos de antemão:

scp -p file2.txt host.name:/tmp/
ssh host.name "/path/to/program -a /dev/stdin -b /tmp/file2.txt && rm /tmp/file2.txt" < file1.txt

Isso também limpa o arquivo copiado após uma execução bem-sucedida (altere &&para a ;se você deseja removê-lo independentemente do sucesso, mas observe que você perderá o valor de saída).


Se isso não for aceitável, eu sugeriria mexer com /path/to/programum invólucro para separar os dois arquivos de um único fluxo de entrada, como:

awk 'FNR == 1 && NR > 1 { printf "%c%c%c", 28, 28, 28 } 1' file1.txt file2.txt \
  | ssh host.name /path/to/tweaked_program

Isso usa o separador de informações ASCII quatro (separador de arquivos, FS) e o triplicou, para minimizar a chance de um arquivo binário coincidentemente conter essa sequência. Você tweaked_programdividiria a entrada, dado o separador, e depois operaria nos dois arquivos salvos como variáveis.

Obviamente, se você usa uma linguagem que possui bibliotecas para lidar com tarballs, uma abordagem mais segura e limpa seria simplesmente tarinserir esse código da sshseguinte maneira:

tar -zpc file1.txt file2.txt |ssh host.name /path/to/tweaked_program

E você tweaked_programdescompactaria e abriria o arquivo morto, salvaria cada arquivo em uma variável diferente e executaria a programlógica do original nas variáveis.


1

Se você tiver permissão para encaminhar portas sshe tiver acesso à wgetmáquina remota e à busyboxmáquina local, poderá fazer algo como:

mkdir /tmp/test; cd /tmp/test
echo 1st_file > 1st_file
echo 2nd_file > 2nd_file

busybox httpd -f -p 127.0.0.1:11080 &
ssh USER@HOST -R 10080:127.0.0.1:11080 '
        cat <(wget -q -O- http://localhost:10080/1st_file) \
            <(wget -q -O- http://localhost:10080/2nd_file)
'
kill $!

(usando catcomo um programa de exemplo que recebe dois argumentos de arquivo).

Somente a capacidade de encaminhar portas -Ré essencial - em vez de fazer o http, você pode usar outros métodos, por exemplo. se o seu netcatsuporta as opções -de -N:

nc -Nl localhost 11001 < 1st_file &
nc -Nl localhost 11002 < 2nd_file &
ssh USER@HOST -R 10001:localhost:11001 -R 10002:localhost:11002 '
        cat <(nc -d localhost 10001) <(nc -d localhost 10002)

Pode haver maneiras de substituir as substituições do <(...)processo se o shell de login na máquina remota não for do tipo ksh ou bash.

Em suma, isso não é muito bom - um melhor conhecimento sobre o sistema exato / shells / config / Permissions (que você não forneceu) pode permitir soluções mais inteligentes.


-1

UPDATE: Isso realmente não funciona, o ssh tem uma idéia muito definida do que stdin e stdout / stderr significam e não permite realmente que o usurpador stderr o leia. Excluirei esta resposta em alguns dias, pois ela não funciona. Obrigado pela discussão muito interessante !!!


Infelizmente, não há uma ótima maneira de fazer isso diretamente, pois o sshcliente passará apenas os três descritores de arquivo ( stdin, stdoute stderr) para o servidor e não possui disposições para passar descritores de arquivo adicionais (o que seria útil para esse caso de uso específico). .)

(Observe também que o protocolo SSH tem disposições para passar descritores de arquivos adicionais, apenas o sshcliente não implementa uma maneira de usar esse recurso. Teoricamente, estender o cliente com um patch seria suficiente para expor esse recurso.)

Uma maneira hacky de realizar o que você está procurando é usar o descritor de arquivo 2 (o stderrdescritor de arquivo) para passar o segundo arquivo. Algo como:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/stderr \
      <file1.txt 2<file2.txt

Isso deve funcionar, pois pode causar um problema se programtentar gravar no stderr. Você pode contornar isso manipulando novamente os descritores de arquivo na extremidade remota antes de executar o programa. Você pode passar file2.txtpara o descritor de arquivo 3 e reabrir o stderr para espelhar o stdout:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/fd/3 \
      '3<&2' '2>&1' \
      <file1.txt 2<file2.txt

Eu realmente não testei isso (digitando em um telefone), mas pelo menos em teoria eu esperava que funcionasse. Deixe-me saber se, por algum motivo, não. Felicidades!
filbranden

Isso não vai funcionar. Nem mesmo com um ssh hackeado. O AFAIK stderr não usa um canal separado, mas algum tipo especial de mensagem. Mas sim, ser capaz de acessar canais ssh como pipes ou soquetes de domínio unix não nomeados (em vez de ter que fazer encaminhamentos de soquete) seria ótimo, simplesmente não parece ser possível em nenhum tipo ou forma.
mosvy

@mosvy, fd 0, 1 e 2 no comando remoto serão 3 tubos diferentes. Os dados trafegam por 3 canais diferentes, multiplexados na conexão ssh, mas são unidirecionais (do cliente para o servidor para fd 0 e do servidor para o cliente para 1 e 2), portanto, mesmo em sistemas em que os tubos são bidirecionais, isso ocorre ' não funciona. No Linux, abrir / dev / stderr no modo somente leitura dá na outra extremidade do canal, portanto, também não funcionaria.
Stéphane Chazelas

Observe que o último assume que o shell de login do usuário no remote.name é do tipo Bourne ( 3<&2é um operador de shell Bourne e o sshd executa o shell de login do usuário para interpretar o comando enviado pelo cliente)
Stéphane Chazelas

2
@ StéphaneChazelas Não, um único canal ssh é usado para stdin / stdout / stderr, e os dados do stderr são transferidos por SSH_MSG_CHANNEL_EXTENDED_DATAmensagens com o tipo definido como SSH_EXTENDED_DATA_STDERR. Você pode ler a coisa toda aqui
mosvy
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.