ssh 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 / Someディレクトリを引用する方法がわかりません)。

クォートがどれほど複雑/クレイジーであっても、コマンドを渡すことができる一般的な解決策はありますか、これは私が到達した何らかの制限ですか?他の可能性のある、おそらくより読みやすいソリューションはありますか?


11
スクリプトを$ remoteにコピーしてから実行します。
user9517 14

それは機能しますが、スクリプトファイルを残さないソリューション(何かが失敗した場合など)が少しすっきりするでしょう。
VoY 14

9
各実行の終わりに、スクリプトRM自体を持っている
thanasisk

3
ファブリックfabfile.orgの使用を検討してください。リモートでsudoを頻繁に実行する必要がある場合は、生活がずっと楽になります。
ニルストートマン14

2
ansible-docのスクリプト:あなたはAnsibleがインストールされている場合、このコマンドは、ユースケースのドキュメントを示す
bbaassssiiee

回答:


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"
ニクラスB. 14

1
ほとんどの場合、base64の代わりにlzopまたはgzipを使用して、転送時間を短縮できることに注意してください。ただし、エッジケースがある場合があります。YMMV。
CodeGnome 14

1
良い注意ですが、スクリプトの場合、転送が数キロバイトを超えるとは思いません。
トリウムBR 14

7
ここにもエコーの無駄な使用があります。base64 script | ssh remotehost 'base64 -d | sudo bash'十分でしょう:)
ホッブズ14

私はこのソリューションをテストしていませんが、スクリプトの結果を出力しますか(たとえば、stdoutの 'yum check-update'など)誰かが私が明らかに素朴なことをしていると思う場合、私はいくつかのものを拾うことができるので、私は尋ねたかった。
ソハムチャクラボルティ

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

私がよくすることの1つは、vimを使用して:!cat % | ssh -tt somemachineトリックを使用することです。


3
もちろん:!cat % | (command):w !(command)またはとして実行できます:!(command) < %
スコット14

2
はい。マイラインは、猫の虐待
moebius_eye

ランニングは、ssh -tt特定のコマンドが実行された後、私のsshが(追加終了しなくなりexit解決しない終わり)

10

最も簡単な解決策は、@ thanasiskのコメントの修正にあると思います。

scpマシンにスクリプトを作成し、実行します。

rm最初にスクリプト自体を用意します。シェルがファイルを開いたため、ファイルがロードされ、問題なく削除できます。

この順序で(rm最初に、他のものを2番目に)実行することで、ある時点で失敗した場合でも削除されます。


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を使用しなければなりませんでしたが、それを常に忘れています。これを投稿してくれてありがとう:-)
ジョン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
ブライアン

これは、リモートアクセスの場合に「ssh -t」と組み合わせると最適に機能します。これは質問に含まれていますが、「私はこの/ whole /ページを読むことができません」人々のために繰り返します。:)
ダニーザウアー

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

例を含めるように変更できますsudo -u scriptuserか?マシンにパイピングするスクリプトに変数を含める必要があることを考えると、heredocは使用できますか?
VoY 14

私がしようとしていた:echo "sudo `cat fileForSsh.txt`" | ssh ...しかし、私は取得し続けsudo: sorry, you must have a tty to run sudoます。
jas_raj 14

が、この質問は、あなたが得ることができれば助けるかもしれない/etc/sudoersファイルが変更
jas_raj

1
これは私のために働く:ssh -tq user@host "sudo bash -s" < test.sh
AVee 14


1

十分に最新のBashシェルを使用している場合、コマンドを関数に入れて、を使用してその関数を文字列として出力できます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.