CeontOS/RHEL 7 で certbot を実行しようとした際、pyOpenSSL のバージョンエラーが発生した場合の対処方法

とにかく解決方法だけ知りたい方は[こちら](#具体的な方法)

## 公式に書いてある方法

### certbot のインストール方法

```bash
sudo yum install certbot certbot-apache
```

[参照](https://certbot.eff.org/lets-encrypt/centosrhel7-apache)

### 実行方法

```bash
sudo certbot --apache
```

### 発生するエラー

```bash
Traceback (most recent call last):
File "/bin/certbot", line 7, in
from certbot.main import main
File "/usr/lib/python2.7/site-packages/certbot/main.py", line 17, in
from certbot import client
File "/usr/lib/python2.7/site-packages/certbot/client.py", line 10, in
from acme import client as acme_client
File "/usr/lib/python2.7/site-packages/acme/client.py", line 31, in
requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() # type: ignore
File "/usr/lib/python2.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 112, in inject_into_urllib3
_validate_dependencies_met()
File "/usr/lib/python2.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 147, in _validate_dependencies_met
raise ImportError("'pyOpenSSL' module missing required functionality. "
ImportError: 'pyOpenSSL' module missing required functionality. Try upgrading to v0.14 or newer.
```


## 原因と解決方法


### 原因

CentOS/RHEL 7 向けに提供されている pyOpenSSL のバージョンが古いため発生する様子。
実際 `sudo yum install pyOpenSSL` を実行しようとすると version 0.13 がインストールされるのでこれが諸悪の根源。

### 解決方法

解決方法については [公式の Issue](https://github.com/certbot/certbot/issues/4514) でも議論されているが、
どれを試しても私の環境では解決せずでした。

が、議論のなかの [rhoerbe](https://github.com/rhoerbe) 氏の方法を応用することで解決しました。

> yum remove pyOpenSSL.x86_64
> cd
> mkdir virtualenv
> cd virtualenv/
> virtualenv --no-site-packages -p /usr/bin/python2.7 certbot
> . ~/virtualenv/certbot/bin/activate
> pip import six packaging appdirs
> pip install setuptools --upgrade
> pip install certbot
> certbot help


### 具体的な方法

### 1. `virtualenv` をインストールし `virtualenv` が使用できる環境を作成します。

```bash
sudo pip install virtualenv
cd ~
mkdir virtualenv
cd virtualenv/
virtualenv --no-site-packages -p /usr/bin/python2.7 certbot
```

#### 2. `virtualenv` を有効化し、必要な Python パッケージをインストールします。

```bash
. ~/virtualenv/certbot/bin/activate
pip install -U setuptools
```

#### 3. `certbot` をインストールします。

```bash
pip install certbot==14.0 certbot-apache==14.0 certbot-nginx==14.0
```

念の為バージョン指定でインストールします。記事作成時の時点で最新バージョンは 15.0 でしたが、 14.0 を指定します。


__次の作業から重要です。__
__これをやらずに実行しようとしても新たなエラーが発生するので必ず実行してください。__


#### 4. `pyopenssl` と `parsedatetime` パッケージのバージョンをしていてインストールし直します。

```bash
pip install -U pyopenssl==16.2.0 parsedatetime==2.5
```

`certbot` インストール時に勝手にインストールされる `pyopenssl` と `parsedatetime` は以下のエラーが発生します。

<details>
<summary>pyopenssl のバージョンが問題で発生するエラー<summary>

```bash
Traceback (most recent call last):
  File "/home/opc/virtualenv/certbot/bin/certbot", line 7, in <module>
    from certbot.main import main
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/certbot/main.py", line 2, in <module>
    from certbot._internal import main as internal_main
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/certbot/_internal/main.py", line 10, in <module>
    import josepy as jose
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/josepy/__init__.py", line 44, in <module>
    from josepy.interfaces import JSONDeSerializable
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/josepy/interfaces.py", line 7, in <module>
    from josepy import errors, util
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/josepy/util.py", line 7, in <module>
    import OpenSSL
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/OpenSSL/__init__.py", line 8, in <module>
    from OpenSSL import rand, crypto, SSL
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/OpenSSL/SSL.py", line 124, in <module>
    SSL_ST_INIT = _lib.SSL_ST_INIT
AttributeError: 'module' object has no attribute 'SSL_ST_INIT'
```

</details>

<details>
<summary>parsedatetime のバージョンが原因で発生するエラー </summary>

```bash
Traceback (most recent call last):
  File "/home/opc/virtualenv/certbot/bin/certbot", line 7, in <module>
    from certbot.main import main
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/certbot/main.py", line 2, in <module>
    from certbot._internal import main as internal_main
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/certbot/_internal/main.py", line 21, in <module>
    from certbot._internal import cert_manager
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/certbot/_internal/cert_manager.py", line 16, in <module>
    from certbot._internal import storage
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/certbot/_internal/storage.py", line 79, in <module>
    def add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()):
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/parsedatetime/__init__.py", line 270, in __init__
    self.ptc = Constants()
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/parsedatetime/__init__.py", line 2381, in __init__
    self.locale = get_icu(self.localeID)
  File "/home/opc/virtualenv/certbot/lib/python2.7/site-packages/parsedatetime/pdt_locales/icu.py", line 56, in get_icu
    result['icu'] = icu = pyicu.Locale(locale)
AttributeError: 'module' object has no attribute 'Locale'
```

</details>

#### 5. 証明書をインストールします

##### Apache の場合

```bash
sudo ~/virtualenv/certbot/bin/certbot --apache
```

##### Nginx の場合

```bash
sudo ~/virtualenv/certbot/bin/certbot --nginx
```

実行するたびに `~/virtualenv/certbot/bin/certbot`という長いパスを指定するのがめんどくさいという方は 

```bash
sudo ln -s ~/virtualenv/certbot/bin/certbot /usr/bin/certbot
```
 としておけば、

```bash 
sudo certbot --apache
```

という感じで実行できます。


## 雑談

`details` タグ仕事してくれ