Ssh üzerinde sudo kullanarak keyfi bir şekilde karmaşık komutu nasıl çalıştırabilirim?


80

Yalnızca kullanıcı adımın (myuser) altında oturum açabileceğim bir sistem var, ancak komutları diğer kullanıcı (scriptuser) olarak çalıştırmam gerekiyor. Şimdiye kadar, ihtiyacım olan komutları çalıştırmak için aşağıdakileri buldum:

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

Ancak, daha karmaşık bir komut çalıştırmayı denediğimde, [[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"hızlıca alıntı yapmakta zorlanıyorum. Bu örnek karmaşık komutu nasıl iletebileceğimi bilmiyorum bash -c, \"zaten geçtiğim komutun sınırlarını ne zaman sınırlandırıyorsa (ve / tmp / boşluk içeren bir dizini nasıl alıntılayacağımı bilmiyorum).

Alıntı ne kadar karmaşık / çılgın olursa olsun herhangi bir komutu geçmeme izin veren genel bir çözüm var mı, yoksa bu bir çeşit sınırlama oldu mu? Başka olası ve belki daha okunabilir çözümler var mı?


11
Bir betiği $ remote'a kopyalayın ve sonra çalıştırın.
user9517

İşe yarayacak, ancak hiçbir komut dosyasını arkada bırakmayacak bir çözüm buldum (bir şey olmazsa vs.) biraz daha temiz olurdu.
VoY

9
betiğin her çalışmanın sonunda kendisini rm
yapmasını sağlayın

3
Kumaş fabfile.org'u kullanmayı düşünün . Uzaktan çok sudo yapmak zorunda kalırsan hayatını kolaylaştırabilir.
Nils Toedtmann

2
Ansible kurulumunu yaptıysanız, bu komut kullanım durumunuzun belgelerini gösterir: ansible-doc script
bbaassssiiee

Yanıtlar:


146

Bazen kullandığım bir hile, komutları kodlamak için base64 kullanmak ve onu başka bir sitede bash olarak kullanmak:

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

Bu, komut dosyasını, herhangi bir virgül, ters eğik çizgi, tırnak ve değişkenlerin güvenli bir dize içinde kodlamasını ve diğer sunucuya göndermesini sağlar. ( -w0varsayılan olarak sütun 76'da olan satır kaydırmayı devre dışı bırakmak için gereklidir). Diğer tarafta, $(base64 -d)betiğin kodunu çözecek ve yürütülmesi için bash'a besleyecektir.

Senaryo ne kadar karmaşık olursa olsun hiçbir zaman sorun yaşamadım. Kaçma problemini çözer, çünkü hiçbir şeyden kaçmanıza gerek yoktur. Uzak ana bilgisayarda bir dosya oluşturmaz ve çok karmaşık komut dosyalarını kolaylıkla çalıştırabilirsiniz.


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

1
Çoğu durumda, daha hızlı transfer süreleri için base64 yerine lzop veya gzip kullanabilirsiniz. Ancak, kenar vakaları olabilir. YMMV.
CodeGnome 4:14

1
İyi not, ancak bir komut dosyasıysa, herhangi bir aktarımın birkaç kb'den daha fazla olmasını beklemiyorum.
ThoriumBR 4:14

7
Burada da yankı kullanmanın faydası yok. base64 script | ssh remotehost 'base64 -d | sudo bash'Yeterince :) olacağını
Hobbs

Bu çözümü test etmemiş olsam da, betiğin çıktısını alacak mı, örneğin stdout'taki 'yum check-update' diyecek mi? Sormak istedim, çünkü eğer birisi açıkça saf bir şey yaptığımı düşünüyorsa, birkaç şey alabilirim.
Soham Chakraborty,

34

Seçeneğe bak -tt? ssh(1)Kılavuzu oku .

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

Sık yaptığım tek şey vim kullanmak ve :!cat % | ssh -tt somemachinenumarayı kullanmak .


3
Tabii :!cat % | (command)olarak yapılabilir :w !(command)ya da :!(command) < %.
Scott

2
Evet.
Çizgim

koşu ssh -ttbelli komutlar çalıştırılır sonra benim ssh (ekleyerek sonlandırmak değil neden olur exityardım etmez sonunda)

10

Bence en kolay çözüm @ thanasisk'in yorumunda bir değişiklik yapmaktan kaynaklanıyor.

Bir komut dosyası oluşturun scp, makineye, sonra çalıştırın.

Betiğin rmbaşında kendiniz olun . Kabuk dosyayı açtı, böylece yüklenmiştir ve problemsiz bir şekilde çıkarılabilir.

Bu sırayla bir şeyler yaparak ( rmönce diğer şeyler ikinci), bir noktada başarısız olduğunda bile kaldırılır.


8

Sizin için kaçış değişkenine dikkat etmek için %qformat belirleyicisini kullanabilirsiniz printf:

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

printf -vçıktıyı bir değişkene yazar (bu durumda, $cmd_str). Bunun yapmanın en basit yolu olduğunu düşünüyorum. Herhangi bir dosyayı transfer etmeye veya komut dizesini kodlamaya gerek yok (hile istediğim kadar).

İşte köşeli parantez ve işareti gibi şeyler için de işe yaradığını gösteren daha karmaşık bir örnek:

$ 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

Test etmedim sudoancak bu kadar basit olmalı:

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

İsterseniz bir adımı atlayabilir ve ara değişkeni oluşturmaktan kaçınabilirsiniz:

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

Ya da tamamen bir değişken oluşturmaktan kaçının:

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

1
bu harika bir çözüm. Aslında benzer bir durum için daha önce printf kullanmak zorunda kaldım, ancak bunu hep unutuyorum. Bu :-) gönderdiğiniz için teşekkür ederiz
Jon L.

8

İşte bash böyle bir şey yürütmek için "doğru" (sözdizimsel) yolu:

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

Bunun nasıl çalıştığına dair ayrıntılı bir açıklama için, lütfen https://stackoverflow.com/a/21761956/111948


5

Size sudoseçilen kullanıcı olarak komutları çalıştırabileceğiniz bir kabuk vermek için kullanabileceğinizi biliyor musunuz ?

-i, --login

Hedef kullanıcının şifre veritabanı girişi tarafından belirtilen kabuğu bir giriş kabuğu olarak çalıştırın. Bu, .profile veya .login gibi girişe özgü kaynak dosyalarının kabuk tarafından okunacağı anlamına gelir. Bir komut belirtilirse, kabuğun -c seçeneği ile yürütülmek üzere kabuğa geçirilir. Hiçbir komut belirtilmezse, etkileşimli bir kabuk yürütülür. sudo, kabuğu çalıştırmadan önce bu kullanıcının ana dizinine geçmeye çalışır. Komut, kullanıcının oturum açarken alacağı benzer bir ortam ile çalıştırılır. Sudoers (5) kılavuzundaki Komut Ortamı bölümü, -i seçeneğinin bir komutun çalıştığı ortamı nasıl etkilediğini belgelemektedir. sudoers politikası kullanılıyor.


Bu bana açık bir çözüm gibi görünüyor. > sudo -i -u brian
code_monk

Uzaktan erişim durumunda "ssh-t" ile birleştirildiğinde en iyi sonucu verir. Soruya dahil olan, ancak "Bu / bütün / sayfa okuyamıyorum" millet için tekrarlayan ayılar. :)
dannysauer

4

Komut dosyasını yerel makinenizde tanımlayabilir ve ardından catuzak makineye ekleyebilirsiniz:

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 kullanabilirsiniz <
user9517

Eklenecek örneği değiştirebilir misiniz sudo -u scriptuser? Heredoc, makinede kullanacağım senaryoda değişkenlere ihtiyacım olduğunu düşünerek kullanılabilir mi?
VoY

Deniyordum: echo "sudo `cat fileForSsh.txt`" | ssh ...ama almaya devam ediyorum sudo: sorry, you must have a tty to run sudo.
jas_raj

Her ne kadar bu soru/etc/sudoers dosyayı değiştirebilirseniz yardımcı olabilir
jas_raj

1
Bu benim için çalışıyor:ssh -tq user@host "sudo bash -s" < test.sh
AVee

3

Basit ve kolay.

ssh kullanıcısı @ servidor "bash -s" <script.sh


1

Yeterince modern bir Bash kabuğu kullanıyorsanız, komutlarınızı bir işleve koyabilir ve bu işlevi kullanarak bir dize olarak yazdırabilirsiniz export -p -f function_name. Bu dizgenin sonucu daha sonra mutlaka root olarak çalıştırılması gerekmeyen isteğe bağlı komutlar içerebilir.

Unix.SE'deki cevabımdaki boruları kullanan bir örnek :

#!/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"

Alınan bir örnek, giriş yapan kullanıcı için dosyayı kaydeder ve bir programa kök yükler:

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"

Komutun tamamını kullanarak çalıştırmak sudoistiyorsanız, bunu kullanabilirsiniz:

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

İşte bunun için (gerçekten test edilmemiş) berbat çözümüm:

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

Yapamazsın:

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

Sonraki adım, bettersshzaten bunu yapan bir komut dosyası oluşturmaktır :

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

1

Parametreye betiğe geçmesi gerekenler için, şöyle olmalıdır:

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

1

Önce yerel bir komut dosyası oluşturun ve ardından follow komutunu kullanarak uzaktan çalıştırın:

cat <Local Script.sh> | ssh user@server "cat - | sudo -u <user> /bin/bash"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.