15
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「この環境変数をあのサーバでも!」リモートサーバへのログイン時に環境変数を設定する方法

Last updated at Posted at 2018-07-19

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 コマンドインジェクションされちゃうかもしれませんよ?

どういうことかいまいちわからない人は試しに $VARHOGE 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 に下記の関数を登録しておくと、 ssheessh というコマンドを打てるようになります。

~/.bashrc
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"; }

(関数の解説も書こうかと一瞬思ったけど、 うん、だめだ、やめておこう。。。)

ssheessh もやれることは一緒で、 ssh の引数と環境変数名を -- で区切って渡すとリモートサーバにログインする時に指定した環境変数の内容をコピーしてくれます。
但し、 ssheessh は引数の順番が異なっており、それぞれ

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 に定義します。

~/.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 からクレデンシャル情報を削除してくれるので緩和はしていますが、それでもセキュリティ的にはあんまりよろしくないです。

まとめ

おしまい。

FYI

参考にしたページ

類似するページ

  1. もちろん rm -rf / なんて root でログインしなければほぼ無力な上にナウい rm コマンドだと root でも止まってくれますが、 rm -rf ./ にすれば有効な打撃となるシーンは格段に増えるでしょう。

  2. フリではない。

  3. そもそも、 root 権限を行使した盗聴を意識しなければならないサーバで作業なんかしたくはありませんけれども。

15
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?