古めの環境で Let's Encrypt で常時 SSL 化する際のトラブルとその対処法

ウェブサイトは常時 SSL(TLS) 化！と言われるようになって久しいですが、**古い環境**を Let's Encrypt で常時 SSL 化する際にハマりやすいポイントとその解決策を、備忘録も兼ねて書いておきます。

片手間でサーバーも多少かじっているだけ…といった感じの、初心者向けの内容です。

--------------------------------------------------------------------------------

## 想定している環境

- しばらく update していない CentOS 6 系
- Apache 2.2 系

等、大人の事情により古い環境のまま運用する場合や、古い環境からアップデートする場合を想定しています。

--------------------------------------------------------------------------------

## 常時 SSL 化テンプレート

**それなりに新しい環境**なら、以下のような流れで、無料で簡単に常時 SSL 化できます。慣れれば 5 分とかかりません。良い時代になったものです。


### 1. certbot-auto で webroot プラグインを使用する

webroot  プラグインは、ドキュメントルートの下に一時的に `.well-known/acme-challenge` というディレクトリを作成し、そこを使って認証作業をしてくれるものです。certbot-auto が全部勝手にやってくれます。

```bash
#-- certbot-auto最新版のインストール
$ cd /usr/bin/
$ wget https://dl.eff.org/certbot-auto
$ chmod 700 ./certbot-auto

#-- certbot-autoのバージョン(2019年4月17日時点：0.32.1)
#-- 最近のバージョンでは、certbot-autoを動作させるのに必要なPython等がインストールされていなかった場合、
#-- このタイミングで自動的にインストールされるようになっています。
$ certbot-auto --version

#-- 証明書の発行
$ certbot-auto certonly \
 --agree-tos \
 --webroot \
 -w /path/to/document/root \
 -d example.com \
 -d www.example.com \
 -m info@example.com --no-eff-email
```


### 2. Apache の VirtualHost を設定

```apache
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /path/to/document/root

    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE]
</VirtualHost>

<Virtualhost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /path/to/document/root

    SSLEngine On
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
</VirtualHost>

#-- IPアドレス制限等をかけている場合は、webrootプラグインが利用するディレクトリの制限を外す
#<Directory "/path/to/document/root/.well-known/acme-challenge">
#    Require all granted
#</Directory>
```


### 3. 証明書を自動更新するように設定

```bash
#-- とりあえず dry run してみて…
$ certbot-auto renew --dry-run

#-- 問題がなければ、cron に証明書を更新する処理を登録
$ certbot-auto renew --post-hook "Apache を再起動させるコマンド"
```

dry-run でこける時は、以下も参考にしてみてください。

- https://qiita.com/fallout/items/6676011cdfe42be6334a


### 4. Qualys SSL Labs で判定してもらう

SSL 化した結果を判定してくれるサービスは幾つかありますが、**Qualys SSL Labs** が有名だと思います。

- https://www.ssllabs.com/ssltest/

--------------------------------------------------------------------------------

# 古い環境でのトラブル

…と、たった 5 分程度で終わる作業の筈が…

--------------------------------------------------------------------------------

## wget（あるいは curl）で怒られる

```bash
$ wget https://dl.eff.org/certbot-auto
```

で `ERROR: cannot verify dl.eff.org's certificate` と怒られるような環境は、その後の作業や構築した環境で色々と問題が出る可能性が非常に高いため、特に支障がないのであれば、`update` した方が良いです。（いきなり乱暴）

```bash
$ yum clean all && yum upgrade -y
```

古い環境では、ここで以下のようなエラーが出る場合もあります。

```
Error: Cannot retrieve repository metadata (repomd.xml) for repository: XXXXX
```

その場合は、該当リポジトリを無効化した上で実行します。

```bash
$ yum upgrade -y --disablerepo=XXXXX

#-- 複数ある場合は
$ yum upgrade -y --disablerepo=XXXXX,YYYYY,ZZZZZ
```

もしくは、yum の設定を変更してから実行します。

```bash
$ cd /etc/yum.repos.d

#-- enabled=1 を enabled=0 に変更します
$ cp XXXXX.repo XXXXX.repo.org && /
 vi XXXXX.repo
```

--------------------------------------------------------------------------------

## certbot-auto が動かない - 1

certbot-auto 初回利用時には…

```bash
$ certbot-auto --version
```

…としてバージョンを確認する癖を付けておくと、最近のバージョンでは、certbot-auto の動作に必要な環境を自動的に整えてくれるので、非常に楽です。

しかし、ここで以下のようなエラーが出てしまった場合は…

```
To use Certbot, packages from the EPEL repository need to be installed.
Enable the EPEL repository and try running Certbot again.
```

EPEL リポジトリを有効化してね！という事ですので…

```bash
#-- enabled=0 を enabled=1 に変更します
$ yum install -y epel-release && /
 cd /etc/yum.repos.d && /
 cp epel.repo epel.repo.org && /
 vi epel.repo
```

--------------------------------------------------------------------------------

## certbot-auto が動かない - 2

IP 制限やベーシック認証等をかけている場合、webroot プラグインの仕様上、`.well-known/acme-challenge` の制限を外してやる必要がある事は、テンプレにも書いた通りです。

ところが、きちんと制限を外したにも関わらず…

```
 - The following errors were reported by the server:
   Domain: example.com
   Type:   unauthorized
   Detail: Invalid response from http://example.com/.well-known/acme-challenge/....
```

…のように怒られてしまった場合、Apache2.4 系 と 2.2 系 との書き方の違いが原因かもしれません。Apache2.2 系では、`Require all granted` ではなく `Allow from all` と書きます。

```apache
<Directory "/path/to/document/root/.well-known/acme-challenge">
    Allow from all
</Directory>
```

また、Apache2.2 系では、`NameVirtualHost` ディレクティブを書く必要がある点にも注意です。

```apache
NameVirtualHost *:80
NameVirtualHost *:443

<VirtualHost *:80>
    #略#
</VirtualHost>

<Virtualhost *:443>
    #略#
</VirtualHost>
```

--------------------------------------------------------------------------------

## http ⇒ https の自動リダイレクトがおかしい

http ⇒ https と自動的にリダイレクトさせたい時は、以下のように書くとよく紹介されていますが、**これには罠があるので注意が必要**です。

```apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
```

この書き方の場合、Apache のバージョンとクライアントの組み合わせによっては、問題が発生します。

例えば、`http://example.com/?%E9%AF%96` にアクセスした場合…

```
HTTP/1.1 301 Moved Permanently
Location: https://example.com/?%E9%AF%96
```

…と、http:// が https:// に置換された結果を期待しますよね？ ところが、この設定の場合は…

```
HTTP/1.1 301 Moved Permanently
Location: https://example.com/?%25E9%25AF%2596
```

…と、URL エンコードされた部分がおかしくなる（'%' が更に二重で URL エンコードされている）ケースがあります。

これは、`RewriteRule` で指定したリダイレクト先の URL を、自動的にエスケープするという機能があるためです。これを無効化するためには `[NE] = No Escape` を指定します。

```apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE]
```

ちなみに、`^(.*)$` ではなく `^.*$` と `( )` を省いて書いているのは、正規表現でキャプチャする必要がないからです。

mod_rewrite で「全てにマッチ」を表現するには、他にも以下のような書き方がありますが、正規表現の意味を理解しないまま、安易に `( )` を使うべきではないのではないでしょうか。

- `.*`
- `^`
- `$`

--------------------------------------------------------------------------------

## Qualys SSL Labs の判定で怒られる

様々な苦労を経てやっと SSL 化に成功！

仕上げに [Qualys SSL Labs](https://www.ssllabs.com/ssltest/) で判定してもらって、**最低でも A 判定**をもらいたいところですが、結果は B や C…酷いと F 判定なんて事もあります。

--------------------------------------------------------------------------------

### Apache2.2 系では、証明書の指定方法が違う

Apache2.4 系（正確には 2.4.8 以降）では、以下のように 2 行で指定します。

```apache
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
```

しかし Apache2.2 系では、以下のように 3 行で指定します。

```apache
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
```

**ここを間違っていても、Apache2.2 系では問題なく動いているように見えてしまう**ので、注意が必要です。

参考：https://httpd.apache.org/docs/2.4/ja/mod/mod_ssl.html#sslcertificatechainfile

--------------------------------------------------------------------------------

### Apache の SSL 周りの設定がおかしい

Qualys SSL Labs で A 判定が取れる、**Apache のデフォルト設定に近い最低限の設定**は、以下の通りです。

- Apache2.2 系では、デフォルトで RC4 が無効化されていないので注意です。

```apache
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:MEDIUM:!RC4:!aNULL:!MD5
```

- Apache2.4 系

```apache
SSLProtocol all -SSLv3
SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
```

TLS1.2 に限定したい、A+ 判定を狙いたい等、よりセキュアな設定については、[「apache SSLCipherSuite」辺りで適当にググって](https://www.google.com/search?q=apache+SSLCipherSuite&ie=utf-8&oe=utf-8)みてください。

--------------------------------------------------------------------------------

### Apache が TLS1.2 に対応していない

レアケースだと思いますが、Apache をソースコードからビルド（configure ⇒ make）している場合、その時の環境があまりにも古く、ビルド時の OpenSSL 等のバージョンが低いと（？）、Apache が TLS 1.2 に対応していない場合があります。

その場合、Apache をビルドし直すのが無難です。前回の configure の内容は、configure した際のディレクトリ内の、`config.nice` というファイルに書いてありますので…

```bash
$ cat source/to/httpd-2.2.x/config.nice
```

その内容を参考に、2.2 系の~~最新~~最終バージョンである 2.2.34 をビルドし直します。

```bash
#-- md5sum というところが泣けます(笑)
$ cd /path/to/work/dir && \
 wget http://archive.apache.org/dist/httpd/httpd-2.2.34.tar.gz && \
 wget http://archive.apache.org/dist/httpd/httpd-2.2.34.tar.gz.md5 && \
 md5sum --check httpd-2.2.34.tar.gz.md5

$ tar -zxvf httpd-2.2.34.tar.gz && \
 cd ./httpd-2.2.34

#-- configureの内容は環境に応じて…
$ ./configure \
 --prefix=/path/to/apache \
 --enable-so \
 --enable-ssl \
 --enable-rewrite

$ make clean all
$ make install
```

もちろん、2.4 系で問題ないなら、この際 2.4 系にします。流れ的には以下のような感じです。最新バージョンのダウンロード先は、↓で確認してください。

- http://httpd.apache.org/
- http://apr.apache.org/download.cgi

```bash
$ cd /path/to/work/dir && \
 wget http://ftp.riken.jp/net/apache//httpd/httpd-2.4.39.tar.gz && \
 tar -zxvf httpd-2.4.39.tar.gz && \
 wget http://ftp.riken.jp/net/apache//apr/apr-1.7.0.tar.gz && \
 tar -zxvf apr-1.7.0.tar.gz && \
 wget http://ftp.riken.jp/net/apache//apr/apr-util-1.6.1.tar.gz && \
 tar -zxvf apr-util-1.6.1.tar.gz

#-- APRとAPR-utilをsrclibディレクトリ内へ移動
$ mv apr-1.7.0 httpd-2.4.39/srclib/apr && \
 mv apr-util-1.6.1 httpd-2.4.39/srclib/apr-util && \
 cd ./httpd-2.4.39

#-- configureの内容は環境に応じて…
$ ./configure \
 --prefix=/path/to/apache \
 --with-included-apr \
 --enable-so \
 --enable-ssl \
 --enable-rewrite

$ make && make install
```

PHP 等もソースコードからビルドしていた場合は、同様の流れで、`config.nice` で前回の configure を確認してからビルドし直すという事になります。