Wie kann ich einen beliebig komplexen Befehl mit sudo über ssh ausführen?


80

Ich habe ein System, bei dem ich mich nur unter meinem Benutzernamen (myuser) anmelden kann, aber ich muss Befehle als anderer Benutzer (scriptuser) ausführen. Bisher habe ich mir Folgendes ausgedacht, um die Befehle auszuführen, die ich benötige:

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

Wenn ich jedoch versuche, einen komplexeren Befehl auszuführen, wie [[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"ich schnell Probleme mit dem Zitieren bekomme. Ich bin nicht sicher, wie ich diesen komplexen Beispielbefehl übergeben könnte bash -c, wenn \"bereits die Grenzen des Befehls, den ich übergebe , abgegrenzt sind (und daher weiß ich nicht, wie ich / tmp / Some in Anführungszeichen setzen soll, der ein Leerzeichen enthält.

Gibt es eine allgemeine Lösung, die es mir erlaubt, jeden Befehl weiterzugeben, egal wie komplex / verrückt das Zitieren ist, oder ist dies eine Art von Einschränkung, die ich erreicht habe? Gibt es andere mögliche und vielleicht besser lesbare Lösungen?


11
Kopieren Sie ein Skript nach $ remote und führen Sie es aus.
user9517

Das würde funktionieren, aber ich finde eine Lösung, die keine Skriptdateien zurücklässt (falls etwas fehlschlägt usw.), wäre ein bisschen sauberer.
VoY

9
Habe

3
Erwägen Sie, fabric fabfile.org zu verwenden . Kann Ihnen das Leben viel einfacher machen, wenn Sie viel aus der Ferne arbeiten müssen.
Nils Toedtmann

2
Wenn Sie Ansible installiert haben, zeigt dieser Befehl die Dokumentation für Ihren Anwendungsfall an: ansible-doc script
bbaassssiiee

Antworten:


146

Ein Trick, den ich manchmal benutze, ist, base64 zu verwenden, um die Befehle zu kodieren, und es an die andere Site weiterzuleiten:

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

Dadurch wird das Skript mit Kommas, Backslashes, Anführungszeichen und Variablen in einer sicheren Zeichenfolge codiert und an den anderen Server gesendet. ( -w0Dies ist erforderlich, um den Zeilenumbruch zu deaktivieren, der standardmäßig in Spalte 76 erfolgt.) Auf der anderen Seite $(base64 -d)wird das Skript dekodiert und zur Ausführung an die Bash übergeben.

Ich habe nie ein Problem damit bekommen, egal wie komplex das Skript war. Behebt das Problem mit der Flucht, weil Sie nichts zu entkommen brauchen. Es wird keine Datei auf dem Remote-Host erstellt, und Sie können mühelos äußerst komplizierte Skripts ausführen.


2
Oder auch:ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash"
Niklas B.

1
In den meisten Fällen können Sie base64 durch lzop oder gzip ersetzen, um die Übertragungszeiten zu verkürzen. Es kann jedoch Randfälle geben. YMMV.
CodeGnome

1
Gute Nachricht, aber wenn es ein Skript ist, erwarte ich keine Übertragung von mehr als ein paar KB.
ThoriumBR

7
Es gibt auch hier eine nutzlose Verwendung von Echo. base64 script | ssh remotehost 'base64 -d | sudo bash'wäre genug :)
hobbs

Wenn ich diese Lösung noch nicht getestet habe, wird dann das Ergebnis des Skripts ausgegeben, z. B. 'yum check-update' in der Standardausgabe? Ich wollte fragen, denn wenn jemand denkt, ich mache etwas offensichtlich Naives, kann ich ein paar Dinge aufgreifen.
Soham Chakraborty

34

Sehen Sie die -ttOption? Lesen Sie das ssh(1)Handbuch.

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

Eine Sache, die ich oft mache, ist, vim zu benutzen und den :!cat % | ssh -tt somemachineTrick anzuwenden .


3
Natürlich :!cat % | (command)kann als :w !(command)oder getan werden :!(command) < %.
Scott

2
Ja. Meine Linie ist ein Katzenmissbrauch
moebius_eye

running ssh -ttbewirkt , dass mein ssh nicht beendet wird, nachdem bestimmte Befehle ausgeführt wurden (das Hinzufügen exitam Ende hilft nicht)

10

Ich denke, die einfachste Lösung liegt in einer Änderung des Kommentars von @ thanasisk.

Erstellen Sie ein Skript, senden Sie scpes an den Computer, und führen Sie es dann aus.

Habe das Drehbuch rm selbst am Start. Die Shell hat die Datei geöffnet, wurde also geladen und kann dann problemlos entfernt werden.

Wenn Sie die Dinge in dieser Reihenfolge rmerledigen ( erstens, zweitens), wird sie sogar entfernt, wenn sie irgendwann fehlschlägt.


8

Sie können den %qFormatbezeichner mit verwenden printf, um die für Sie entstehende Variable zu pflegen:

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

printf -v schreibt die Ausgabe in eine Variable (in diesem Fall $cmd_str ). Ich denke, dass dies der einfachste Weg ist, es zu tun. Es ist nicht erforderlich, Dateien zu übertragen oder die Befehlszeichenfolge zu codieren (so oft ich den Trick mag).

Hier ist ein komplexeres Beispiel, das zeigt, dass es auch für Dinge wie eckige Klammern und kaufmännisches Und funktioniert:

$ 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

Ich habe es noch nicht getestet, sudoaber es sollte so einfach sein wie:

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

Wenn Sie möchten, können Sie einen Schritt überspringen und das Erstellen der Zwischenvariablen vermeiden:

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

Oder vermeiden Sie es einfach, eine Variable vollständig zu erstellen:

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

1
Das ist eine großartige Lösung. Ich musste eigentlich printf für eine ähnliche Situation verwenden, aber ich vergesse es immer. Danke, dass du das gepostet hast :-)
Jon L.

8

Hier ist die "richtige" (syntaktische) Methode, um so etwas in bash auszuführen:

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
)"

Eine ausführliche Beschreibung der Funktionsweise finden Sie unter https://stackoverflow.com/a/21761956/111948


5

Ist Ihnen bewusst, dass Sie sudoeine Shell verwenden können, in der Sie als ausgewählter Benutzer Befehle ausführen können?

-i, --login

Führen Sie die vom Kennwortdatenbankeintrag des Zielbenutzers angegebene Shell als Anmeldeshell aus. Dies bedeutet, dass anmeldungsspezifische Ressourcendateien wie .profile oder .login von der Shell gelesen werden. Wenn ein Befehl angegeben ist, wird er über die Option -c der Shell zur Ausführung an die Shell übergeben. Wird kein Befehl angegeben, wird eine interaktive Shell ausgeführt. sudo versucht, in das Ausgangsverzeichnis dieses Benutzers zu wechseln, bevor die Shell ausgeführt wird. Der Befehl wird in einer Umgebung ausgeführt, die der Umgebung ähnelt, die ein Benutzer beim Anmelden erhalten würde. Der Abschnitt Befehlsumgebung im sudoers (5) -Handbuch dokumentiert, wie sich die Option -i auf die Umgebung auswirkt, in der ein Befehl ausgeführt wird, wenn Die sudoers-Richtlinie wird verwendet.


Das scheint mir die naheliegende Lösung zu sein. > sudo -i -u brian
code_monk

Das funktioniert am besten in Kombination mit "ssh -t" bei Fernzugriff. Was in der Frage enthalten ist, aber für die Leute "Ich kann diese / ganze / Seite nicht lesen" wiederholt werden muss. :)
dannysauer

4

Sie können das Skript auf Ihrem lokalen Computer definieren und es dann catan den Remote-Computer weiterleiten:

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

5
UUOC können Sie <
user9517

Können Sie das Beispiel ändern, um einzuschließen sudo -u scriptuser? Wäre heredoc verwendbar, wenn ich bedenke, dass das Skript Variablen enthalten muss, die an die Maschine weitergeleitet werden sollen?
VoY

Ich habe versucht: echo "sudo `cat fileForSsh.txt`" | ssh ...aber ich bekomme weiter sudo: sorry, you must have a tty to run sudo.
jas_raj

Obwohl diese Frage helfen kann, wenn Sie die /etc/sudoersDatei geändert bekommen können
jas_raj

1
Dies funktioniert für mich:ssh -tq user@host "sudo bash -s" < test.sh
AVee

3

Simpel und einfach.

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


1

Wenn Sie eine ausreichend moderne Bash-Shell verwenden, können Sie Ihre Befehle in eine Funktion einfügen und diese Funktion als Zeichenfolge mit ausgeben export -p -f function_name. Das Ergebnis dieser Zeichenfolge kann dann beliebige Befehle enthalten, die nicht unbedingt als root ausgeführt werden müssen.

Ein Beispiel mit Pipes aus meiner Antwort auf 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"

In einem Beispiel, das abgerufen wird, wird die Datei für den angemeldeten Benutzer gespeichert und ein Programm als Root installiert:

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"

Wenn Sie den gesamten Befehl mit ausführen möchten sudo, können Sie Folgendes verwenden:

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

Hier ist meine (nicht wirklich getestete) beschissene Lösung dafür:

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

Nicht können Sie tun:

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

Der nächste Schritt besteht darin, ein bettersshSkript zu erstellen, das dies bereits tut:

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

1

Für diejenigen von Ihnen, die Parameter an das Skript übergeben müssen, sollte dies wie folgt aussehen:

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

1

Erstellen Sie zunächst ein lokales Skript und führen Sie es anschließend mit dem folgenden Befehl aus der Ferne aus:

cat <Local Script.sh> | ssh user@server "cat - | sudo -u <user> /bin/bash"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.