AWS API GatewayでプライベートAPIというのが作れます。VPC内からしか呼び出せないので、リソースポリシーをきちんと設定することで、内部用途でのみ利用する(パブリックに公開しない)APIを実現することができます。
今回はこのプライベートAPIを踏み台のEC2経由で呼び出す場合のやりかたについて書きたいと思います。 (踏み台サーバーにsshログインして、そこからcurlコマンドなどでAPIを呼び出せることは確認済みとします)
今回の構成
以下のような構成だと仮定します。
- プライベートAPIのエンドポイント:
https://abcde.execute-api.ap-northeast-1.amazonaws.com/v1/users
- 踏み台サーバーのIP:
13.xxx.yyy.zzz
発生した課題
踏み台を使った接続でよくやるのは、SSHローカルフォワード設定をしてSSHトンネル接続を使うやり方だと思います。
しかし、プライベートAPI呼び出しのときにはこのやり方ではうまくいきませんでした。
プライベートAPIはhttpsで接続するので、サーバー証明書のCN(Common Name)やSANs(Subject Alternative Names)とリクエスト宛先のurlと一致している必要があります。
ローカルフォワードを使うと、クライアントからはhttps://localhost:8888/path/to/resourceみたいな形式で呼び出すことになるため、localhost
というホスト名が証明書と一致せずにhttps接続できずにエラーとなります。
リバースプロキシをVPC内に用意して、リパプロとプライベートAPI間をhttpsで通信させることでも回避できそうですが、そのためだけにリバースプロキシを立てたくもありません。 そこで今回はSSHのダイナミック転送を使い、プライベートAPIのリクエストは、SOCKSプロキシ経由で通信することで解決しました。
SOCKSプロキシを利用してリクエストを送信する
SSH接続ではダイナミック転送という機能を使うことができます。ダイナミック転送を使うと、sshクライアントはSOCKSプロキシとして動作します。SOCKSプロキシを経由した通信では、指定されたエンドポイント(今回はプライベートAPI)にはsshサーバー側から接続に行きます。このため、VPC内からしか呼び出せないプライベートAPIを呼び出すことができます。かつ、呼び出し元のリクエストURLもプライベートAPIのエンドポイントを指定しているので、課題で書いたようなホスト名の不一致によるエラーも起きません、
~/.ssh/config
の例
Host bastion Hostname 13.xxx.yyy.zzz Port 22 User ec2-user IdentityFile ~/.ssh/id_rsa DynamicForward 9999
DynamicForwardの設定が重要です。LocalForwardではなく、DynamicForwadを使います。
このSSH設定で、踏み台とのコネクションを確立します。
すると、DynamicForward
で指定したポート番号がSOCKSプロキシとして動作してくれるので、各ツールなどでそれぞれSOCKSプロキシを指定すれば、プライベートAPIを実行することができます。
curlコマンドの場合の実行例
$ curl --proxy socks5h://localhost:9999 https://abcde.execute-api.ap-northeast-1.amazonaws.com/v1/users
※プロキシとしてsocks5h://〜で指定しています。 SOCKSプロキシはデフォルトではDNS名前解決をクライアント側で行ってからプロキシに渡す動きとなります。しかし、プライベートAPIはVPC内からでないと名前解決できないので、名前解決をSOCKSプロキシ側で行うようにオプション指定してやる必要があるので注意してください。 curlの場合、socks5h://と指定することで名前解決をプロキシ側で行ってくれます。(socks5://だと、クライアント側で名前解決してからプロキシに渡す)
ブラウザ用にプロキシ構成ファイルを準備する
今回はブラウザでも動くようにしたいので、FireFoxに読み込ませるプロキシ構成ファイルを用意します。 Chromeなど他のブラウザでも対応可能ですが、以下の理由によりFireFoxを使うことにしました。
以下の内容でファイルを作成し、bastion_proxy.pac
と名前を付けて保存する。ファイル名は任意だが、拡張子は.pac
としておく
function FindProxyForURL(url, host) { // プライベートAPIのDNS名のときはSOCKSプロキシを使う if (host === "abcde.execute-api.ap-northeast-1.amazonaws.com") { return "SOCKS5 127.0.0.1:9999"; } // 通常はプロキシを使わない return "DIRECT"; }
このプロキシ構成ファイルの中で、宛先ホストがプライベートAPIのときは、SOCKSプロキシを使用するように記述している。
このファイルを、どこでも良いので任意のフォルダに置く(C:¥work¥bastion_proxy.pac など)
FireFoxを開き、メニュー>オプションを開く画面を下にスクロールして、ネットワーク設定>「接続設定」を開く【インターネット接続】画面で以下のとおり設定する以上で準備OK。
- ②ファイルパスの指定は、
file://
で開始する必要があるので注意 - ③SOCKSプロキシでは通常は名前解決をローカルで行ってから、SOCKSプロキシに渡す挙動になる。しかし、プライベートAPI場合はVPC内からでないと名前解決できないため、ここにチェックを入れる。これにより、SOCKSプロキシ側(=VPC内)で名前解決が行われる。
APIの呼び出し
あとは、ブラウザからプライベートAPIを使用しているサイトを開けばOK! プライベートAPIの呼び出しだけがSOCKSプロキシを利用して通信が行われます。