ฉันจะเรียกใช้คำสั่งที่ซับซ้อนตามอำเภอใจโดยใช้ sudo over ssh ได้อย่างไร


80

ฉันมีระบบที่สามารถเข้าสู่ระบบภายใต้ชื่อผู้ใช้ของฉัน (myuser) แต่ฉันต้องเรียกใช้คำสั่งในฐานะผู้ใช้อื่น (scriptuser) จนถึงตอนนี้ฉันได้พบกับสิ่งต่อไปนี้เพื่อเรียกใช้คำสั่งที่ฉันต้องการ:

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

หาก แต่เมื่อฉันพยายามเรียกใช้คำสั่งที่ซับซ้อนมากขึ้นเช่น[[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"ฉันประสบปัญหากับการอ้างอิงอย่างรวดเร็ว ฉันไม่แน่ใจว่าฉันจะส่งคำสั่งที่ซับซ้อนตัวอย่างนี้ไปได้bash -cอย่างไรเมื่อ\"จำกัด ขอบเขตของคำสั่งที่ฉันส่งไปแล้ว (และฉันไม่รู้วิธีการอ้างอิง / tmp / บางไดเร็กทอรีซึ่งรวมถึงช่องว่างด้วย

มีวิธีการแก้ปัญหาทั่วไปที่ช่วยให้ฉันสามารถส่งคำสั่งใด ๆ ไม่ว่าจะมีความซับซ้อน / บ้าอ้างถึงหรือเป็นข้อ จำกัด บางอย่างที่ฉันได้มาถึง? มีวิธีแก้ปัญหาอื่น ๆ


11
คัดลอกสคริปต์ไปยัง $ remote จากนั้นดำเนินการ
user9517

แต่ฉันหาวิธีแก้ปัญหาที่ทิ้งไฟล์สคริปต์ไว้ข้างหลัง (ในกรณีที่มีบางอย่างผิดพลาด ฯลฯ ) จะค่อนข้างสะอาดกว่า
VoY

9
มีสคริปต์ตัวเองในตอนท้ายของการทำงานแต่ละครั้ง
thanasisk

3
พิจารณาที่จะใช้ผ้าfabfile.org สามารถทำให้ชีวิตคุณง่ายขึ้นถ้าคุณต้องทำ sudo จากระยะไกล
Nils Toedtmann

2
หากคุณติดตั้ง Ansible คำสั่งนี้จะแสดงเอกสารประกอบสำหรับกรณีการใช้งานของคุณ: สคริปต์
ansible

คำตอบ:


146

เคล็ดลับที่ฉันใช้ในบางครั้งคือการใช้ base64 เพื่อเข้ารหัสคำสั่งและไพพ์เพื่อทุบตีในเว็บไซต์อื่น:

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

สิ่งนี้จะเข้ารหัสสคริปต์ด้วยเครื่องหมายจุลภาคแบ็กสแลชเครื่องหมายคำพูดและตัวแปรภายในสตริงที่ปลอดภัยและส่งไปยังเซิร์ฟเวอร์อื่น ( -w0จำเป็นต้องปิดการใช้งานการตัดบรรทัดซึ่งเกิดขึ้นที่คอลัมน์ 76 โดยค่าเริ่มต้น) ในอีกด้านหนึ่ง$(base64 -d)จะถอดรหัสสคริปต์และฟีดมันเพื่อทุบตีที่จะดำเนินการ

ฉันไม่เคยมีปัญหาใด ๆ กับมันไม่ว่าสคริปต์จะซับซ้อนแค่ไหน แก้ปัญหาด้วยการหลบหนีเพราะคุณไม่จำเป็นต้องหลบหนีอะไรเลย มันไม่ได้สร้างไฟล์บนรีโมตโฮสต์และคุณสามารถเรียกใช้สคริปต์ที่ซับซ้อนอย่างมากมายได้อย่างง่ายดาย


2
หรือแม้กระทั่ง:ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash"
Niklas B.

1
เป็นที่น่าสังเกตว่าในกรณีส่วนใหญ่คุณสามารถแทนที่ lzop หรือ gzip สำหรับ base64 เพื่อการถ่ายโอนที่รวดเร็วขึ้น อย่างไรก็ตามอาจมีกรณีขอบ YMMV
CodeGnome

1
ขอให้ทราบดี แต่ถ้าเป็นสคริปต์ฉันไม่คาดหวังว่าการถ่ายโอนใด ๆ จะมีขนาดเกินสองสามกิโลไบต์
ThoriumBR

7
มีการใช้เสียงสะท้อนที่ไร้ประโยชน์ในที่นี่เช่นกัน base64 script | ssh remotehost 'base64 -d | sudo bash'จะ :) พอ
ฮอบส์

ในขณะที่ฉันยังไม่ได้ทดสอบวิธีแก้ปัญหานี้ผลลัพธ์ของสคริปต์จะพูดเช่น 'yum check-update' ใน stdout หรือไม่ ฉันอยากจะถามเพราะถ้ามีคนคิดว่าฉันกำลังทำอะไรที่ไร้เดียงสาอย่างเห็นได้ชัดฉันสามารถรับบางสิ่งได้
Soham Chakraborty

34

เห็น-ttตัวเลือกหรือไม่ อ่านssh(1)คู่มือ

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

สิ่งหนึ่งที่ฉันมักทำคือใช้เสียงเรียกเข้าและใช้:!cat % | ssh -tt somemachineเคล็ดลับ


3
แน่นอน:!cat % | (command)สามารถทำได้หรือ:w !(command) :!(command) < %
สกอตต์

2
ใช่. บรรทัดของฉันคือการทารุณกรรมแมว
moebius_eye

การทำงานssh -ttทำให้ ssh ของฉันไม่ยุติหลังจากรันคำสั่งบางคำสั่ง (การเพิ่มexitที่ส่วนท้ายไม่ช่วย)

10

ฉันคิดว่าทางออกที่ง่ายที่สุดอยู่ในการแก้ไขความคิดเห็นของ @ thanasisk

สร้างสคริปต์scpไปยังเครื่องจากนั้นเรียกใช้

มีสคริปต์rmในตอนเริ่มต้น เชลล์ได้เปิดไฟล์ดังนั้นมันจึงถูกโหลดและสามารถลบออกได้โดยไม่มีปัญหา

ด้วยการทำสิ่งต่าง ๆ ตามลำดับนี้ ( rmก่อนอื่นสิ่งที่สอง) มันจะถูกลบออกเมื่อมันล้มเหลวในบางจุด


8

คุณสามารถใช้ตัว%qระบุรูปแบบด้วยprintfเพื่อดูแลการหลีกเลี่ยงตัวแปร:

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

printf -vเขียนเอาต์พุตไปยังตัวแปร (ในกรณีนี้$cmd_str) ฉันคิดว่านี่เป็นวิธีที่ง่ายที่สุดที่จะทำ ไม่จำเป็นต้องถ่ายโอนไฟล์ใด ๆ หรือเข้ารหัสสตริงคำสั่ง (เท่าที่ฉันชอบเคล็ดลับ)

นี่คือตัวอย่างที่ซับซ้อนมากขึ้นซึ่งแสดงว่ามันใช้งานได้กับสิ่งต่างๆเช่นวงเล็บเหลี่ยมและเครื่องหมายแอมป์

$ 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

ฉันไม่ได้ทดสอบด้วยsudoแต่ควรง่ายเหมือน:

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

หากคุณต้องการคุณสามารถข้ามขั้นตอนและหลีกเลี่ยงการสร้างตัวแปรกลาง:

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

หรือแม้แต่หลีกเลี่ยงการสร้างตัวแปรทั้งหมด:

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

1
นี่เป็นทางออกที่ยอดเยี่ยม ฉันต้องใช้ printf มาก่อนในสถานการณ์ที่คล้ายกัน แต่ฉันมักจะลืมมันเสมอ ขอขอบคุณที่โพสต์สิ่งนี้ :-)
Jon L.

8

นี่คือวิธี "ถูกต้อง" (วากยสัมพันธ์) เพื่อดำเนินการบางอย่างเช่นนี้ใน 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
)"

สำหรับคำอธิบายโดยละเอียดเกี่ยวกับวิธีการทำงานของมันโปรดดูที่https://stackoverflow.com/a/21761956/111948


5

คุณทราบหรือไม่ว่าคุณสามารถใช้sudoเพื่อให้เชลล์แก่คุณซึ่งคุณสามารถเรียกใช้คำสั่งในฐานะผู้ใช้ที่เลือกได้?

-i, --login

รันเชลล์ที่ระบุโดยรายการฐานข้อมูลรหัสผ่านของผู้ใช้เป้าหมายเป็นเชลล์ล็อกอิน ซึ่งหมายความว่าไฟล์ทรัพยากรเฉพาะการเข้าสู่ระบบเช่น. profile หรือ .login จะอ่านโดยเชลล์ หากระบุคำสั่งคำสั่งจะถูกส่งไปยังเชลล์เพื่อดำเนินการผ่านตัวเลือกเชลล์ของ -c หากไม่ได้ระบุคำสั่งเชลล์เชิงโต้ตอบจะถูกดำเนินการ sudo พยายามเปลี่ยนเป็นโฮมไดเร็กทอรีของผู้ใช้นั้นก่อนที่จะรันเชลล์ คำสั่งถูกเรียกใช้ด้วยสภาพแวดล้อมที่คล้ายกับที่ผู้ใช้จะได้รับเมื่อเข้าสู่ระบบส่วนคำสั่งสภาพแวดล้อมในเอกสารคู่มือ sudoers (5) คู่มือวิธีตัวเลือก -i ส่งผลกระทบต่อสภาพแวดล้อมที่คำสั่งทำงานเมื่อ มีการใช้นโยบาย sudoers


ดูเหมือนว่าทางออกที่ชัดเจนสำหรับฉัน > sudo -i -u brian
code_monk

ที่ทำงานได้ดีที่สุดเมื่อรวมกับ "ssh -t" ในกรณีของการเข้าถึงระยะไกล ซึ่งรวมอยู่ในคำถาม แต่หมีซ้ำสำหรับคน "ฉันไม่สามารถอ่านนี้ / ทั้ง / หน้า" :)
dannysauer

4

คุณสามารถกำหนดสคริปต์บนเครื่องโลคัลของคุณจากนั้นcatและไพพ์สคริปต์ไปยังเครื่องรีโมต:

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 คุณสามารถใช้ <
user9517

คุณสามารถแก้ไขตัวอย่างเพื่อรวมได้sudo -u scriptuserหรือไม่ heredoc จะสามารถใช้งานได้หรือไม่เมื่อฉันต้องมีตัวแปรในสคริปต์ที่ฉันจะส่งไปยังเครื่อง
VoY

ฉันพยายาม: echo "sudo `cat fileForSsh.txt`" | ssh ...แต่ฉันได้รับsudo: sorry, you must have a tty to run sudoเรื่อย ๆ
jas_raj

แม้ว่าคำถามนี้อาจช่วยได้ถ้าคุณสามารถ/etc/sudoersเปลี่ยนแปลงไฟล์ได้
jas_raj

1
สิ่งนี้ใช้ได้กับฉัน:ssh -tq user@host "sudo bash -s" < test.sh
Avee


1

export -p -f function_nameถ้าคุณใช้เปลือกทุบตีทันสมัยพอคุณสามารถใส่คำสั่งของคุณในการทำงานและการพิมพ์ที่ทำงานเป็นสตริงใช้ ผลลัพธ์ของสตริงนั้นสามารถมีคำสั่งโดยพลการที่ไม่จำเป็นต้องรันในฐานะรูท

ตัวอย่างการใช้ไพพ์จากคำตอบของฉันบน 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"

ตัวอย่างที่ดึงข้อมูลบันทึกไฟล์สำหรับผู้ใช้ที่ล็อกอินและติดตั้งโปรแกรมที่รูท:

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"

หากคุณต้องการรันคำสั่งทั้งหมดโดยใช้sudoคุณสามารถใช้สิ่งนี้:

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

นี่คือโซลูชันเส็งเคร็งของฉัน (ไม่ได้ทดสอบจริงๆ) สำหรับสิ่งนี้:

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

ไม่สามารถทำได้:

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

ขั้นตอนต่อไปคือการสร้างbettersshสคริปต์ที่ทำสิ่งนี้แล้ว:

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

1

สำหรับผู้ที่ต้องการส่งพารามิเตอร์ไปยังสคริปต์ควรเป็นดังนี้:

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

1

ก่อนอื่นให้สร้างสคริปต์ท้องถิ่นแล้วเรียกใช้งานจากระยะไกลโดยใช้คำสั่ง follow:

cat <Local Script.sh> | ssh user@server "cat - | sudo -u <user> /bin/bash"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.