web-dev-qa-db-fra.com

Comment puis-je exécuter une commande arbitrairement complexe en utilisant Sudo sur ssh?

J'ai un système auquel je ne peux me connecter que sous mon nom d'utilisateur (myuser), mais je dois exécuter des commandes en tant qu'autre utilisateur (scriptuser). Jusqu'à présent, j'ai trouvé ce qui suit pour exécuter les commandes dont j'ai besoin:

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

Si toutefois, lorsque j'essaie d'exécuter une commande plus complexe, telle que [[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory" J'ai rapidement des ennuis avec les citations. Je ne sais pas comment je pourrais passer cet exemple de commande complexe à bash -c, quand \" délimite déjà les limites de la commande que je passe (et donc je ne sais pas comment citer le répertoire/tmp/Some, qui comprend un espace.

Existe-t-il une solution générale me permettant de passer n'importe quelle commande, quelle que soit la complexité/la folie de la citation, ou est-ce une sorte de limitation que j'ai atteinte? Existe-t-il d'autres solutions possibles et peut-être plus lisibles?

81
VoY

Une astuce que j'utilise parfois est d'utiliser base64 pour encoder les commandes et de le diriger vers bash sur l'autre site:

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

Cela encodera le script, avec des virgules, des barres obliques inverses, des guillemets et des variables dans une chaîne sûre, et l'enverra à l'autre serveur. (-w0 Est requis pour désactiver le retour à la ligne, ce qui se produit à la colonne 76 par défaut). De l'autre côté, $(base64 -d) décodera le script et l'alimentera en bash à exécuter.

Je n'ai jamais eu de problème avec ça, peu importe la complexité du script. Résout le problème de l'évasion, car vous n'avez rien à échapper. Il ne crée pas de fichier sur l'hôte distant et vous pouvez exécuter facilement des scripts extrêmement complexes.

148
ThoriumBR

Voir l'option -tt? Lisez le manuel ssh(1).

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

Une chose que je fais souvent est d'utiliser vim et d'utiliser l'astuce :!cat % | ssh -tt somemachine.

35
moebius_eye

Je pense que la solution la plus simple réside dans une modification du commentaire de @ thanasisk.

Créez un script, scp sur la machine, puis exécutez-le.

Ayez le script rm lui-même au début. Le shell a ouvert le fichier, il a donc été chargé et peut ensuite être supprimé sans problème.

En faisant les choses dans cet ordre (rm d'abord, d'autres choses ensuite), il sera même supprimé quand il échouera à un moment donné.

10
dr. Sybren

MISE À JOUR: Les exemples utilisent désormais explicitement Sudo.

Voici un moyen d'utiliser la syntaxe Bash avec des affectations composées pour exécuter des commandes arbitrairement complexes sur SSH avec Sudo:

CMD=$( cat <<'EOT'
echo "Variables like '${HOSTNAME}' and commands like $( whoami )"
echo "will be interpolated on the server, thanks to the single quotes"
echo "around 'EOT' above."
EOT
) \
SSHCMD=$(
  printf 'ssh -tq myuser@hostname Sudo -u scriptuser bash -c %q' "${CMD}" ) \
bash -c '${SSHCMD}'

CMD=$( cat <<EOT
echo "If you want '${HOSTNAME}' and $( whoami ) to be interpolated"
echo "on the client instead, omit the the single quotes around EOT."
EOT
) \
SSHCMD=$(
  printf 'ssh -tq myuser@hostname Sudo -u scriptuser bash -c %q' "${CMD}" ) \
bash -c '${SSHCMD}'

Le formatage correct des commandes à exécuter dynamiquement ailleurs (par exemple, des shells imbriqués, des shells SSH distants, etc.) peut être très délicat - voir https://stackoverflow.com/a/53197638/111948 pour une bonne description de pourquoi. Les structures bash compliquées, comme la syntaxe ci-dessus, peuvent aider ces commandes à fonctionner correctement.

Pour plus d'informations sur la façon dont cat et << se combinent pour former des documents en ligne ici , voir https://stackoverflow.com/a/21761956/111948

8
Dejay Clayton

Vous pouvez utiliser le %q spécificateur de format avec printf pour prendre en charge la variable qui s'échappe pour vous:

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

printf -v écrit la sortie dans une variable (dans ce cas, $cmd_str). Je pense que c'est la façon la plus simple de le faire. Il n'est pas nécessaire de transférer des fichiers ou d'encoder la chaîne de commande (autant que j'aime l'astuce).

Voici un exemple plus complexe montrant que cela fonctionne aussi pour des choses comme les crochets et les esperluettes:

$ 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

Je ne l'ai pas testé avec Sudo mais ça devrait être aussi simple que:

ssh user@Host "Sudo -u scriptuser bash -c $cmd_str"

Si vous le souhaitez, vous pouvez ignorer une étape et éviter de créer la variable intermédiaire:

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

Ou évitez même de créer une variable entièrement:

ssh user@Host "bash -c $(printf '%q' "[[ -f test ]] && echo 'this works as well'")"
8
Tom Fenech

Savez-vous que vous pouvez utiliser Sudo pour vous donner un Shell où vous pouvez exécuter des commandes en tant qu'utilisateur choisi?

-i, --login

Exécutez le shell spécifié par l'entrée de la base de données de mots de passe de l'utilisateur cible en tant que shell de connexion. Cela signifie que les fichiers de ressources spécifiques à la connexion tels que .profile ou .login seront lus par le shell. Si une commande est spécifiée, elle est transmise au Shell pour exécution via l'option -c du Shell. Si aucune commande n'est spécifiée, un shell interactif est exécuté. Sudo tente de passer au répertoire personnel de cet utilisateur avant d'exécuter le shell. La commande est exécutée avec un environnement similaire à celui qu'un utilisateur recevrait à la connexion. La section Environnement de commande du manuel sudoers (5) documente comment l'option -i affecte l'environnement dans lequel une commande est exécutée lorsque la stratégie sudoers est en cours d'utilisation.

5
justinpc

Vous pouvez définir le script sur votre machine locale puis cat et le diriger vers la machine distante:

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
4
jas_raj

Simple et facile.

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

3
rafaelrms

Si vous utilisez un Bash Shell suffisamment moderne, vous pouvez placer vos commandes dans une fonction et imprimer cette fonction sous forme de chaîne en utilisant export -p -f function_name. Le résultat de cette chaîne peut alors contenir des commandes arbitraires qui ne doivent pas nécessairement s'exécuter en tant que root.

Un exemple utilisant des pipes de ma réponse sur 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 exemple qui récupère enregistre le fichier pour l'utilisateur de connexion et installe un programme à la racine:

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 vous souhaitez exécuter la commande entière en utilisant Sudo, vous pouvez utiliser ceci:

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"
1
Lekensteyn

Créez d'abord un script local, puis exécutez-le à distance à l'aide de la commande suivante:

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

Pour ceux d'entre vous qui doivent passer des paramètres au script, cela devrait être le suivant:

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

Voici ma solution merdique (pas vraiment testée) pour cela:

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

Vous ne pouvez pas faire:

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

L'étape suivante consiste à créer un script betterssh qui le fait déjà:

betterssh -tq myuser@hostname Sudo -u scriptuser bash -c "$(Shell-escape ls -al)"
1
ysdx