[実践]CertbotでEC2をSSL化してみよう

![001samune.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/4ff737a5-c37a-49d3-bf0d-5d07449e57b2.png)

# 概要
EC2をWebサーバーとして利用する際に、EC2までの通信をHTTPSで暗号化したいケースがあると思います。
そんな時に便利なのがCertbotです。今回は、Certbotを使ってEC2のSSL化を試してみます。

# Certbotとは
Let's Encryptを認証局とした無料かつ自動でSSL/TLSサーバー証明書を発行することができる無料のOSSツールです。
`ACME (Automatic Certificate Management Environment)`という証明書の管理を自動化するプロトコルを使い、Let's Encryptに対してSSL/TLSサーバー証明書の検証及び発行を実施します。

# ACM (AWS Certificate Manager)ではできないのか
結論、できます。
ACMは各証明書のプロビジョニング及び管理するマネージドサービスです。
* Amazonの認証局 (Amazon Trust Services)から発行したパブリック証明書
* ACM Private Certificate Authority (プライベート認証局)から発行したプライベート証明書
* インポートしたサードパーティー製のパブリック/プライベート証明書

ACMで管理する証明書をプロビジョニングできるAWSサービスは公式サイトに記載されている通りです。

https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/acm-services.html

そのうちの一つにある`AWS Nitro Enclaves`というEC2の機能を利用すれば、ACMで管理する証明書を使ってEC2への通信をHTTPSとすることが可能です。
ただし、利用可能な`インスタンスタイプ及びOSに制限`があります。

https://docs.aws.amazon.com/ja_jp/enclaves/latest/user/nitro-enclave.html#nitro-enclave-reqs

# 無料証明書ってどうなのか
以下の記事が非常にわかりやすいため掲載させていただきます。

https://ssl.sakura.ad.jp/column/free-or-paid/?gad_source=1&gclid=Cj0KCQjwv_m-BhC4ARIsAIqNeBtufL8lY1572wkY7Ab4uN47oeZAMXOrSlMloHrV9GIaDB3pjYiph70aAtRxEALw_wcB

# 実践
実際にCertbotを使って無料のSSL/TLSサーバー証明書を作成し、EC2にHTTPSで繋いで行きましょう。
構成図は以下の通りです。詳細はGitHubを参照してみてください。

![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/5c0f6066-9640-48e1-be99-e0f99159c2fe.png)

https://github.com/tyskJ/aws-certbot-ec2-ssl

### 前提条件
* ドメイン取得済みで、EC2に対するAレコードが登録済みであること
* 以下作業は`root`ユーザーで実施していきます

### Nginxインストール
まずは`Nginx`をインストールし、ブラウザからHTTPでアクセスできるかを確認していきます。
##### パッケージリスト更新
```shell-session
# dnf update
```

##### インストール
```shell-session
# dnf install nginx -y
```

##### Nginx起動&自動起動設定
```shell-session
# systemctl enable --now nginx
```

##### HTTPでブラウザからアクセス確認
![画像3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/5f816081-5dc0-4bc3-8495-5c380b02a11c.png)


### Python仮想環境設定
`Certbot`のインストール方法は`Docker`などもありますが、今回は`pip (Pythonパッケージ管理ツール)`を使った方法で実施していきます。
具体的には、`venv (仮想環境ツール)`を使ってPython仮想環境を作成し、その環境にCertbotをインストールします。そのための準備をしていきましょう。

https://qiita.com/yasu_qita/items/197c94b2ad3003233407

##### パッケージインストール
* python3: Python
* augeas-libs: Apacheプラグイン
```shell-session
# dnf install python3 augeas-libs -y
```

##### 仮想環境作成
```shell-session
# mkdir /opt/certbot
```
```shell-session
# python3 -m venv /opt/certbot
```
```shell-session
# /opt/certbot/bin/pip install --upgrade pip
```

### Certbotインストール
##### Certbot&Nginx用パッケージインストール
```shell-session
# /opt/certbot/bin/pip install certbot certbot-nginx
```

##### シンボリックリンク作成
```shell-session
# ln -s /opt/certbot/bin/certbot /usr/bin/certbot
```

### SSL/TLSサーバー証明書作成
##### nginx.confファイル修正
後述するコマンドを実行すると、`Certbot`が自動で`nginx.conf`を修正してくれます。
ただその際に、ファイル内の`server_name`を確認するため、事前に修正します。
```diff_conf:/etc/nginx/nginx.conf
# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        listen       [::]:80;
-        server_name  _;
+        server_name  www.example.com; # 自身の登録するドメイン名に修正
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl;
#        listen       [::]:443 ssl;
#        http2        on;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers PROFILE=SYSTEM;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        error_page 404 /404.html;
#        location = /404.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#        location = /50x.html {
#        }
#    }
}
```

##### 証明書作成&Nginx設定ファイル自動編集
```shell-session
# certbot --nginx
```
:::note info
上記コマンドを実行すると、メールアドレスや登録するドメイン情報の入力が求められます。
:::

##### nginx.confの確認
実際の変更点は以下の通りです。
```diff_conf:/etc/nginx/nginx.conf
# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        server_name  www.example.com;
        root         /usr/share/nginx/html;
                                                                                                                                                                                                                 or        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }

+    listen [::]:443 ssl ipv6only=on; # managed by Certbot
+    listen 443 ssl; # managed by Certbot
+    ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem; # managed by Certbot
+    ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem; # managed by Certbot
+    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl;
#        listen       [::]:443 ssl;
#        http2        on;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers PROFILE=SYSTEM;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        error_page 404 /404.html;
#        location = /404.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#        location = /50x.html {
#        }
#    }


+    server {
+    if ($host = www.example.com) {
+        return 301 https://$host$request_uri;
+    } # managed by Certbot
+
+
+        listen       80;
+        listen       [::]:80;
+        server_name  www.example.com;
+    return 404; # managed by Certbot
+}}
```

### 動作確認
HTTPSでアクセスできています。
![画像4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/49f9e0ec-19ed-41fc-8c13-fe91bf9a04dc.png)

証明書も`Let's Encrypt`から発行されているのが確認できます。
![画像5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/ee1c67be-4e87-4744-9d53-0f23be0259de.png)

### 自動更新設定（おまけ）
```shell-session
# echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo certbot renew -q" | sudo tee -a /etc/crontab > /dev/null
```

### 証明書失効（おまけ）
```shell-session
# /opt/certbot/bin/certbot revoke --cert-path /etc/letsencrypt/live/www.example.com/fullchain.pem

Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you like to delete the certificate(s) you just revoked, along with all
earlier and later versions of the certificate?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es (recommended)/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certificate(s) are selected for deletion:

  * www.example.com

WARNING: Before continuing, ensure that the listed certificates are not being
used by any installed server software (e.g. Apache, nginx, mail servers).
Deleting a certificate that is still being used will cause the server software
to stop working. See https://certbot.org/deleting-certs for information on
deleting certificates safely.

Are you sure you want to delete the above certificate(s)?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Deleted all files relating to certificate www.example.com.
Congratulations! You have successfully revoked the certificate that was located at /etc/letsencrypt/live/www.example.com/fullchain.pem.
```

### 証明書削除（おまけ）
```shell-session
# /opt/certbot/bin/certbot delete --cert-name www.example.com
```


# まとめ
今回は`Certbot`を使ったEC2のSSL化を実施してみました。
やってみると非常に簡単でした。
今回は`Let's Encrypt`が`Certbot`へのドメイン認証方式として`HTTP-01 チャレンジ`を利用しているため、認証する際には`80番ポートの解放`が必要となります。自動更新設定を入れるとなると常時80番ポートの解放が必要となるため、本番環境での利用では`DNS-01 チャレンジ`方式の採用を検討した方が良いかなと思います。
この記事が誰かの役に立てば幸いです。

# 参考資料
### リファレンス
* [Welcome to the Certbot documentation!](https://eff-certbot.readthedocs.io/en/stable/index.html)
* [ACME クライアント実装 - Let's Encrypt](https://letsencrypt.org/ja/docs/client-options/)
* [ACMEについて - JPRS](https://jprs.jp/pubcert/about/ACME/)

### ブログ
* [無料証明書と有料証明書の違い - SAKURA internet](https://ssl.sakura.ad.jp/column/free-or-paid/?gad_source=1&gclid=Cj0KCQjwv_m-BhC4ARIsAIqNeBtufL8lY1572wkY7Ab4uN47oeZAMXOrSlMloHrV9GIaDB3pjYiph70aAtRxEALw_wcB)
* [ACM for Nitro Enclaves を利用してNginxへのHTTPS通信を実装してみた](https://blog.serverworks.co.jp/acm-nitro-enclaves-nginx-https)