なにこれ
最近こんな案件に当たりました
- サーバーの種類がかなりの台数ある上に、踏み台経由じゃないとアクセスできない
- 認証鍵もポートもユーザー名も全部バラバラ
- 踏み台サーバーは定期的に再生成されIPがたまに変わる
・・・お堅い仕事は結構ですが、とにかくサーバーあたりの情報指定が多くssh/confignが汚染されるのが嫌なので、別ファイルを使ってsshを叩けるようにしたい!と思ったのですが、適当にProxyCommand
書いてもssh: Could not resolve hostname
が出て詰まったので調査がてら勉強しなおしました。
sshの基本についてもついでに書くので、本題にしか興味がない人は本題まで飛ばしてください。
最近忙しすぎて年単位で記事書けてなかった&保守もできてなかったので、記事を書くリハビリを兼ねます
sshでサーバーにアクセス
単純にsshコマンドでアクセスする
まずは基本に立ち返って地道にsshします(本題以前の話です。興味ない人は飛ばしてください)
Portが2222
が空いているIP192.168.0.1
のサーバーにmochisuna
という名前でアクセスしたい場合、
- 公開鍵/秘密鍵を生成(クソ雑ネーム
ssh_my_key
)
例:ssh-keygen -t ed25519 -C "hirokazu.maruta@gmail.com" -f ssh_my_key
- 接続先サーバー(
192.168.0.1
)の~/.ssh/authorized_keys
に公開鍵を追記
を設定して以下のようなコマンドを打つことで、そのサーバーにアクセスすることができるようになります。
ssh -i ~/.ssh/ssh_my_key mochisuna@192.168.0.1 -p 2222
-iオプション
はどの認証情報を使うか(≒どの鍵の情報を使うか)というもので、事前に提供した公開鍵に合致する秘密鍵を指定する必要があります。
ただ、ssh-add ~/.ssh/ssh_my_key
とかしてあげれば毎回 -i オプション
を入れなくともアクセスできるようになります。
ここまでは基本。
では192.168.0.1
はただの踏み台で、ここを経由して他のサーバーにアクセスしたい場合はどうしましょうか?
多くの場合は次のように ProxyComannd
をかけてアクセスするでしょう(192.168.0.3にアクセスすることを仮定)
ssh -i ~/.ssh/ssh_my_key -o ProxyCommand='ssh -i ~/.ssh/ssh_my_key mochisuna@192.168.0.1 -p 2222 -W %h:%p' mochisuna@192.168.0.3
内容的には-oオプション
で踏み台サーバーにsshするコマンドを実行してから改めて目的のサーバーを叩くというようなことをやっているというもので、記法に慣れれば難しくないし便利です。
ただこのケース「ちょっと目的のサーバーにアクセスする」くらいならいいんですが、アクセス先のサーバーが増えてくると、管理・タイピング共にとても大変になってきます。
そこで .ssh/config
を活用してアクセスします。
~/.ssh/config
をカスタマイズしてアクセスする
~/.ssh/config
にサーバーの情報を記載することでアクセス先の管理を大幅に簡略化できます。
Host step
User mochisuna
HostName 192.168.0.1
IdentityFile ~/.ssh/ssh_my_key
Port 2222
Host web1
User mochisuna
HostName 192.168.0.3
IdentityFile ~/.ssh/ssh_my_key
ProxyCommand ssh mochisuna@192.168.0.1 -p 2222 -W %h:%p
Host web2
User mochisuna
HostName 192.168.0.5
IdentityFile ~/.ssh/ssh_my_key
ProxyCommand ssh mochisuna@192.168.0.1 -p 2222 -W %h:%p
このようにsshコマンドで記述するパラメータを構造体のような形で書いてあげることで、ssh web2
のような簡単なコマンドだけでアクセスできるようになります。
自動的に定義したパラメータで置換してアクセスしてくれるわけですね。便利ー。
ただ、この書き方はあまり良くありません
例えば毎週stepサーバーのIPが変わるような仕組みが取られていると、毎回書き直すのに絶望することになります。・・・まぁそんな設定入れることはないだろうけど、要は起点のIPが変わると保守が大変ということです。
なので以下のように書き換えます。
Host step
User mochisuna
HostName 192.168.0.1
IdentityFile ~/.ssh/ssh_my_key
Port 2222
Host web1
User mochisuna
HostName 192.168.0.3
IdentityFile ~/.ssh/ssh_my_key
ProxyCommand ssh step -W %h:%p
Host web2
User mochisuna
HostName 192.168.0.5
IdentityFile ~/.ssh/ssh_my_key
ProxyCommand ssh step -W %h:%p
こうすればstepサーバーの内容をいい感じに補填してくれるようになるので、stepサーバーのIP設定を変えるだけでssh web2
のでアクセスできるようになります。
超便利。
ただ、僕は複数のプロジェクトに関わっているため、全てのプロジェクト設定を~/.ssh/config
に盲目的に入れていくと大変なことになります。
step
だけだと、どのプロジェクトのどこのサーバーに行くかわかったもんじゃないのです。
そこでssh_config
というようなファイルをプロジェクト毎に定義します。
そしてssh -F sugoi-project/ssh_config web1
のように指定してあげれば、任意の定義ファイルでアクセスができるようになる・・・ハズですが、実はそのままではやろうとすると ssh: Could not resolve hostname
が出てアクセスできません。
ファイルの記載をさらに改造しましょう。
(2023/05/12追記)
@fumiyasさんからこういうアプローチがあるというご意見いただきました。
プロジェクト毎に接頭辞をつけることで補完が効くようになって便利という話
確かにこれは賢い。
ProxyJumpを使って踏み台サーバー経由でアクセスする
本題です
~/.ssh/config
の汚染を避けるためsugoi-project
プロジェクト配下のssh_config
のというファイル(~/.ssh/config
と同じ記法)を利用することを前提にします。
この時、HOSTの記載がProxyCommand ssh step -p 2222 -W %h:%p
のように記載されていた場合ssh -F sugoi-project/ssh_config web1
のようにしても以下のようなエラーが出てアクセスできないかと思います。
$ ssh -F sugoi-project/ssh_config
ssh: Could not resolve hostname step: nodename nor servname provided, or not known
kex_exchange_identification: Connection closed by remote host
これはProxyCommand
が実行される際にsshのプロセスには「sugoi-project/ssh_config
ファイルからサーバー設定を読み取った」という情報が渡されることがないため ~/.ssh/config
の中からstep
サーバー情報を探してしまうことが原因です。
そこでProxyJumpを利用した形に書き換えます(OpenSSH 7.3以降に限り)
Host step
User mochisuna
HostName 192.168.0.1
IdentityFile ~/.ssh/ssh_my_key
Port 2222
Host web1
User mochisuna
HostName 192.168.0.3
IdentityFile ~/.ssh/ssh_my_key
ProxyJump step
Host web2
User mochisuna
HostName 192.168.0.5
IdentityFile ~/.ssh/ssh_my_key
ProxyJump step
このように定義してあげることでssh -F sugoi-project/ssh_config web2
というような形でアクセスできるようになります。
僕はさらに手を抜いてsshcとかいう適当なコマンドでアクセスできる仕組みを作ってます。
sshc ssh_config
って打つだけでほぼ脳死でアクセスできるのでオススメです。
グループにしてコピペを減らす(2023/05/12追記)
これまでの方法ですでにある程度便利になったのですが、物事は突き詰めたくなるものです。
例えば、特定のグループ毎に同じユーザー名やポート番号を利用したいケースの場合では、上述の方法でも何度もコピペが発生しファイルの巨大化や管理の面倒さなどが発生してしまいます。
そこで、これまでの条件に以下のようにケースを追加して考えたいと思います。
- ユーザー名やポートは特定の機能毎に同じものが利用される
- アプリケーションサーバー(app1 ~ app4)
- NTPサーバー(ntp1 ~ ntp3)
- NMSサーバー(nms1 ~ nms2)
- syslogサーバー(syslog)
このような場合、以下の書き換えてあげることで記述事項の使い回しができるようになります。
Host app1
HostName 192.168.0.1
Host app2
HostName 192.168.0.2
Host app3
HostName 192.168.0.3
Host app4
HostName 192.168.0.4
Host ntp1
HostName 192.168.2.1
Host ntp2
HostName 192.168.2.2
Host ntp3
HostName 192.168.2.3
Host nms1
HostName 192.168.0.15
Host nms2
HostName 192.168.0.16
Host syslog
HostName 192.168.1.123
User logger
Port 12322
Host app?
User ubuntu
Port 2022
IdentityFile ~/.ssh/ssh_app
Host nms?
User centos
Port 2222
Host ntp?
User rocky
Port 1022
Host *
IdentityFile ~/.ssh/ssh_default
ProxyJump step
よく見ると app?
や nms?
のように定義済みのホストを参照した記述と *
のように、いかにもワイルドカードな記述があるのがわかるかと思います。
このように記述すると
- app1 ~ app4はIPは各々のものを利用し、それ以外はユーザー名:
ubuntu
、Port:2022
、IdentityFile:~/.ssh/ssh_app
を利用する - nms、 ntpなどもappと同様にそれぞれのユーザー名とPortを利用するが、デフォルトとして
~/.ssh/ssh_default
を利用する
という処理が実行されるようになります。
規模が大きくなってくる場合は共通化・一般化してあげるようと便利そうですね。
まとめ
-
ssh-add
使うとsshコマンドの-i
オプションを省略できるけど、それやるくらいなら.ssh/config
を利用したほうが楽 -
ProxyCommand
を使うなら外部ファイルではなく~/.ssh/config
で使う -
ProxyCommand
、ProxyJump
は定義済のHost情報を利用できるのでIPとかはベタ書きしない -
ProxyJump
を使えば sshコマンドの-F
オプションでも定義済Host情報を使えるので便利
stackoverflowでもちょくちょく上がる内容ぽいので、知っておくと便利かもしれません。