¿Cómo puedo ejecutar comandos arbitrariamente complejos usando sudo sobre ssh?

80

Tengo un sistema en el que solo puedo iniciar sesión con mi nombre de usuario (myuser), pero necesito ejecutar comandos como otro usuario (scriptuser). Hasta ahora, se me ocurrió lo siguiente para ejecutar los comandos que necesito:

ssh -tq myuser@hostname "sudo -u scriptuser bash -c \"ls -al\""

Sin embargo, si trato de ejecutar un comando más complejo, como [[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"rápidamente me meto en problemas con las citas. No estoy seguro de cómo podría pasar este comando complejo de ejemplo bash -c, cuando \"ya delimita los límites del comando que estoy pasando (por lo que no sé cómo citar / tmp / Some directorio, que incluye espacios).

¿Existe una solución general que me permita pasar algún comando sin importar cuán compleja / loca sea la cita, o es esta una especie de limitación que he alcanzado? ¿Hay otras soluciones posibles y quizás más legibles?

VoY
fuente
11
Copie un script en $ remote y luego ejecútelo.
user9517
Eso funcionaría, pero encuentro una solución que no deja ningún archivo de script (en caso de que algo falle, etc.) sería un poco más limpio.
VoY
99
tener el script rm en sí mismo al final de cada ejecución
thanasisk
3
Considere usar la tela fabfile.org . Puede hacerte la vida mucho más fácil si tienes que sudo mucho de forma remota.
Nils Toedtmann
2
Si tiene instalado Ansible, este comando muestra la documentación de su caso de uso: script ansible-doc
bbaassssiiee

Respuestas:

146

Un truco que uso a veces es usar base64 para codificar los comandos y canalizarlo para bash en el otro sitio:

MYCOMMAND=$(base64 -w0 script.sh)
ssh user@remotehost "echo $MYCOMMAND | base64 -d | sudo bash"

Esto codificará el script, con cualquier coma, barra diagonal inversa, comillas y variables dentro de una cadena segura, y lo enviará al otro servidor. ( -w0se requiere para deshabilitar el ajuste de línea, que ocurre en la columna 76 de forma predeterminada). Por otro lado, $(base64 -d)decodificará el script y lo alimentará a bash para que se ejecute.

Nunca tuve ningún problema con eso, no importa cuán complejo fuera el script. Resuelve el problema de escapar, porque no necesita escapar de nada. No crea un archivo en el host remoto, y puede ejecutar scripts muy complicados con facilidad.

ThoriumBR
fuente
2
O incluso:ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash"
Niklas B.
1
Vale la pena señalar que, en la mayoría de los casos, puede sustituir lzop o gzip por base64 para tiempos de transferencia más rápidos. Sin embargo, puede haber casos extremos. YMMV.
CodeGnome
1
Buena nota, pero si se trata de un script, no espero que ninguna transferencia sea más de unos pocos kb.
ThoriumBR
77
Aquí también hay un uso inútil del eco. base64 script | ssh remotehost 'base64 -d | sudo bash'sería suficiente :)
hobbs
Si bien no he probado esta solución, ¿generará el resultado del script, por ejemplo, 'yum check-update' en el stdout? Quería preguntar porque si alguien piensa que estoy haciendo algo obviamente ingenuo, puedo recoger algunas cosas.
Soham Chakraborty
34

Ver la -ttopción? Lee el ssh(1)manual.

ssh -tt root@host << EOF
sudo some # sudo shouldn't ask for a password, otherwise, this fails. 
lines
of
code 
but be careful with \$variables
and \$(other) \`stuff\`
exit # <- Important. 
EOF

Una cosa que hago a menudo es usar vim y usar el :!cat % | ssh -tt somemachinetruco.

moebius_eye
fuente
3
Por supuesto :!cat % | (command)se puede hacer como :w !(command)o :!(command) < %.
Scott, el
2
Si. Mi línea es un abuso de gatos
moebius_eye
en ejecución ssh -tthace que mi ssh para no termina después de ciertos comandos se ejecutan (añadiendo exital final no ayuda)
10

Creo que la solución más fácil radica en una modificación del comentario de @ thanasisk.

Cree un script scppara la máquina y luego ejecútelo.

Tener el script en rmsí mismo al comienzo. El shell ha abierto el archivo, por lo que se ha cargado y luego se puede eliminar sin problemas.

Al hacer las cosas en este orden ( rmprimero, otras cosas en segundo lugar), incluso se eliminará cuando falle en algún momento.

Dr. Sybren
fuente
8

Puede usar el %qespecificador de formato con printfpara encargarse del escape de variables por usted:

cmd="ls -al"
printf -v cmd_str '%q' "$cmd"
ssh user@host "bash -c $cmd_str"

printf -vescribe el resultado en una variable (en este caso, $cmd_str). Creo que esta es la forma más sencilla de hacerlo. No es necesario transferir ningún archivo o codificar la cadena de comandos (por mucho que me guste el truco).

Aquí hay un ejemplo más complejo que muestra que funciona para cosas como corchetes y símbolos también:

$ ssh user@host "ls -l test"
-rw-r--r-- 1 tom users 0 Sep  4 21:18 test
$ cmd="[[ -f test ]] && echo 'this really works'"
$ printf -v cmd_str '%q' "$cmd"
$ ssh user@host "bash -c $cmd_str"
this really works

No lo he probado sudopero debería ser tan simple como:

ssh user@host "sudo -u scriptuser bash -c $cmd_str"

Si lo desea, puede omitir un paso y evitar crear la variable intermedia:

$ ssh user@host "bash -c $(printf '%q' "$cmd")"
this really works

O incluso simplemente evite crear una variable por completo:

ssh user@host "bash -c $(printf '%q' "[[ -f test ]] && echo 'this works as well'")"
Tom Fenech
fuente
1
Esta es una solución increíble. De hecho, he tenido que usar printf antes para una situación similar, pero siempre me olvido de eso. Gracias por publicar esto :-)
Jon L.
8

Aquí está la forma "correcta" (sintáctica) de ejecutar algo como esto en bash:

ssh user@server "$( cat <<'EOT'
echo "Variables like '${HOSTNAME}' and commands like $( uname -a )"
echo "will be interpolated on the server, thanks to the single quotes"
echo "around 'EOT' above.
EOT
)"

ssh user@server "$( cat <<EOT
echo "If you want '${HOSTNAME}' and $( uname -a ) to be interpolated"
echo "on the client instead, omit the the single quotes around EOT."
EOT
)"

Para obtener una explicación detallada de cómo funciona esto, consulte https://stackoverflow.com/a/21761956/111948

Dejay Clayton
fuente
5

¿Sabe que puede usarlo sudopara darle un shell donde puede ejecutar comandos como el usuario elegido?

-i, --login

Ejecute el shell especificado por la entrada de la base de datos de contraseña del usuario objetivo como un shell de inicio de sesión. Esto significa que el shell leerá los archivos de recursos específicos de inicio de sesión como .profile o .login. Si se especifica un comando, se pasa al shell para su ejecución a través de la opción -c del shell. Si no se especifica ningún comando, se ejecuta un shell interactivo. sudo intenta cambiar al directorio de inicio de ese usuario antes de ejecutar el shell. El comando se ejecuta con un entorno similar al que recibiría un usuario al iniciar sesión. La sección Entorno del comando en el manual de sudoers (5) documenta cómo la opción -i afecta el entorno en el que se ejecuta un comando cuando La política de sudoers está en uso.

justinpc
fuente
Esta parece ser la solución obvia para mí. > sudo -i -u brian
code_monk
Eso funciona mejor cuando se combina con "ssh -t" en el caso de acceso remoto. Lo cual está incluido en la pregunta, pero vale la pena repetirlo para la gente "No puedo leer esto / todo / página". :)
dannysauer
4

Puede definir el script en su máquina local y luego caty canalizarlo a la máquina remota:

user@host:~/temp> echo "echo 'Test'" > fileForSsh.txt
user@host:~/temp> cat fileForSsh.txt | ssh localhost

Pseudo-terminal will not be allocated because stdin is not a terminal.
stty: standard input: Invalid argument
Test
jas_raj
fuente
55
UUOC puede usar <
user9517
¿Puedes modificar el ejemplo para incluir sudo -u scriptuser? ¿Se podría usar heredoc teniendo en cuenta que necesito tener variables en el script que estaría canalizando a la máquina?
VoY
Lo intentaba, echo "sudo `cat fileForSsh.txt`" | ssh ...pero sigo haciéndolo sudo: sorry, you must have a tty to run sudo.
jas_raj
Aunque esta pregunta puede ayudar si puede /etc/sudoerscambiar el archivo
jas_raj
1
Esto funciona para mí:ssh -tq user@host "sudo bash -s" < test.sh
AVee
3

Simple y fácil.

ssh user @ servidor "bash -s" <script.sh

rafaelrms
fuente
1

Si usa un shell Bash lo suficientemente moderno, puede poner sus comandos en una función e imprimir esa función como una cadena usando export -p -f function_name. El resultado de esa cadena puede contener comandos arbitrarios que no necesariamente tienen que ejecutarse como root.

Un ejemplo usando tuberías de mi respuesta en Unix.SE :

#!/bin/bash
remote_main() {
   local dest="$HOME/destination"

   tar xzv -C "$dest"
   chgrp -R www-data "$dest"
   # Ensure that newly written files have the 'www-data' group too
   find "$dest" -type d -exec chmod g+s {} \;
}
tar cz files/ | ssh user@host "$(declare -pf remote_main); remote_main"

Un ejemplo que recupera guarda el archivo para el usuario de inicio de sesión e instala un programa como root:

remote_main() {
    wget https://example.com/screenrc -O ~/.screenrc
    sudo apt-get update && sudo apt-get install screen
}
ssh user@host "$(declare -pf remote_main); remote_main"

Si desea ejecutar todo el comando usando sudo, puede usar esto:

remote_main() {
    wget https://example.com/screenrc -O ~user/.screenrc
    apt-get update && apt-get install screen
}
ssh user@host "$(declare -pf remote_main);
    sudo sh -c \"\$(declare -pf remote_main); remote_cmd\""
# Alternatively, if you don't need stdin and do not want to log the command:
ssh user@host "$(declare -pf remote_main);
    (declare -pf remote_main; echo remote_cmd) | sudo sh"
Lekensteyn
fuente
1

Aquí está mi (mal probada) solución horrible para esto:

#!/usr/bin/env ruby
# shell-escape: Escape each argument.
ARGV.each do|a|
  print " '#{a.gsub("'","\'\\\\'\'")}' "
end

No puedes hacer:

ssh -tq myuser@hostname "$(shell-escape sudo -u scriptuser bash -c "$(shell-escape ls -al)")"

El siguiente paso es crear un bettersshscript que ya haga esto:

betterssh -tq myuser@hostname sudo -u scriptuser bash -c "$(shell-escape ls -al)"
ysdx
fuente
1

Para aquellos de ustedes que necesitan pasar parámetros al script, debe ser el siguiente:

ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash -s <param1> <param2> <paramN>"
Romande
fuente
1

Primero, cree un script local y luego ejecútelo de forma remota con el siguiente comando:

cat <Local Script.sh> | ssh user@server "cat - | sudo -u <user> /bin/bash"
Franciscon Santos
fuente