ProxyCommandを使って踏み台(Bastion)経由で直接ssh/scpする

man_ssh

自分の作業環境の作り直しをしているのですが、
たまに理解が怪しくなるので、確認ついでにここに吐き出しておこうと思います。

背景

リモート環境にあるホストAにsshあるいはscpするためには、
 踏み台(Bastion)となるホストXに一度SSHログインしないとならない

というケース/環境はそこそこ多いと思います。

これがsshなのであればまあ、ssh X -> ssh Aとするだけなのでそこまででもありませんが、
scpだった場合は概ねこんな手順になりますよね。

  1. まずssh XとしてホストXにログイン
  2. ホストX上でscp -p A:/path/to/datafile ~/(オプションはお好み)
  3. コピーが終わるのを待ってから、Xからログアウト
  4. scp -p X:~/datafile .
  5. コピーが終わるのを待ってssh X rm ~/datafile

これで何が困るかというと、

  • コピー(scp)が2回必要なので、時間が余計にかかる
  • 1回目のコピーが終わったことに気づかず、あるいは終電 業務時間が終了するなどして、さらに時間がかかる
  • ホストXの空き容量がたりないときがある
    • 分割したり、X以外に使えそうなホストを探し回ったり作ったりと余計な時間やコストがかかる
  • ホストX上の一時ファイルを消し忘れる ←
    • むしろ「もしもの時のためにとっておこう、用が済んだらあとで消そう」などと積極的に残してしまう
    • そして忘れる1
    • 結果ホストXのディスクが一杯になって他の利用者や管理者に怒られる

こうなるからです。2

これは手動のときだけの話ではなく、
定期的にscpを実行させたいときや、スクリプトやバッチ処理時にsshでコマンド実行しないとならないときにも問題になります。

本来ならば、データ受け渡しのための仕組みをちゃんと用意すべきところではありますが、逆に(様々な事情で)恒常的な仕組みを設けられない場合もありますし、 一時的なメンテナンスや調査等でしか必要にならない場合、「そこまでしなくても…」と思ってしまうのも人情というものではないでしょうか。

ProxyCommandに-Wオプションという救世主

定番中の定番という話ではあるのですが、
ProxyCommandを使えば、ワンライナで実行できます。

$ ssh -oProxyCommand='ssh -W %h:%p X' A
$ scp -p -oProxyCommand='ssh -W %h:%p X' A:/path/to/datafile .

ProxyCommand中の%h %pが、
本来の接続先である A とそのポート(無指定だとデフォルトの 22)に置換される、という動作になります。

実例としてはこんな感じになるかと。

$ ssh -oProxyCommand='ssh -W %h:%p ec2-user@ec2-13-112-xxx-xxx.ap-northeast-1.compute.amazonaws.com' ec2-user@ip-172-31-xxx-xxx.ap-northeast-1.compute.internal
  • Host X ... ec2-13-112-xxx-xxx.ap-northeast-1.compute.amazonaws.com
  • Host A ... ip-172-31-xxx-xxx.ap-northeast-1.compute.internal

鍵指定(-i)やポート番号の指定(-P9022-oPort=9022等)といったオプションも、
勿論上記コマンドラインの中に含めることが可能です。

その際は、
ホストXに対するオプションをProxyCommand内に、
ホストAに関するものを外側(-oProxyCommandと同列)に記述してください。

なお、実際にAにアクセスするのはXなので、
コマンドを実行するホストがAのホスト名を名前解決できなくても構いませんし、
勿論ホスト名ではなく、IPアドレスで書いても構いません。

むしろ注意すべきなのは、ワンライナで書こうともAに接続するのはあくまでXなので、
Xが名前解決・接続できるようにAを指定(ホスト名、IPアドレス)する必要があるというところです。

要は普段Xで実行している内容そのままを記述すればいいのですが、
一方でSSHとしてAにログインする、つまりホストAと鍵の交換を行うのは手元のホストになるので、
鍵の置き場やknown_hostsの記述などは、ホストXではなく手元の環境になります。

関係する全インスタンスのユーザやSSH鍵が同じならそこまで問題にはならないでしょうけど、
インスタンス先によってユーザや鍵が変わる場合は若干ややこしいですね。。。

~/.ssh/configに記述して楽をする

このような操作をそこそこ繰り返し行うなら、~/.ssh/configに記述してしまえば楽になります。

といいますか、そういった話はさすがに定番どころの設定なので、Developers.IOでも何回か記事になっていて参考になります。

蛇足がてら付け加えるなら、ssh_configHostパターン指定ができるので、

Host *.compute.internal 172.31.*
	ProxyCommand	ssh -W %h:%p X

のように、VPC内のホストはすべてホストX経由で接続する…という記述しておくとより楽です。

例えば上述の例で言えば、

Host *.compute.internal
	ProxyCommand	ssh -W %h:%p ec2-13-112-xxx-xxx.ap-northeast-1.compute.amazonaws.com

Host *
	User				ec2-user

としておくことで、下記のように短くなります:

$ ssh ip-172-31-xxx-xxx.ap-northeast-1.compute.internal

ちなみに

この指定方法は、Ansibleやrsync等、バックエンドにssh/scpを使っているコマンドにも有効です。

参考:

また直接関係はありませんが、このような踏み台ホストは頻繁にアクセスすることになるので、
ControlMasterなども設定するとより幸せになれたことを併せてご報告いたします。

参考:


  1. はい、わたしのことです。 
  2. 経験には個人差が有ります。