Let'sEncrypt+certbotでSSL証明書を作る

## 目標
- Let'sEncryptで以下の4種類のSSL証明書を作る
    - シングルドメイン
    - マルチドメイン（SANs）：同じコンテンツ
    - マルチドメイン（SANs）：異なるコンテンツ
    - ワイルドカード
- 証明書の自動更新もできるようにする

## 環境
- サーバOS
    - CentOS7
- Webサーバ
    - apache2.4 + mod_ssl
- ワイルドカード証明書のDNS-01認証に使うDNSサーバ
    - さくらのクラウド

## 準備
certbotをインストールする

```shell
yum -y install certbot
```

epelが未インストールの場合は事前にインストールする必要がある
certbotのバージョンは1.3.0

```shell
[root@localhost ~] certbot --version
certbot 1.3.0
```

## シングルドメイン証明書
### ドメインとDocumentRoot
<font color="red">**ドメインmydomain.foobarはもちろん架空**</font>

| ドメイン | DocumentRoot |
|:-:|:-:|
|  ssl.mydomain.foobar | /var/www/html.ssl  |

```apache
# SSL化前のApacheの設定
<VirtualHost *:80>
  ServerName  ssl.mydomain.foobar
  DocumentRoot /var/www/html.ssl
</VirtualHost>
```

### 証明書作成

```shell
certbot certonly \
--webroot -w /var/www/html.ssl -d ssl.mydomain.foobar \
-m <メールアドレス> --no-eff-email \
--agree-tos --manual-public-ip-logging-ok
```

<dl>
<dt>certonly</dt>
<dd>証明書の作成を行う（インストールはしない）</dd>
<dt>--webroot</dt>
<dd>ドメイン認証にHTTP-01を使う</dd>
<dt>-w /var/www/html.ssl</dt>
<dd>DocumentRootの指定</dd>
<dt>-d ssl.mydomain.foobar</dt>
<dd>SSL証明書の対象のドメイン</dd>
<dt>-m &lt;メールアドレス&gt;</dt>
<dd>メールアドレスを登録する。このアドレスに更新案内等のメールが配信される</dd>
<dt>--no-eff-email</dt>
<dd>EFF(Let'sEncryptのスポンサー団体）からのDMを許可しない。<br>
許可する場合は代わりに --eff-email を指定する。<br>
どちらも指定しなければ、処理中に入力を求められる。</dd>
<dt>--agree-tos</dt>
<dd>規約に承認する。<br>
ここで指定しておかないと、処理中に入力を求められる。</dd>
<dt>--manual-public-ip-logging-ok<dt>
<dd>実行中のサーバのIPアドレスを記録しても構わないという承認をする。<br>
ここで指定しておかないと、処理中に入力を求められる。</dd>
</dl>

### 作成された証明書
/etc/letsencrypt/live/ssl.mydomain.foobar/に下記のファイルが作られている

| ファイル名 | 用途 |
|:--|:--|
| cert.pem | SSL証明書 |
| chain.pem  | OSCP用中間証明書 |
| fullchain.pem  | 非OSCP用中間証明書 |
| privkey.pem | 秘密鍵 |

```apache
# SSL対応のApacheの設定
<VirtualHost *:443>
    ServerName   ssl.mydomain.foobar
    DocumentRoot /var/www/html.ssl
    SSLEngine on
    ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
    SSLCertificateFile      /etc/letsencrypt/live/ssl.mydomain.foobar/cert.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/ssl.mydomain.foobar/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/ssl.mydomain.foobar/fullchain.pem 
</VirtualHost>
```

## マルチドメイン(SANs)：同じコンテンツ
### ドメインとDocumentRoot

| ドメイン | DocumentRoot |
|:--|:--|
| www\.mydomain.foobar  | /var/www/html.www  |
| mydomain.foobar  | /var/www/html.www  |

```apache
# SSL化前のApacheの設定
<VirtualHost *:80>
  ServerName      www.mydomain.foobar
  ServerAlias     mydomain.foobar
  DocumentRoot    /var/www/html.www
</VirtualHost>
```

### 証明書作成

```shell
certbot certonly 
--webroot -w /var/www/html.www -d www.mydomain.foobar -d mydomain.foobar \
-m <メールアドレス> --no-eff-email \
--agree-tos --manual-public-ip-logging-ok
```

<dl>
<dt>-w /var/www/html.www</dt>
<dd>DocumentRootの指定<br>
www.mydomain.foobarとmydomain.foobarで共通</dd>
<dt>-d www.mydomain.foobar -d mydomain.foobar<dt>
<dd>証明書を利用するドメインを列挙する</dd>
</dl>

### 作成された証明書
/etc/letsencrypt/live/www\.mydomain.foobar/にシングルドメインの場合と同じファイルが作られている

```apache
# SSL対応のApacheの設定
<VirtualHost *:443>
    ServerName   www.mydomain.foobar
    ServerAlias  mydomain.foobar
    DocumentRoot /var/www/html.www
    SSLEngine on
    ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
    SSLCertificateFile      /etc/letsencrypt/live/www.mydomain.foobar/cert.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/www.mydomain.foobar/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/www.mydomain.foobar/fullchain.pem 
</VirtualHost>
```

## マルチドメイン（SANs）：異なるコンテンツ
### ドメインとDocumentRoot

| ドメイン | DocumentRoot |
|:--|:--|
| san1.mydomain.foobar  | /var/www/html.san1  |
| san2.mydomain.foobar  | /var/www/html.san2  |
| san2.mydomain.foobar  | /var/www/html.san3  |


```apache
# SSL化前のApacheの設定
<VirtualHost *:80>
  ServerName      san1.mydomain.foobar
  DocumentRoot    /var/www/html.san1
</VirtualHost>
<VirtualHost *:80>
  ServerName      san2.mydomain.foobar
  DocumentRoot    /var/www/html.san2
</VirtualHost>
<VirtualHost *:80>
  ServerName      san3.mydomain.foobar
  DocumentRoot    /var/www/html.san3
</VirtualHost>
```

### 証明書作成

```shell
certbot certonly 
--webroot \
-w /var/www/html.san1 -d san1.mydomain.foobar \
-w /var/www/html.san2 -d san2.mydomain.foobar \
-w /var/www/html.san3 -d san3.mydomain.foobar \
-m <メールアドレス> --no-eff-email \
--agree-tos --manual-public-ip-logging-ok 
```

`-w`と`-d`の順番が重要
ドメインとドキュメントルートの対応がわかるように、`-w -d`の順で正しい組み合わせで繰り返し記述する。

<dl>
<dt>-w /var/www/html.san1 -d san1.mydomain.foobar</dt>
<dd>san1.mydomain.foobar の DocumentRoot は /var/www/html.san1</dd>
<dt>-w /var/www/html.san2 -d san2.mydomain.foobar</dt>
<dd>san2.mydomain.foobar の DocumentRoot は /var/www/html.san2</dd>
<dt>-w /var/www/html.san3 -d san2.mydomain.foobar</dt>
<dd>san2.mydomain.foobar の DocumentRoot は /var/www/html.san2</dd>
</dl>

### 作成された証明書
/etc/letsencrypt/live/san1.mydomain.foobar/にシングルドメインの場合と同じファイルが作られている
最初の`-d`で指定したドメインがディレクトリ名に採用される

```apache
# SSL対応のApacheの設定
<VirtualHost *:443>
  ServerName      san1.mydomain.foobar
  DocumentRoot    /var/www/html.san1
  SSLEngine on
  ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
  SSLCertificateFile      /etc/letsencrypt/live/san1.mydomain.foobar/cert.pem
  SSLCertificateKeyFile   /etc/letsencrypt/live/san1.mydomain.foobar/privkey.pem
  SSLCertificateChainFile /etc/letsencrypt/live/san1.mydomain.foobar/fullchain.pem 
</VirtualHost>
<VirtualHost *:443>
  ServerName      san2.mydomain.foobar
  DocumentRoot    /var/www/html.san2
  SSLEngine on
  ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
  SSLCertificateFile      /etc/letsencrypt/live/san1.mydomain.foobar/cert.pem
  SSLCertificateKeyFile   /etc/letsencrypt/live/san1.mydomain.foobar/privkey.pem
  SSLCertificateChainFile /etc/letsencrypt/live/san1.mydomain.foobar/fullchain.pem 
</VirtualHost>
<VirtualHost *:443>
  ServerName      san3.mydomain.foobar
  DocumentRoot    /var/www/html.san3
  SSLEngine on
  ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
  SSLCertificateFile      /etc/letsencrypt/live/san1.mydomain.foobar/cert.pem
  SSLCertificateKeyFile   /etc/letsencrypt/live/san1.mydomain.foobar/privkey.pem
  SSLCertificateChainFile /etc/letsencrypt/live/san1.mydomain.foobar/fullchain.pem 
</VirtualHost>
```

## ワイルドカード
### 方針

- `*.mydomain.foobar`のワイルドカード証明書を作る
- ワイルドカードの場合はドメイン認証にHTTP-01は使えず、DNS-01で認証する。
- DNS-01認証のために、mydomain.foobarのDNSで`_acme-challenge.mydomain.foobar`のTXTレコードを登録する必要がある。手作業で登録してもいいのだが、そうすると（おそらく）証明書更新が自動でできない。
    - 登録する値が毎回変わるため
- 自動更新するためには、そのためのcertbotのプラグインを利用すればよい。プラグインは多数のDNSサーバに対応したものが提供されている。
- `mydomain.foobar`のDNSはプラグインが対応していない。とは言え、DNSサーバ自体を移設するのは何かと大変でやりたくない。
- サブドメイン`_acme-challenge.mydomain.foobar`をプラグイン対応しているDNSサーバに委譲する。プラグイン対応しているDNSサーバ側で、`_acme-challenge.mydomain.foobar`のTXTレコードのみを処理してもらうことにする。
- 今回はプラグイン対応DNSサーバとして[さくらインターネットのさくらのクラウド](https://cloud.sakura.ad.jp/)を利用することにする。
    - さくらのクラウドのアカウントを元から持っていたことと、DNSサービス利用料が１ゾーン数十円/月のコストで済むことから決定した。
- HTTP-01認証ではないので、証明書作成／更新時にhttp接続可能なWebサーバが起動していないくてもよい。

### さくらクラウドの準備
1. さくらクラウドのアカウントを作る（持っていない場合）
1. DNSサービスにゾーン`mydomain.foobar`を追加作成する
2. 作成したゾーンのDNSサーバホスト名（２つあるはず）をメモっておく。DNSゾーンの詳細情報ページに書いてある。
3. さくらクラウドのAPIキーを作成する。作成したキーの`アクセストークン`と`アクセストークンシークレット`をメモっておく。

- 作成するゾーンは`_acme-challenge.mydomain.foobar`ではなく`mydomain.foobar`にする、そうしないとプラグインがうまく動かない
- ゾーンの作成だけでOK。レコードは必要ない。
- <font color="red">**mydomain.foobar自体のDNSをレジストラで変更してはいけない。元のDNSサーバが使えなくなって大惨事になる。**</font>

### 本来のDNSにレコード追加
mydomain.foobarのDNSを管理している本来のDNSサーバに下記のレコードを追加して、`_acme-challenge.mydomain.foobar`をさくらクラウドに委譲する。

```shell
_acme-challenge IN NS <さくらクラウドのDNSサーバ名1>
_acme-challenge IN NS <さくらクラウドのDNSサーバ名2>
```

### プラグインのインストール

```shell
yum -y install python-certbot-dns-sakuracloud
```

### プラグイン用の設定ファイルを用意
プラグインが使用する設定ファイルにさくらクラウドのAPIキー情報を書いて保存する。今回は`/root/letsencrypt/sakura_api.conf`というファイル名にする。
設定ファイルのパーミッションは600にしないとプラグインがエラーを吐いて動作しない。

```shell:/root/letsencrypt/sakura_api.conf
dns_sakuracloud_api_token = "<さくらクラウドAPIキーのアクセストークン>"
dns_sakuracloud_api_secret = "<さくらクラウドAPIキーのアクセストークンシークレット>"
```

更新時もこのプラグインがこのファイルを読み込むので、うっかり削除してしまわないように、あまり適当な名前にしないほうがよいだろう。

### 証明書作成

```shell
certbot certonly \
--dns-sakuracloud --dns-sakuracloud-credentials /root/letsencrypt/sakura_api.conf \
-d *.mydomain.foobar \
-m <メールアドレス> --no-eff-email \
--agree-tos --manual-public-ip-logging-ok \
```

<dl>
<dt>--dns-sakuracloud</dt>
<dd>DNS-01認証にさくらクラウドDNS用プラグインを使用する</dd>
<dt>--dns-sakuracloud-credentials /root/letsencrypt/sakura_api.conf</dt>
<dd>さくらクラウドDNS用プラグインの設定（APIキー情報）ファイルを指定</dd>
<dt>-d *.mydomain.foobar</dt>
<dd>*.mydomain.foobarのワイルドカード用SSL証明書を作る</dd>
</dl>

### 作成された証明書
/etc/letsencrypt/live/mydomain.foobar/にシングルドメインの場合と同じファイルが作られている

```apache
# SSL対応のApacheの設定
<VirtualHost *:443>
  ServerName      host.mydomain.foobar
  DocumentRoot    /var/www/html.host1
  SSLEngine on
  ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
  SSLCertificateFile      /etc/letsencrypt/live/mydomain.foobar/cert.pem
  SSLCertificateKeyFile   /etc/letsencrypt/live/mydomain.foobar/privkey.pem
  SSLCertificateChainFile /etc/letsencrypt/live/mydomain.foobar/fullchain.pem 
</VirtualHost>
<VirtualHost *:443>
  ServerName      another.mydomain.foobar
  DocumentRoot    /var/www/html.another
  ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
  SSLCertificateFile      /etc/letsencrypt/live/mydomain.foobar/cert.pem
  SSLCertificateKeyFile   /etc/letsencrypt/live/mydomain.foobar/privkey.pem
  SSLCertificateChainFile /etc/letsencrypt/live/mydomain.foobar/fullchain.pem 
</VirtualHost>
<VirtualHost *:443>
  ServerName      any.mydomain.foobar
  DocumentRoot    /var/www/html.any
  ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
  SSLCertificateFile      /etc/letsencrypt/live/mydomain.foobar/cert.pem
  SSLCertificateKeyFile   /etc/letsencrypt/live/mydomain.foobar/privkey.pem
  SSLCertificateChainFile /etc/letsencrypt/live/mydomain.foobar/fullchain.pem 
</VirtualHost>
```

### 課題
本来はさくらクラウドDNSには`mydomain.foobar`ではなく`_acme-challenge.mydomain.foobar`のゾーンを作成して運用したかった。
元のDNSから委譲しているのが`_acme-challenge.mydomain.foobar`なのに委譲される側のゾーンが不一致なのが気持ち悪いからである。
certbotのさくらクラウドDNS用プラグインで、そうした設定（作成する証明書のドメインとさくらクラウドDNSのゾーンが一致しない）で使用できない（多分）ので、仕方なく今回のような設定にしている。
そもそもRFC的にこの運用が許されるのかどうかも怪しいと思っている。⇒どなたかお教えください。
また、さくらDNSクラウドでゾーンを作成する時に次の注意文が表示される。

> 作成後表示される当社DNSサーバに対して、権限委譲をお願いします。
> なお、1ヶ月以内に委譲がなされない場合、登録が削除されることがあります。

`_acme-challenge.mydomain.foobar`は委譲しているが`mydomain.foobar`は委譲していないので１ヶ月後にゾーン情報を削除されてしまうかもしれない。

## 自動更新
Let'sEncryptのSSL証明書は有効期限が３ヶ月と通常の有償のものと比べると短い。
そのため、自動更新できる仕組みを構築しておきたい。

手動で更新するのであれば、シェルから下記のコマンドを実行すればよい。

```shell
certbot renew
```

そのホストで作成したLet'sEncryptの証明書を全て更新できる。
ただし、有効期限が30日以上残っている証明書は更新されない。

上記コマンドで更新すると、新規作成時と同じ設定（HTTP-01認証やDNS-01認証など）で更新処理が行われる。
そのため、新規作成時とDocumentRootが変わっていたりDNS設定が変わっていたりすると更新に失敗するので注意が必要。

自動更新用のスクリプトを用意する。

```shell:/root/letsencrypt/renew.sh
#!/bin/bash

certbot renew
systemctl reload httpd.service
```

そのスクリプトをrootのcronで月2回実行する。頻度は運用に合わせて調整してほしい。

```shell
0 4 10,25 * * /root/letsencrypt/renew.sh
```

<font color="red">**実際に更新がうまくできるたどうかは、3ヶ月後に追記します**</font>

## HTTP-01認証のTips

### httpsへのリダイレクト
http⇒httpsにリダイレクトしている場合、-wにはhttpsのDocumentRootを指定すればよい。
このとき、httpsの証明書が勝手証明書（mod_sslのデフォルト証明書とか）でも大丈夫。

```apache
# http接続をhttps接続にリダイレクト
<VirtualHost *:80>
  ServerName  www.mydomain.foobar
  Redirect    temp / https://www.mydomain.foobar/
</VirtualHost>

# https接続のSSL証明書と秘密鍵はmod_sslのデフォルトのもの
<VirutalHost *:443>
  ServerName   www.mydomain.foobar
  DocumentRoot /var/www/html.www
  SSLEngine on
  ### 中略：SSLProtocolとかSSLCipherSuiteとか ###
  SSLCertificateFile      /etc/pki/tls/certs/localhost.crt
  SSLCertificateKeyFile   /etc/pki/tls/private/localhost.key
</VirtualHost>
```

```shell
# 証明書作成コマンド
# DocumentRootはhttps://www.mydomain.foobar/のDocumentRoot
certbot certonly 
--webroot -w /var/www/html.www -d www.mydomain.foobar \
-m <メールアドレス> --no-eff-email \
--agree-tos --manual-public-ip-logging-ok
```

### DocumentRootの変更

初回のSSL証明書作成時からDocumentRootを変更すると、更新時にHTTP-01認証が失敗する。
この場合は証明書を作り直してしまうか、一度ドキュメントルートを指定し直して手動更新するなどの対応が必要だと思われる。

### BASIC認証を掛けている場合

httpアクセスしたとき（あるいは、そこからhttpsにリダイレクトしたとき）にBASIC認証が掛かっていると、SSL証明書の作成／更新ができない。

```apache
<VirtualHost *:80>
	ServerName	basic.mydomain.foobar
	DocumentRoot	/var/www/html.basic
</VirtualHost>

# BASIC認証
<Directory /var/www/html.basic>
	<RequireAll>
		AuthName	members
		AuthType	Basic
		AuthUserFile	/var/www/htpwd/basic
		Require		valid-user
	</RequireAll>
</Directory>
```

HTTP-01で使用するディレクトリ（<DocumentRoot>/.well-known/acme-challenge）にはBASIC認証がかからないようにすればいいと思われるが、未検証。
特定ユーザ向けサイトなどで、サイト立ち上げの初回の証明書作成時は掛けていないが、運用開始に合わせてBasic認証を掛けた場合に更新ができずにハマりそうである。

## 参考URL等
下記サイトを参考にさせて頂きました。ありがとうございました。
[Let'sEncrypt公式](https://letsencrypt.org/ja/)
[certbot公式](https://certbot.eff.org/)
[Let's Encrypt (certbot) でワイルドカード証明書できた！](https://qiita.com/chamaharun/items/566c78bb8a13b4c2bc16)
[さくらのクラウド](https://cloud.sakura.ad.jp/)
[certbot-dns-sakuracloudのドキュメント（公式じゃないかも？）](https://certbot-dns-sakuracloud.readthedocs.io/en/stable/)