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?

VoY
quelle
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.

ThoriumBR
quelle
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 .

moebius_eye
quelle
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.

DR. Sybren
quelle
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'")"
Tom Fenech
quelle
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

Dejay Clayton
quelle
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.

justinpc
quelle
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
jas_raj
quelle
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

rafaelrms
quelle
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"
Lekensteyn
quelle
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)"
ysdx
quelle
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>"
Romande
quelle
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"
Franciscon Santos
quelle