証明書を自動設定するリバースプロキシを試す


追記1(2019/9/27)： [lua-resty-auto-ssl](https://github.com/GUI/lua-resty-auto-ssl)と、それが利用する古いバージョンの[dehydrated](https://github.com/lukas2511/dehydrated)のバグにより、現在パッチを当てないと動かない状態になっています。([対応するissue](https://github.com/GUI/lua-resty-auto-ssl/issues/192))
追記2(2019/9/27)： [カスタマイズ版](https://github.com/otolab/docker-nginx-auto-ssl)にパッチを当てました
追記3(2019/10/1): 問題が修正されたlua-resty-auto-ssl v0.13および、修正版の[v0.13.1](https://github.com/GUI/lua-resty-auto-ssl/releases/tag/v0.13.1)がリリースされました。dehydratedが0.6.5までアップデートされています。

----

[Let's Encript](https://letsencrypt.org/)いいですよね。

いまやHTTPSでないと動かないブラウザの機能があったり、HTTPだと危険なサイト扱いを受けたりと、証明書は必須という風潮ですから、証明書はなんとかしなければならない課題となっています。

ただ、Let's Encryptは証明書の期限が短く、台数が増えたりするとライセンスの更新処理などけっこう面倒です。(ツールをcronで動かして自動更新するのが主流ではあるようですが)

そんな場合に使える手法として、必要に応じてオンデマンドにLet's Encriptの証明書を取得する機能の利用があります。初回や更新時のアクセスは数秒遅延が発生しますが、メンテナンスフリーである点は大きい。

今回はコレをかんたんに用意してみます。


構成
------------

自動取得の実装にはいろいろあるようですが、[Nginxとlua-resty-auto-sslで全自動なLet's Encrypt環境を作る](https://qiita.com/Ets/items/8e6a1df730e24de239e4)の記事に習ってNginxのリバースプロキシを作ってみます。


この構成で核となるツールは、nginxのlua拡張の上で動くライブラリ[lua-resty-auto-ssl](https://github.com/GUI/lua-resty-auto-ssl)です。コレ自体はさらに[OpenResty](https://openresty.org/en/)というnginxの拡張パッケージ的な環境で動かすことができます。

で、それがDocker Imageなどになっていると便利ですね。

こちらです。

https://github.com/Valian/docker-nginx-auto-ssl

すでにありました。すごい世の中だ。



docker-nginx-auto-sslを試す
----------

テスト用の公開サーバを立て、その中でdocker-compose.yml設定して試すのが最もかんたんだと思います。

### DNSの設定

DNSの設定は必要です。何かしらドメイン名を確保して、テスト用サーバに向けます。

### docker-compose.yml

書くべきことは[リポジトリのREADME.mdに書いてある](https://github.com/Valian/docker-nginx-auto-ssl#usage)ので、そのままコピー＆環境に合わせて書き換えで大丈夫なはずです。自分はちょっとカスタマイズしたかったため、docker-nginx-auto-sslを展開してbuildするように書きました。

### myappのところ

README.mdに書かれたdocker-compose.ymlの例では、myappというコンテナに転送するように書かれています。デフォルトだとnginxのコンテナが単に立ち上がるだけです。


### 実環境への適用

docker-compose.ymlは便利ですが、実運用では設定を参考にしつつk8sを使ったり、myappの部分をexternalに置いて運用するのが良いのかなと思います。

実運用までにはいくつか気になったこともありました。後述します。


カスタマイズする
-----------------

docker-nginx-auto-sslの設定ではlua-resty-auto-sslのストレージアダプタはファイルになっており、`/etc/resty-auto-ssl`以下に認証ファイルのデータなどを保存するようになっています。そのディレクトリをVolumeとして設定することで、コンテナの停止時に消えないようにしてあります。

この方法は簡便で良いのですが、lua-resty-auto-sslにはRedisをストレージとして利用するオプションがあります。冗長化やパフォーマンスのためにはこちらを使ったほうがよいという話があったので、そのカスタマイズを行ってみました。

https://github.com/otolab/docker-nginx-auto-ssl （[差分]
(https://github.com/Valian/docker-nginx-auto-ssl/compare/cc900e0e02eeb41935b610dbd26f61283caa3d27...otolab:master)）


### 設定の追加

メインはこの辺です。

```
     auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis")

     auto_ssl:set("redis", {
        host = "$REDIS_HOST_ADDR",
        port = "$REDIS_PORT"
    })
```

https://github.com/GUI/lua-resty-auto-ssl#storage_adapter この辺に書いてあるやつです。


### redisコンテナとつなぐ

restyが使うDNSとして8.8.8.8を指しているので、docker環境内のDNSが参照されません。したがって、nginxの設定内で使う名前は、予め解決済みのIPアドレスで書く必要があります。

docker-nginx-auto-sslでは、entrypoint.shで名前を引き、設定ファイルを毎起動ごとに作り直すことで対応しています。redisのコンテナのアドレスを引くためにこの処理を追加しました。

```
export REDIS_HOST_ADDR=$(nslookup $REDIS_HOST | grep "Address" | grep -v "#53" | cut -d" " -f3)

# let's substitute $ALLOWED_DOMAINS and $LETSENCRYPT_URL into OpenResty configuration
envsubst '$ALLOWED_DOMAINS,$LETSENCRYPT_URL,$REDIS_HOST_ADDR,$REDIS_PORT' \
```


気になるところ
--------------

### nginxの名前引き

Nginxは高速化のためにDNSを起動時の一回しか呼ばないという制限があります。問題が出たらreloadなりなんなりしてくれ、ということらしいのですが、ちょっと厄介な仕様ではあります。

また、docker-nginx-auto-sslだと起動時のentrypoint.shでmyappの名前を解決してしまうので、myapp（相当のナニカ）のアドレスが変わるような状況ではコンテナ単位での再起動が必要になります。

この辺は運用を考えると良くない感じがするので、何かしら手を加えると良さそうなところですね。


### ipv6を無効化

名前を引くときにipv6が有効だとワーニングがでるという問題があるようです。とりあえずdnsの設定のところでipv6=offしました。


まとめ
-----------

なんにも考えず、ぽんと運用レベルで稼働するというわけではないですが、HTTPSなテストサーバを立てたり、デモ用のサイトを立ち上げるなど簡便な用途なら十分使えるなという印象です。

いろいろいたずらするベースにもよさそうです。次は動的なルーティングなど入れてみようかな。


ではでは。




参考
---------

* [Nginxとlua-resty-auto-sslで全自動なLet's Encrypt環境を作る](https://qiita.com/Ets/items/8e6a1df730e24de239e4)
* [nginx＋ngx_luaで証明書動的読み込みの常時HTTPS用リバースプロキシを手軽に立ててみる
](https://qiita.com/hmatsu47/items/54bfcd599546a5988bd2)
* [ngx_mrubyで最初のHTTPSアクセス時に自動で証明書を設定可能にするFastCertificateの提案とPoC](https://hb.matsumoto-r.jp/entry/2017/03/23/173236)