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 [email protected] "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 [email protected] "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 [email protected] "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 [email protected] << 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 [email protected] "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 [email protected] "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 [email protected] "bash -c $cmd_str"
this really works

Je ne l'ai pas testé avec sudomais cela devrait être aussi simple que:

ssh [email protected] "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 [email protected] "bash -c $(printf '%q' "$cmd")"
this really works

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

ssh [email protected] "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 [email protected] "$( 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 [email protected] "$( 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:

[email protected]:~/temp> echo "echo 'Test'" > fileForSsh.txt
[email protected]:~/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 [email protected] "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 [email protected] "$(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 [email protected] "$(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 [email protected] "$(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 [email protected] "$(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 [email protected] "$(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 [email protected] 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 [email protected] "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 [email protected] "cat - | sudo -u <user> /bin/bash"
Franciscon Santos
la source