~/.ssh/configファイルをGitリポジトリで管理する
リモートホストにSSH接続する際に便利な ~/.ssh/configファイルを、Git管理し、かつSSH踏み台ホスト上へのデプロイも簡単にできるようにする、というプロジェクトの実装例です。
実装自体は、以下のGitHubリポジトリに公開しています。複雑なことはやっていないので、多分リポジトリのコードを直接みるのが一番早いです。
この記事では、実装の考え方を中心に、このリポジトリの出来上がり方を記載しています。
~/.ssh/configファイルとは
~/.ssh/configファイルとは、ssh
コマンドでいつも指定する接続情報をあらかじめ記載(複数可)しておき、ssh <接続先のエイリアス>
でスッとSSH接続できるようにする、ありがたいファイルです。
# Development Host (Amazon Linux 2)
Host devel
HostName 192.168.1.10
User ec2-user
IdentityFile ~/.ssh/devel.pem
# Mail Host (Ubuntu)
Host mail
HostName 192.168.2.50
User ubuntu
IdentityFile ~/.ssh/mail.pem
$ ssh devel # これと同じ:ssh -i ~/.ssh/devel.pem ec2-user@192.168.1.10
詳細は割愛します。参考に上げたリンクを確認するか、「ssh config」でググってみてください。
AWS EC2インスタンスの踏み台サーバ利用について
AWS Systems Manager (SSM) Session Managerを使うことで、SSM AgentをインストールしたEC2インスタンスに対しては、SSH踏み台ホスト無しで直接アクセスするようにできます[1]。検討してみてください。
ただし、SSM Agentをインストールできない製品VMでは利用できなかったり、いくつかの要件が合わなかったり[2]で、やむを得ずSSH踏み台ホストを作成・利用するケースもあると思います。
前提
「踏み台ホストから他のホストにSSH接続するための秘密鍵(例:*.pem)は、~/.ssh/配下のどこかに置いてある」という想定とします。つまり、独自の専用のサブディレクトリ(深さ問わず)に秘密鍵を集めていても大丈夫です。
OKなディレクトリ構成の例
実装方針
まずは、実装の方針を確認しましょう。
-
~/.ssh/配下のファイルのGit管理について:
- する:config
- しない:SSHのキー(authorized_keys, 秘密鍵)、known_hosts, known_hosts.old
-
~/.ssh/configの調整・更新作業について:
- ~/.ssh/ディレクトリにて、直接作業ができるようにする
- ごく普通の、configファイル編集、
git add config
&git commit
、git push
という単純なフローになるようにする
-
簡単なデプロイ操作にする
- 初期デプロイ:
『最新の~/.ssh/configに置き換え』+『~/.ssh/ディレクトリのGitリポジトリ化』 - 2回目以降のデプロイ:
『最新の~/.ssh/configに置き換え』
- 初期デプロイ:
デプロイ操作イメージ
3の「簡単なデプロイ操作」について、具体的に考えておきます。
3-1. 初期デプロイ
初期デプロイでは、『最新の~/.ssh/configに置き換え』+『~/.ssh/ディレクトリのGitリポジトリ化』を同時に行います。
手数少なく、かつ適当なディレクトリで作業が完遂できるように実装することにします。
$ git clone <リモートGitリポジトリのURL> ssh-bastion-config
$ ./ssh-bastion-config/deploy.sh # デプロイ用シェルスクリプトの実行
# =>
# * 最新の~/.ssh/configに置き換え
# * ~/.ssh/ディレクトリのGitリポジトリ化
# * git cloneしてきた./ssh-bastion-config/ディレクトリを削除(掃除)
ここで、deploy.shに期待する動作要件は、次のとおりです:
-
git clone
してきたディレクトリ内のconfigファイルを~/.ssh/配下に配置する - ~/.ssh/をGitリポジトリにする
- 今適当な場所に
git clone
してきたディレクトリを、削除する(掃除)
3-2. 2回目以降のデプロイ
2回目以降のデプロイでは、『最新の~/.ssh/configに置き換え』を行います。
git pull
を活用します。
$ cd ~/.ssh/
(~/.ssh)$ git pull
# => 最新の~/.ssh/configに置き換え、他のファイルはそのまま
ここで、git pull
に期待する動作要件は、次のとおりです:
- ~/.ssh/configファイルに更新が走る
- ~/.ssh/配下の、既存のSSHのキー(authorized_keys, 秘密鍵)、known_hosts, known_hosts.oldはそのまま
- (Gitリポジトリ用のファイルももちろん更新が走る(README.md, .gitignoreなど))
git pull
による更新ファイルイメージ(SSH関係のファイルのみ記載)
実装
configファイル
まず、conifgファイルを作成します。
# Development Host (Amazon Linux 2)
Host devel
HostName 192.168.1.1
User ec2-user
IdentityFile ~/.ssh/devel.pem
あるいは、次のように、踏み台サーバにてconfigファイルを直接作成してもよいです(こちらの方が調整も容易です):
- 一旦全部コメントアウト(
#
を行の先頭に置く)した雛形configファイルを作っておく - このあと作る.gitignoreファイルとdeploy.shと一緒にコミットして、リモートGitリポジトリに
git push
- 踏み台ホスト上で、リモートGitリポジトリを初期デプロイ(
git clone
してdeploy.sh実行) - ~/.ssh/ディレクトリがGitリポジトリになっているので、踏み台ホスト上で~/.ssh/configを調整しながら作成する(フロー詳細は、次節[使い方補足] configファイルの更新フローを参照)
Git管理するファイルを決める(.gitignore)
次に、.gitignoreファイルを、以下のように作成します:
# Ignore SSH keys.
authorized_keys
*.pem
# Ignore known_hosts, known_hosts.old
known_hosts*
こうすれば、SSHに関係するファイルについて、以下をすべて満たすことができます:
-
~/.ssh/配下のファイルのGit管理について(実装方針1):
- する:config
- しない:SSHのキー(authorized_keys, 秘密鍵)、known_hosts, known_hosts.old
-
~/.ssh/configの調整・更新作業について(実装方針2):
- ~/.ssh/ディレクトリにて、直接作業ができるようにする
- ごく普通の、configファイル編集、git add config&git commit、git pushという単純なフローになるようにする
-
git pull
したときに...(実装方針3-2):- ~/.ssh/configファイルを更新
- ~/.ssh/配下の、SSHのキー(authorized_keys, 秘密鍵)、known_hosts, known_hosts.oldはそのまま
初期デプロイ用スクリプト(deploy.sh)
最後に、実装方針3-1. 初期デプロイで使うスクリプトdeploy.shを作成します。
操作イメージと動作要件を再掲します:
適当なディレクトリにて(ディレクトリ名ssh-bastion-configとしてgit clone)$ git clone <リモートGitリポジトリのURL> ssh-bastion-config $ ./ssh-bastion-config/deploy.sh # デプロイ用シェルスクリプトの実行
git clone
してきたディレクトリ内のconfigファイルを~/.ssh/配下に配置する- ~/.ssh/をGitリポジトリにする
- 今適当な場所に
git clone
してきたディレクトリを、削除する(掃除)
実際には、次のような動作で、これらの要件を満たすようにします:
- ~/.ssh/配下の、SSHのキー(authorized_keys, 秘密鍵)、known_hosts, known_hosts.oldを、
git clone
してきたディレクトリに持ってくる- 秘密鍵(例:*.pem)は、~/.ssh/配下のサブディレクトリに格納している可能性もある。その場合は同名のサブディレクトリを作成して、その中に秘密鍵を入れる
-
git clone
してきたディレクトリで、~/.ssh/ディレクトリを、上書きする- .git/ごと移動するので、Gitリポジトリになる
-
git clone
してきたGitリポジトリに入っていたconfigファイルが~/.ssh/configになる - ついでに
git clone
してきたディレクトリの削除(掃除)も完了する
このとき、元々の~/.ssh/ディレクトリに入っている「1で持ってこないファイル」については、2にて上書きされて消えてしまいます...というのは乱暴なので、そういうファイルがある場合はエラーで中断するようにします。
動作イメージ(秘密鍵用のサブディレクトリがあるパターン)
#!/bin/bash
# 秘密鍵の拡張子('.'抜き)
private_key_ext="pem"
# ------------------------------------------------------------
# Gitリポジトリの中(=このdeploy.shと同じディレクトリ)に移動
cd $(dirname $0)
ssh_dirpath="${HOME}/.ssh"
repository_dirpath=$(pwd)
echo "Start to deploy ${ssh_dirpath}/config."
# 0. 余計なファイルが~/.sshディレクトリ配下に含まれていないかどうかの確認
disallowed_files=$(find ${ssh_dirpath} -type f \
\( ! -name authorized_keys \) \
-and \( ! -name "*.${private_key_ext}" \) \
-and \( ! -name "known_hosts*" \))
## 余計なファイルが含まれている場合は中止
if [ "${disallowed_files}" != "" ]; then
echo -e "[\e[31mERROR\e[m] Please remove these files from ${ssh_dirpath}:"
echo ${disallowed_files} | tr " " "\n" \
| xargs -I @ echo " x @"
echo "Deployment canceled."
exit 1
fi
# 1. 既存のSSHのキー(authorized_keysファイルと秘密鍵)、
# known_hosts, known_hosts.oldを、このGitリポジトリのディレクトリに移動
echo "[1] Moved existing files:"
## authorized_keys
mv ${ssh_dirpath}/authorized_keys ./
echo " * ${ssh_dirpath}/authorized_keys -> ${repository_dirpath}/authorized_keys"
## 秘密鍵
find ${ssh_dirpath} -type f -name \*.${private_key_ext} \
| while read fullpath; do
sed_script="s|^$(echo ${ssh_dirpath} | sed -r 's|\.|\\.|g')/?(.*)?$|\1|"
subdirpath=$(dirname $(echo ${fullpath}) | sed -r "${sed_script}")
# 秘密鍵がサブディレクトリにある場合は、サブディレクトリを作成
if [ "${subdirpath}" != "" ] && [ ! -d ./${subdirpath} ]; then
mkdir -p ./${subdirpath}
fi
mv ${fullpath} ./${subdirpath}
filename=$(basename ${fullpath})
echo " * ${fullpath} -> ${repository_dirpath}/${subdirpath}/${filename}" | tr -s /
done
## known_hosts*
find ${ssh_dirpath} -type f -name 'known_hosts*' \
| while read fullpath; do
mv ${fullpath} ./
filename=$(basename ${fullpath})
echo " * ${fullpath} -> ${repository_dirpath}/${filename}"
done
# 2. ~/.sshディレクトリを削除
rm -rf ${ssh_dirpath}
echo "[2] Removed ${ssh_dirpath}/ directory."
# 3. このGitリポジトリを~/.sshディレクトリに
mv ${repository_dirpath} ${ssh_dirpath}
echo "[3] Moved ${repository_dirpath}/ as a new ${ssh_dirpath}/ directory."
echo "Mission Accomplished!"
実行例
- 無難に完了:
$ cd /tmp
(/tmp)$ ./ssh-bastion-host-config/deploy.sh
Start to deploy /home/ubuntu/.ssh/config.
[1] Moved existing files:
* /home/ubuntu/.ssh/authorized_keys -> /tmp/ssh-bastion-host-config/authorized_keys
* /home/ubuntu/.ssh/private_keys/baz.pem -> /tmp/ssh-bastion-host-config/private_keys/baz.pem
* /home/ubuntu/.ssh/private_keys/devel/foo.pem -> /tmp/ssh-bastion-host-config/private_keys/devel/foo.pem
* /home/ubuntu/.ssh/private_keys/devel/bar.pem -> /tmp/ssh-bastion-host-config/private_keys/devel/bar.pem
* /home/ubuntu/.ssh/known_hosts -> /tmp/ssh-bastion-host-config/known_hosts
[2] Removed /home/ubuntu/.ssh/ directory.
[3] Moved /tmp/ssh-bastion-host-config/ as a new /home/ubuntu/.ssh/ directory.
Mission Accomplished!
- 余計なファイルが含まれているのでエラーで終了(実際のメッセージの
ERROR
は赤字です):
$ ./ssh-bastion-host-config/deploy.sh
Start to deploy /home/ubuntu/.ssh/config.
[ERROR] Please remove these files from /home/ubuntu/.ssh:
x /home/ubuntu/.ssh/private_keys/baz
x /home/ubuntu/.ssh/.foobar
x /home/ubuntu/.ssh/config
Deployment canceled.
Step 2について、秘密鍵やknown_hosts, known_hosts.oldファイルは、初期デプロイ実施時点の踏み台ホスト~/.ssh/配下には存在しない可能性もあるので、find
コマンドの結果で引っかかった分だけファイルの移動を実行しています。
また、工夫として、スクリプト実行時に環境変数HOME
を変更することで、別ユーザの~/ssh/configのセットアップが可能な形にしています([使い方補足] rootユーザとしてデプロイを行う場合の節を参照)。
そうそう、スクリプトファイル作成後に、実行権限を割り振るのを忘れないでください:
$ chmod 755 deploy.sh
これで実装は終わりです。
コミットして、リモートGitリポジトリにgit push
し、踏み台ホストにて使用します。
[使い方補足] configファイルの更新フロー
初回のdeploy.shによる踏み台ホスト上へのデプロイが済んでしまえば、以降は単純なフローになります:
- ~/.ssh/ディレクトリに移動する
- ~/.ssh/configファイルを直接編集する
- ~/.ssh/configファイルを
git add
&git commit
する - リモートリポジトリに
git push
する
[使い方補足] rootユーザとしてデプロイを行う場合
ユーザ本人でなくrootユーザが代理でデプロイを行う場合、以下のようにデプロイ操作を変更してください(ユーザ「ec2-user」のセットアップ例です):
$ git clone <GitリポジトリのURL> ssh-bastion-config # ここは同じ
$ HOME=/home/ec2-user ./ssh-bastion-config/deploy.sh # 変更点1
$ chown -R ec2-user:ec2-user /home/ec2-user/.ssh # 変更点2
- deploy.shは、環境変数
HOME
の値で作用する.sshディレクトリが変わる。
rootユーザではHOME=/root/
なので、そのままdeploy.shを実行すると、/root/.sshが対象となってしまう。そこで、実行時のみHOME
を変更する - ユーザ本人が.ssh/configを使えるように、ファイルの所有者・グループを変更する
SSHのキー(authorized_keys, 秘密鍵)とknown_hosts, known_hosts.oldの管理・デプロイについて
セキュリティの観点から、特にSSHのキー(authorized_keys, 秘密鍵)の管理・デプロイは、別のセキュアな方法で行ってください。
known_hostsやknown_hosts.oldファイルは、無くなっても問題はないです。
(これらファイルが無い状態で、踏み台サーバから別のホストにSSH接続すると、その初回に『接続しようとしているのはこのホストで問題ないですか』という認証が発生するだけ。)
参考
-
- AWS Systems Manager Session Manager(AWS公式ドキュメント)
- さらば踏み台サーバ。Session Managerを使ってEC2に直接SSHする(karakaram-blog)
-
SSM Session Managerを使った踏み台サーバ構築(NRI Netcom BLOG):「SSM Session Managerを採用しなかった」記事です。どの要件を重視して採用するか否かを決める過程が書かれていて、参考になります。 ↩︎
Discussion
デプロイ用スクリプト、こんなややこしい動作にせずに、
「リポジトリ内の.git/, .gitignore, config, deploy.shを、~/.ssh/配下に
mv
する」でよいのでは...?