元茹でガエルなエンジニアの記録

ちょっとやってみたことや考えたことなどを不定期に書き残していきます

踏み台経由でAWS API GatewayのプライベートAPIにアクセスする方法

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呼び出しのときにはこのやり方ではうまくいきませんでした。 プライベートAPIhttpsで接続するので、サーバー証明書の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名前解決をクライアント側で行ってからプロキシに渡す動きとなります。しかし、プライベートAPIVPC内からでないと名前解決できないので、名前解決をSOCKSプロキシ側で行うようにオプション指定してやる必要があるので注意してください。 curlの場合、socks5h://と指定することで名前解決をプロキシ側で行ってくれます。(socks5://だと、クライアント側で名前解決してからプロキシに渡す)

ブラウザ用にプロキシ構成ファイルを準備する

今回はブラウザでも動くようにしたいので、FireFoxに読み込ませるプロキシ構成ファイルを用意します。 Chromeなど他のブラウザでも対応可能ですが、以下の理由によりFireFoxを使うことにしました。

  • FireFoxだとシステムの設定とは独立してプロキシ設定をカスタマイズできる(pacファイルを指定する)
  • Chromeは普段使っているので、設定変更したくない

以下の内容でファイルを作成し、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プロキシを使用するように記述している。

  • host===のところは、プライベートAPIDNS名に応じて変更・追加する
  • SOCKS5のところは、localhost + SSH接続でDynamicForward指定したポート番号を指定する

このファイルを、どこでも良いので任意のフォルダに置く(C:¥work¥bastion_proxy.pac など)

FireFoxを開き、メニュー>オプションを開く画面を下にスクロールして、ネットワーク設定>「接続設定」を開く【インターネット接続】画面で以下のとおり設定する以上で準備OK。

f:id:jiig999:20200713160027p:plain
FireFoxの設定

  • ②ファイルパスの指定は、file://で開始する必要があるので注意
  • ③SOCKSプロキシでは通常は名前解決をローカルで行ってから、SOCKSプロキシに渡す挙動になる。しかし、プライベートAPI場合はVPC内からでないと名前解決できないため、ここにチェックを入れる。これにより、SOCKSプロキシ側(=VPC内)で名前解決が行われる。

APIの呼び出し

あとは、ブラウザからプライベートAPIを使用しているサイトを開けばOK! プライベートAPIの呼び出しだけがSOCKSプロキシを利用して通信が行われます。