CentOS8（VPS）立ち上げからマルチドメインのLet's Encrypt設定完了まで

## はじめに
自分の趣味用に、遊ばせていたドメイン x3 と新たに VPS（Conoha）を契約して
環境構築しようとやってみたメモです。
（どちらかというと全体の流れとポイントのまとめ）

目的にしていたわけではないですが比較的シンプルな設定（※当社比）で
[SSL Server Test](https://www.ssllabs.com/ssltest/index.html)（SSL暗号化の安全性チェック）が一発 A+ だったので ~~手間が省けて~~ 結構うれしい。

## なにする
VPS で CentOS8 を新規に立ち上げた状態から

* SSH 用一般ユーザーの作成
* Apache のインストール
* Let's encrypt で SSL対応、自動更新設定

までをざっくり流します。

#### 環境

* CentOS 8.1
* Apache 2.4

## 前提・導入

* ドメイン（は元々持ってた。VPSとは無関係のとこ）
* VPS契約
* CentOS8.1イメージでサーバー追加

最初なので root ログイン（秘密鍵で）

#### パッケージ更新
```bash
[root@(host) ~]# dnf update
```

※ CentOS8 において yum と dnf はともに dnf-3 のシンボリックリンク

## （証明書取得までに）DNS を設定

ドメインを契約しているレジストラ等で
`Aレコード` に VPS の グローバル IP を設定しておく。

（証明書の取得時に正引きできる必要がある。
　ドメインから IP を引けるようになるまで時間かかるので一応先に書いておいた）

## 一般ユーザーの作成

#### ユーザー追加
```bash
[root@(host) ~]# useradd user1
[root@(host) ~]# passwd user1
（パスワードを設定）
```

（比較用）ユーザーの属するグループ

```bash
[root@(host) ~]# groups user1
user1 : user1
```

#### 副グループを追加

```bash
[root@(host) ~]# usermod -G wheel user1
[root@(host) ~]# groups user1
user1 : user1 wheel
```

※ wheel グループ：スーパーユーザー（root）特権を得られる既定のグループ

#### su 可能なユーザーを wheel グループに限定

```bash
[root@(host) ~]# vi /etc/pam.d/su
# ↓ 変更前
#auth           required        pam_wheel.so use_uid
# ↓ 変更後（コメント外しただけ）
auth           required        pam_wheel.so use_uid
```

#### ssh ログイン用に鍵を生成

teratermで作りました。

▼ 参考
https://support.conoha.jp/v/addusersshkey/#01
※ 手抜きですみません…
※ 私は「鍵を生成する」の項のみ行った形です
　 （rootのまま作業した）

私の場合は鍵を生成後
root のまま `id_rsa.pub` を放り込んで（scpで送信して）、
（送信先未設定の場合、ssh ログインしたユーザーのホームディレクトリに配置されます。）

```bash
[root@(host) ~]# mkdir /home/user1/.ssh
[root@(host) ~]# chown user1:user1 /home/user1/.ssh
[root@(host) ~]# chmod 700 /home/user1/.ssh

[root@(host) ~]# chown user1:user1 id_rsa.pub
[root@(host) ~]# chmod 600 id_rsa.pub
[root@(host) ~]# mv id_rsa.pub /home/miyapei/.ssh/authorized_keys
```
みたいな感じで
user1にchownしたものを /home/user1/.ssh/ 以下に authorized_keys にリネームして配置。

#### 追加したユーザーでログイン＆ su できるかを確認

ログインできたら su できるかも確認。

```bash
[user1@(host) ~]$ sudo su -
```

## root ログインを禁止
```bash
[root@(host) ~]# vi /etc/ssh/sshd_config
### ↓ rootログイン禁止
#PermitRootLogin yes
PermitRootLogin no
### ↓ パスワードログイン禁止
PasswordAuthentication no
```
```bash
### ↓ 反映
[root@(host) ~]# systemctl reload sshd
```
※ 私の環境では VPS 追加時に root ログインの公開鍵を設定していたため
　 最初からパスワードログイン不可でした
※ ちなみに Fail2ban もオプションで最初から入れてました



## Apache インストール
```bash
### ↓ インストール
[root@(host) ~]# dnf install httpd
### ↓ 起動
[root@(host) ~]# systemctl start httpd
### ↓ 自動起動有効化
[root@(host) ~]# systemctl enable httpd
### ↓ 確認
[root@(host) ~]# systemctl status httpd
```
（雑ぅ）

#### ファイアウォール
```bash
### ↓ 動いてるか確認
[root@(host) ~]# systemctl status firewalld
   Active: active (running)
### ↓ 今のzoneを確認
[root@(host) ~]# firewall-cmd --get-default-zone
public
### ↓ publicのzoneにhttpを恒久的に追加
[root@(host) ~]# firewall-cmd --add-service=http --zone=public --permanent
success
### ↓ 反映
[root@(host) ~]# firewall-cmd --reload
success
### ↓ 確認
[root@(host) ~]# firewall-cmd --list-all-zones | less
[root@(host) ~]# systemctl status firewalld
   Active: active (running)
```

ただ
> WARNING: AllowZoneDrifting is enabled. This is considered an insecure configuration option. It will be removed in a future release. Please consider disabling it now.

って言われるので無効にして再確認した

```bash
[root@(host) ~]# vi /etc/firewalld/firewalld.conf
#AllowZoneDrifting=yes
AllowZoneDrifting=no

[root@(host) ~]# systemctl restart firewalld
[root@(host) ~]# systemctl status firewalld
```

ここまでで IP 直打ちでテストページは見えるはず

#### Apache設定
とりあえず ServerName 設定

```bash
[root@(host) conf]# cd /etc/httpd/conf/
[root@(host) conf]# cp -a httpd.conf httpd.conf-original
[root@(host) conf]# vi httpd.conf
ServerName domain1.com:80
```

#### VirtualHost 設定

```
[root@(host) conf]# cd ../conf.d/
[root@(host) conf.d]# vi virtualhost.conf
<VirtualHost _default_:80>
    ServerName any
    <Location />
        Require all denied
    </Location>
</VirtualHost>
<VirtualHost _default_:*>
    ServerName any
    <Location />
        Require all denied
    </Location>
</VirtualHost>
<VirtualHost *:80>
    ServerName domain1.com
    DocumentRoot /var/www/domain1.com
    ErrorLog logs/domain1.com-error_log
    CustomLog logs/domain1.com-access_log combined
</VirtualHost>
<VirtualHost *:80>
    ServerName domain2.info
    DocumentRoot /var/www/domain2.info
    ErrorLog logs/domain2.info-error_log
    CustomLog logs/domain2.info-access_log combined
</VirtualHost>
<VirtualHost *:80>
    ServerName domain3.monster
    DocumentRoot /var/www/domain3.monster
    ErrorLog logs/domain3.monster-error_log
    CustomLog logs/domain3.monster-access_log combined
</VirtualHost>
```

基本のバーチャルホスト設定は大体こんな感じ。
_default_:80 は設定外のドメインやIP直打ちのアクセスを禁止するため。
DNS設定済みならアクセスして確認できると思います。

autoindex.conf、welcome.conf は内容を空にする
（ディレクトリ一覧は表示させないのと、ウェルカムページも表示させないので不要）

```bash
[root@(host) conf.d]# cp -a autoindex.conf autoindex.conf_original
[root@(host) conf.d]# vi autoindex.conf
[root@(host) conf.d]# cp -a welcome.conf welcome.conf_original
[root@(host) conf.d]# vi welcome.conf
```

ちなみにディレクトリ一覧を表示させない Options Indexes の設定をしてないけど
初期値は /var/www/html に設定されているため
今回のドキュメントルートでは指定しなくても見えない状態です。

（あとドキュメントルートのディレクトリにはSGID指定した。
　コンテンツ更新用にユーザーを別途追加する。つもり。）

```bash
### ↓ 設定の確認
[root@(host) conf.d]# apachectl configtest
### ↓ 反映
[root@(host) conf.d]# systemctl reload httpd
```

#### security.conf 作成

この項に関しては下記の方の記事を参照頂いたほうが良いと思います。
https://qiita.com/bezeklik/items/1c4145652661cf5b2271
※ バージョン外の項目は記述していないのと
　 一部用途の都合で変えていますが、おおよそ倣いました。

## SSL対応

#### mod_ssl インストール
```bash
[root@(host) conf.d]# dnf install mod_ssl
```

#### ファイアウォール
publicのzoneにhttpsを追加

```bash
[root@(host) conf.d]# firewall-cmd --add-service=https --zone=public --permanent
success
[root@(host) conf.d]# firewall-cmd --reload
success
```

## Let's encrypt 証明書を取得

#### certbot-auto のインストール
EPALリポジトリから取得…と思いきや
CentOS8用にはまだ含まれていないようだったので
certbot-auto をダウンロードして使用します。

```bash
[root@(host) conf.d]# cd /tmp
[root@(host) tmp]# wget https://dl.eff.org/certbot-auto
[root@(host) tmp]# chmod 755 certbot-auto
[root@(host) tmp]# mv certbot-auto /usr/local/bin/
```

#### 証明書の取得
DNSで正引きできている必要があります。
（ドメインでアクセスできてるなら大丈夫）

```
[root@(host) tmp]# certbot-auto certonly --apache -w /var/www/domain1.com -d domain1.com -w /var/www/domain2.info -d domain2.info -w /var/www/domain3.monster -d domain3.monster --email user@example.com --server https://acme-v02.api.letsencrypt.org/directory --agree-tos --no-eff-email
```

マルチドメインの場合
`-w ドキュメントルート -d ドメイン` という形で
ドキュメントルートの指定と、その後に指定したドメイン名がひも付きます。
これをドメイン分記載します。
※ ドキュメントルートが同じ場合、`-w /path/to -d domain1 -d domain2` という形でOK

※ 作成される証明書は1つです。
　 最初に指定したものがメインのドメインということになり、
　 1つ目のドメイン名のみで作成されます。
　 1つの証明書で複数ドメインを証明する形になります。

* --email メールアドレスです。有効期限のお知らせや緊急の更新およびセキュリティ通知に使用される
* --agree-tos は 規約の同意  
* --no-eff-email は  
スポンサーのElectronic Frontier Foundation（=EFF）にもメールアドレスを共有していいかという質問で  
（EFFのメルマガのようなものが送られるようです。）  
拒否する場合のオプションです

（メールや規約等のオプションは書かなくても途中で聞かれます）

実行が完了すると証明書が作成されます。

```bash
[root@(host) tmp]# ls -l /etc/letsencrypt/live/domain1.com/
```
※ マルチドメインの場合でも、
　 1つ目のドメイン名のみで作成されます。（2回目）

|ファイル|概要|
|---|---|
|privkey.pem|秘密鍵|
|fullchain.pem|SSL証明書＆中間証明書|
|cert.pem|SSL証明書|
|chain.pem|中間証明書|

バージョン2.4.8以降の場合は、SSLCertificateFile ディレクティブでfullchain.pemが利用できる

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

バージョン2.4.8未満の場合は

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

#### ssl.conf

最初から記載されている VirtualHost ディレクティブをまるっと削除します。

```bash
[root@(host) conf.d]# vi ssl.conf
```

マルチドメインの証明書の場合は残しておいても大丈夫そうだったのですが
マルチドメイン用でない証明書の場合、
VirtualHost *:443 設定がメイン（=1件目）になっていると証明書エラーになる気がします。
（でも自前で設定する場合はやっぱり要らないので削除しておくのが無難かなと思います。）

#### VirtualHost 再編集
```bash
[root@(host) conf.d]# vi virtualhost.conf
SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLHonorCipherOrder on
SSLCipherSuite PROFILE=SYSTEM
SSLProxyCipherSuite PROFILE=SYSTEM
Header always set Strict-Transport-Security "max-age=63072000;"

<VirtualHost _default_:80>
    ServerName any
    <Location />
        Require all denied
    </Location>
</VirtualHost>
<VirtualHost _default_:443>
    ServerName any
    <Location />
        Require all denied
    </Location>
    SSLCertificateFile /etc/pki/tls/certs/localhost.crt
    SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>
<VirtualHost _default_:*>
    ServerName any
    <Location />
        Require all denied
    </Location>
</VirtualHost>

<VirtualHost *:80>
    ServerName domain1.com
    DocumentRoot /var/www/domain1.com
    ErrorLog logs/domain1.com-error_log
    CustomLog logs/domain1.com-access_log combined
    <Directory /var/www/domain1.com>
        RewriteEngine on
        RewriteCond %{HTTPS} off
        RewriteRule ^(.*)$ https://domain1.com/$1 [R=301,L]
    </Directory>
</VirtualHost>
<VirtualHost *:443>
    SSLEngine on
    ServerName domain1.com
    DocumentRoot /var/www/domain1.com
    ErrorLog logs/domain1.com-ssl-error_log
    CustomLog logs/domain1.com-ssl-access_log combined
    SSLCertificateFile /etc/letsencrypt/live/domain1.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/domain1.com/privkey.pem
    <Directory /var/www/domain1.com>
        AllowOverride All
    </Directory>
</VirtualHost>

<VirtualHost *:80>
    ServerName domain2.info
    DocumentRoot /var/www/domain2.info
    ErrorLog logs/domain2.info-error_log
    CustomLog logs/domain2.info-access_log combined
    <Directory /var/www/domain2.info>
        RewriteEngine on
        RewriteCond %{HTTPS} off
        RewriteRule ^(.*)$ https://domain2.info/$1 [R=301,L]
    </Directory>
</VirtualHost>
<VirtualHost *:443>
    SSLEngine on
    ServerName domain2.info
    DocumentRoot /var/www/domain2.info
    ErrorLog logs/domain1.com-ssl-error_log
    CustomLog logs/domain1.com-ssl-access_log combined
    SSLCertificateFile /etc/letsencrypt/live/domain1.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/domain1.com/privkey.pem
    <Directory /var/www/domain2.info>
        AllowOverride All
    </Directory>
</VirtualHost>

<VirtualHost *:80>
    ServerName domain3.monster
    DocumentRoot /var/www/domain3.monster
    ErrorLog logs/domain3.monster-error_log
    CustomLog logs/domain3.monster-access_log combined
    <Directory /var/www/domain3.monster>
        RewriteEngine on
        RewriteCond %{HTTPS} off
        RewriteRule ^(.*)$ https://domain3.monster/$1 [R=301,L]
    </Directory>
</VirtualHost>
<VirtualHost *:443>
    SSLEngine on
    ServerName domain3.monster
    DocumentRoot /var/www/domain3.monster
    ErrorLog logs/domain1.com-ssl-error_log
    CustomLog logs/domain1.com-ssl-access_log combined
    SSLCertificateFile /etc/letsencrypt/live/domain1.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/domain1.com/privkey.pem
    <Directory /var/www/domain3.monster>
        AllowOverride All
    </Directory>
</VirtualHost>
```
※ マルチドメインの場合でも
　 1つの証明書で複数ドメインを証明する形になります。（3回目）
　 作成された、1つ目のドメイン名のファイルをそれぞれに指定します

`SSLCipherSuite` は個別に指定等はせず、
デフォルトで設定されているプロファイルをそのまま使用しています。

> OpenSSH は、RHEL のシステム全体の暗号化ポリシーを使用し、
> デフォルトのシステム全体の暗号化ポリシーレベルは、現在の脅威モデルに安全な設定を提供します。
> 暗号化の設定をより厳格にするには、現在のポリシーレベルを変更します。
> 
> ```bash
> # update-crypto-policies --set FUTURE
> ```




```bash
### ↓ 確認
[root@(host) conf.d]# apachectl configtest
### ↓ 反映
[root@(host) conf.d]# systemctl restart httpd
```

実際にアクセスして確認できたら
SSL暗号化の安全性をチェックしよう
▼ SSL Server Test
https://www.ssllabs.com/ssltest/index.html

## Let's Encrypt 証明書の更新

まずは `--dry-run` （実際には更新せず確認できるオプション）で一応確認

```bash
[root@(host) conf.d]# certbot-auto renew --dry-run
```

> Congratulations, all renewals succeeded. 

が出たらOK

#### crontab で更新と再起動を自動化
```bash
[root@(host) conf.d]# crontab -e
00 5 * * 5 /usr/local/bin/certbot-auto renew -q --deploy-hook "systemctl restart httpd"
```
※ root権限


…これでSSL証明書の設定まで完了となります。
お疲れさまでした！！（疲れた）