Pasar múltiples archivos a través de stdin (sobre ssh)

15

Tengo un programa en un host remoto, cuya ejecución necesito automatizar. El comando ejecutar ese programa, en la misma máquina, se parece a esto:

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

En este caso, file1.txty file2.txtse usan para cosas completamente diferentes dentro del programa, por lo que no puedo simplemente catjuntarlas. Sin embargo, en mi caso, el file1.txty file2.txtque quiero pasar al programa solo existen en mi dispositivo, no en el host donde necesito ejecutar el programa. Sé que puedo alimentar al menos un archivo a través de SSH pasándolo stdin:

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

pero, dado que no se me permite almacenar archivos en el host, también necesito una forma de obtenerlos file2.txtallí. Estoy pensando que podría ser posible mediante el abuso de las variables de entorno y el uso creativo de caty sedjuntos, pero no conozco las herramientas lo suficientemente bien como para entender cómo las usaría para lograr esto. ¿Es factible y cómo?

Chico de capa verde
fuente
2
caty sedno son la solución aquí.
Slyx
Probablemente podría montar, pero dados los poderes y las restricciones de seguridad, no estoy seguro de poder escapar.
Green Cloak Guy
¿Tiene permiso en la máquina remota para montar una carpeta ssh?
Slyx
1
si puede abrir una sesión ssh desde la máquina remota a la local, por lo que no hay problema a nivel de red para montar una carpeta SSH.
Slyx
¿Puedes hacer reenvíos? ¿Qué sistemas y shells tienes en el extremo local y remoto?
mosvy

Respuestas:

18

Si los archivos proporcionados como argumentos para su programa son archivos de texto, y puede controlar su contenido (conoce una línea que no aparece dentro de ellos), puede usar varios documentos aquí:

{
    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

Aquí cathay un comando de muestra que toma nombres de archivos como argumentos. Podría ser en su lugar:

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

Reemplace cada uno EOTcon algo que no ocurra en cada uno de los archivos, respectivamente.

Mosvy
fuente
1
¡Este enfoque es bastante inteligente! Si los archivos no son texto, puede codificarlos / decodificarlos con algo como base64 o incluso un buen código antiguo ... ¡Muy bien!
filbranden
3
Tenga en cuenta que varias (la mayoría) de las implementaciones de sh implementan aquí documentos utilizando archivos temporales, por lo que puede que no funcione para el OP si no se les permite almacenar archivos en el host remoto.
Stéphane Chazelas
2
dash(el /bin/shen debian, ubuntu, etc.), busybox shel /bin/shde FreeBSD y yashno use archivos temporales para documentos aquí. De hecho, lo he probado con un chroot de solo lectura. Por supuesto, los /dev/fd/archivos tienen que estar disponibles, solo en un sistema de stock de FreeBSD /dev/fd/0-2, por lo fdescfsque deben montarse /dev/fd.
mosvy
1
Soy fanático de los separadores de información ASCII , que están diseñados para delimitar campos sin obstrucción. U+001Ces "Information Separator Four" (separador de archivos, FS) y es ideal para este caso, aunque los archivos binarios pueden utilizarlo de manera coincidente. Por lo tanto, sugeriría EOT="$(printf $'\x1c')"en shells avanzados o de lo contrario EOT="$(awk 'BEGIN{printf"%c",28}')"para la compatibilidad. Deberá citarlo cuando lo coloque en su lugar, como echo "$EOT$EOT$EOT"(triplicado para reducir las probabilidades de que coincida con un archivo binario).
Adam Katz
Si vas a usar el tty, ¿por qué no usar el ttycomando? A menos que me falte algo, ¿qué supongo que es posible?
Pryftan
14

Tal vez no sea exactamente lo que quieres ... Pero tal vez considere enviar un tarball a través de la tubería abierta por ssh?

Dijiste eso:

No tengo permitido almacenar archivos en el host.

Es posible que no tenga un directorio personal de escritura u otra ubicación conveniente para almacenar archivos a largo plazo, pero diría que es poco probable que no tenga una ubicación de escritura, incluso si hay un temporal tmpfsque estará disponible solo para su conexión particular

Muchos programas (e incluso rutinas de libc) requieren una escritura /tmp, por lo que es muy probable que haya uno disponible para usted.

Luego, puede usar un script que descomprima el tarball en un directorio temporal, ejecute su programa y limpie a través de la conexión 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
  '

Esto podría necesitar un cuidado especial con las rutas de archivo y hay algunos casos de esquina para considerar (probar para ellos), pero el enfoque general debería funcionar.

Si no hay un directorio de escritura disponible, un posible enfoque sería modificar programpara tomar un tarball como entrada única y descomprimir su contenido en la memoria. Por ejemplo, si se programtrata de un script de Python, el uso del tarfilemódulo incorporado fácilmente lograría algo así.

filbranden
fuente
3
Incluso sin pasar por la molestia de tarballing: he decidido más o menos escribir un archivo /tmp/y usarlo directamente.
Green Cloak Guy
2
El enfoque tarball tiene algunas ventajas, ya que usa una única conexión ssh (no es necesario probar el éxito / fracaso) y es probable que no se ejecuten varias ejecuciones simultáneas entre sí (use y / o elimine archivos en / tmp para ejecuciones separadas) .) Pero creo que eso es lo mejor que obtendrás de ssh tal como existe ahora. ¡Buena suerte!
filbranden
3
@GreenCloakGuy Así que después de todo, usted puede almacenar archivos en el servidor muy bien. Modifique la pregunta en consecuencia.
mosvy
1
@mosvy puede , sí, pero no quiero . La pregunta es, en un "¿es posible hacer esto sin almacenar ningún archivo", cuya respuesta parece ser "tal vez, pero no en mi caso"
Green Cloak Guy
5

Si puede configurar un escucha TCP (puede ser un puerto más alto), entonces podría usar una segunda sesión SSH para establecer la segunda fuente de entrada nc.

Ejemplo:

Hay este script en el servidor ( ~/script.bash):

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

Y existen estos dos archivos localmente:

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

Ahora primero inicie la segunda fuente ( $serves el servidor):

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

Y ejecute el comando apropiado:

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

fuente
2

Se estableció en un comentario que /tmpse puede escribir, así que simplemente copie previamente uno de los archivos:

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

Esto también limpia el archivo copiado después de una ejecución exitosa (cambie &&a a ;si desea eliminarlo independientemente del éxito, pero luego tenga en cuenta que perderá el valor de salida).


Si eso no es aceptable, propondría retoques /path/to/programo un contenedor que pueda separar los dos archivos de una sola secuencia 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

Esto utiliza el separador de información ASCII cuatro (separador de archivos, FS) y lo ha triplicado para minimizar la posibilidad de que un archivo binario coincida con esa cadena. Su tweaked_programentonces dividir la entrada dado el separador y luego operar en los dos archivos guardados como variables.

Por supuesto, si usa un lenguaje que tiene bibliotecas para manejar tarballs, un enfoque más seguro y limpio sería simplemente canalizar tardicho código de sshesta manera:

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

Y tweaked_programdescomprimiría y abriría el archivo, guardaría cada archivo en una variable diferente y luego ejecutaría la programlógica del original en las variables.

Adam Katz
fuente
1

Si se le permite reenviar puertos sshy tiene acceso a wgetla máquina remota y a busyboxla máquina local, puede hacer 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 $!

(utilizando cat como programa de ejemplo que toma dos argumentos de archivo).

Solo la capacidad de reenviar puertos -Res esencial; en lugar de hacer http, puede usar otros métodos, por ejemplo. si su netcatadmite las opciones -dy -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)

Puede haber formas de reemplazar el <(...) proceso si el shell de inicio de sesión en la máquina remota no es similar a ksh o bash.

En general, esto no es demasiado bueno: un mejor conocimiento sobre el sistema / shells / config / permisos (que no ha proporcionado) podría permitir soluciones más inteligentes.

Mosvy
fuente
-1

ACTUALIZACIÓN: Esto realmente no funciona, ssh tiene una idea muy clara de lo que significa stdin y stdout / stderr y realmente no permite que usurpando stderr lea de él. Eliminaré esta respuesta en unos días ya que no funciona. Gracias por la muy interesante discusión !!!


Desafortunadamente, no hay una excelente manera de hacer esto directamente, ya que el sshcliente solo pasará los tres descriptores de archivo ( stdin, stdoutystderr ) en el servidor y que no tiene disposiciones para pasar descriptores de archivos adicionales (que sería útil para este caso en particular .)

(También tenga en cuenta que el protocolo SSH tiene disposiciones para pasar descriptores de archivo adicionales, solo que el sshcliente no implementa una forma de usar esa característica. Teóricamente, extender el cliente con un parche sería suficiente para exponer esta característica).

Una forma hacky de lograr lo que está buscando es usar el descriptor de archivo 2 (el stderrdescriptor de archivo) para pasar el segundo archivo. Algo como:

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

Esto debería funcionar, podría causar un problema si programintenta escribir en stderr. Puede evitar eso volviendo a hacer malabarismos con los descriptores de archivos en el extremo remoto antes de ejecutar el programa. Puede pasar file2.txtal descriptor de archivo 3 y volver a abrir stderr para duplicar stdout:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/fd/3 \
      '3<&2' '2>&1' \
      <file1.txt 2<file2.txt
filbranden
fuente
Realmente no he probado esto (escribiendo en un teléfono) pero al menos en teoría esperaría que funcione. Avíseme si por alguna razón no lo hace. ¡Salud!
filbranden
Eso no funcionará en absoluto. Ni siquiera con un ssh pirateado. AFAIK stderr no usa un canal separado, sino algún tipo especial de mensaje. Pero sí, poder acceder a los canales ssh como canalizaciones o tomas de dominio Unix sin nombre (en lugar de tener que hacer reenvíos de socket) sería genial, simplemente no parece posible en ningún tipo o forma.
mosvy
@mosvy, fd 0, 1 y 2 en el comando remoto serán 3 canales diferentes. Los datos viajan a través de 3 canales diferentes multiplexados en la conexión ssh, pero esos son unidireccionales (de cliente a servidor para fd 0 y de servidor a cliente para 1 y 2), por lo que incluso en sistemas donde las tuberías son bidireccionales, eso ganó ' t trabajo. En Linux, abrir / dev / stderr en modo de solo lectura da en el otro extremo de la tubería, por lo que nunca funcionaría tampoco.
Stéphane Chazelas
Tenga en cuenta que el último supone que el shell de inicio de sesión del usuario en remote.name es similar a Bourne ( 3<&2es un operador de shell Bourne, y sshd ejecuta el shell de inicio de sesión del usuario para interpretar el comando enviado por el cliente)
Stéphane Chazelas
2
@ StéphaneChazelas Nope, se usa un solo canal ssh para stdin / stdout / stderr, y los datos de stderr se transfieren a través de SSH_MSG_CHANNEL_EXTENDED_DATAmensajes con el tipo establecido en SSH_EXTENDED_DATA_STDERR. Puedes leer todo aquí
mosvy