TL;DR
リモートサーバにログインする時に、ローカルサーバの特定の環境変数を引き継いでくれると少し楽なんだけどなー。と思うことがたまーにあるのですが、そういった時にイケてる感じに環境変数を設定してログインする方法です。
前提条件
ローカルサーバもリモートサーバも Linux で動いている前提です。
それって ~/.ssh/environment
?
いいえ、ここでは使いません。
というか、 ~/.ssh/environment
って、リモートサーバ側の sshd で PermitUserEnvironment no
になっていることが多くてあまり使える場所がないです。。。
もちろんリモートサーバ側の sshd の設定を変えれば使えるのですが、商用サーバだったり多人数で触っているサーバだったりして設定をいじりにくいんですよね。。。
~/.ssh/environment
じゃないやり方
sshd の設定を変更できない(したくない)のなら別の方法をとりましょう。
こうやるんだ!
ssh
コマンドでちょっとおまじないを書けば、簡単に環境変数を渡すことができます。
[ktooi@local ~]$ VAR="HOGE"
[ktooi@local ~]$ ssh -t remote.example.com "VAR=$(printf %q "$VAR") bash"
[ktooi@remote ~]$ echo $VAR
HOGE
カンタンでしょう?
ちなみに、この方法で環境変数を引き継いだ場合には、リモートサーバ側のシェルではあたかも export
した環境変数のようにふるまいます。
[ktooi@remote ~]$ cat <<\__EOT__ | bash
echo $VAR
__EOT__
HOGE
複数の環境変数の渡し方
VAR=$(printf %q "$VAR")
と同様の書き方で、環境変数名を変えたうえで半角スペースで区切れば OK です。
[ktooi@local ~]$ VAR1="HOGE"
[ktooi@local ~]$ VAR2="FUGA"
[ktooi@local ~]$ ssh -t remote.example.com "VAR1=$(printf %q "$VAR1") VAR2=$(printf %q "$VAR2") bash"
[ktooi@remote ~]$ echo "$VAR1, $VAR2"
HOGE, FUGA
もっとシンプルにやりたい?
え、こんなの毎回打ち込んでいられないですか?
なら 本稿のこの利用例 を参考にしてみてください。だいぶ楽になると思います。
解説
上記のやり方を見て納得できた人はこの項目をスキップしましょう。
いまいちピンとこなかった人は読んでみてください。
では、下記のコマンドを例にして解説します。
$ VAR="HOGE"
$ ssh -t remote.example.com "VAR=$(printf %q "$VAR") bash"
■ ssh
の -t
オプション
このオプションで仮想端末を割り当てます。これがないと、リモートサーバにログインした後に下記のようにプロンプトが表示されなかったり、タブ補完が効かなかったりしていろいろと悲しい思いをします。。。
[ktooi@local ~]# ssh remote.example.com "VAR=$(printf %q "$VAR") bash"
hostname
remote.example.com
■ VAR=$(printf %q "$VAR") bash
この部分は、ローカルサーバで解釈されるところと、リモートサーバで解釈されるところがあるので2つに分けて考えます。
■ $(printf %q "$VAR")
この部分はローカルサーバ側で解釈されます。
これは $VAR
に含まれる文字を、必要に応じてエスケープしてくれる処理です。例えば
(半角スペース)や ;
, #
などの記号をエスケープし、これらの文字が含まれていた場合に意図しない動作を防ぐ効果があります。 下記はこのエスケープ処理の例です。
$ VAR="STEINS;GATE 0"
$ printf %q "$VAR"
STEINS\;GATE\ 0
なお、エスケープが必要な文字が含まれないことが明らかな場合には、下記のように単に $VAR
とすることも可能です。
$ ssh -t remote.example.com "VAR=$VAR bash"
但し、エスケープ処理を省略する時は必ず $VAR
の値が信用できる場合のみにしてください。
そうでないと OS コマンドインジェクションされちゃうかもしれませんよ?
どういうことかいまいちわからない人は試しに $VAR
に HOGE ls -la;
を代入してから ssh
してみましょう。
$ VAR="HOGE ls -la;"
$ ssh -t remote.example.com "VAR=$VAR bash"
リモートサーバにログインした直後に、リモートサーバのカレントディレクトリ内の一覧が表示されたはずです。
そう、 $VAR
に含まれていたコマンドが実行されてしまったわけです。
もし ls -la
の代わりに rm -rf /
なんて値が設定されていたら目も当てられません。1
やるなよ、絶対にやるなよ。2
■ VAR=~ bash
この部分はリモートサーバ側で解釈されます。
これは bash
を実行する際に、 VAR
という環境変数に ~
の値を代入して実行するという書き方です。本稿の例だと、 ~
の部分は HOGE
または STEINS\;GATE\ 0
となりますので、下記のように解釈されます。
$ VAR=HOGE bash
$ VAR=STEINS\;GATE\ 0 bash
これは、 export
を利用して
$ export VAR=HOGE
$ bash
等としたときと似たように振るいまいますが、 export
の場合は export
を実行した後から unset
等するまでに実行されるすべてのコマンドに VAR=HOGE
が設定されるのに対し、 VAR=HOGE コマンド名
だとその時に実行したコマンドにしか影響を及ぼしません。
利用例
ローカルサーバのプロンプトをリモートサーバで再現
オレサマ謹製のプロンプトをリモートサーバでの作業にも適用したいのだけれども、アカウントが共有(ec2-user
とか)だったり運用上の制約で .bash_profile
や .bashrc
を触れないときにローカルサーバのオレサマ謹製プロンプトをコピーする方法です。
[08:59:34][ktooi@local ~]$ ssh remote.example.com
[ktooi@remote ~]$ exit # 普通にログインするとプロンプトが RHEL 系のデフォルト。
Connection to remote.example.com closed.
[09:00:00][ktooi@local ~]$ ssh -t remote.example.com "PS1=$(printf %q "$PS1") bash"
[09:00:10][ktooi@remote ~]$ # ローカルサーバのプロンプトのフォーマットがコピーされた。
そもそものプロンプトの変更については記事を書かれている方がいらっしゃるので、試しに変更してみたい方は下記辺りを参照してみてください。
パスワードをエクスポート
※ 後述の 注意 を参照の事。
Linux 作業手順書からべた書きパスワードをなくすシンプルなアイディア で記載した方法でパスワードを環境変数に入れ、それをリモートサーバに渡してリモートサーバ上でのパスワード入力を省く方法です。
[ktooi@local ~]$ read -sp "Please input your password: " __pass
Please input your password: # ← パスワードを入力
[ktooi@local ~]$ ssh -t remote.example.com "__pass=$(printf %q "$__pass") bash"
[ktooi@remote ~]$ curl -u "user:${__pass}" http://example.com
AWS CLI のクレデンシャル情報をエクスポート
※ 後述の 注意 を参照の事。
まぁ、普通はおとなしく AWS CLI を実行するサーバの ~/.aws/credentials
に「AWS アクセスキー ID」と「AWS シークレットアクセスキー」を保存していると思うので、下記はあくまで例ですが。
[ktooi@local ~]$ AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
[ktooi@local ~]$ AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
[ktooi@local ~]$ ssh -t remote.example.com "AWS_ACCESS_KEY_ID=$(printf %q "$AWS_ACCESS_KEY_ID") AWS_SECRET_ACCESS_KEY=$(printf %q "$AWS_SECRET_ACCESS_KEY") bash"
[ktooi@remote ~]$ aws ec2 describe-instances --region us-east-1"
本稿の趣旨からは外れますが、そもそもリモートサーバで aws
コマンドを実行したいだけだったら下記のようにやると手間が減ります。
$ AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
$ AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
$ ssh -t remote.example.com "AWS_ACCESS_KEY_ID=$(printf %q "$AWS_ACCESS_KEY_ID") AWS_SECRET_ACCESS_KEY=$(printf %q "$AWS_SECRET_ACCESS_KEY") aws ec2 describe-instances --region us-east-1"
.bashrc
あたりに関数を登録してラクラク環境変数コピーライフを送る
自分の ~/.bashrc
に下記の関数を登録しておくと、 sshe
と essh
というコマンドを打てるようになります。
function sshe { local ssh_opts=(); for opt in "$@"; do [ "$opt" == "--" ] && shift && break; ssh_opts=("${ssh_opts[@]}" $1); shift; done; local envs=(); for env in "$@"; do grep -q '^[_a-zA-Z][_a-zA-Z0-9]*$' <<< "$env" || { echo "invalid environment variable name \"$env\""; return 1; }; envs=("${envs[@]}" "$env=$(printf %q "$(eval echo \"\$$env\")")"); done; ssh "${ssh_opts[@]}" -t "${envs[*]} bash"; }
function essh { local envs=(); for env in "$@"; do [ "$env" == "--" ] && shift && break; grep -q '^[_a-zA-Z][_a-zA-Z0-9]*$' <<< "$env" || { echo "invalid environment variable name \"$env\""; return 1; }; envs=("${envs[@]}" "$env=$(printf %q "$(eval echo \"\$$env\")")"); shift; done; local ssh_opts=(); for opt in "$@"; do ssh_opts=("${ssh_opts[@]}" $1); done; ssh "${ssh_opts[@]}" -t "${envs[*]} bash"; }
(関数の解説も書こうかと一瞬思ったけど、 うん、だめだ、やめておこう。。。)
sshe
も essh
もやれることは一緒で、 ssh
の引数と環境変数名を --
で区切って渡すとリモートサーバにログインする時に指定した環境変数の内容をコピーしてくれます。
但し、 sshe
と essh
は引数の順番が異なっており、それぞれ
sshe [ssh のオプション...] -- [環境変数名...]
essh [環境変数名...] -- [ssh のオプション...]
のようになります。なお、 [ssh のオプション...]
に -t
を入れる必要はありません。関数のなかにハードコードしてあります。
また、 [ssh のオプション...]
には -o ConnectTimeout=10
といったオプションを指定することも可能です。
実際の使い方はそれぞれ下記のようになります。
$ VAR1=HOGE
$ VAR2=FUGA
$ sshe remote.example.com -- VAR1 VAR2 PS1
または
$ essh VAR1 VAR2 PS1 -- remote.example.com
どちらの場合も、下記のコマンドを実行したときと同じような動作になります。
$ ssh -t remote.example.com "VAR1=$(printf %q "$VAR1") VAR2=$(printf %q "$VAR2") PS1=$(printf %q "$PS1") bash"
応用編
特定の環境変数を必ずリモートサーバにコピーしたい場合は、 ~/.bashrc
にさらに新たな関数を定義すれば可能です。
例えば $PS1
を必ずコピーしたい場合は、次のような関数を ~/.bashrc
に定義します。
function psssh { essh PS1 -- "$@"; }
function psessh { essh PS1 "$@"; }
function pssshe { sshe "$@" PS1; }
この場合は、下記のように実行することができます。
$ psssh remote.example.com # コピーする環境変数を指定できないが、シンプルに実行可能。
$ VAR=HOGE
$ psessh VAR -- remote.example.com # コピーする環境変数を追加指定できるが、コマンドが長くなる。
$ pssshe remote.example.com -- VAR # コピーする環境変数を追加指定できるが、コマンドが長くなる。
注意
この方法は便利なのですが、リモートサーバで root
権限を持っている第三者がいた場合、渡した環境変数の名前と値を簡単に見ることができてしまうので注意が必要です。
クレデンシャルな情報を渡すときは気を付けましょう。3
また、クレデンシャル情報を引数にしてコマンドに渡すのもセキュアではありません。コマンド実行中に第三者に ps
コマンドを叩かれたりすると引数を含むプロセスのリストを取得されてしまいます。 curl
等一部のコマンドはプロセス起動後に /proc/<PID>/cmdline
からクレデンシャル情報を削除してくれるので緩和はしていますが、それでもセキュリティ的にはあんまりよろしくないです。
まとめ
-
$ ssh -t remote.example.com "VAR=$(printf %q "$VAR") bash"
といった感じにssh
するとお手軽に環境変数をコピーできる。 - .bashrc あたりに関数を登録してラクラク環境変数コピーライフを送る を参考にしてもらうともっとお手軽に環境変数をコピーできる。
おしまい。