OpenSSH の設定ファイル ~/.ssh/config
のファイル分割について考察してみることにしました。
- 2016年8月リリースの OpenSSH 7.3 から Include キーワードが導入されています(参考)。この記事は、古い OpenSSH の環境への配慮などから、Include キーワードを使わない方法をそのまま解説しています。
世間で模索される設定ファイルの分割
古めの OpenSSH の設定ファイルは分割だったり外部ファイル読み込みをサポートしていません。Apache の Include ディレクティブのようなものがあるといいのですが、現状では分割管理したい人が独自に色々な手法を試みているようです。
- ssh config を分割する - Qiita
- .ssh/configをファイル分割管理するようにした - Qiita
- ~/.ssh/configを分割して管理する - Qiita
- [Mac]シロク流、sshの設定ファイル分割管理 - Qiita
だいたいの手法は以下のように分別できます。
- 分割管理したファイルを手作業で
~/.ssh/config
に結合する手法 - 分割管理したファイルを自動的に
~/.ssh/config
として結合するssh
コマンドなどの上書き or 別定義 - 別の設定ファイル体系を持つ
ssh
をベースとした自作別コマンド
alias ssh='cat ~/.ssh/conf.d/*.conf > ~/.ssh/config ; ssh'
など、結局は裏で ~/.ssh/config
を作る方法に収斂されるわけですが、いくつか悩ましい部分があります。
-
~/.ssh/config
が作られたまま残ってしまうので、うっかりそちらを編集してしまい、分割したファイルのメンテナンスを忘れることがある- 意識の問題でもある
-
cat
で結合生成してssh
した後にrm
すればいいという意見も
- 既に
~/.ssh/config
があって結合の必要がない場合も結合することを容認しても、/.ssh/config
のタイムスタンプ (mtime) に惑わされることもある
個人のコマンドなので結合コストの問題は無視したとしても、大元のデータがどこであるとかメタデータであるとかで油断したときに混乱させられるのは嫌だなぁと感じました。
~/.ssh/config
を恒久的に置かないという作戦であれば
function ssh {
cat ~/.ssh/conf.d/*.conf > ~/.ssh/config
builtin ssh "$@"
rm ~/.ssh/config
}
といった方法や、さらにコマンド内でファイルディスクリプタを作って捨てるような
function ssh {
builtin ssh -F <(cat ~/.ssh/conf.d/*.conf) "$@"
}
# または
alias ssh='ssh -F <(cat ~/.ssh/conf.d/*.conf)'
といった方法もありそうです。
<()
オペラントを知るなら echo <(:)
といったコマンドを打ってみるとよいでしょう(: は何もしない bash 組み込みコマンド)。
ただ、私は分割した設定ファイルをバージョン管理していることもあり、結合は意識的にやったほうがあっているようでした。また、ssh
コマンド類を上書き定義している or 別のラッパーコマンドがあるというのもしっくりこなかったです(別の環境で混乱しそうとか)。
設定ファイルを分割するメリット
普通には分割はできないわけで手間がデメリットだったりする設定ファイルの分割ですが、メリットもあるので行いたいという人もいます。私が考えるメリットを列挙してみます。
- 接続ホストが大量になってくると、設定ファイルを分割することで見通しが良くなる
- バージョン管理時には、大きなファイルを管理するよりも小さな複数のファイルを管理する方がコンフリクトが起こりづらかったりといったメリットがある
- 他人に OpenSSH の設定ファイルを例示する際、開示してはいけないところを回避しつつ開示して良いところだけをすぐに出しやすい
- ファイル分割という習慣だけで設定の片付けができる(一枚ファイルに書いていく方法だとどうしても散らかりやすい)
設定ファイルのバージョン管理をしている人は最近では珍しくなくなりましたが、特に他人に例示というところは意外なメリットだったりします。
私の設定ファイル管理
なるべくシンプルに、意識的な手間と利便性と環境依存性の少なさを取った方法を模索していますが、今のところは以下の様な方法をとっています。
バージョン管理
2004年くらいから CVS を使って雑にバージョン管理を行っていましたが、最近は Git でバージョン管理をしています。秘匿性の少ないドットファイル類は GitHub で公開していますが、OpenSSH のような秘匿性の高いものは BitBucket でプライベートリポジトリを作って管理しています。
今の OpenSSH 設定ファイルは ssh_config という BitBucket プライベートリポジトリで以下のような感じ。
使いたい場合には初回に
$ cd ~/.ssh/ ; git clone git@bitbucket.org:xtetsuji/ssh_config.git vc
などしています。ワーキングディレクトリは vc
とか目立つ短いものにしておくといいかなという考えです。後々変わるかもしれません。
~/.ssh/
自体をバージョン管理する方法も考えたのですが、さすがに秘密鍵はリモートリポジトリに入れたくないし、known_hosts
のように無意識的に編集されるファイルのバージョン管理は私には面倒事が多かったのでバージョン管理はやめました(気が向いた時にコミットしたらプッシュ時にコンフリクトしたりとか)。
秘密鍵はローカルメディアにバックアップ(または Mac の TimeMachine バックアップ)、konwn_hosts
などは世代バックアップをしています。
独特の設定があるリモートホストについては、hosts/ホスト名/
といったディレクトリに config
などの各種ファイルを放り込む運用にしています。
結合の手法
ファイル群が新たなファイルを作るというのはまさに Make の仕事。だいたいどこにでも入っている make
コマンドを使うことにしました。
CP = cp -p
CONFIG_FILES = $(shell echo conf.d/*.conf)
usage:
@echo "Usage:"
@echo " update git update"
@echo " config generate config from conf.d/*.conf"
@echo " ../config same as config"
@echo " keylist show keylist from keylist.txt"
@echo " deploy deploy config and keylist.txt to ~/.ssh/"
@echo " clean remove config and deploy flag"
update:
git fetch
git pull origin master
config ../config: $(CONFIG_FILES)
{ \
echo "# -*- ssh-config -*-" ; \
cat $(CONFIG_FILES) | grep -E -v '^#( -\*-|//)' ; \
} > $@
perl -p -i -e 's/^# \$$sh\((.*)\)$$/ join "\n", qx{$$1} /e' $@
keylist: keylist.txt
grep -v '^#' $<
deploy: config keylist.txt
$(CP) config $(SSH_DIR)
$(CP) keylist.txt $(SSH_DIR)
echo "This file is flag file" > deploy
clean:
rm -f config
rm -f deploy
最初は make config deploy clean
というコマンドで作成→デプロイ→掃除の流れをやっていたのですが、面倒だしワーキングディレクトリに config
ができるのもややこしいので、make ../config
だけにしました。これは ~/.ssh/vc
がワーキングディレクトリであれば、../config
が古ければ(conf.d/*.conf
の mtime の最も新しいものより古ければ)作りなおすという自然な手順となります。mtime の計算をシェルスクリプトでやる必要などがないのが楽。
意識的にファイルを結合する方法
上記手順では、ファイル結合に関する設定ファイルの編集のサイクルは
-
~/.ssh/vc/conf.d
以下のファイルを更新 make -C ~/.ssh/vc ../config
- 設定の正しさを確認(
ssh
できるかくらい) - 変更を add && commit && push
といったものです。結局バージョン管理するなら意識的にならざるをえないし、今のところはこれで満足しています。
バージョン管理されているところは ~/.ssh/vc
だということを意識さえしておけば、~/.ssh/config
が存在し続けたとしてもバージョン管理上の無意識な失敗(`~/.ssh/config 自体をいじってしまうとか)もだいたい避けられます。