Nginxとlua-resty-auto-sslで全自動なLet's Encrypt環境を作る


[Let's Encrypt](https://letsencrypt.org/)は便利なサービスですよね。
ですが、ワイルドカード証明書に対応していないため、頻繁にFQDNを変える環境には不向きでした。

そんなとき、「Auto SSL」なんていうキャッチーなNginx拡張があることを発見しました。

- https://github.com/GUI/lua-resty-auto-ssl

これは、lua-resty-auto-sslを検証したときのメモです。
なお、検証環境はCentOS7です。

# インストール手順

## Nginx(OpenResty)を入れる

Nginxと・・・なんてタイトルに書いてますが、luaのための自前ビルドが面倒なのでOpenRestyを使います。
[オフィシャルに手順が書いてある](https://openresty.org/en/linux-packages.html
)のですが、一応私の行った手順を以下に記載しておきます。


OpenRestyはオフィシャルのパッケージを使うことにします。
skipされたくないのでskip_ifをコメントアウトします。

```bash
$ vim /etc/yum.repos.d/openresty.repo

[openresty]
name=Official OpenResty Repository
baseurl=https://copr-be.cloud.fedoraproject.org/results/openresty/openresty/epel-$releasever-$basearch/
##skip_if_unavailable=True
gpgcheck=1
gpgkey=https://copr-be.cloud.fedoraproject.org/results/openresty/openresty/pubkey.gpg
enabled=1
enabled_metadata=1
```

以上で、yumコマンドで入れられるはずです。

```bash
$ yum install openresty
```

## LuaRocksを入れる

CentOS7では、EPELのtestingリポジトリにRPMがあります。
EPELが有効になっている環境なら簡単に入ります。

```bash
$ yum install luarocks --enablerepo=epel-testing
```


## lua-resty-auto-sslを入れる

これも[オフィシャルに手順が書いてある](https://github.com/GUI/lua-resty-auto-ssl)のですが、メモとして書いておきます。


```bash
$ luarocks install lua-resty-auto-ssl
```


# Nginxの設定例

## nginxユーザーを作る

これは管理上の好みの問題なので、飛ばしても大丈夫です。

```bash
$ groupadd -g GID nginx
$ useradd -u UID -g nginx -d /var/cache/nginx -s /sbin/nologin nginx
```


## OpenRestyの設定ファイルパスをNginxに合わせる

OpenRestyの2016/09/02時点の最新バージョンでは、以下のパスにNginxのファイル群がインストールされます。

```bash
$ find /usr/local/openresty/nginx/ -type d
/usr/local/openresty/nginx/
/usr/local/openresty/nginx/conf
/usr/local/openresty/nginx/html
/usr/local/openresty/nginx/sbin
/usr/local/openresty/nginx/tapset
/usr/local/openresty/nginx/logs
```

OpenRestyの設定ファイルのパスをNginx風にします。（飛ばしてもよいです）

```bash
$ ln -s /usr/local/openresty/nginx/conf /etc/nginx
```


## 設定ファイルを作る

シンボリックリンクを作っていない場合は、設定ファイルのパスを読み替えてください。

Nginxの大元の設定ファイル(nginx.conf)に、LuaRocksで入れたパッケージを読むための設定を追加します。
そのためには、以下のコマンドで確認できるパスを`lua_package_path`と`lua_package_cpath`に書き加える必要があります。

```bash
$ luarocks path

### 表示例
export LUA_PATH='/root/.luarocks/share/lua/5.1/?.lua;/root/.luarocks/share/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua;./?.lua;/usr/lib64/lua/5.1/?.lua;/usr/lib64/lua/5.1/?/init.lua'
export LUA_CPATH='/root/.luarocks/lib64/lua/5.1/?.so;/usr/lib64/lua/5.1/?.so;./?.so;/usr/lib64/lua/5.1/loadall.so'
```

・・・と言ったものの、 **これを全部書き加えたら私の環境ではInvalidになってしまいました。**（多すぎるようです）

以下は、最小限のlua-resty-auto-sslのパッケージを読みこむようにした例です。
バージョン番号が含まれているので、環境に合わせて調整してください。
またconf.d以下を読み込むようにinclude設定を書き加えています。

```/etc/nginx/nginx.conf
前略

http {
    中略

    lua_package_path '/usr/share/lua/5.1/?.lua;;';
    include /etc/nginx/conf.d/*.conf;
}
```



続いて、lua-resty-auto-ssl用の設定を書いていきます。

```/etc/nginx/conf.d/lua-resty-auto-ssl.conf
lua_shared_dict auto_ssl 1m;

resolver 127.0.0.1;
resolver_timeout 2s;

# Initial setup tasks.
init_by_lua_block {
  auto_ssl = (require "resty.auto-ssl").new()

  auto_ssl:set("allow_domain", function(domain)
    return ngx.re.match(domain, "^(SSLを適用させたいドメインの正規表現)$", "ijo")
  end)

  auto_ssl:init()
}

init_worker_by_lua_block {
  auto_ssl:init_worker()
}

server {
  listen 443 ssl;

  ssl_certificate_by_lua_block {
    auto_ssl:ssl_certificate()
  }

  ssl_certificate 適当なSSL証明書;
  ssl_certificate_key 適当なSSL証明書の秘密鍵;

  ### ルートディレクトリやアプリケーションの設定を書く
  #   省略
}

# HTTP server
server {
  listen 80;

  # Endpoint used for performing domain verification with Let's Encrypt.
  location /.well-known/acme-challenge/ {
    content_by_lua_block {
      auto_ssl:challenge_server()
    }
  }
}

# Internal server running on port 8999 for handling certificate tasks.
server {
  listen 127.0.0.1:8999;
  location / {
    content_by_lua_block {
      auto_ssl:hook_server()
    }
  }
}
```

以上でSNI対応のlua-resty-auto-sslが設定されます。

細々と設定値がありますので、本番環境のようにちゃんと設定すべき時はオフィシャルのドキュメントを読みましょう。
※ 特に冗長構成などを考えるとstorage_adapterを変更したほうが良い気がします

### allow_domain関数について

上記設定例の重要なポイントはここです。

```
auto_ssl:set("allow_domain", function(domain)
  return ngx.re.match(domain, "^(SSLを適用させたいドメインの正規表現)$", "ijo")
end)
```

オフィシャルにはこのように書かれています。

> By default, resty-auto-ssl will not perform any SSL registrations until you define the allow_domain function.

動作させるために`allow_domain`という関数を定義しろ、と。

オフィシャルでも、本書の例でも、正規表現でドメイン名チェックをして許可を返しています。
**正規表現でOKですので、ワイルドカード証明書のように大胆なallowルールも掛けてしまいます。**

関数ですので、気力があればそれ以外の実装も自由に定義できるでしょう。


# 動作確認

## OpenResty起動

設定が終わったらOpenRestyを起動します。

```bash
$ systemctl start openresty
```

もしドメインの登録をしていなければ、このOpenRestyサーバーに向くようにDNSに登録しましょう。

## 動作確認

curlしてみます。

```bash
$ curl https://登録したドメイン/ -I

HTTP/1.1 200 OK
Server: openresty
Date: Fri, 02 Sep 2016 08:21:26 GMT
Content-Type: text/html
Content-Length: 22
Connection: keep-alive
Last-Modified: Fri, 02 Sep 2016 07:54:09 GMT
ETag: "53b81a2aafb16"
Accept-Ranges: bytes
```

証明書の警告もなく、200 OKになりました！
ということで、SSLの取得を自動的にやってくれたことがわかりますね。

サーバーのlua-resty-auto-sslのフォルダを確認すると証明書ファイルが配置されていることが確認できます。

```bash
# ls -l /etc/resty-auto-ssl/letsencrypt/certs/ドメイン名/
total 20
-rw------- 1 nginx nginx 1716 Sep  2 16:48 cert-1472802497.csr
-rw------- 1 nginx nginx 2208 Sep  2 16:48 cert-1472802497.pem
lrwxrwxrwx 1 nginx nginx   19 Sep  2 16:48 cert.csr -> cert-1472802497.csr
lrwxrwxrwx 1 nginx nginx   19 Sep  2 16:48 cert.pem -> cert-1472802497.pem
-rw------- 1 nginx nginx 1647 Sep  2 16:48 chain-1472802497.pem
lrwxrwxrwx 1 nginx nginx   20 Sep  2 16:48 chain.pem -> chain-1472802497.pem
-rw------- 1 nginx nginx 3855 Sep  2 16:48 fullchain-1472802497.pem
lrwxrwxrwx 1 nginx nginx   24 Sep  2 16:48 fullchain.pem -> fullchain-1472802497.pem
-rw------- 1 nginx nginx 3239 Sep  2 16:48 privkey-1472802497.pem
lrwxrwxrwx 1 nginx nginx   22 Sep  2 16:48 privkey.pem -> privkey-1472802497.pem
```

素敵。

# あとがき

以上で完全に自動化されたSSL by Let's Encrypt環境が出来ました。
動きにオーバーヘッドがありますので本番環境で利用するにはリスクがありますが、簡単であることが重要な環境ではとても役に立つと思います。

## 手順に出てこなかった注意事項

### ディレクトリが存在しないという失敗
lua-resty-auto-sslはroot動作を期待しているのか、lua-resty-auto-ssl関連ディレクトリが無いとNginx起動時にmkdir/chmodに失敗するかもしれません。

その場合、先回りして作りましょう。

```
$ mkdir -p /etc/resty-auto-ssl/storage
$ chown nginx:nginx /etc/resty-auto-ssl/storage
$ mkdir /etc/resty-auto-ssl/storage/file
$ chown nginx:nginx /etc/resty-auto-ssl/storage/file
$ chmod 700 /etc/resty-auto-ssl/storage/file
```


### systemdとOpenResty + lua_resty_auto_sslのsockproc
CentOS7環境だとOpenRestyとsystemctlコマンドの相性が悪い感じがしました。
※ 無応答になったり、stop/restartコマンドを送ってもKILL出来なかったり。
これについてはOpenRestyの話ですので、改善することでしょう。

追記(2016/11/04):
この辺の怪しい挙動は、lua_resty_auto_sslの`sockproc`というプロセスも一緒に悪さしているようでした。
[Nginxオフィシャルを参考](https://www.nginx.com/resources/wiki/start/topics/examples/systemd/)にして、自分でsystemdの設定ファイルを`/etc/systemd/system/openresty.service`に記載すれば問題解決できましたので、参考に載せておきます。

```/etc/systemd/system/openresty.service
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/bin/openresty -t
ExecStart=/usr/bin/openresty -c /etc/nginx/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID $(pidof sockproc)
PrivateTmp=true

[Install]
WantedBy=multi-user.target
```

上記を書いたら適用しておきましょう。

```
$ systemctl daemon-reload
```

### resolver指定について

resolverに127.0.0.1を指定していますが、これはdnsmasq環境だからです。
dnsmasqを入れていないサーバーの場合は、resolv.confに書いているDNSを指定しておきましょう。