Let's EncryptでSSL証明書を取得してHTTP/2な世界にGopherくんを召喚してみた

f:id:dojineko:20170102154432j:plain

独自ドメインを持ってるなら、オレオレ証明書なんか使わずに
VALIDなHTTPSでヤッテイコーという試み。


Let's Encrypt で証明書を取得する

事前知識

Let's Encrypt で取得できる証明書は ドメイン認証SSL証明書です。
(SSL証明書には企業認証SSL証明書や、EVSSL証明書などがあります。)

そのため、Let's Encrypt で証明書を取得するには、自分で好き勝手できる独自ドメインを用意し、 Automated Certificate Management Environment (ACME) というドメインの所有を確認する 認証プロセスを通過する必要があります。

ついては以下のいずれかの組み合わせが必要です。
基本的には独自ドメインを持ってさえすればあとはなんとかなるはず。。。

  1. 独自ドメイン と WEBサーバー (外部からの参照が可能なもの)
    • HTTP-01 という認証方式で必要となります。Webサーバーに指定のトークンを記述したファイルを 配置することができるかどうかで認証を行います。
  2. 独自ドメインDNSサーバー
    • DNS-01 という認証方式で必要となります。DNSサーバーのTXTレコードに指定のトークンを 設定することができるかどうかで認証を行います。

というわけで今回は、「独自ドメインDNSサーバー」の組み合わせの認証方法 「DNS-01」 ですすめていきます。

今回はWindowsじゃなくてArchlinuxが個人的にアツいのでVagrantのArchlinux上で処理を進めていきます。

下記のような Vagrant 環境を整えているのでよければどうぞ。
(zplug でプラグインをインストールするか聞かれると思うので y でインストールすると良いです)

github.com

dehydrated の用意

過去 letsencrypt.sh として知られていたシェルスクリプトツール。
いまは dehyrated (デハイドレテッド) という名前に変わっています。

ちなみに Let's Encrypt 公式の certbot (旧 letsencrypt-cli) というツールもありますが
pythonに依存することと、ちょっと扱いづらいので今回はシンプルな dehydrated を使います。

下記からおもむろに git clone もしくは ghq get しておきます。

github.com

# git clone
git clone https://github.com/lukas2511/dehydrated.git
cd dehydrated

ここから先の手順は下記を参考にさせていただきました。

qiita.com

ではすすめていきましょう。まずは元になるファイルをコピーします

cp -ip docs/examples/hook.sh ./

続いてコピーしたファイルを開き deploy_challenge を下記の様に差し替えます。 内容としてはDNSレコードにTXTレコードとしてTOKENの登録を促すようにし、
digを引いてみてTOKENが一致するかチェックするようにしています。

deploy_challenge() {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
    local RECORD="_acme-challenge.${DOMAIN}"
    cat <<EOS
Set TXT record of ${RECORD} to ${TOKEN_VALUE}
Press Enter to continue...
EOS
    read
    while :; do
        local result="$(dig +short txt ${RECORD} | tr -d '"')"
        [ "${result}" == "${TOKEN_VALUE}" ] && break
        cat <<EOS
Token does not match.
Set TXT record of ${RECORD} to ${TOKEN_VALUE}
Press Enter to retry...
EOS
        read
    done
}

これで準備ができました。

証明書の取得

と、いうわけで実際にDNS-01による認証を経て証明書の取得を行いましょう。
dehydrated を下記のように実行します。ドメインのところは任意の値を設定します。

./dehydrated -c -t dns-01 -k ./hook.sh -d <ドメイン>

# -c cronモードの指定
# -t 認証方式の指定
# -k hook.shの指定
# -d 証明書のCNに対応するドメイン

試しに実行した結果は下記のとおりです。

$ ./dehydrated -c -t dns-01 -k ./hook.sh -d example.jp
# INFO: Using main config file /home/vagrant/works/src/github.com/lukas2511/dehydrated/config
Processing example.jp
 + Signing domains...
 + Creating new directory /home/vagrant/works/src/github.com/lukas2511/dehydrated/certs/example.jp ...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for example.jp...
Set TXT record of _acme-challenge.example.jp to ********************************
Press Enter to continue...

・・・と、ここで hook.sh で追加した処理の通りTXTレコードの登録を求められるのでその通りに設定してあげます。 設定が終わったらターミナルに戻ってきてEnterを押します。

もし設定が反映されてなかったり間違っていたら再度設定するように求められます。
(ここでネガティブキャッシュを持ってしまったら nscd -i hosts とかでキャッシュを消して上げると良いかも)

 + Responding to challenge for example.jp...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Done!

無事に証明書が発行できました。
取得結果は ./certs/<ドメイン> ディレクトリ以下に出力されています。

$ find ./certs/example.jp/*.pem
./certs/example.jp/cert.pem      # 証明書
./certs/example.jp/chain.pem     # 中間証明書
./certs/example.jp/fullchain.pem # フルチェイン証明書 (証明書 + 中間証明書)
./certs/example.jp/privkey.pem   # 証明書に対応するRSA秘密鍵

更新などで複数回発行した場合は、上記は最新のファイルへのシンボリックリンクになっています。 実際に見てみましょう。

$ openssl x509 -text -in ./certs/example.jp/cert.pem | grep -E "(Issuer|Subject):|Not\ "
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
            Not Before: Jan  2 06:57:00 2017 GMT
            Not After : Apr  2 06:57:00 2017 GMT
        Subject: CN=example.jp

指定のドメインで使える証明書ができていますね。期限が3ヶ月なのはLet's Encryptの仕様です。
実際に本番運用する場合には、cronなどで dehydrated を定期実行して証明書を更新してあげるとよいでしょう。

また、初回に dehydrated を実行した場合には、証明書発行とは別に秘密鍵を自動で作成して、
Let's Encrypt に登録するようになっています。

登録周りのファイルは account ディレクトリ配下に保存されます。
証明書の更新などで必要となるため削除しないようにしておきましょう。

HTTP/2 なWebサーバーを立てる

せっかく証明書を取得したので、試しに使ってみましょう。
GoのEchoというパッケージを使って軽いHTTPサーバーを立ててみます。

Echo のインストール

sandbox には go を入れてないのでまずインストールします。

pacman -S go

次は echo を go get します。

go get -u github.com/labstack/echo

作業用ディレクトリを用意して証明書と鍵をコピーしておきましょう。

mkdir ~/test
cp -ip ./certs/example.jp/{fullchain,privkey}.pem ~/test

最後に実行するスクリプトを用意しておきましょう。

cd ~/test
vim main.go
package main

import "github.com/labstack/echo"

func main() {
    e := echo.New()
    e.File("/", "index.html")
    e.Logger.Fatal(e.StartTLS(":443", "fullchain.pem", "privkey.pem"))
}

コンテンツも index.html として用意しておきます

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Echo</title>
</head>
<body>
<div style="text-align: center;">
    <img src="https://raw.githubusercontent.com/golang-samples/gopher-vector/master/gopher-front.png" alt="gopher-front">

    <div style="font-size:8px;">
        <p>by <a href="https://github.com/golang-samples/gopher-vector">Takuya Ueda</a></p>
        <p>The Gopher character is based on the Go mascot designed by <a href="http://reneefrench.blogspot.jp/">Renée French</a>.</p>
    </div>
</div>
</body>
</html>

あとは、Aレコードを設定するかhostsをいじるかして、
CNに設定したドメインでHTTPサーバーにアクセスできるようにしておきます。

sandboxを使う場合には 192.168.33.10 を対応付けるよう hosts に記載しておきましょう。

実行&動作確認

ではではサクッと実行してみましょう。
ポートのListenするので実行にはRoot権限が必要です。

sudo go run main.go

満を持してブラウザで https で建てたサーバーにアクセスしてみます。

f:id:dojineko:20170102183209p:plain

見事正常なHTTPSGopherくんを召喚できました!
しかもデフォルトでHTTP/2が有効になっています!!ステキ!!!

ちなみに この青いイナヅママークを出しているのは「HTTP/2 and SPDY indicator」というChrome拡張です。

chrome.google.com

まとめ

今回は Let's Encryptで証明書を取得して、実際に証明書を使ってアクセスするとこまでやってみました。
SSL証明書といえばお金がそこそこかかるのであちこち手軽に導入するというわけには行きませんでしたが、 Let's Encrypt の登場で手軽に導入できることがわかりました。
自分のドメインを持っているなら使わない手はないのではないかと思います。

ちなみに今回は dehydrated を使ってCLIベースで証明書の取得を行いましたが、 「SSLなう!」というサービスも有り、これはブラウザのみでSSL証明書の発行まで行うことができます。 単発でテスト用の証明書を発行するにはとても便利な手段だと思いました。

sslnow.ml


関連リンク

www.pakutaso.com

www.techscore.com

echo.labstack.com