ConoHa VPSでSSL証明書取得

前回はdockerを使ったプロキシサーバーを立ててみました。

今回はいよいよhttpsに必要なSSL証明書の取得に取り組みます。

前提となる知識を簡単に

httpsとは

ブラウザなどで使用するURLの先頭にあるアレです。昨今Webにあるものは大抵httpsで、SSL/TLSプロトコル(SSL)で暗号化されたHTTP通信のことです。
http:で始まるURLの場合は、httpsではないので暗号化されていません。

SSL証明書とは

SSL/TLSプロトコル(SSL)に必要な証明書です。SSL/TLSでは公開鍵/秘密鍵を使用した情報の暗号化/復号化を行うのですが、今回は証明書に記載されたVPSのドメイン(CN)が、同じく記載されている公開鍵と紐付いていることを証明する証明書になります。紐付けが正しくないと詐称が出来てしまうので、第三者が署名した証明書を発行するわけです。

Let’s Encryptを使います

つまり今回は「公開鍵/秘密鍵を作成し、その公開鍵をVPSのドメインに紐づけて第三者に署名してもらってSSL証明書を取得する」というわけです。公開鍵はhttps通信(SSL/TLSプロトコル)時に証明書にくっついてクライアント側に渡り、復号化のために使用されます。秘密鍵はサーバー側で暗号化のために使用されます。

ただし(察しのいい人は分かると思いますが)、第三者が証明書を発行する以上、通常タダではありません。しかし近年、遅々として100%にならないhttps化を憂い、Let’s Encryptなる団体?がSSL証明書をタダで発行してくれるようになりました。今回はこれを利用してSSL証明書を取得しようというわけです(ちなみに引っ越し元のVALUE SERVERでもLet’s Encryptが発行した証明書が使われていました)。

なおタダである以上、最大のコスト発生源である人手を使って署名してくれるわけではありません。機械的にプログラムで判定し、署名するわけです。この判定方法はACMEプロトコルと呼ばれ、このプロトコルのサーバー側実装を運用しているのがLet’s Encryptです。クライアント側も各種言語でいろいろ実装されてますが、証明書を取得したい人がいずれかを選んで使用します。また機械的に取得できる以上、証明書の有効期限はあまり長くなく、たったの90日です。なので証明書は定期的に更新する必要があります。

Let’s EncryptでSSL証明書取得

ACMEクライアント実装の選定

これもdocker上で動かしたく、しかもなるべく軽いのがいいわけです。公式には

https://letsencrypt.org/ja/docs/client-options/

という一覧があり、この中でオススメされているCertbotにはdockerに関する記述があります。

https://certbot.eff.org/docs/contributing.html#running-the-client-with-docker

もちろんdocker-hubにofficial imageもあります。

https://hub.docker.com/r/certbot/certbot

今回使用する実装はこれで決まりですね。

SSL証明書の種類と対象ドメイン

SSL証明書には、発行した認証局がどこまで認証したのかを表す程度があります。それら認証の種類は「ドメイン認証」「企業実在認証」「EV認証」の3つがあり、ACMEで機械的に認証しているのは「ドメイン認証」に該当しています(有料のものだとそれ以上の認証を行っています)。

ドメイン認証の証明書にも種類があり、単一のドメインを含む証明書、複数のドメインを含む証明書ワイルドカードのドメインの証明書の3つがあります。Let’s Encryptでは3つとも取得できるようになっています。

ワイルドカード証明書とは、*.hogehoge.comみたいなhogehoge.comのサブドメイン全てに共通の証明書のことです。これを取得するにはDNSに機能が必要で、前節のcertbotでは特定サービス用のプラグインを用意して対応しています。今回ワイルドカード証明書を取得する場合は、ConoHaのDNSにその機能が必要で、それをcertbotから利用するためのプラグインも必要なのですが、調査不足のため存在しているかどうかは分かっていません

今回取得しようとしている証明書の対象ドメインは、

  • elephantcat.work
  • test.elephantcat.work
  • blog.elephantcat.work(結局作っていない)

の3つです。それぞれ別のWebサーバーに設置できるようにしたいため(同じにWebサーバーにするかもしれないが、まだ決めていない)、セキュリティの観点から複数のドメインを含む証明書は適切ではなく、単一のドメインを含む証明書を3つ取得しようと思っています。同じ理由からワイルドカード証明書も控えます。

サブドメインの作成

VPSにはグローバルなIPアドレスは1つしか与えられていないため、本来ドメインはelephantcat.workだけでいいはずです。しかし、複数のドメインが同じIPに紐付けられてもいいことになっているので、今回はtest.elephantcat.workも、blog.elephantcat.workも、同じIPにしてしまいます(ただし、DNSでは1つのIPに対して、紐付けられるドメインは1つだけにしないといけないので、逆引きで出てくるのはelephantcat.workだけになります)。なぜこんな面倒なことをするかというと、WebサーバーにはVirtualHostという概念があり、実際には同じホストでもURLとして別の名前のホストでアクセスされている場合は、ルーティングを別途定義できるようになっているからです。

例えば、https://elephantcat.work/とhttps://test.elephantcat.work/は実際には全く同じIPであっても、全く違うページを表示させることができます。なのでサブドメインを追加できれば、ホストは1台しかなくても、その数だけ別目的のサーバーを立てられるというわけです。

DNSにCNAMEレコードの追加

同じIPを持つ別のサブドメインはCNAMEレコードで定義します。

私の場合はConoHaのDNSで以下のレコードを追加しました。

タイプ名称TTL
CNAMEblog3600elephantcat.work
CNAMEtest3600elephantcat.work

nslookupで例えば以下のように確認できます。

$ nslookup blog.elephantcat.work
Server:         自宅のDNSのアドレス
Address:        自宅のDNSのアドレス#53

Non-authoritative answer:
blog.elephantcat.work   canonical name = elephantcat.work.
Name:   elephantcat.work
Address: VPSのIPアドレス

$

(test.elephantcat.workも同様に確認できます)

Certbotでの証明書取得

ACMEのチャレンジタイプとその動作について

Certbotが対応しているACMEのチャレンジタイプ

  • HTTP-01 チャレンジ
  • DNS-01 チャレンジ
  • その他

があります。今回の証明書取得に必要なチャレンジタイプはHTTP-01 チャレンジになります。これは、

  • webrootに./.well-known/acme-challenge/xxxxのようなファイルを設置し、外部からポート80にHTTPで/.well-known/acme-challenge/xxxxのようなリクエストを投げ、設置した内容のファイルを取得できるかどうかを試す、というようなサーバーの管理権限を確認するチャレンジ

です。

ポート80の一時開放

このサーバーではポート80は開放してないのですが、証明書を取得・更新するときだけ開放するようにしています。

$ sudo ufw allow http

戻すときはこんな感じです。

$ sudo ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] 443/tcp                    ALLOW IN    Anywhere                  
[ 2] Anywhere                   ALLOW IN    (自宅IP)
[ 3] 80/tcp                     ALLOW IN    Anywhere                  
[ 4] 443/tcp (v6)               ALLOW IN    Anywhere (v6)             
[ 5] 80/tcp (v6)                ALLOW IN    Anywhere (v6)             

$ sudo ufw delete 5
Deleting:
 allow 80/tcp
Proceed with operation (y|n)? y
Rule deleted (v6)
$ sudo ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] 443/tcp                    ALLOW IN    Anywhere                  
[ 2] Anywhere                   ALLOW IN    (自宅IP)
[ 3] 80/tcp                     ALLOW IN    Anywhere                  
[ 4] 443/tcp (v6)               ALLOW IN    Anywhere (v6)             

$ sudo ufw delete 3
Deleting:
 allow 80/tcp
Proceed with operation (y|n)? y
Rule deleted
$

なお、一度チャレンジに成功すると結果は30日間キャッシュされるので、ポートを開放しなくても取得できるケースがあります。

CertbotによるHTTP-01チャレンジを含む証明書取得

HTTP-01チャレンジでは、Certbotを実行するホストがインターネットに公開されており、そのホストがポート80のHTTPアクセスが可能で、任意のレスポンスを返す権限を持っていることが試されます。Certbotは、既存のapacheサーバーやnginxサーバーの設定を自分で読み書きし、制御コマンドを自分で実行することで、それらを試す機能が用意されていますし、自前のWebサーバーも用意しています。今回Certbotの公式イメージでwordpressなどを動かすつもりはないので、Certbotが自前で持っているWebサーバー(standalone)を使ってHTTP-01チャレンジを実行し、証明書を取得します。

source ./env.$1
docker run -it --rm --name certbot \
	-v "$PWD/etc/letsencrypt:/etc/letsencrypt" \
	-v "$PWD/var/lib/letsencrypt:/var/lib/letsencrypt" \
	-v "$PWD/webroot:/webroot" \
	-p 80:80 \
	certbot/certbot --standalone -d "$DOMAIN" --email "$EMAIL" certonly

certbot/certbotイメージのentrypointがcertbotになっているため、実行されるコマンドは

certbot --standalone -d "$DOMAIN" --email "$EMAIL" certonly

みたいな感じです。certbotコマンドはこれだけで対称鍵を生成し、Let’s EncryptにACMEを使ってHTTP-01チャレンジを要求しSSL証明書まで発行してもらいます。1行目で設定してる環境ファイルの中身はこんな感じです。

DOMAIN="ドメイン(私の場合はelehphantcat.work)"
EMAIL="更新通知用メールアドレス(60日経過時にメールが来ます)"

これを登録ドメインごとに用意するだけ。使い方は例えばenv.test環境ファイルを使うなら

$ bash regsiter.sh test

となります。成功すると、./etc/letsencrypt/live/(ドメイン)/に、秘密鍵と(公開鍵を含む)SSL証明書などが作成されます。

ファイル名内容
privkey.pem秘密鍵
fullchain.pem(公開鍵を含む)SSL証明書

ファイルの中身を見ても味気ない数字の羅列なので、例えば公開鍵を含む証明書の内容を意味的に表示させたい場合は、

$ sudo openssl x509 -text -noout -in ./etc/letsencrypt/live/(ドメイン)/fullchain.pem

で表示できます。これらのファイルをWebサーバーの設定ファイルに記述することで、httpsの通信が可能になるわけです。

取得した証明書の更新(renew)

取得した証明書は前述したとおり、90日しか有効期限がなく、定期的な更新が必要です。その方法は…

docker run -it --rm --name certbot \
        -v "$PWD/etc/letsencrypt:/etc/letsencrypt" \
        -v "$PWD/var/lib/letsencrypt:/var/lib/letsencrypt" \
        -v "$PWD/webroot:/webroot" \
        -p 80:80 \
        certbot/certbot --standalone renew

今回はドメインの指定がありませんが、上で作成した証明書を全て更新しようとしてくれます。ただし、期限に余裕がある場合更新は行われません。

取得した証明書の破棄(revoke)

source ./env.$1
docker run -it --rm --name certbot \
        -v "$PWD/etc/letsencrypt:/etc/letsencrypt" \
        -v "$PWD/var/lib/letsencrypt:/var/lib/letsencrypt" \
        -v "$PWD/webroot:/webroot" \
        -p 80:80 \
        certbot/certbot revoke --cert-name "$DOMAIN"

ドメイン(環境ファイル)を指定して証明書を破棄します。

まとめ

自動更新などはまだ入れていませんが、手動でLet’s Encryptを使った証明書の取得ができました。

次回は取得したSSL証明書を使って、いよいよhttpsでWebサーバーを動かします。