OAuthが絡むアプリケーションを開発していると、時折 https://localhost/ でローカルのアプリケーションサーバーにアクセスをしたくなる状況があります。 セキュリティ上、リダイレクト先URLの指定が必要で、かつhttpsしか設定できない場合があるためです。

手元にプロキシサーバーを立てて回避する手法がありますが、調べてみた限り手元の環境を汚してしまうものでした。 PC内環境美化に力を入れている私としては(ちなみに部屋のエントロピーはお察し)、何とか余計なインストールは避けたいところ。

そこで、Dockerを使ってSSL Terminationをするプロキシを立ててみました。 Githubで公開していますので、需要がある方はご利用ください - Docker Local SSL Termination Proxy

以下はこれに関する説明です。

簡単にはhttpsでアクセスできない

ローカルに立てたサーバーにhttpsでアクセスできるようにするには、証明書を作ったり、https用の設定をしてあげないといけません。
一般的な開発用サーバーではhttpで立ち上がるでしょうから、この手順は手間です。

NginxやApacheでプロキシを立て、localhostにhttpsでアクセス

localhostにhttpsでアクセスしたい時、よく使われるのは「ホストマシンに(直に)NginxやApacheを立てる」という方法です。
もちろんこれでも実現できますが、このためだけにNginxをインストールする必要があったり、手順が複雑だったり、起動しているかどうか分からなかったりします。
この処理は常に必要なものではないので、上記の問題はDockerを使うと上手く解決できます。

そこで、設定済みのNginxをDockerコンテナとして立てて、簡単便利なhttpsアクセスを可能にしてみます。

Docker内で立てたNginxプロキシの問題点

問題となるのは、Nginxの設定ファイルに、如何にしてホストのIPを埋め込むか という点です。

1点目の厄介ポイントは(Dockerコンテナからホストネットワークへアクセスするための)IPの取得からです。 Docker for Macでは、ネットワークモードがhostだとホストマシンからlocalhostでアクセスができず(Linuxなら可能だと思います)、bridgeモードにホストのIPを渡す、という手法を取る必要があります。 これはDockerコンテナ内からは取得できないため、起動時に環境変数として渡します。

2点目の厄介は、Nginxの設定ファイルは環境変数を使うように出来ていない、ということです。 ありがたいことに、こちらのドキュメントでenvsubstコマンドを使った方法について解説されています (Docker上のNginxのconfに環境変数(env)を渡すたったひとつの全く優れてない方法(修正:+優れている方法))。 この方法を利用して環境変数を埋め込みます。

他の点に関しては、普通にホストローカルにプロキシサーバーを立てるのと変わりません。

コードの解説

このリポジトリのファイルに沿って流れを説明します。

ホストのIPをDockerコンテナに渡す

まず、下記run.sh を使って起動&ポートを指定します。例として3000番を指定します。

$ ./run.sh 3000

すると、このスクリプトの中で環境変数の設定が行われます。 HOST_IPを取得するのが主な役割です。

$ export HOST_IP=`ipconfig getifaddr en0`

(Docker for Mac上で動かすことしか考えていないので、このコマンドをLinux用に変更する必要があります)

環境変数を設定後、おもむろにdocker-composeを立ち上げます。

docker-compose

Dockerfileで出来上がるimageとの単純な橋渡しをしているだけです。 443番のポートをバインドし、環境変数をそのまま渡します。

Dockerfile

Nginxを使った、普通のSSL Termination Proxyです。

Nginxの設定ファイルは、image内ではまだテンプレートです。 実際の起動時にentrypoint.shを呼び出すことで、環境変数を置換して本来の設定ファイルになります。

nginx.confでは環境変数名を書いておく

以下のように、環境変数混じりの指定をしています。 この部分がentrypoint.shで置換されます。

実際の設定は、443 -> 3000に転送をするだけです。

http {
  ...
  upstream app {
    server ${HOST_IP}:${PORT};

envsubstを使ってnginx.confを置換する

最後に、entrypoint.shでenvsubstを使って置換し、その設定ファイルを使ってNginxを起動します。

envsubst '$$PORT$$HOST_IP' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
nginx

以上が今回行った事です。 ./run.sh を叩くと、裏側でこのような処理が行われます。

備考

  • もっと簡単な方法がありそう……。
  • MacとLinuxで共通してIPを取得するコマンド、ご存知の方はPRください!
筆者のTwitter。Webアプリ開発について呟いたり呟かなかったりします。