web-dev-qa-db-ja.com

sshでSudoを使用して任意の複雑なコマンドを実行するにはどうすればよいですか?

ユーザー名(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ディレクトリ。

引用がどれほど複雑でクレイジーであっても、コマンドを渡すことができる一般的な解決策はありますか、またはこれは私が到達したある種の制限ですか?他に可能な、そしておそらくもっと読みやすい解決策はありますか?

81
VoY

私が時々使用するトリックは、base64を使用してコマンドをエンコードし、それを他のサイトのbashにパイプすることです。

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

これにより、安全な文字列内のコンマ、バックスラッシュ、引用符、変数を使用してスクリプトがエンコードされ、他のサーバーに送信されます。 (_-w0_は、デフォルトで列76で行われる行の折り返しを無効にするために必要です)。一方、$(base64 -d)はスクリプトをデコードし、実行するためにbashにフィードします。

スクリプトがどれほど複雑であっても、問題はありませんでした。何もエスケープする必要がないため、エスケープの問題を解決します。リモートホスト上にファイルを作成せず、非常に複雑なスクリプトを簡単に実行できます。

148
ThoriumBR

_-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_トリックを使用することです。

35
moebius_eye

最も簡単な解決策は、@ thanasiskのコメントを変更することだと思います。

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

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

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

10
dr. Sybren

更新:例ではSudoを明示的に使用するようになりました。

Sudoを使用してSSHで任意の複雑なコマンドを実行するために、複合割り当てでBash構文を使用する方法を次に示します。

CMD=$( cat <<'EOT'
echo "Variables like '${HOSTNAME}' and commands like $( whoami )"
echo "will be interpolated on the server, thanks to the single quotes"
echo "around 'EOT' above."
EOT
) \
SSHCMD=$(
  printf 'ssh -tq myuser@hostname Sudo -u scriptuser bash -c %q' "${CMD}" ) \
bash -c '${SSHCMD}'

CMD=$( cat <<EOT
echo "If you want '${HOSTNAME}' and $( whoami ) to be interpolated"
echo "on the client instead, omit the the single quotes around EOT."
EOT
) \
SSHCMD=$(
  printf 'ssh -tq myuser@hostname Sudo -u scriptuser bash -c %q' "${CMD}" ) \
bash -c '${SSHCMD}'

他の場所(ネストされたシェル、リモートSSHシェルなど)で動的に実行されるようにコマンドを適切にフォーマットすることは非常に難しい場合があります- を参照してください 理由の適切な説明。上記の構文のように複雑なbash構造は、これらのコマンドが適切に機能するのに役立ちます。

cat<<結合してインラインのドキュメントを作成しますhttps://stackoverflow.com/a/21761956/を参照してください111948

8
Dejay Clayton

%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'")"
8
Tom Fenech

Sudoを使用して、選択したユーザーとしてコマンドを実行できるシェルを提供できることをご存知ですか?

-i、-login

ターゲットユーザーのパスワードデータベースエントリで指定されたシェルをログインシェルとして実行します。これは、.profileや.loginなどのログイン固有のリソースファイルがシェルによって読み取られることを意味します。コマンドが指定されている場合、シェルの-cオプションを介して実行するためにコマンドがシェルに渡されます。コマンドを指定しない場合、対話型シェルが実行されます。 Sudoは、シェルを実行する前に、そのユーザーのホームディレクトリへの変更を試みます。コマンドは、ユーザーがログイン時に受け取る環境と同様の環境で実行されます。sudoers(5)マニュアルのコマンド環境セクションでは、-iオプションがコマンドの実行環境にどのように影響するかを説明しています。 sudoersポリシーが使用されています。

5
justinpc

ローカルマシンでスクリプトを定義してから、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
4
jas_raj

シンプルで簡単。

ssh user @ servidor "bash -s" <script.sh

3
rafaelrms

十分に新しいBashシェルを使用している場合は、コマンドを関数に入れ、export -p -f function_nameを使用してその関数を文字列として出力できます。その文字列の結果には、必ずしもルートとして実行する必要がない任意のコマンドを含めることができます。

nix.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
Lekensteyn

まず、ローカルスクリプトを作成し、次のコマンドを使用してリモートで実行します。

cat <Local Script.sh> | ssh user@server "cat - | Sudo -u <user> /bin/bash"
1

スクリプトにパラメーターを渡す必要があるこれらのユーザーにとっては、次のようになります。

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

これが私の(実際にはテストされていない)くだらない解決策です:

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