Jak mogę uruchomić dowolnie złożone polecenie za pomocą sudo over ssh?

80

Mam system, do którego mogę się logować tylko pod moją nazwą użytkownika (myuser), ale muszę uruchamiać polecenia jako inny użytkownik (scriptuser). Do tej pory wymyśliłem następujące polecenia, aby uruchomić potrzebne mi polecenia:

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

Jeśli jednak, gdy spróbuję uruchomić bardziej złożone polecenie, takie jak [[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"szybko, mam problem z cytowaniem. Nie jestem pewien, w jaki sposób mógłbym przekazać to przykładowe złożone polecenie bash -c, gdy \"już wyznaczam granice przekazywanego polecenia (a więc nie wiem, jak cytować katalog / tmp / Some, który zawiera spacje.

Czy istnieje ogólne rozwiązanie pozwalające mi przekazać dowolne polecenie bez względu na to, jak skomplikowane / szalone jest cytowanie, czy też jest to jakieś ograniczenie, które osiągnąłem? Czy istnieją inne możliwe i być może bardziej czytelne rozwiązania?

VoY
źródło
11
Skopiuj skrypt do $ remote, a następnie go uruchom.
user9517
To by działało, ale znajduję rozwiązanie, które nie pozostawia żadnych plików skryptu (na wypadek, gdyby coś się nie powiedzie itp.), Byłoby nieco czystsze.
VoY
9
mieć skrypt rm sam na końcu każdego uruchomienia
thanasisk
3
Rozważ użycie tkaniny fabfile.org . Może znacznie ułatwić ci życie, jeśli będziesz musiał dużo sudo.
Nils Toedtmann
2
Jeśli masz zainstalowany program Ansible, to polecenie pokazuje dokumentację dla twojego przypadku użycia: skrypt ansible-doc
bbaassssiiee

Odpowiedzi:

146

Trik, którego czasami używam, to użycie base64 do kodowania poleceń i potokowanie go w celu uderzenia w drugą stronę:

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

Spowoduje to zakodowanie skryptu ze wszystkimi przecinkami, ukośnikami odwrotnymi, cudzysłowami i zmiennymi wewnątrz bezpiecznego ciągu i wysłanie go na inny serwer. ( -w0jest wymagany do wyłączenia zawijania linii, co domyślnie ma miejsce w kolumnie 76). Z drugiej strony, $(base64 -d)dekoduje skrypt i podaje go do bash, aby został wykonany.

Nigdy nie miałem z tym żadnego problemu, bez względu na to, jak skomplikowany był skrypt. Rozwiązuje problem ucieczki, ponieważ nie musisz niczego uciekać. Nie tworzy pliku na zdalnym hoście i możesz z łatwością uruchamiać bardzo skomplikowane skrypty.

ThoriumBR
źródło
2
Lub nawet:ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash"
Niklas B.
1
Warto zauważyć, że w większości przypadków można zastąpić lzop lub gzip base64, aby skrócić czas transferu. Mogą jednak wystąpić przypadki brzegowe. YMMV.
CodeGnome,
1
Dobra uwaga, ale jeśli jest to skrypt, nie oczekuję, że jakikolwiek transfer będzie większy niż kilka KB.
ThoriumBR
7
Jest tu także bezużyteczne użycie echa. base64 script | ssh remotehost 'base64 -d | sudo bash'wystarczyłoby :)
hobbs
Chociaż nie przetestowałem tego rozwiązania, czy wyświetli wynik skryptu, powiedz na przykład „mniam check-update” na standardowym wyjściu? Chciałem zapytać, bo jeśli ktoś myśli, że robię coś oczywiście naiwnego, mogę wyłapać kilka rzeczy.
Soham Chakraborty,
34

Widzisz -ttopcję? Przeczytaj ssh(1)instrukcję.

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

Często używam vima i :!cat % | ssh -tt somemachinetrików.

moebius_eye
źródło
3
Oczywiście :!cat % | (command)można to zrobić jako :w !(command)lub :!(command) < %.
Scott
2
Tak. Moja linia jest molestowaniem kota
moebius_eye
uruchamianie ssh -ttpowoduje, że mój ssh nie kończy się po uruchomieniu niektórych poleceń (dodawanie exitna końcu nie pomaga)
10

Myślę, że najłatwiejszym rozwiązaniem jest modyfikacja komentarza @ thanasisk.

Utwórz skrypt scpna maszynie, a następnie uruchom go.

Miej rmsam skrypt na początku. Powłoka otworzyła plik, więc został załadowany i można go bez problemu usunąć.

Wykonując czynności w tej kolejności ( rmpo pierwsze, inne po drugie), zostanie nawet usunięty, gdy w pewnym momencie zawiedzie.

dr. Sybren
źródło
8

Możesz użyć specyfikatora %qformatu, printfaby zająć się zmienną dla Ciebie:

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

printf -vzapisuje dane wyjściowe do zmiennej (w tym przypadku $cmd_str). Myślę, że to najprostszy sposób na zrobienie tego. Nie ma potrzeby przesyłania żadnych plików ani kodowania ciągu poleceń (tak jak lubię lewę).

Oto bardziej złożony przykład pokazujący, że działa również w przypadku nawiasów kwadratowych i ampersandów:

$ 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

Nie testowałem tego, sudoale powinno to być tak proste, jak:

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

Jeśli chcesz, możesz pominąć krok i uniknąć tworzenia zmiennej pośredniej:

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

Lub nawet po prostu unikaj tworzenia zmiennej całkowicie:

ssh user@host "bash -c $(printf '%q' "[[ -f test ]] && echo 'this works as well'")"
Tom Fenech
źródło
1
to niesamowite rozwiązanie. Właściwie musiałem wcześniej użyć printf do podobnej sytuacji, ale zawsze o tym zapominam. Dzięki za opublikowanie tego :-)
Jon L.
8

Oto „poprawny” (syntaktyczny) sposób wykonania czegoś takiego w 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
)"

Aby uzyskać dokładne wyjaśnienie, jak to działa, zobacz https://stackoverflow.com/a/21761956/111948

Dejay Clayton
źródło
5

Czy wiesz, że możesz użyć, sudoaby dać ci powłokę, w której możesz uruchamiać polecenia jako wybrany użytkownik?

-i, --login

Uruchom powłokę określoną przez wpis bazy danych haseł użytkownika docelowego jako powłokę logowania. Oznacza to, że pliki zasobów specyficzne dla logowania, takie jak .profile lub .login, zostaną odczytane przez powłokę. Jeśli podano polecenie, jest ono przekazywane do powłoki w celu wykonania za pomocą opcji -c powłoki. Jeśli nie podano polecenia, wykonywana jest powłoka interaktywna. sudo próbuje przejść do katalogu domowego tego użytkownika przed uruchomieniem powłoki. Polecenie jest uruchamiane w środowisku podobnym do środowiska, które użytkownik otrzyma podczas logowania. Sekcja Środowisko poleceń w dokumentacji podręcznika sudoers (5), w jaki sposób opcja -i wpływa na środowisko, w którym polecenie jest uruchamiane, gdy używana jest polityka sudoers.

justinpc
źródło
wydaje mi się to oczywistym rozwiązaniem. > sudo -i -u brian
code_monk
Działa to najlepiej w połączeniu z „ssh -t” w przypadku dostępu zdalnego. Który jest zawarty w pytaniu, ale należy powtarzać dla osób „Nie mogę przeczytać tej / całej / strony”. :)
dannysauer,
4

Możesz zdefiniować skrypt na komputerze lokalnym, a następnie catprzesłać go do zdalnego komputera:

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
źródło
5
UUOC możesz użyć <
user9517
Czy możesz zmodyfikować przykład, aby uwzględnić sudo -u scriptuser? Czy heredoc byłby użyteczny, biorąc pod uwagę, że muszę mieć zmienne w skrypcie, który przesyłam do komputera?
VoY
Próbowałem: echo "sudo `cat fileForSsh.txt`" | ssh ...ale ciągle dostaję sudo: sorry, you must have a tty to run sudo.
jas_raj,
Chociaż to pytanie może pomóc, jeśli można /etc/sudoerszmienić plik
jas_raj
1
To działa dla mnie:ssh -tq user@host "sudo bash -s" < test.sh
AVee
3

Proste i łatwe.

ssh użytkownik @ servidor "bash -s" <script.sh

rafaelrms
źródło
1

Jeśli używasz wystarczająco nowoczesnej powłoki Bash, możesz umieścić swoje polecenia w funkcji i wydrukować tę funkcję jako ciąg znaków export -p -f function_name. Wynik tego ciągu może następnie zawierać dowolne polecenia, które niekoniecznie muszą być uruchamiane jako root.

Przykład użycia potoków z mojej odpowiedzi na 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"

Przykład, który pobiera, zapisuje plik dla zalogowanego użytkownika i instaluje program root:

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"

Jeśli chcesz uruchomić całą komendę sudo, możesz użyć tego:

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
źródło
1

Oto moje (nie tak naprawdę przetestowane) gówniane rozwiązanie:

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

Nie możesz zrobić:

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

Następnym krokiem jest utworzenie bettersshskryptu, który już to robi:

betterssh -tq myuser@hostname sudo -u scriptuser bash -c "$(shell-escape ls -al)"
ysdx
źródło
1

Dla tych z was, którzy muszą przekazać parametry do skryptu, powinno to wyglądać następująco:

ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash -s <param1> <param2> <paramN>"
Romande
źródło
1

Najpierw utwórz skrypt lokalny, a następnie uruchom go zdalnie za pomocą następującego polecenia:

cat <Local Script.sh> | ssh user@server "cat - | sudo -u <user> /bin/bash"
Franciscon Santos
źródło