Ubuntu 20.04LTS の Apache + Flask で Let's Encrypt の SAN 証明書を作成・更新する話（更新できなかった問題を解決）

# はじめに
Certbot の証明書が切れそうだったので以下のように更新をしようとしても失敗しましたが、解決できなかったので自分用のメモも兼ねて記録。abc.example.com だけでなく、www.abc.example.com の証明書も一緒に取る SAN 証明書（abc.example.com の証明書に www.abc.example.com もエイリアスとして証明書を発行）のやり方の取得も行っております。

```sh
# certbot certonly --webroot -w /var/www/html/ -d abc.example.com -d www.abc.example.com --email info@example.com
```

Flask で http://abc.example.com/ の / ディレクトリを使用しない場合、ダミーでトップページの作成をしておらず 403 エラーが出るために、その作成方法をおまけとして記述しております。

# 環境
- Ubuntu 20.04LTS
- Apache/2.4.41 (Ubuntu)
- abc.example.com のルートディレクトリは `/var/www/html/`

# エラー症状

エラーは以下のように出ました。

```sh
# certbot renew --force-renewal
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/abc.example.com-0001.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for abc.example.com
Using the webroot path /var/www/html for all unmatched domains.
Waiting for verification...
Challenge failed for domain abc.example.com
http-01 challenge for abc.example.com
Cleaning up challenges
Attempting to renew cert (abc.example.com-0001) from /etc/letsencrypt/renewal/abc.example.com-0001.conf produced an unexpected error: Some challenges have failed.. Skipping.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/abc.example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Plugins selected: Authenticator apache, Installer apache
Renewing an existing certificate

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed with reload of apache server; fullchain is
/etc/letsencrypt/live/abc.example.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certs could not be renewed:
  /etc/letsencrypt/live/abc.example.com-0001/fullchain.pem (failure)

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

The following certs were successfully renewed:
  /etc/letsencrypt/live/abc.example.com/fullchain.pem (success)

The following certs could not be renewed:
  /etc/letsencrypt/live/abc.example.com-0001/fullchain.pem (failure)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 renew failure(s), 0 parse failure(s)

IMPORTANT NOTES:
 - The following errors were reported by the server:

   Domain: abc.example.com
   Type:   unauthorized
   Detail: (IP ADDRESS): Invalid response from
   https://abc.example.com/.well-known/acme-challenge/N6LQ-oAE87fhrjVkCkgH7iNJpfxU22jAJ3D3vrWTZp4:
   404

   To fix these errors, please make sure that your domain name was
   entered correctly and the DNS A/AAAA record(s) for that domain
   contain(s) the right IP address.
```

でも abc.example.com にアクセスするとしっかりページは表示されるし、なんで 404 エラーが出るのかわからず…

# 解決策
結局なんで 404 エラーが出てしまったのかは分かりませんでしたが、いったん証明書を削除して、`certbot certonly --apache`とすると解決しました。今まで証明書を取得するときは `certbot certonly --webroot` として動いていたのに、`--standalone`としても動かず焦っていましたが、`--apache` とするとうまく行きました。最後に Apache の再起動をします。

```sh
# certbot revoke --cert-path /etc/letsencrypt/live/abc.example.com/cert.pem
# certbot certonly --apache -w /var/www/html/ -d abc.example.com -d www.abc.example.com --email info@example.com
# systemctl restart apache2
```

各種ログは最後に記述。

# おまけ
いちいち Flask を systemctl でオフにする必要ないようにダミーページを作成しました。プロジェクト名は `hogeProject` です。

```Python:/var/www/html/hogeProject/top/url.py
from django.urls import path
 
from . import views
 
# app_name = index
urlpatterns = [
    path('', views.index, name='index')
]
```

```Python:/var/www/html/hogeProject/top/views.py
# from django.shortcuts import render
 
from django.http import HttpResponse
 
def index(request):
    return HttpResponse('This Page is for auto-renew SSL Certificate.')
```

```Python:/var/www/html/hogeProject/hogeProject/urls.py
（中略）
urlpatterns = [
    path('hoge/', include('hoge.urls', namespace='hoge')),
    path('admin/', admin.site.urls),
    path('', include('top.urls')),
]

# urlpatterns += static.static('/static/', document_root='static')
urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static.static(settings.CERT_URL, document_root=settings.CERT_ROOT)
```

```Python:/var/www/html/hogeProject/hogeProject/settings.py
（中略）
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CERT_ROOT = os.path.join(BASE_DIR, '.well-known')
CERT_URL = '/.well-known/'
```

# 各種ログ

```sh
# certbot certonly --apache -w /var/www/html/ -d abc.example.com -d www.abc.example.com --email info@example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator apache, Installer apache
Cert is due for renewal, auto-renewing...
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for abc.example.com
Enabled Apache rewrite module
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/abc.example.com-0001/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/abc.example.com-0001/privkey.pem
   Your cert will expire on 2023-09-10. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

root@paris:/# certbot renew --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/abc.example.com-0001.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator apache, Installer apache
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for abc.example.com
http-01 challenge for www.abc.example.com
Enabled Apache rewrite module
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed with reload of apache server; fullchain is
/etc/letsencrypt/live/abc.example.com-0001/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/abc.example.com-0001/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# certbot revoke --cert-path /etc/letsencrypt/live/abc.example.com/cert.pem
Saving debug log to /var/log/letsencrypt/letsencrypt.log

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Deleted all files relating to certificate abc.example.com.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully revoked the certificate that was located
at /etc/letsencrypt/live/abc.example.com/cert.pem

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# certbot certonly --webroot -w /var/www/html/ -d abc.example.com -d www.abc.example.com --email info@example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Cert is due for renewal, auto-renewing...
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for abc.example.com
Using the webroot path /var/www/html for all unmatched domains.
Waiting for verification...
Challenge failed for domain abc.example.com
http-01 challenge for abc.example.com
Cleaning up challenges
Some challenges have failed.

IMPORTANT NOTES:
 - The following errors were reported by the server:

   Domain: abc.example.com
   Type:   unauthorized
   Detail: (IP ADDRESS): Invalid response from
   https://abc.example.com/.well-known/acme-challenge/mujAsvwM5sxRsn_ecERGFTUNGicgFWnlHJs4zJWH9VM:
   404

   To fix these errors, please make sure that your domain name was
   entered correctly and the DNS A/AAAA record(s) for that domain
   contain(s) the right IP address.

# certbot certonly --standalone -w /var/www/html/ -d abc.example.com -d www.abc.example.com --email info@example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Cert is due for renewal, auto-renewing...
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for abc.example.com
Cleaning up challenges
Problem binding to port 80: Could not bind to IPv4 or IPv6.

# certbot certonly --apache -w /var/www/html/ -d abc.example.com -d www.abc.example.com --email info@example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator apache, Installer apache
Cert is due for renewal, auto-renewing...
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for abc.example.com
Enabled Apache rewrite module
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/abc.example.com-0001/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/abc.example.com-0001/privkey.pem
   Your cert will expire on 2023-09-10. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

# systemctl restart apache2
```

# 参考文献
https://qiita.com/s-katsumata/items/2286052aa8d375c26a39