Let's EncryptのSSL証明書、Certbotで更新できず、GetSSLで更新


2016年11月24日時点の情報です。

Amazon Linux上で動かしているサービスで、認証局として[「Let's Encrypt」](https://letsencrypt.jp/)を使っていて、Certbotを使ってSSL証明書の更新をしたところ、「Installing Python packages...」というメッセージを最後に、それ以上先に進まなくなり、証明書が更新できないという事態に遭遇しました。試行錯誤の結果、Certbotによる更新を諦め、別クライアントであるGetSSLで更新ができたので、状況をまとめました。この現象は一時的なものかもしれませんが、同じ現象に遭遇している方もいらっしゃると思い、回避策を共有します。

## 環境

今回対象となったサービスはIoTクラウドサービス[「Ambient」(https://ambidata.io)](https://ambidata.io)で、Amazon Linux上で稼働しており、認証局として[「Let's Encrypt」](https://letsencrypt.jp/)という無料の認証局サービスを使っています。

Let's Encryptは、認証局となるLet's Encryptサーバーと、証明対象となるサーバー上で動作し、認証局サーバーとやりとりして証明書を取得したり更新したりするクライアントから構成されます。Let's Encryptの概要や仕組みについては[「Let's Encrypt総合ポータル」](https://letsencrypt.jp/)に詳しく書かれているので、そちらをご参照ください。クライアントには実装によりいくつかの種類がありますが、推奨されているのはCertbotというクライアントで、Pythonで記述されています。

AmbientではLet's Encryptで何回か証明書の更新を行なっており、直近ではCertbotで更新をしていますので、動作実績はありました。

## Certbotで更新できない

2016年11月22日にCertbotでSSL証明書を更新したところ、いろいろなモジュールを更新した後、次のように「Installing Python packages...」まで表示され、次に進まなくなりました。

```
いろいろなモジュールの更新メッセージ

更新:
  ca-certificates.noarch 0:2015.2.6-65.0.1.16.amzn1                             
  libffi-devel.x86_64 0:3.0.13-16.5.amzn1                                       
  openssl.x86_64 1:1.0.1k-15.96.amzn1                                           
  openssl-devel.x86_64 1:1.0.1k-15.96.amzn1                                     
  python27-pip.noarch 0:6.1.1-1.23.amzn1                                        
  python27-virtualenv.noarch 0:12.0.7-1.13.amzn1                                

依存性を更新しました:
  libffi.x86_64 0:3.0.13-16.5.amzn1                                             

完了しました!
Creating virtual environment...
Installing Python packages...
```

この状態で数分待ちましたが、サーバーの負荷が上昇し、データー登録APIにも反応しなくなったため、SSL証明書の更新処理を中断しました。ネットで調べたところ、Certbotの処理が「Installing Python packages...」でスタックするという複数の記事が見つかりました。例えば
* [I got stucked when I use ./letsencrypt-auto #2832](https://github.com/certbot/certbot/issues/2832)
などです。ネットの情報をもとに試行錯誤をしましたが、結局CertbotではPython packageの環境が構築できず、SSL証明書の更新ができませんでした。

## 別クライアントのGetSSLで更新

Let's EncryptにはCertbot以外にもいくつかのクライアントがあり、[「ACME Client Implementations」](https://letsencrypt.org/docs/client-options/)で紹介されています。今回はリストの先頭で紹介されていたGetSSLを使いました。GetSSLはbashスクリプトで実現されています。

[GetSSLはGithubに詳しい説明があり](https://github.com/srvrco/getssl)、それに従ってSSL証明書を更新しました。

### GetSSLをインストール

まず、Amazon LinuxにGetSSLをインストールします。

```bash
$ curl --silent https://raw.githubusercontent.com/srvrco/getssl/master/getssl > getssl
$ chmod 700 getssl
```

### 設定ファイルを生成、編集

次に、設定ファイルを生成します。

```bash
$ ./getssl -c 証明書を更新するサーバーのドメイン名
```

今回証明書を更新するサーバーのドメインはambidata.ioですので、具体的には次のようなコマンドになります。

```bash
$ ./getssl -c ambidata.io
```

すると、次のディレクトリーとファイルが生成されます。以下、ambidata.ioとあるところは証明書を更新するサーバーのドメイン名に読み替えてください。

```
~/.getssl
~/.getssl/getssl.cfg                --アカウントの設定ファイル
~/.getssl/ambidata.io
~/.getssl/ambidata.io/getssl.cfg    --ドメインの設定ファイル
```

二つの設定ファイルの中身を、[「Config variables」](https://github.com/srvrco/getssl/wiki/Config-variables)を参考にして編集します。

~/.getssl/getssl.cfg    --アカウントの設定ファイル

```bash
5c5  --ステージング環境をコメントアウトし、
< CA="https://acme-staging.api.letsencrypt.org"
---
> #CA="https://acme-staging.api.letsencrypt.org"
7c7 --本番環境を活かす
< #CA="https://acme-v01.api.letsencrypt.org"
---
> CA="https://acme-v01.api.letsencrypt.org"
9c9 --アグリーメントを活かす
< #AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"
---
> AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"
12c12 --メールアドレスを指定
< #ACCOUNT_EMAIL="me@example.com"
---
> ACCOUNT_EMAIL="自分の連絡先メールアドレス"
```

~/.getssl/ambidata.io/getssl.cfg    --ドメインの設定ファイル

```bash
29,30c29,30 --ACL(ACME Challenge Location)をサーバーに合わせて修正
< #ACL=('/var/www/ambidata.io/web/.well-known/acme-challenge'
< #     'ssh:server5:/var/www/ambidata.io/web/.well-known/acme-challenge'
---
> ACL=('/ドキュメントルート/.well-known/acme-challenge')
> #     'ssh:ambidata.io:/var/www/html/.well-known/acme-challeng'
42c42 --サーバー証明書と中間証明書が結合されたファイルを生成(http-proxyで必要)
< #DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert
---
> DOMAIN_CHAIN_LOCATION="." # this is the domain cert and CA cert
```

Ambientではhttp-proxyによるリバースプロキシーを使用しており、http-proxyのSSL認証情報にはサーバー証明書と中間証明書が結合されたファイルが必要なため、DOMAIN_CHAIN_LOCATIONという設定変数を活かしています。

### SSL証明書を取得

設定ファイルを生成、編集したら、getsslを実行してSSL証明書を取得します。

```bash
$ ./getssl ambidata.io
creating account key ~/.getssl/account.key
creating domain key - ~/.getssl/account.key
Generating RSA private key, 4096 bit long modulus
........................................................................................................................................++
.........................................................................++
e is 65537 (0x10001)
creating domain key - ~/.getssl/ambidata.io/ambidata.io.key
Generating RSA private key, 4096 bit long modulus
................................................................................................................................................................++
.........++
e is 65537 (0x10001)
creating domain csr - ~/.getssl/ambidata.io/ambidata.io.csr
Registering account
Registered
Verify each domain
Verifying ambidata.io
copying challenge token to /ドキュメントルート/.well-known/acme-challenge/AD0nw9MPFQmxsd2hsHa5068zDbxHuhxhC4IxmCaXL7o
Pending
Verified ambidata.io
Verification completed, obtaining certificate.
Certificate saved in ~/.getssl/ambidata.io/ambidata.io.crt
The intermediate CA cert is in ~/.getssl/ambidata.io/chain.crt
copying full chain to ~/.getssl/ambidata.io/.
getssl: ambidata.io - certificate obtained but certificate on server is different from the new certificate
```

最後の行の「証明書は取得したが、サーバーの証明書とは違う」というワーニングは、まだ取得した証明書をサーバーに反映させていないために出力されたものです。

これで以下のファイルが作られます。

```
~/.getssl/account.key
~/.getssl/ambidata.io/ambidata.io.crt          --サーバー証明書
~/.getssl/ambidata.io/ambidata.io.key          --ドメインの秘密鍵
~/.getssl/ambidata.io/ambidata.io.csr          --証明書署名要求
~/.getssl/ambidata.io/ambidata.io_chain.pem    --サーバー証明書と中間証明書が結合されたファイル
~/.getssl/ambidata.io/chain.crt                --中間証明書

~/.getssl/ambidata.io/archive/2016_11_24_19_03/    --アーカイブ
ambidata.io.crt  ambidata.io.csr  ambidata.io.key  chain.crt
```

Certbotで作られる証明書とはファイル名が違っていますが、次のような対応関係になります。

```
GetSSLの生成ファイル        Certbotの生成ファイル
ambidata.io.crt          cert.pem             --サーバー証明書
ambidata.io.key          privkey.pem          --ドメインの秘密鍵
ambidata.io/chain.crt    chain.pem            --中間証明書
ambidata.io_chain.pem    fullchain.pem        --サーバー証明書と中間証明書が結合されたファイル
```

GetSSLで得られた証明書をCertbotのファイルに置き換え、Webサーバーを再起動して、SSL証明書が更新できました。

## 最後に

Certbotが「Installing Python packages...」でスタックするという件は[Githubのcertbotエントリー](https://github.com/certbot/certbot/issues/2832)で状況を説明し、原因と対策を質問していますが、現時点でまだ回答はありません。回答が得られたらここでも報告しようと思います。