Comment puis-je exécuter des commandes arbitrairement complexes en utilisant sudo sur ssh?

80

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'ici, je suis venu avec 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 problèmes avec les citations. Je ne sais pas comment je pourrais passer cet exemple de commande complexe à bash -c, alors que \"les limites de la commande que je passe sont déjà délimitées (et que je ne sais donc pas comment citer / tmp / Some directory, qui inclut des espaces.

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

VoY
la source
11
Copiez un script dans $ remote puis exécutez-le.
user9517 le
Cela fonctionnerait, mais je trouverais une solution qui ne laisse aucun fichier de script (en cas de problème, etc.) serait un peu plus propre.
VoY
9
avoir le script lui-même à la fin de chaque exécution
thanasisk
3
Pensez à utiliser le tissu fabfile.org . Peut vous rendre la vie beaucoup plus facile si vous devez vous déplacer beaucoup à distance.
Nils Toedtmann
2
Si vous avez installé Ansible, cette commande affiche la documentation de votre cas d'utilisation: script ansible-doc
bbaassssiiee

Réponses:

146

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

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

Ceci encodera le script, avec des virgules, des barres obliques inverses, des guillemets et des variables dans une chaîne sécurisée, et l'enverra à l'autre serveur. ( -w0est nécessaire pour désactiver le retour à la ligne, ce qui se produit par défaut à la colonne 76). De l’autre côté, $(base64 -d)décodera le script et le transmettra à bash pour qu’il soit exécuté.

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

ThoriumBR
la source
2
Ou même:ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash"
Niklas B.
1
Il est à noter que dans la plupart des cas, vous pouvez remplacer l64 par lzop ou par gzip pour un temps de transfert plus court. Cependant, il peut y avoir des cas extrêmes. YMMV.
CodeGnome
1
Bonne note, mais si c'est un script, je ne m'attends pas à ce que le transfert soit supérieur à quelques ko.
ThoriumBR
7
Il y a une utilisation inutile d'écho ici aussi. base64 script | ssh remotehost 'base64 -d | sudo bash'serait assez :)
hobbs
Bien que je n’aie pas testé cette solution, le résultat du script sera-t-il affiché, par exemple, «yum check-update» dans la sortie standard? Je voulais poser la question parce que si quelqu'un pense que je fais quelque chose de naïf, je peux en prendre quelques-unes.
Soham Chakraborty
34

Voir l' -ttoption? Lire le ssh(1)manuel.

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 le :!cat % | ssh -tt somemachinetruc.

moebius_eye
la source
3
Bien sûr, :!cat % | (command)peut être fait comme :w !(command)ou :!(command) < %.
Scott
2
Oui. Ma ligne est un abus de chat
moebius_eye
en cours d'exécution ssh -tt, mon ssh ne s'arrête pas après l'exécution de certaines commandes (l'ajout exità la fin n'aide pas)
10

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

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

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

En faisant les choses dans cet ordre (le rmpremier, les autres ensuite), il sera même supprimé s'il échoue à un moment donné.

dr. Sybren
la source
8

Vous pouvez utiliser le %qspécificateur de format avec printfpour 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 manière 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 le truc).

Voici un exemple plus complexe montrant que cela fonctionne aussi bien entre crochets et 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 sudomais cela 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 même simplement éviter de créer une variable entièrement:

ssh user@host "bash -c $(printf '%q' "[[ -f test ]] && echo 'this works as well'")"
Tom Fenech
la source
1
c'est une solution géniale. J'ai déjà dû utiliser printf auparavant pour une situation similaire, mais je l'oublie toujours. Merci de poster ceci :-)
Jon L.
8

Voici la manière "correcte" (syntaxique) d'exécuter quelque chose comme ceci dans 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
)"

Pour une explication détaillée du fonctionnement de cette procédure, veuillez consulter https://stackoverflow.com/a/21761956/111948.

Dejay Clayton
la source
5

Êtes-vous conscient que vous pouvez utiliser sudopour 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 de base de cet utilisateur avant d'exécuter le shell. La commande est exécutée dans un environnement similaire à celui qu'un utilisateur recevrait lors de la connexion. La section relative à l'environnement de commande dans le manuel de sudoers (5) explique comment l'option -i affecte l'environnement dans lequel une commande est exécutée lorsque la politique sudoers est en cours d'utilisation.

justinpc
la source
cela semble être la solution évidente pour moi. > sudo -i -u brian
code_monk
Cela fonctionne mieux lorsqu'il est combiné avec "ssh -t" dans le cas de l'accès à distance. Ce qui est inclus dans la question, mais mérite d'être répété pour les gens "je ne peux pas lire ceci / tout / page". :)
dannysauer
4

Vous pouvez définir le script sur votre machine locale, puis le catdiriger 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
jas_raj
la source
5
UUOC vous pouvez utiliser <
user9517 le
Pouvez-vous modifier l'exemple pour inclure sudo -u scriptuser? Hérédoc serait-il utilisable étant donné que je devais avoir des variables dans le script, je serais en train de me diriger vers la machine?
VoY
J'essayais: echo "sudo `cat fileForSsh.txt`" | ssh ...mais je continue à obtenir sudo: sorry, you must have a tty to run sudo.
jas_raj
Bien que cette question puisse aider si vous pouvez obtenir le /etc/sudoersfichier modifié
jas_raj
1
Cela fonctionne pour moi:ssh -tq user@host "sudo bash -s" < test.sh
AVee
3

Simple et facile.

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

rafaelrms
la source
1

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

Un exemple utilisant les tuyaux 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 connecté et installe un programme 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 toute la commande 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"
Lekensteyn
la source
1

Voici ma solution (pas vraiment testée) pourrie:

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

Non vous pouvez faire:

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

La prochaine étape consiste à créer un bettersshscript qui effectue déjà ceci:

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

Pour ceux qui ont besoin de passer des paramètres au script, cela devrait être comme suit:

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

Commencez par créer 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"
Franciscon Santos
la source