如何在ssh上使用sudo运行任意复杂的命令?


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 / Some目录,其中包括空格。

有没有一种通用的解决方案,无论引用多么复杂/疯狂,我都可以传递任何命令,还是我已经达到了某种限制?还有其他可能甚至更易读的解决方案吗?


11
将脚本复制到$ remote,然后执行它。
user9517 2014年

那会行得通,但是我发现一个不留下任何脚本文件的解决方案(以防万一出现故障等)会更干净一些。
2014年

9
在每次运行结束时都有脚本rm本身
thanasisk

3
考虑使用fabric fabfile.org。如果您需要远程进行sudo操作,可以使您的生活更加轻松。
Nils Toedtmann 2014年

2
如果您已安装Ansible,则此命令将显示您的用例文档
ansible

Answers:


146

我有时使用的一个技巧是使用base64对命令进行编码,并将其通过管道传输到另一个站点上的bash:

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

这将对脚本进行编码,并在安全字符串中包含任何逗号,反斜杠,引号和变量,然后将其发送到另一台服务器。(-w0需要禁用换行,默认情况下,换行发生在第76列)。另一方面,$(base64 -d)将解码脚本并将其馈送到bash以执行。

不管脚本多么复杂,我都没有遇到任何问题。通过转义来解决问题,因为您无需逃避任何操作。它不会在远程主机上创建文件,并且您可以轻松运行极其复杂的脚本。


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

1
值得注意的是,在大多数情况下,您可以将lzop或gzip替换为base64,以加快传输速度。但是,可能存在边缘情况。YMMV。
CodeGnome

1
请注意,但如果是脚本,我希望任何传输都不会超过几kb。
ThoriumBR 2014年

7
这里也没有回声的使用。base64 script | ssh remotehost 'base64 -d | sudo bash'足够了:)
霍布斯(Hobs)2014年

虽然我尚未测试此解决方案,但它会输出脚本的结果,例如在stdout中输出“ yum check-update”吗?我想问一下,因为如果有人认为我做的事情显然很幼稚,那么我可以拿点东西。
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

我经常做的一件事是使用vim并使用:!cat % | ssh -tt somemachine技巧。


3
当然:!cat % | (command)可以按:w !(command)或进行:!(command) < %
斯科特

2
是。我的路线是虐待猫
moebius_eye 2014年

运行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,但是我总是忘记了。感谢您发布此内容:-)
乔恩·

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用来给您一个Shell,您可以在其中以所选用户身份运行命令?

-i,-登录

运行目标用户的密码数据库条目指定的外壳程序作为登录外壳程序。这意味着外壳将读取特定于登录的资源文件,例如.profile或.login。如果指定了命令,则通过外壳的-c选项将其传递到外壳以执行。如果未指定命令,则执行交互式外壳程序。sudo尝试在运行外壳程序之前更改为该用户的主目录。该命令在与用户登录时会收到的环境类似的环境下运行。sudoers(5)手册中的“命令环境”部分说明了-i选项如何影响在以下情况下运行命令的环境sudoers策略正在使用中。


这似乎是我的明显解决方案。> sudo -i -u brian
code_monk 2014年

在远程访问的情况下,与“ ssh -t”结合使用时效果最佳。该问题已包含在内,但需要重复“我看不懂此/ whole /页面”的内容。:)
dannysauer 2015年

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可以使用吗?
2014年

我正在尝试:echo "sudo `cat fileForSsh.txt`" | ssh ...但是我一直在努力sudo: sorry, you must have a tty to run sudo
jas_raj 2014年

尽管如果您可以更改文件,此问题可能会有所帮助/etc/sudoers
jas_raj 2014年

1
这对我ssh -tq user@host "sudo bash -s" < test.sh
有用


1

如果使用足够现代的Bash shell,则可以将命令放在函数中,然后使用将该函数打印为字符串export -p -f function_name。然后,该字符串的结果可以包含任意命令,这些命令不一定必须以root用户身份运行。

我的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"

检索示例保存登录用户的文件并以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"

如果您想使用来运行整个命令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>"

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.