dockerとcloudflareを使ってWebサイトを簡単にSSL化対応（ワイルドカード証明書） & 面倒な証明書更新も自動化してみた

# Chromeでhttpサイトが見れなくなる？？
Chromeの開発元であるGoogleは以下のような声明を出しました。
（引用元: [https://security.googleblog.com/2019/10/no-more-mixed-messages-about-https_3.html](https://security.googleblog.com/2019/10/no-more-mixed-messages-about-https_3.html)）

> Today we’re announcing that Chrome will gradually start ensuring that https:// pages can only load secure https:// subresources. In a series of steps outlined below, we’ll start blocking mixed content (insecure http:// subresources on https:// pages) by default. This change will improve user privacy and security on the web, and present a clearer browser security UX to users.

> 本日、Chrome では、https:// ページが安全な https:// サブリソースのみを読み込むことができるようにすることを段階的に開始することを発表しました。以下に概説する一連の手順で、混合コンテンツ ( https:// ページの安全でない http:// サブリソース) をデフォルトでブロックするようになります。この変更により、ウェブ上でのユーザーのプライバシーとセキュリティが改善され、より明確なブラウザセキュリティUXをユーザーに提示することができます。

## 混合コンテンツ
混合コンテンツとは、https内に存在するhttpなコンテンツのことを指します。
つまり「[https://xxxxxxxxx.com/](https://xxxxxxxxx.com/)」というセキュアなサイト内で「[http://yyyyyyyyyyy.com/image/test.png](http://yyyyyyyyyyy.com/image/test.png)」のようなセキュアでないコンテンツを呼び出している状態を意味します。

つまり、httpなサイトはChromeで閲覧できなくなる可能性があるということです。
さらにこの問題が顕著なのは、日本におけるChromeの利用率が高いという点です。
（参照元: [https://lab.syncer.jp/Statistic/Browser/2019/10/](https://lab.syncer.jp/Statistic/Browser/2019/10/)）

```
・ デスクトップのシェア：66.14%（1位）
・ モバイルのシェア：26.46%（2位）
・ タブレットのシェア：36.56%（2位）
```

社内だけのサービス（退勤管理やソース管理など）だからといって、セキュア化していないとアクセスできない。なんてことにならないように社内のネットワーク整備をすることにしました。

では、社会人1年目、新卒入社の筆者が社内ネットワーク整備に奮闘した一部始終をどうぞ！！

# 環境
- CentOS 7  
→ 社内サービスがDocker on CentOS7で動いてた
- Let's Encrypt  
→ 無料で使えるSSL証明書。90日で期限が切れるため更新が必須
- Cloudflare  
→ ワイルドカード証明書を発行するのにDNSレコードを変更する必要があるため、無料で使えるCloudflareを採用。（このためだけにNSを変更したといっても過言ではない）
- lego（[https://go-acme.github.io/lego/](https://go-acme.github.io/lego/)）  
→ Let's Encryptの環境がさくっと作れるDocker環境。大体のDNS Providerに対応してて、それぞれの設定方法や実行方法が書いてあってありがたい。
（対応してるDNS Provider一覧は[こちら](https://go-acme.github.io/lego/dns/)）

# 改修フロー
https化へのざっくりとしたフローはこちらです。

1. 社内サービスへのアクセスをサブドメインで行えるようにする  
→ いままではIPアドレス、ポート指定のアクセスだったので、サービスが増えると何かと面倒
2. ドメインのネームサーバー（NS）をCloudflareに変更  
→ 現在運用していたところではDNSレコードの書き換えが制限されていた & API経由での書き換えができなかったため
3. Cloudflareでソーン編集用APIアクセスキーを作成
4. 社内サービスコンテナ内で自動更新スクリプト等の作成
5. Nginxなどに証明書を食わせる

今回は検証用の個人的なドメイン（choipre.com）（Google Domains）を使って解説します。
ドメイン取ったけど、個人サービスが完成していないので、何しても大丈夫だし。

**！！ 注意 ！！**
設定を間違えるとサイトにアクセスできなくなったりする可能性があります。必ず変更点（Diff）などはメモするようにしておいた方が、万が一のためです。バックアップなどを取りつつ作業を進めることをおすすめします。

# 1. 社内サービスへのアクセスをサブドメインで行えるようにする
これまで、社内サービスにはIPアドレスを使ってアクセスしていました。そのためhttps化できず、httpな状態で運用していました。まずは、社内サービスへのアクセスをサブドメインでアクセスできるようにするため、社内DNSを整備します。ドメインは、会社HPなどで元々利用しているものを利用しました。

社内だけで使えるサービスやシステムを複数運営していると、IPアドレスでのアクセスは大変になってきます。

そこで、社内サービスにもドメインを使ってアクセスできるように社内DNSを整備しました。

## 前提
- ドメインは取得済み 
- 取得ドメイン：choipre.com

構築後のネットワーク図は以下のような感じになります
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/2-1.png)

## 構築
DNSはDockerを使って構築します。CoreDnsというDocker-imageが公開されているので、そちらを利用します。フォルダ構成としては以下の感じです。

```
drwxr-xr-x   - nomunomu  2 11 18:01 dns
.rw-r--r-- 244 nomunomu  4 11 16:00 ├── docker-compose.yml
.rw-r--r-- 115 nomunomu  4 11 16:00 ├── Dockerfile
drwxr-xr-x   - nomunomu  2 11 17:58 └── volumes
drwxr-xr-x   - nomunomu  4 11 16:14    └── config
.rw-r--r-- 181 nomunomu  4 11 16:32       ├── Corefile
.rw-r--r--  53 nomunomu  4 11 16:14       └── hosts
```

## Docker系設定

```yaml:docker-compose.yml
version: '3.1'
services:
  coredns:
    build: .
    container_name: coredns
    restart: on-failure
    expose:
      - '53'
      - '53/udp'
    ports:
      - '53:53'
      - '53:53/udp'
    volumes:
      - './volumes/config:/etc/coredns'
```

```dockerfile:Dockerfile
FROM coredns/coredns:1.7.0

EXPOSE 53
EXPOSE 53/udp

ENTRYPOINT ["/coredns"]
CMD ["-conf", "/etc/coredns/Corefile"]
```

## CoreDns設定

どのドメインとIPアドレスが紐づくかをhostsファイルに記述します。Corednsの設定はファイルはCorefileというファイルに記述します。

```json:Corefile
. {
    whoami
    forward . 192.168.0.100:53   # ここはPublic-DNSでもよい
    errors
    log . "{proto} {remote} is Request: {name} {type} {>id}"
    hosts /etc/coredns/hosts {
        fallthrough
    }
    reload
}
```

```:hosts
192.168.0.110 xxxxxx.choipre.com
192.168.0.111 yyyyyy.choipre.com
192.168.0.112 zzzzzz.choipre.com
```

## 起動
docker-compose.ymlがあるディレクトリで以下のコマンドを流すと、DNSが起動します。

```shell
$ docker-compose up
```

docker-composeを走らせたPC（192.168.0.100）へ各クライアントのDNS解決先を向けることでローカルのIPに対してもドメインでアクセスすることができるようになります。

# 2. ドメインのネームサーバー（NS）をCloudflareに移行する
なぜNSをCloudflareに変更したかというと、CloudflareではAPIを使ってドメインのゾーン情報（A, AAAA, TXTなどのDNSレコード情報）を書き換えることができます。
（参考: [https://api.cloudflare.com/#zone-subscription-zone-subscription-details](https://api.cloudflare.com/#zone-subscription-zone-subscription-details)）

この仕組みを利用して、スクリプト上でワイルドカードのSSL証明書を発行するのに必要なレコード内容を自動で書き込みたかったからです。
（証明書更新する度に手動でDNSレコードに追記したり、削除したりするのって面倒だし、ネットワークの知識がない人が更新しないといけないってなったときに、このコマンド一発叩けばいいよ！の方がいいよね？）

## Cloudflareにドメインを登録
Cloudflareのアカウントを持っていない人はアカウント作成してください。

アカウント作成後、トップ画面に 【サイトを追加する】 を押します。
（※ 画像は既に登録済みのドメインがあるため、Cloudflareの管理下にあるドメイン一覧がありますが、初期登録後の人は何も表示されていません。）
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-1.png)

Cloudflareで管理したいドメインを入力します。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-2.png)

プラン選択画面が表示されるので 【Freeプラン】 を選択します。
（Freeプランでここまで出来るってCloudflare様様...）
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-3.png)

## Cloudflareに既存DNSレコードを移植
ドメインの登録が完了すると、現在設定されているDNSレコードを読み取って、Cloudflareに書き込み準備をしてくれます。このときにDNSレコードを修正することもできますが、必要がない人はそのまま 【続行】 します。
ただし、AレコードとMXレコードが同一の場合、AレコードをCloudflareのプロキシから外す必要があります。プロキシから外してDNS解決のみにしないとメールサーバーが正常に動作しないためです。
（参考: [Cloudflareの使用時にメールが配信不能になる - メール関連のDNSレコード](https://support.cloudflare.com/hc/ja/articles/200168876-Cloudflare%E3%81%AE%E4%BD%BF%E7%94%A8%E6%99%82%E3%81%AB%E3%83%A1%E3%83%BC%E3%83%AB%E3%81%8C%E9%85%8D%E4%BF%A1%E4%B8%8D%E8%83%BD%E3%81%AB%E3%81%AA%E3%82%8B)）
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-4.png)
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-5.png)

## CloudflareのNSに変更する
Cloudflareに取り込むDNSレコードの設定が終わったら、ドメインのNSをCloudflareに変更するように促されます。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-6.png)

各ドメインの管理画面からドメインのNSを変更します。今回の検証用ドメインはGoogle Domainsで取得しているため、Google Domainsの設定画面から変更します。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-7.png)

ドメインのNS変更が終わったら、Cloudflareで引き続き設定を行います。ここでは、ドメインへのアクセス、コンテンツのキャッシュ・圧縮設定ができます。利用に応じた設定を行ってください。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-8.png)

検証用ドメインでは、以下のように設定しました。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-9.png)

## Cloudflare-NSの変更チェック
次にNSレコードのチェックをします。既にNSの変更が検知できている場合は、以下のような表示になります。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-10.png)

まだ、検知できていない場合は以下のような表示になります。ドメインの設定がネットワーク全体に浸透するまでは24~48時間程かかります。Cloudflareでチェックしても変更されていなかった場合は、少し時間をおいてください。ちなみに、Cloudflareで正常にNSが変更されていることを検知すると、アカウント登録時に設定したメールアドレスに通知がきます。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/3-11.png)

# 3. ゾーン編集用APIアクセスキーを作成
legoのライブラリを利用するために必要な、CloudflareのDNSゾーン情報APIアクセスキーを作成します。キーは２種類作成します。
ゾーン情報の【DNS編集キー】と【ゾーン編集キー】です。Cloudflareのアカウント設定のAPIトークンから作成することができます。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/4-1.png)

まず、APIアクセスキーを作成する前に、画面下部にある「Global Api key」も取得しておきましょう。4.のdocker-compose.ymlに記述する必要があるためです。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/4-2.png)

アクセスキーを作成する際に、いくつかテンプレートが用意されています。今回は【ゾーンDNSを編集する】というテンプレートを編集して利用します。テンプレートを利用せずに０から作成することもできますが、慣れてから使うか、似たテンプレートを変更したほうが無難そうです。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/4-3.png)

１つのトークンに対して、複数の権限を付与することができますが、トークンが増えてくると管理がしづらくなるので、1トークン1権限で運用することとします。アクセス許可に以下を、それぞれ設定して２つのトークンを作成します。

- 1つ目のトークン  
→ ゾーン：DNS：編集
- 2つ目のトークン  
→ ゾーン：ゾーン：編集
- ２つの共通設定（ゾーンリソース）  
→ 包含：特定のゾーン：ワイルドカード証明書を発行したいドメイン

![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/4-4.png)

トークン作成を進めると、以下のような画面が表示されます。このときに表示されるトークンをメモしてください。ここで表示されたトークンは、画面にもあるように再表示されません。そのため、必ずメモするようにしてください。メモし忘れたら、APIトークンを削除して、再作成してください。
![](https://res.cloudinary.com/nomunomu/image/upload/v1626282513/books/assets/d374aaf0df308f/4-5.png)

# 4. SSL証明書の自動更新スクリプトをdocker-composeサービスとして作成
会社の社内サービスが動いているDockerでは、以下のような運用がされています。

- 平日（月〜金）は昼夜問わず常時起動
- 休日（土日）に自動シャットダウン
- 月曜にサービスを立ち上げる

このことから、サービスを立ち上げるときにSSL証明書の更新処理を走るようにしておけば、最低週に１回は確認することになり、更新忘れによる証明書失効を防ぐことが出来ると考えました。サービス自体がDockerで運用されているため、SSL更新処理もDocker化して、Serviceとして登録する方向にしました。

lego（[https://go-acme.github.io/lego/](https://go-acme.github.io/lego/)）を使ったSSL証明書更新用Dockerは以下のように設定しました。

```yaml:docker-compose.yml
services:
  certbot-lego:
    image: goacme/lego:latest
    environment:
      - CLOUDFLARE_API_EMAIL=[Cloudflareで登録したメールアドレス]
      - CLOUDFLARE_API_KEY=[3.で取得したGlobal Api Key]
      - CLOUDFLARE_DNS_API_TOKEN=[3.で作成したゾーン:DNS編集APIキー]
      - CLOUDFLARE_ZONE_API_TOKEN=[3.で作成したゾーン:ゾーン編集APIキー]
    command: >
      --path /lego
      --dns cloudflare          # 利用しているDNSプロバイダに書き換えてください
      --dns.resolvers 8.8.8.8   # DNSレコードが伝搬していない可能性があるのでパブリックDNSで解決させる
      --email [Cloudflareで登録したメールアドレス]
      --domains "*.choipre.com" # 適宜置き換えてください
      --domains "choipre.com"   # 適宜置き換えてください
      --accept-tos
      run
    volumes:
      - /opt/cert:/lego         # /opt/cert 配下に証明書等が吐き出されます。適宜書き換えてください
```

docker-compose.yml を作成して、docker-composeを実行します。

```shell
$ docker-compose up
```

実行が終わると、以下のようなログが流れ、`/opt/cert` 配下に証明書本体が格納されているディレクトリと、更新に必要なアカウント情報が含まれているディレクトリが生成されます。

```shell
❯ docker-compose up
Recreating certbot-cloudflare_certbot-lego_1 ... done
Attaching to certbot-cloudflare_certbot-lego_1
certbot-lego_1  | 2020/11/28 06:44:01 [INFO] acme: Registering account for [Cloudflareに登録したメールアドレス]
certbot-lego_1  | !!!! HEADS UP !!!!
certbot-lego_1  |
certbot-lego_1  | Your account credentials have been saved in your Let's Encrypt
certbot-lego_1  | configuration directory at "/lego/accounts".
certbot-lego_1  |
certbot-lego_1  | You should make a secure backup of this folder now. This
certbot-lego_1  | configuration directory will also contain certificates and
certbot-lego_1  | private keys obtained from Let's Encrypt so making regular
certbot-lego_1  | backups of this folder is ideal.
certbot-lego_1  | 2020/11/28 06:44:01 [INFO] [*.choipre.com, choipre.com] acme: Obtaining bundled SAN certificate
certbot-lego_1  | 2020/11/28 06:44:03 [INFO] [*.choipre.com] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/8920315819
certbot-lego_1  | 2020/11/28 06:44:03 [INFO] [choipre.com] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/8920315820
certbot-lego_1  | 2020/11/28 06:44:03 [INFO] [*.choipre.com] acme: use dns-01 solver
certbot-lego_1  | 2020/11/28 06:44:03 [INFO] [choipre.com] acme: Could not find solver for: tls-alpn-01
certbot-lego_1  | 2020/11/28 06:44:03 [INFO] [choipre.com] acme: Could not find solver for: http-01

（省略）

certbot-lego_1  | 2020/11/28 06:44:11 [INFO] [*.choipre.com, choipre.com] acme: Validations succeeded; requesting certificates
certbot-lego_1  | 2020/11/28 06:44:12 [INFO] [*.choipre.com] Server responded with a certificate.
certbot-cloudflare_certbot-lego_1 exited with code 0
```

```shell
drwxr-xr-x     - nomunomu 28 11 15:43   └── opt
drwxr-xr-x     - nomunomu 28 11 15:44      └── cert
drwx------     - nomunomu 28 11 15:43         ├── accounts
drwx------     - nomunomu 28 11 15:43         │  └── acme-v02.api.letsencrypt.org
drwx------     - nomunomu 28 11 15:44         │     └── xxxxxxxxxxx@gmail.com
.rw-------   225 nomunomu 28 11 15:44         │        ├── account.json
drwx------     - nomunomu 28 11 15:43         │        └── keys
.rw-r--r--   227 nomunomu 28 11 15:43         │           └── xxxxxxxxxx@gmail.com.key
drwx------     - nomunomu 28 11 15:44         └── certificates
.rw-------  3.3k nomunomu 28 11 15:44            ├── _.choipre.com.crt
.rw-------  1.6k nomunomu 28 11 15:44            ├── _.choipre.com.issuer.crt
.rw-------   234 nomunomu 28 11 15:44            ├── _.choipre.com.json
.rw-------   227 nomunomu 28 11 15:44            └── _.choipre.com.key
```

この生成されたフォルダで、accountsフォルダは今後証明書を更新していくなかで必要になるフォルダです。証明書類はcertificatesに保管されています。
「_.choipre.com.crt」がchoipre.comのワイルドカード証明書になります。

一度、証明書発行を実行したので、今後は更新という処理を行う設定にしておく必要があります。そのため、docker-compose.ymlを以下のように変更します。

```yaml:docker-compose.yml
services:
  certbot-lego:
    image: goacme/lego:latest
    environment:
      - CLOUDFLARE_API_EMAIL=[Cloudflareで登録したメールアドレス]
      - CLOUDFLARE_API_KEY=[3.で取得したGlobal Api Key]
      - CLOUDFLARE_DNS_API_TOKEN=[3.で作成したゾーン:DNS編集APIキー]
      - CLOUDFLARE_ZONE_API_TOKEN=[3.で作成したゾーン:ゾーン編集APIキー]
    command: >
      --path /lego
      --dns cloudflare          # 利用しているDNSプロバイダに書き換えてください
      --dns.resolvers 8.8.8.8   # DNSレコードが伝搬していない可能性があるのでパブリックDNSで解決させる
      --email [Cloudflareで登録したメールアドレス]
      --domains "*.choipre.com" # 適宜置き換えてください
      --domains "choipre.com"   # 適宜置き換えてください
      --accept-tos
      renew
      --days 30
    volumes:
      - /opt/cert:/lego         # /opt/cert 配下に証明書等が吐き出されます。適宜書き換えてください
```

変更点は、`run` から `renew` コマンドに変更することです。パラメーターとして `--days` を指定することで指定した日数を切ってから更新させることができます。

この、docker-composeの実行をcronなどに設定することで自動更新を行えるようになります。

# 5. 発行した証明書をNginxに適用してhttps化
4.で生成した証明書をNginxに適用していきます。証明書類は/opt/cert配下にあるものとします。

Nginxの設定ファイルに以下の記述を追記します。追記場所はNginxの設定ファイルの使用したいServer設定の中です。デフォルト構成を利用している人は　`server{ ... }` の中に追記します。

```
ssl_certificate      /opt/cert/_.choipre.com.crt;
ssl_certificate_key  /opt/cert/_.choipre.com.key;
```

記述ができたら、Nginxのプロセスを再起動します。Nginxのプロセスを再起動したら、対象のアドレスに対してhttpsアクセスをすると証明書が適用されていることが分かります。

# まとめ
以上で、自動更新可能なワイルドカード証明書発行の実装が終わりました。
初期設定は大変ですが、今後はあらゆるサブドメインに対してSSL通信を行うことができます。これでChromeでhttpコンテンツが閲覧できなくなっても、安心ですね。