Let's EncryptでSSL証明書の新規作成と自動更新(http-01編)
Let's Encrypt とは
Lets' Encrypt は認証局(Certificate Authority)のブランドの一つである。
これは「Symantec(旧Verisign)セキュア・サーバーID」「CyberTrust SureServer」「SecomTrust セコムパスポート for Web3.0」「GlobalSign クイック認証SSL」「GeoTrust RapidSSL」「Comodo PositiveSSL」などの一つと考えれば良い。
- たぶんどれかは聞いたことあるはずと思う。アレが抜けてるというツッコミは却下で:-)。
- 他の認証局と同じような点は、
- 1証明書をどのように(複数IP、複数バックエンド、複数プロトコル)使用しても1取得で済む(安い認証局は大抵そうだよね)。
- 親ドメインをまたぐ、マルチドメイン証明書(Subject Alternative Names)に対応している(全ての認証局で対応してるね)。
いよいよワイルドカード証明書に対応(対応してるブランドと対応してないブランドとあるね)。
DV(Domain Validation)証明書のみ提供(DVだけでなくOV・EVにも対応してるブランドもあるね)。
ただしOV(Organization Validation)証明書やEV(Extended Validation)証明書との純技術的な優劣は無い。
- 他の認証局と明確に違う点は、
- 無償。
RSA(2048bit、3072bit、4096bit), ECDSA(prime256v1、secp384r1) の5種類の鍵が選べる(ここまで選べるブランドは限られるね)。DSAが無い?時代だ。諦めろ。
ACME(Automated Certificate Management Environment)プロトコルによる証明書の認証から発行までの一連のバッチ化(自動化)が可能。
- 今どき誤差だけど、扱える端末が(他の認証局と比べて)少ない。
- ごく一部のエンドユーザーが粘り強く使用しているような、全アクセスの0.1%未満の端末であってもサポートしないといけない用途であるならお勧めしない。
- 逆に今どきのメジャーどころの端末・ブラウザは対応している。
よってPC相手にはほぼ問題無い(Windows XP? IE6? 知らんがな)。
取得数制限(特に単位時間あたりの)があるので注意。詳しくは Rate Limits を参照のこと。
検証(ステージング)用認証局も用意されているので、セットアップ時の検証や、ACMEクライアントの開発といった用途ではこちらを使う。
「現在の」ルート証明書は「IdentTrust|DST(Digital Signature Trust) Root CA X3」である。
IdentTrust なの? DST なの? については旧会社のブランドも残ってるらしい、としか自分は認識してない。詳しくは会社概要でも読んでくれい。
少なくとも中間証明書の発行者(Issuer)はそうである(Let's Encrypt運用元の ISRG - Internet Security Research Group ではない)。
- このルート証明書がインストールされた端末が対応端末となる。
中間証明書(Subject)は「Let's Encrypt Authority X3」である(場合によってはこっち「も」入ってることがあるかもしれない)。
ここでは全て dehydrated を使用を前提に解説する。certbotとdehydratedの違いについては特に解説しない。
dehydratedを選んだ理由は、
dehydratedはBash/ZSH依存スクリプトであるため、特別な言語環境(Python)を必要としない。
certbotの場合、Pythonに依存する分には問題無いが、依存するPythonモジュールが極めて大量にあって維持が大変。
dehydratedはまだ依存が少ない(curl のせいでずいぶん増えてるが)。
dehydratedの場合、わけわかんなくなっても、シェルスクリプトなのでソースコード読んで理解できる。また長いコードではない。
dehydratedはWebサーバー機能を内蔵していないため、Webサーバーとの競合に配慮しなくてよい。
dehydratedはエイリアス機能により、同じコモンネームでRSA/ECDSA両方の証明書取得が可能である。
http-01 とは
- サイト利用者を証明するための「チャレンジ・レスポンス」のやりとりをHTTP(http-01)で行う方法である。
- 一応HTTPSで行う tls-sni-01 もあるが、まぁ使うことは無いでしょう。
- 「SSL証明書利用」イコール「Webサーバーで使う」というユースケースにおいては、手軽で罠も少なく、わかりやすい手順かと。
複数のWebサーバー、他のプロトコル(SMTP、IMAP4等)での利用が想定されるのであれば、もう一方の手順をお勧めする。
- もちろん共存できない話では無いので、こちらで慣れたところで、もう一方に手を出すのは全く問題無し。
- そのあたりは証明書単位で選択可能なので、共存不可能でも無いです。
目次
目次
検証環境
- 以下のソフトウェアの利用を前提に検証を実施した。いずれも最新のリリースということで確認しているが、ある程度古い環境でも問題無いと思われる。
- OS: FreeBSD 11.0-R
- ACMEクライアント: dehydrated 0.4.0
- Webサーバー: Apache 2.4.27
- 上記以外の環境では、以下の点に相違が発生する。必要に応じて読み替えたし。
- インストール方法
- インストールされるディレクトリ
- 自動更新のための手続きとその設定
- 逆に以下の点は参考にできる。
- 設定パラメータとその意味
- 運用事例
検証作業内容
- 本例では、www.example.jp というサイトに対して証明書を発行するものとする。
- また証明書取得後のWebサーバー(Apache 2.4 での例)への反映(Deploy)までを手順化する。
dehydrated のセットアップ
ports/security/dehydrated をインストールする。
この時 ports/security/py-certbot のインストールは不要である。
- certbot はあって動かなくなることは無いが、あっても使用されることも無い。
- また、定期実行が設定ファイルに1行記述するだけで制御できるレベルで簡単である。
- ただ、この事情は ports メンテナの功績によるものなので:-)、他の(OS)環境でそうであるかは不明である。
初期セットアップ
いくつかのディレクトリの作成および設置ルールを決める。
証明書の設置場所
- ports/security/dehydrated では /usr/local/etc/dehydrated/certs/コモンネーム/ 以下に、以下のファイルが設置される。
- /usr/local/etc/dehydrated/certs/コモンネーム/cert-UNIXタイムスタンプ.csr
- /usr/local/etc/dehydrated/certs/コモンネーム/cert-UNIXタイムスタンプ.pem
- /usr/local/etc/dehydrated/certs/コモンネーム/chain-UNIXタイムスタンプ.pem
- /usr/local/etc/dehydrated/certs/コモンネーム/fullchain-UNIXタイムスタンプ.pem
- /usr/local/etc/dehydrated/certs/コモンネーム/privkey-UNIXタイムスタンプ.pem
- 設置場所についてはプログラム中に直接記載されているため、変更は不可能である。
- その代わり、シンボリックリンクで誘導することは可能。
- また、最新(現在)の証明書に対してシンボリックリンクが張られる。
# cd /usr/local/etc/dehydrated/certs/コモンネーム # ls -alF total 122 -rw------- 1 root wheel 436 May 29 06:02 cert-1464469337.csr -rw------- 1 root wheel 1533 May 29 06:02 cert-1464469337.pem lrwx------ 1 root wheel 19 May 29 06:02 cert.csr@ -> cert-1464469337.csr lrwx------ 1 root wheel 19 May 29 06:02 cert.pem@ -> cert-1464469337.pem -rw------- 1 root wheel 1647 May 29 06:02 chain-1464469337.pem lrwx------ 1 root wheel 20 May 29 06:02 chain.pem@ -> chain-1464469337.pem -rw------- 1 root wheel 3180 May 29 06:02 fullchain-1464469337.pem lrwx------ 1 root wheel 24 May 29 06:02 fullchain.pem@ -> fullchain-1464469337.pem -rw------- 1 root wheel 302 May 29 06:02 privkey-1464469337.pem lrwx------ 1 root wheel 22 May 29 06:02 privkey.pem@ -> privkey-1464469337.pem
- cert.pem(署名済みSSL証明書公開鍵)+chain.pem(中間証明書)=fullchain.pem(両者を足したモノ) の使い分けは利用するサーバープログラムの仕様に従うこと。
- なお、Let's Encrypt の中間証明書(chain.pem)を世間一般のブラウザが持っていることは「現在」期待しない方がよい。限定的には持っているブラウザもあるが、広く世間で使おうと思ったら、持っていない前提で運用すべき。
ドメイン所有者確認トークンディレクトリの指定
- ドメイン所有者確認トークンの設置場所(ディレクトリ)を決める。
- 「ドメイン所有者確認トークン」はドメインの所有者であること確認するための「トークン」(一時発行キー)である。
- これはWebサーバー側の設定とも連動する話なので、その前提で決定する。
- 以下はアクセスログ(例)である。
66.133.109.36 - - [29/May/2016:06:02:21 +0900] "GET /.well-known/acme-challenge/チャレンジトークン HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
- デフォルトで /usr/local/www/dehydrated となっている。
- またこの設定は「WELLKNOWN」変数で変更可能である。
アカウントキーの保存ディレクトリの指定
- アカウントキーと呼ばれるJSONとRSA秘密鍵の保存ディレクトリを指定する。
- デフォルトで /usr/local/etc/dehydrated/private_key.{json,pem} となる。
- これら設定は「ACCOUNT_KEY_JSON」(.json)と「ACCOUNT_KEY」(.pem)設定で指定が可能である。
設定ファイル(/usr/local/etc/dehydrated/config)
- 上記準備と合わせて、設定ファイルに記載する。いわゆるシェルスクリプトなので、その記法はそれに準じる。
- 設定ファイルは /usr/local/etc/dehydrated/config である(シェル文法で記述)。
- ports の場合、config.example が同じディレクトリにインストールされるので、合わせて参照して欲しい。
- 色々と設定する項目はあるが、下記の設定のみ意識すればよい。実際にはもっと使わない。
- WELLKNOWN
- ACCOUNT_KEY
- ACCOUNT_KEY_JSON
- RENEW_DAYS
- PRIVATE_KEY_RENEW
- KEY_ALGO / KEYSIZE
- CONTACT_EMAIL
- WELLKNOWN, ACCOUNT_KEY, ACCOUNT_KEY_JSONについてはすでに解説済みなので、残りについて説明する。
- RENEW_DAYS
- 有効期限「残」日数を指定する。今日を基準に有効期限が指定日数(秒換算)未満になったら更新を行う。
- 勘違いしやすいのでもう一度。「今日+RENEW_DAYS」が有効期限を越えると更新を行う。
- 「60」だと「有効期限の60日前に更新」、「30」だと「有効期限の30日前に更新」となる。
- なおFreeBSDでは、更新そのものは毎週行われる(periodic の weekly 指定、つまり7日単位に丸められる)。
- PRIVATE_KEY_RENEW
- 更新する毎に秘密鍵を作り直すか否かを指定する。
- デフォルトで「yes」である(毎回作り直す)。
- 指定可能な値は「yes」か「それ以外」かしか無いので、「Yes」と書いてもNoと解釈される。
- また「作り直さない」選択にメリットも無ければ推奨でもないので、ごく極めて例外的な何かが無ければ「yes」を選択すること。
- KEY_ALGO / KEYSIZE
- SSL証明書の鍵アルゴリズム(KEY_ALGO)と鍵サイズ(KEYSIZE)を指定する。
- KEY_ALGO での選択可能な識別子は「rsa」「prime256v1」「secp384r1」。
- KEY_ALGO が「rsa」の時に限って KEYSIZE を指定する(2048、3072、4096)。
- CONTACT_EMAIL
- Let's Encrypt では連絡用メールアドレスの指定が必要となるので、メールアドレスを指定する。
設定ファイル例(/usr/local/etc/dehydrated/config)
コモンネームファイル(/usr/local/etc/dehydrated/domains.txt)
- 発行するSSL証明書のコモンネームを列挙したファイルを作成する。
- このファイル名は /usr/local/etc/dehydrated/domains.txt で指定される。
- 1行1証明書で、1カラム目はコモンネームとなり、2カラム目以降はSANs(Subject Alternative Names)を指定する。
- 以下のサンプルでは example.org, example.com の2枚の証明書を作成/更新する。
example.org www.example.org example.com www.example.com wiki.example.com
- また、「example.org」のSANとして「www.example.org」が、「example.com」のSANとして「www.example.com」と「wiki.example.com」が合わせて設定される。
- 他にも、以下のルールで処理される。
- 大文字は小文字に変換される。
- 複数の空白類(スペース、タブなど)で区切り、1個の空白として処理される。
- 行頭が # や空行は無視。
初回実行ないしは手動更新の仕方
- 以上の設定が完了したら、コマンドを root で実行する。
-c オプションは --cron と同じ意味である(だからどうした!:-)。
dehydrated --register --accept-terms dehydrated -c
- とりあえずプログラムを読んだ限りでは環境変数汚染依存はなさそう。
- 正直、LANG、LC_* くらいはケアして欲しいところだけど。
自動更新設定
ports/security/dehydrated では定期実行に periodic(8) を採用している。
- よって定期実行は /etc/periodic.conf ないしは /etc/periodic.conf.local ファイルにて設定を行う。
weekly_dehydrated_enable="YES" weekly_dehydrated_deployscript="/usr/local/etc/dehydrated/deploy.sh"
単純に更新するだけなら、weekly_dehydrated_enable="YES" と設定するだけで良い。
- 全ての更新が完了した後、サーバーへの反映を行うスクリプトを weekly_dehydrated_deployscript 変数で指定できる。
- weekly_dehydrated_deployscript 変数で指定されたファイルは、実行権限(chmod +x)も必要。
- ただし、証明書の更新が無くても実行されるので、厳密には証明書が更新されたか確認した方がいい。
- とは言え、現地時間で毎週土曜日04時05分(/etc/crontab の periodic weekly 行参照のこと)に実行されるので、そのあたりはテキトーでもいいかもしれない。
なお dehydrated -gc は古い証明書の削除(クリーンナップ)を行う。
- わざと残しておくのであれば実行は不要である。
/usr/local/etc/dehydrated/deploy.sh 例
#/bin/sh /usr/sbin/service apache24 restart && /usr/local/bin/dehydrated -gc
Apache の設定例
SSL証明書の参照方法
- 例えば Apache(2.4.8以上)では以下のように指定することになる。
SSLCertificateFile /usr/local/etc/dehydrated/certs/コモンネーム/fullchain.pem SSLCertificateKeyFile /usr/local/etc/dehydrated/certs/コモンネーム/privkey.pem
- 古い Apache(2.4.8 未満・2.4.8 以降でもOK)だと以下のパターンで指定する。
SSLCertificateFile /usr/local/etc/dehydrated/certs/コモンネーム/cert.pem SSLCertificateChainFile /usr/local/etc/dehydrated/certs/コモンネーム/chain.pem SSLCertificateKeyFile /usr/local/etc/dehydrated/certs/コモンネーム/privkey.pem
チャレンジ・レスポンスファイルの参照設定
- Apache(2.4.x)の場合、以下のように設定する。
Alias /.well-known/acme-challenge/ /usr/local/www/dehydrated/ <Directory /usr/local/www/dehydrated> Options None AllowOverride None Require all granted Header add Content-Type text/plain </Directory>
よくある質問とその答え
Q. ECDSA対応は必須?
A. やってみたけど、必須というほど対応しているブラウザは多く(種類的に)はないですね。 ごく最新の有名どころブラウザはともかく、四方山ブラウザを考慮するとまだまだ。 サーバー側でRSAとECDSAの両方を提案してブラウザが受け入れられる方を…という解決策も無くもないですが(使えるか知らないし興味ない)、 結局RSAの鍵も作らないと行けないことには変わりないので、当面、RSA2048オンリーで行くのが正解かと。
Q. ports/security/dehydrated と ports/security/py-certbot とどっちがいいですか?
A. 目標が違うのでどっちとも言えない。 certbot は Let' Encrypt 推奨ACMEクライアントなのでそちらを頑張るのもありかと。 自分はシェルスクリプトで記述されてる、依存が certbot より少ないという理由で採用しただけとなります。
ACMEクライアントは、certbot、dehydrated だけでなく、他にもあるので、チェックしてみるといいかもしれない。
あと、certbot はWebサーバー内蔵で http(TCP ポート 80)開いてるかぎり使える点がうれしいかも。 ただ、既にWebサーバーが稼働済みの場合、certbot よりも、Webサーバーを内蔵してない dehydrated の方が都合がよいと思われる。
Q. 下記のように所有者確認アクセスに失敗、証明書が更新されません!
66.133.109.36 - - [29/May/2016:04:45:29 +0900] "GET /.well-known/acme-challenge/チャレンジトークン HTTP/1.1" 404 268 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
A. Webサーバーの設定ミスの可能性があります! 自分は「Alias /.well-known/acme-challenge/ /usr/local/www/dehydrated」と最後に「/」を入れ忘れていました! このケースでは Alias の第一、第二引数ともに最後に「/」を含める必要がある。
他にも良くありそうなのが、設定した後にサーバー再起動(ないしは再読み込み)を忘れていたなどが考えられる。
あとリカバリとして、更新に失敗して残ってるCSRや秘密鍵を削除するのに dehydrated -gc を実施すること。
Q. Webサービス立ち上げてないサーバーでSSL証明書使いたいです!
A. そんなあなたにDNS認証(dns-01)。続きはWebで。
参考文献
Rate Limits(SSL証明書取得数制限)
Staging Environment(ステージング環境)
ACME Client Implementations(ACMEクライアント実装)
【付録】 dehydrated v0.40 での変更
v0.40 で、ライセンス受諾の確認が必須になりました。 それまでは暗黙の受諾として、dehydrated が勝手に受諾していましたが、 別途手動で、dehydrated --register --accept-terms を実行する必要があります。
ステージング用のライセンス受諾ともまた別の話となりますので、 CA だけでなく、CA_TERMS も合わせて設定する必要があります。
この振る舞いは /usr/local/etc/dehydrated/accounts/ハッシュキー/account_key.pem の有り無しがトリガーとなりますので、 既にこのファイルがある環境では影響有りません。
初回実行に手動で実行していれば気がつくレベルとなります(エラーメッセージ等「読まない」人についてはノーコメント)。
【付録】 letsencrypt.sh から dehydrated への移行
商標上の理由で、letsencrypt.sh から dehydrated へ名前変更が実施された。 合わせてディレクトリから設定まで色々変更になったので対応についてメモっておく。
ディレクトリ名の変更
/usr/local/etc/letsencrypt.sh から /usr/local/etc/dehydrated へ変更になったわけであるが、更新後は新しいディレクトリが作成されていることもあり、こまめに手を入れる必要がある。
/usr/local/etc/letsencrypt.sh/config.sh
mv /usr/local/etc/letsencrypt.sh/config.sh /usr/local/etc/dehydrated/config
※config.sh から config に変更になったので合わせて変更を実施する。
/usr/local/etc/letsencrypt.sh/deploy.sh
mv /usr/local/etc/letsencrypt.sh/deploy.sh /usr/local/etc/dehydrated/
/usr/local/etc/letsencrypt.sh/domains.txt
mv /usr/local/etc/letsencrypt.sh/domains.txt /usr/local/etc/dehydrated/
/usr/local/etc/letsencrypt.sh/hook.sh
mv /usr/local/etc/letsencrypt.sh/hook.sh /usr/local/etc/dehydrated/
/usr/local/etc/letsencrypt.sh/certs/
mv /usr/local/etc/letsencrypt.sh/certs /usr/local/etc/dehydrated/
- certs ディレクトリはインストール時には作成されない。
- SSL証明書取得時にコモンネームと合わせて作成される。
- 既に /usr/local/etc/dehydrated/certs ディレクトリが作成されている場合は、子ディレクトリ(コモンネーム)を個別にリネーム(移動)すること。
/usr/local/etc/letsencrypt.sh/archive/
mv /usr/local/etc/letsencrypt.sh/archive /usr/local/etc/dehydrated/
- archive ディレクトリはインストール時には作成されない。
- SSL証明書クリーンナップ(-gc オプション指定)時にコモンネームと合わせて作成される。
- 既に /usr/local/etc/dehydrated/archive ディレクトリが作成されている場合は、子ディレクトリ(コモンネーム)を個別にリネーム(移動)すること。
後始末
rmdir /usr/local/etc/letsencrypt.sh
※全ての移動が完了すれば、ディレクトリもファイルも残ってないはずなので削除する。
設定ファイルの変更
リネームによる影響で変更になるファイルは以下の3ファイルである。 ディレクトリ名およびコマンド名に letsencrypt.sh というキーワードが無いか確認し、dehydrated に置き換えること。
- /usr/local/etc/dehydrated/config
- /usr/local/etc/dehydrated/deploy.sh
- /usr/local/etc/dehydrated/hook.sh
SSL証明書利用アプリケーションの設定変更
本例では Apache(2.4)を例に説明した。 当該箇所のディレクトリ名変更を新しいディレクトリ名に変更する。
SSL証明書の旧指定例
SSLCertificateFile /usr/local/etc/letsencrypt.sh/certs/コモンネーム/fullchain.pem SSLCertificateKeyFile /usr/local/etc/letsencrypt.sh/certs/コモンネーム/privkey.pem
トークンディレクトリの旧指定例
Alias /.well-known/acme-challenge/ /usr/local/etc/letsencrypt.sh/.acme-challenges/ <Directory /usr/local/etc/letsencrypt.sh/.acme-challenges> Options None AllowOverride None Require all granted Header add Content-Type text/plain </Directory>
起動設定(periodic)の変更
/etc/periodic.conf あるいは /etc/periodic.conf.local のどちらかの設定で定期更新を実施しているわけだが、変数名が変更になっている。
本例では以下のような設定を実施しているが、
weekly_letsencrypt_enable="YES" weekly_letsencrypt_deployscript="/usr/local/etc/letsencrypt.sh/deploy.sh"
下記のように変更する。
weekly_dehydrated_enable="YES" weekly_dehydrated_deployscript="/usr/local/etc/dehydrated/deploy.sh"
単純に letsencrypt や letsencrypt.sh を dehydrated に置き換えるだけである。