Raspberry PiでLet's EncryptなNginx Proxyを作ろう

# はじめに
最近スマートホームにハマってます。家電とかを外から操作できて便利ですよね。うちではSiriで家電を操作したかったので、HomebridgeをRaspberry PiにインストールしてNature remoを操作しています。そこで、HomebridgeのWebUIに家の外からアクセスしたくてNginx ProxyをRaspberry Piにおいてみることにしました。この際せっかくならセキュアにやろうということで、Let's Encryptも導入してみることにします。証明書も自動更新するようにして、管理も楽にする方向でやっていきます。

# 環境
今回はこんな構成で話を進めていきます。

- Raspberry Pi 3 B+
- Raspbian Stretch

まずRaspbianにDockerを入れて、Docker Composeを使いながらセットアップしていきます。

# 事前準備
当然のことながらRaspbianのセットアップは済ませておいてください。ここでは記事の範囲を超えるので詳しい説明は省きます。

## DockerとDocker Composeのインストール
まず下記コマンドを実行してRaspbianにDockerをインストールします。

```bash
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh
```

続いてDocker Composeもインストールしてしまいましょう。リポジトリから入るバージョンは少し古いのですが、実用上特に問題になることはないはずです。

```bash
$ sudo apt install docker-compose
```

# Docker Composeの設定
Raspberry Pi上のDockerでは当然ながら `x86_64` 向けにビルドされたDockerコンテナを動かすことはできません。そもそものこと、いつも使っている`nginx`イメージですら動かないわけです。というわけでRaspberry Pi用にビルドされたイメージを使います。[nginx-proxy](https://github.com/jwilder/nginx-proxy)の代わりに[rpi-docker-nginx-proxy](https://github.com/Alexander-Krause/rpi-docker-nginx-proxy)を、[docker-letsencrypt-nginx-proxy-companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion)の代わりに[rpi-docker-letsencrypt-nginx-proxy-companion](https://github.com/Alexander-Krause/rpi-docker-letsencrypt-nginx-proxy-companion)をそれぞれ利用します。

## docker-compose.yml
ひとまず`docker-compose.yml`全体をお見せして、細かいところを解説していきます。ということでご覧ください。

```yaml:docker-compose.yml
version: '2'

services:
  nginx-proxy:
    image: alexanderkrause/rpi-nginx-proxy
    restart: always
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - certs:/etc/nginx/certs:ro
      - vhost:/etc/nginx/vhost.d
      - nginx_static:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: ""
  nginx-letsencrypt:
    image: alexanderkrause/rpi-letsencrypt-nginx-proxy-companion
    restart: always
    container_name: nginx-letsencrypt
    volumes:
      - certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - vhost:/etc/nginx/vhost.d
      - nginx_static:/usr/share/nginx/html
  web:
    build: ./api-server
    restart: always
    environment:
      VIRTUAL_HOST: "web.example.com"
      VIRTUAL_PORT: "80"
      LETSENCRYPT_HOST: "web.example.com"
      LETSENCRYPT_EMAIL: "webmaster@example.com"
      SSL_POLICY: "Mozilla-Modern"

volumes:
  certs:
  vhost:
  nginx_static:
```

### Nginx Proxy
まずはNginx Proxy部分を説明していきます。特徴的なところだけかいつまんでいきましょう。

HTTP/HTTPS通信を外から受けるため、ここだけ`80`番と`443`番のポートを外側にバインドしておきます。

証明書などの各種ファイルを読み書きするため、3つほどボリュームをマウントしておきます。ただし証明書が入っている`certs`ボリュームは、こちら側のコンテナからは安全のため読み込み専用にしておきます。また、`docker.sock`はproxy対象のコンテナが建ったときに勝手に設定を追加してもらうために読み込み専用でマウントしておきましょう。ここで利用するボリュームは`volumes`セクションに定義しておくことを忘れないでください。

最後に`labels`を設定します。これはLet's Encryptを自動取得するコンテナ側からこのコンテナを識別するために使います。ちょっと記法が見慣れない感じなのですが、キーにラベル名を、値に空文字列を設定することで有効化されます。

```yaml:nginx-proxy部分
services:
  nginx-proxy:
    image: alexanderkrause/rpi-nginx-proxy
    restart: always
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - certs:/etc/nginx/certs:ro
      - vhost:/etc/nginx/vhost.d
      - nginx_static:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: ""

volumes:
  certs:
  vhost:
  nginx_static:
```

### Let's Encrypt
Let's Encrypt側のコンテナもかいつまんで説明していきましょう。

こちらも同じようなボリュームをマウントしていますが、`certs`だけは書き込む必要があるので`rw`を指定しています。ここだけ注意してください。

```yaml:Let'sEncrypt部分
  nginx-letsencrypt:
    image: alexanderkrause/rpi-letsencrypt-nginx-proxy-companion
    restart: always
    container_name: nginx-letsencrypt
    volumes:
      - certs:/etc/nginx/certs:rw
      - vhost:/etc/nginx/vhost.d
      - nginx_static:/usr/share/nginx/html
      - /var/run/docker.sock:/var/run/docker.sock:ro
```

### プロキシされるコンテナ
プロキシされるWebサービスはあくまでサンプルなのでここは各自の環境に置き換えてほしいのですが、ざっくりと解説していきます。ポイントとなるのは5つの環境変数です。`VIRTUAL_*`としているのがNginx Proxy用の環境変数で、それ以外がLet's Encrypt用の環境変数です。

`VIRTUAL_HOST`にはこのコンテナに割り当てたいドメインを指定します。事前にお使いのドメインに対してサブドメインを生やしておいて、DNSを設定しておいてください。`VIRTUAL_PORT`にはポートフォワーディング先のポート番号を記入します。ここでは仮に`80`としましたが、例えば`5000`番ポートで動いているアプリがある場合はそのように変更することを忘れないでください。

`LETSENCRYPT_HOST`にはTLS証明書を発行する対象となるドメインを指定します。通常は`VIRTUAL_HOST`と同じで良いでしょう。`LETSENCRYPT_EMAIL`に各種確認メールを送信する先のメールアドレスを指定するのも忘れないでください。`SSL_POLICY`にはどのようなTLSプリセットを用いるかを指定できます。デフォルトは`Mozilla-Intermediate`に設定されていますが、今回は`Mozilla-Modern`に設定することでTLSv1.2の利用を強制し、よりセキュアにしています。ただし古い環境からは接続できなくなりますので注意してください。

```yaml:Webサービス部分
  web:
    build: ./api-server
    restart: always
    environment:
      VIRTUAL_HOST: "web.example.com"
      VIRTUAL_PORT: "80"
      LETSENCRYPT_HOST: "web.example.com"
      LETSENCRYPT_EMAIL: "webmaster@example.com"
      SSL_POLICY: "Mozilla-Modern"
```

## 起動
あとはコンテナ群を起動すれば自動的に証明書を取得して繋がるように設定してくれます。

```bash
$ sudo docker-compose up -d
```

# おわりに
Raspberry Pi用にフォークされた便利コンテナを使うことで、ちょっとDocker Composeの設定を書くだけでLet's Encryptを使ったプロキシの設定ができてしまいました。x86_64環境と全く同じとまではいきませんが、それなりに簡単に構築できたかと思います。また、自動的に証明書の更新もかけてくれるのでコンテナが起動してさえいれば更新忘れなどもありません。

皆さんもぜひRaspberry Piでお手軽なHTTPS環境を構築して、誤家庭の充実に役立ててください！