Ubuntu20.04でcertbot-dns-sakuracloudをインストールするとcertbotが動かなくなる問題への対応方法

# はじめに

Ubuntu 20.04でcertbotのさくらのクラウドDNSプラグインである certbot-dns-sakuracloud の最新版をインストールすると、certbotが動かなくなります。また、最悪の場合pip3(pip)も動作しなくなります。

# 条件
- Ubuntu 20.04 LTS
- certbot 0.40.0
- virtualenvやpyenv, Dockerなどは利用していない（パッケージマネージャより提供される環境を利用）

# 事象の詳細

## インストール時

pip3 コマンドで certbot-dns-sacloud をインストールすると、次のメッセージが返されます。

```
# pip3 install certbot-dns-sakuracloud
Collecting certbot-dns-sakuracloud
  Downloading certbot_dns_sakuracloud-2.6.0-py3-none-any.whl (10 kB)
Collecting dns-lexicon>=3.2.1
  Downloading dns_lexicon-3.11.7-py3-none-any.whl (233 kB)
     |????????????????????????????????| 233 kB 2.6 MB/s
Requirement already satisfied: setuptools>=41.6.0 in /usr/lib/python3/dist-packages (from certbot-dns-sakuracloud) (45.2.0)
Collecting acme>=2.6.0
  Downloading acme-2.6.0-py3-none-any.whl (95 kB)
     |????????????????????????????????| 95 kB 3.6 MB/s
Collecting certbot>=2.6.0
  Downloading certbot-2.6.0-py3-none-any.whl (399 kB)
     |????????????????????????????????| 399 kB 38.9 MB/s
Requirement already satisfied: requests>=2 in /usr/lib/python3/dist-packages (from dns-lexicon>=3.2.1->certbot-dns-sakuracloud) (2.22.0)
Collecting beautifulsoup4>=4
  Downloading beautifulsoup4-4.12.2-py3-none-any.whl (142 kB)
     |????????????????????????????????| 142 kB 61.0 MB/s
Requirement already satisfied: pyyaml>=3 in /usr/lib/python3/dist-packages (from dns-lexicon>=3.2.1->certbot-dns-sakuracloud) (5.3.1)
Requirement already satisfied: cryptography>=2 in /usr/lib/python3/dist-packages (from dns-lexicon>=3.2.1->certbot-dns-sakuracloud) (2.8)
Collecting importlib-metadata>=4
  Downloading importlib_metadata-6.6.0-py3-none-any.whl (22 kB)
Collecting tldextract>=2
  Downloading tldextract-3.4.4-py3-none-any.whl (93 kB)
     |????????????????????????????????| 93 kB 2.6 MB/s
Requirement already satisfied: pyrfc3339 in /usr/lib/python3/dist-packages (from acme>=2.6.0->certbot-dns-sakuracloud) (1.1)
Requirement already satisfied: PyOpenSSL!=23.1.0,>=17.5.0 in /usr/lib/python3/dist-packages (from acme>=2.6.0->certbot-dns-sakuracloud) (19.0.0)
Collecting josepy>=1.13.0
  Downloading josepy-1.13.0-py2.py3-none-any.whl (29 kB)
Requirement already satisfied: pytz>=2019.3 in /usr/lib/python3/dist-packages (from acme>=2.6.0->certbot-dns-sakuracloud) (2019.3)
Requirement already satisfied: configobj>=5.0.6 in /usr/lib/python3/dist-packages (from certbot>=2.6.0->certbot-dns-sakuracloud) (5.0.6)
Requirement already satisfied: parsedatetime>=2.4 in /usr/lib/python3/dist-packages (from certbot>=2.6.0->certbot-dns-sakuracloud) (2.4)
Requirement already satisfied: distro>=1.0.1 in /usr/lib/python3/dist-packages (from certbot>=2.6.0->certbot-dns-sakuracloud) (1.4.0)
Requirement already satisfied: ConfigArgParse>=0.9.3 in /usr/lib/python3/dist-packages (from certbot>=2.6.0->certbot-dns-sakuracloud) (0.13.0)
Collecting soupsieve>1.2
  Downloading soupsieve-2.4.1-py3-none-any.whl (36 kB)
Collecting zipp>=0.5
  Downloading zipp-3.15.0-py3-none-any.whl (6.8 kB)
Collecting filelock>=3.0.8
  Downloading filelock-3.12.1-py3-none-any.whl (10 kB)
Collecting requests-file>=1.4
  Downloading requests_file-1.5.1-py2.py3-none-any.whl (3.7 kB)
Requirement already satisfied: idna in /usr/lib/python3/dist-packages (from tldextract>=2->dns-lexicon>=3.2.1->certbot-dns-sakuracloud) (2.8)
Requirement already satisfied: six in /usr/lib/python3/dist-packages (from requests-file>=1.4->tldextract>=2->dns-lexicon>=3.2.1->certbot-dns-sakuracloud) (1.14.0)
ERROR: certbot 2.6.0 has requirement cryptography>=3.2.1, but you'll have cryptography 2.8 which is incompatible.
ERROR: acme 2.6.0 has requirement cryptography>=3.2.1, but you'll have cryptography 2.8 which is incompatible.
Installing collected packages: soupsieve, beautifulsoup4, zipp, importlib-metadata, filelock, requests-file, tldextract, dns-lexicon, josepy, acme, certbot, certbot-dns-sakuracloud
  Attempting uninstall: josepy
    Found existing installation: josepy 1.2.0
    Not uninstalling josepy at /usr/lib/python3/dist-packages, outside environment /usr
    Can't uninstall 'josepy'. No files were found to uninstall.
  Attempting uninstall: acme
    Found existing installation: acme 1.1.0
    Not uninstalling acme at /usr/lib/python3/dist-packages, outside environment /usr
    Can't uninstall 'acme'. No files were found to uninstall.
  Attempting uninstall: certbot
    Found existing installation: certbot 0.40.0
    Not uninstalling certbot at /usr/lib/python3/dist-packages, outside environment /usr
    Can't uninstall 'certbot'. No files were found to uninstall.
Successfully installed acme-2.6.0 beautifulsoup4-4.12.2 certbot-2.6.0 certbot-dns-sakuracloud-2.6.0 dns-lexicon-3.11.7 filelock-3.12.1 importlib-metadata-6.6.0 josepy-1.13.0 requests-file-1.5.1 soupsieve-2.4.1 tldextract-3.4.4 zipp-3.15.0
```

一見するとインストールは成功しているように見えますが、
```
ERROR: certbot 2.6.0 has requirement cryptography>=3.2.1, but you'll have cryptography 2.8 which is incompatible.
ERROR: acme 2.6.0 has requirement cryptography>=3.2.1, but you'll have cryptography 2.8 which is incompatible.
```

というメッセージが表示されており、依存関係のインストールに失敗していることが分かります。


## コマンド実行時

以下のようなコマンドを実行し、証明書の取得または更新を行おうとすると、次のエラーが返されます。

```
# certbot certonly --dns-sakuracloud --dns-sakuracloud-credentials /home/hoge/.sakura -d hoge.example.org -d *.hoge.example.org -m hoge@example.org --manual-public-ip-logging-ok --agree-tos

An unexpected error occurred:
pkg_resources.ContextualVersionConflict: (cryptography 2.8 (/usr/lib/python3/dist-packages), Requirement.parse('cryptography>=3.2.1'), {'acme'})
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /tmp/certbot-log-1ardjgaw/log or re-run Certbot with -v for more details.

```

ここで、エラーメッセージから「cryptographyのバージョンが古い（3.2.1以上にアップグレードすれば良いんだな・・・）」と早合点して `pip3 install --upgrade cryptography` を実行すると、certbot実行時に次のエラーが発生します。

```
Traceback (most recent call last):
  File "/usr/bin/pip", line 11, in <module>
    load_entry_point('pip==20.0.2', 'console_scripts', 'pip')()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 490, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2854, in load_entry_point
    return ep.load()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2445, in load
    return self.resolve()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2451, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "/usr/lib/python3/dist-packages/pip/_internal/cli/main.py", line 10, in <module>
    from pip._internal.cli.autocompletion import autocomplete
  File "/usr/lib/python3/dist-packages/pip/_internal/cli/autocompletion.py", line 9, in <module>
    from pip._internal.cli.main_parser import create_main_parser
  File "/usr/lib/python3/dist-packages/pip/_internal/cli/main_parser.py", line 7, in <module>
    from pip._internal.cli import cmdoptions
  File "/usr/lib/python3/dist-packages/pip/_internal/cli/cmdoptions.py", line 24, in <module>
    from pip._internal.exceptions import CommandError
  File "/usr/lib/python3/dist-packages/pip/_internal/exceptions.py", line 10, in <module>
    from pip._vendor.six import iteritems
  File "/usr/lib/python3/dist-packages/pip/_vendor/__init__.py", line 65, in <module>
    vendored("cachecontrol")
  File "/usr/lib/python3/dist-packages/pip/_vendor/__init__.py", line 36, in vendored
    __import__(modulename, globals(), locals(), level=0)
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 618, in _load_backward_compatible
  File "<frozen zipimport>", line 259, in load_module
  File "/usr/share/python-wheels/CacheControl-0.12.6-py2.py3-none-any.whl/cachecontrol/__init__.py", line 9, in <module>
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 618, in _load_backward_compatible
  File "<frozen zipimport>", line 259, in load_module
  File "/usr/share/python-wheels/CacheControl-0.12.6-py2.py3-none-any.whl/cachecontrol/wrapper.py", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 618, in _load_backward_compatible
  File "<frozen zipimport>", line 259, in load_module
  File "/usr/share/python-wheels/CacheControl-0.12.6-py2.py3-none-any.whl/cachecontrol/adapter.py", line 5, in <module>
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 618, in _load_backward_compatible
  File "<frozen zipimport>", line 259, in load_module
  File "/usr/share/python-wheels/requests-2.22.0-py2.py3-none-any.whl/requests/__init__.py", line 95, in <module>
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 618, in _load_backward_compatible
  File "<frozen zipimport>", line 259, in load_module
  File "/usr/share/python-wheels/urllib3-1.25.8-py2.py3-none-any.whl/urllib3/contrib/pyopenssl.py", line 46, in <module>
  File "/usr/lib/python3/dist-packages/OpenSSL/__init__.py", line 8, in <module>
    from OpenSSL import crypto, SSL
  File "/usr/lib/python3/dist-packages/OpenSSL/crypto.py", line 1553, in <module>
    class X509StoreFlags(object):
  File "/usr/lib/python3/dist-packages/OpenSSL/crypto.py", line 1573, in X509StoreFlags
    CB_ISSUER_CHECK = _lib.X509_V_FLAG_CB_ISSUER_CHECK
AttributeError: module 'lib' has no attribute 'X509_V_FLAG_CB_ISSUER_CHECK'
```

また、pip(pip3)コマンドの実行時にも同様のエラーが発生するようになり、モジュールのインストール・アンインストールを含めた一切の操作が受け付けられない状態となります。

# 対応方法

## １）pip3が動作しなくなってしまった場合

pip3が動作する、あるいはまだ certbot-dns-sakuracloud コマンドをインストールしていない場合は次項へ。運悪くpip3が動作しなくなってしまった場合は、まずpip3を復活させることに専念しましょう。

pip3(python3-pip)の再インストールは不要です。 `apt purge python3-pip` から`apt install python3-pip` をしてpip3の再インストールを行っても、エラーは解消されません。

エラーを解消するには、`/usr/local/lib/python3.8` ディレクトリをリネームするか、削除します。

## ２）モジュールをインストールする

以下のコマンドを実行し、モジュールをインストールします。
```
pip3 install dns-lexicon==3.3.17
pip3 install certbot-dns-sakuracloud==0.31.0
```

その後、certbotコマンドで `--dns-sakuracloud` オプションを利用して証明書の発行または更新を行います（動作を確認します）。エラーが表示されず、正常に実行が完了すれば（証明書の発行まで完了すれば）成功です。

# 最後に

今回はcertbot-dns-sakuracloudのインストールバージョンを、ディストリビューションに含まれるモジュールのバージョンに合わせる形で対応を行いました。

Ubuntu 20.04を含め、ディストリビューションに同梱されるランタイムやモジュール（ライブラリ）のバージョンが古いことで、インストールしたモジュール（コマンド）が動作しない場合があります。

また、ディストリビューションのパッケージマネージャ（apt, dnfなど）経由でインストールされたモジュールを無視する形で同じモジュールをpipからインストールすると、今回の例のようにインストールしたモジュールだけでなく、pipの動作にも影響を及ぼす恐れがあります。また、python3に依存するコマンドやシステムにも影響を及ぼす恐れがありますので、慎重に対応する必要があります。

もし、ディストリビューション標準のPythonへ影響を与えずに各種モジュールを利用したい場合は、virtualenvやpyenv, Dockerなどを検討してみるのも良いでしょう。