ssh-env - ssh実行時に環境変数を設定/変更したい

目的

sshリモートホストでコマンドを実行するときに、PATHやその他の環境変数を設定して実行したい。

例えば、勝手にコマンドのバージョンアップができない重要なサーバで、自分のホーム以下にインストールしたコマンドをssh経由で実行したいのだが、PATHが/usr/local/binなどにしか通っていなくて困っている。

結果

  • sshdの設定変更 (PermitUserEnvironment=yes)、sshd再起動
  • ~/.ssh/environment に環境変数を書く (VAR=VAL 形式で一行一変数で)

※ "~" や "$" の置換はしてくれないので、PATHを書くときはフルパスで書くこと。
※ ~/.ssh/rc に書いてもダメです


以下、詳細。

sshのmanによると…

sshd(8)の "LOGIN PROCESS" の項目によると、sshでログインした時の処理順序は、以下:

  1. (コンソールからのログインや、コマンド指定無しの場合) /etc/motd を表示
  2. (コンソールからのログインの場合)ログイン時間を記録
  3. /etc/nologin が存在したら、中身を表示して終了 (rootは別だけど)
  4. プロセスの実行権限をrootから一般ユーザに変更
  5. 基本的な環境変数設定
  6. ~/.ssh/environment があれば設定 (PermitUserEnvironmentの設定が必要)
  7. ユーザのホームディレクトリに移動
  8. ファイルがあれば、 ~/.ssh/rc か /etc/ssh/sshrc か xauth 実行
  9. ユーザのshellか、引数のcommandを実行

ということは、~/.ssh/environment か、~/.ssh/rc に書いておけばよい、のかな?

試してみる

~/.ssh/environment
PATH=~/bin:$PATH

と書いて、確認すると、

% ssh ss71-build printenv | grep PATH
PATH=/usr/local/bin:/bin:/usr/bin

変わってない。

~/.ssh/rc
PATH=~/bin:$PATH
export PATH

と書いて、確認すると、

% ssh ss71-build printenv | grep PATH
PATH=/usr/local/bin:/bin:/usr/bin

やはり変わってない。

サーバの設定を確認して、再挑戦

/etc/ssh/sshd_config を確認すると、

#PermitUserEnvironment no

環境変数指定ができない設定になっていた(sshのデフォルトがこうなってるみたい)。

ここを、"PermitUserEnvironment=yes" にして sshd 再起動後試してみると、

% ssh ss71-build printenv | grep PATH
zsh: command not found: printenv

printenvが無い?

はっ、もしや$PATHが展開されてない?と思い、"$PATH"→"/usr/bin"と書き直して試したら、

% ssh ss71-build printenv | grep PATH
PATH=~/bin:/usr/bin

今度は変化した。

ただし、rc の方は変化無し。

ソースを見てみる

手元のRHEL4WSではのsshのバージョンは

% ssh -v
OpenSSH_3.9p1, OpenSSL 0.9.7a Feb 19 2003

なので、openssh-3.9p1 のソースを参照する。

PermitUserEnvironment の利用箇所

servconf.cにて、定義している。大文字・小文字関係ないんだ…。

static struct {
	const char *name;
	ServerOpCodes opcode;
} keywords[] = {
  ....
	{ "permituserenvironment", sPermitUserEnvironment },
  ....
};

parse_token()にて、渡されたtokenがkeywords[]のいずれかに一致するか調べてる。parse_token()は、server側/client側の両方で使う関数で、keywordsをそれぞれ使い分けて、同じ関数を共用している。

PermitUserEnvironment が "Yes" の時は、options->permit_user_env=-1 とする。ちなみに options はグローバル変数。なんつーか、大雑把な作り。

一方、permit_user_env の参照箇所は、

  • auth_parse_options() in auth-options.c : こちらは authorized_keys の中での "env=" の処理なので、今回は違う
  • do_setup_env() in session.c : こちらが .ssh/environment を読む箇所

この部分の抜粋:

	/* read $HOME/.ssh/environment. */
	if (options.permit_user_env && !options.use_login) {
		snprintf(buf, sizeof buf, "%.200s/.ssh/environment",
		    strcmp(pw->pw_dir, "/") ? pw->pw_dir : "");
		read_environment_file(&env, &envsize, buf);
	}

read_environment_file() の中の処理は、

  • 1000行になるまで処理(1000行目以降はエラーを表示して処理中止)
  • 行頭のスペースとタブをスキップして、
  • 先頭文字が '#' か改行なら無視、
  • '=' が行内に無ければwarning表示(処理は続行)
  • child_set_env() で、子プロセス用のenv領域などを確保して格納
  • 最後に、確保したenvを上(do_child)に返す

そして、do_child()内で、execveの第三引数に env を指定、と。

setenv(3)を使って設定しているわけじゃないから(それじゃroot権限で環境変数が変わるから、考えたら危険だね)、~/.ssh/rc で export しても変わらなった理由も納得。