Linux/Let's Encryptでサーバ証明書

Let's Encryptでサーバ証明書の導入(2018-03-14、更新:2019-03-31)


個人でも無料で導入できるサーバ証明書です。
これで最近は肩身が狭くなってきた、非httpsサイトからの脱却ができます。

詳細は https://letsencrypt.org/ にありますが、具体的な手順はいろんなサイトで紹介されています。
ここでは参考適度に大まかな流れと、私が引っかかったところをメモしておきます。


(2022/03追記)
当時の手順を残していますが、いまは私は後半のワイルドカード証明書しか使って無いです。

おおまかな流れ

まずはインストールします。
私の環境はLinux(CentOS)でWebに繋がるサーバなので、yum で入りました。

# yum info certbot
# yum install certbot

そして自分が持つWebサーバの認証です。
Webサーバ(ApacheやらNginxやら)は止めずに入れることができます。

# certbot certonly --webroot -w /usr/local/apache/htdocs -d null-i.net -d www.null-i.net 

サブドメイン(上記の例では 「www.」の部分)も漏れなく設定しなければなりませんが
近いうちに(2018/2月現在は延期中の)ワイルドカード証明書が登場するかもしれません。
ワイルドカード(*.null-i.net)で指定したい場合はmanualオプションとDNSサーバの設定が必要です。
(その場合は 若干やり方が変わります

そして、このコマンドで失敗した場合、
まずは一旦落ち着いて下さい(詳細は後述)。

初回取得の場合は規約への同意や、有効期限の連絡先メールアドレスやらの設定も対話式で行います。

デフォルトでは /etc/letsencrypt/ に、証明書が設定されました。
これを手順の例に沿って、Webサーバに設定すれば https 対応の完了です。
とても簡単ですね。
(嘘です。苦戦したので、後述)

ちなみに、私の現時点での構成はWebサーバではなく、リバースプロキシ(squid)でhttps化しているので
squid.conf に上記で取得した証明書を設定します。

https_port 443 accel cert=/etc/letsencrypt/live/null-i.net/fullchain.pem key=/etc/letsencrypt/live/null-i.net/privkey.pem (以下略)

あとは、有効期限は3か月くらいで、
期限切れの20日前に通知メールを送ってくれました。
その際には「certbot renew 」コマンドなどで更新すればOKです。

で、その更新作業を cronに入れて自動化するといいよ、
更新頻度は、証明書の発行数上限に気をつけてね、と多くのサイトで説明されていると思います。
(今ある証明書と新証明書の合計数で、発行上限を超えてしまわないように)
私の場合は、更新時に一部Webサーバの設定を変えたりしているので、しばらくは手動更新で様子を見ています。


それから、これはLet's Encryptに限った話では無いですが、
私のように「オレオレ証明書」から始めた方は
証明書に関する設定、例えばメールの送受信時にSSL/TLSやらSTATTLSやらを使う場合に、
メールアプリ側で証明書の内容を「検証しない」設定になっていると思います。
(正規の認証機関に聞きに行っても存在しない、自分で作った証明書なので)
この場合、たぶん初回アクセス時の証明書を信頼&保持し続けることになると思うので*1
これをLet's Encrypt に切り替えた場合は、
証明書を本来の「検証する」設定に戻す必要があります。

検証しない設定のままだと、切り替えたタイミングや、
Let's Encryptを更新したタイミングで証明書の不一致エラーになるかもしれないのでご注意を。

certbot のエラーについて

例によって、証明書の取得時に何度か失敗して
連続失敗でロックがかかったしまいましたが・・・

(一時間だったっけ?とにかく英語で、
連続失敗したのでしばらく時間をおいてから再実行しなさい
という内容のメッセージが出てきたと思います。
なので、時間に余裕をもって設定作業を行うことをお勧めします)

以下の例は renew(取得済み証明書の有効期限の更新が目的)ですが、
新規取得の「certbot certonly --webroot」でも似た内容になると思います。

# certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/null-i.net.conf
-------------------------------------------------------------------------------
Cert is due for renewal, auto-renewing...
Plugins selected: Authenticator webroot, Installer None
(中略)
Waiting for verification...
Cleaning up challenges
Attempting to renew cert (null-i.net) from /etc/letsencrypt/renewal/null-i.net.conf
 produced an unexpected error: Failed authorization procedure. www.null-i.net (http-01):
 urn:acme:error:unauthorized :: The client lacks sufficient authorization ::
 Invalid response from http://null-i.net/.well-known/acme-challenge/jMXCPEXXXX
 [160.16.214.13]: 403, null-i.net (http-01): urn:acme:error:unauthorized ::
 The client lacks sufficient authorization
(以下省略)

上記の例はサブドメインやらファイル名やら一部マスクしてます(ほんとはもっと長い)
あと、見づらいので改行も入れました。
ポイントは Invalid response from ~ 403 ~ unauthorized で、
HTTPの応答でエラー(403 Forbidden:アクセス禁止)が返って認証に失敗したって言っています。

(前略)
Waiting for verification...
Cleaning up challenges
Attempting to renew cert (null-i.net) from /etc/letsencrypt/renewal/null-i.net.conf 
produced an unexpected error: Failed authorization procedure. null-i.net (http-01): 
urn:acme:error:unauthorized :: The client lacks sufficient authorization :: 
Invalid response from http://null-i.net/.well-known/acme-challenge/ctwZZZZZZZ: 
"<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
(以下省略)

で、次は
認証に必要なファイルが見つからない(404 Not Found)、です。
こんな感じで連続失敗しているうちに、
ロックがかかって、一定時間インストール or アップデートができなくなると思います。

ここに挙げている方法(certbot certonly --webroot)の場合は、
上記ログにも出てきていますが
「Webサーバのコンテンツ置場/.well-known/acme-challenge/認証のチャレンジファイル」
というファイルを一時的に作成して、そこに外からアクセスすることで
あなたがWebサーバの持ち主であることを確認しようとしています。
(webrootオプションではなく、standaloneとかでやれば、また違う方法になるかも)

そして私の場合は、ファイアウォール(fail2ban)やリバースプロキシ(squid)で
一時的に「/.well-known」へのアクセスを許可してあげないと 403エラーや接続失敗エラーになります。
DoS対策などでURLフィルタしている環境は要注意です。

また、404エラー理由の方は
WebサーバでサブドメインやらCGIやら毎にファイルのアクセス先をいじっていた影響でした。
Apacheで言えば、VirtualHost ごとにサブドメインの DocumentRootを変えている
ScriptAlias で接続先を変えている、などなど。

あとは、設定変えた後にWebサーバ他の再読み込みを忘れたとか、
慌てるとろくな事がありません。
一旦落ち着いて、設定を見直しましょう。

あと、蛇足ですが、
過去にアクセスログに「/.well-known」への攻撃跡が残っていた理由が、このとき初めて分かりました。
あれは Let's Encrypt を想定した攻撃だったのかもしれませんね・・・
一時的に許可したファイアウォール等の設定は、作業後に忘れずに元に戻しておきましょう。

(そういうわけで、
私の環境では .well-knownへのアクセスは普段は禁止しているので
cron で自動更新する方法は、後日検討することにしました)

使う証明書を間違えないように

fullchain.pem と cert.pem のどちらを使うかは確認しましょう。

Webブラウザやopensslコマンド*2等で証明書の詳細を確認すると見れるのですが、
私の場合は「null-i.net」を証明する「let's Encrypt」を証明する「DST Root CA」ルート証明という
3段構えの証明書になっています。
fullchainを使えば「null-i.net」とその中間証明書「let's Encrypt」の両方が設定されます。
とはいえ、常に fullchain を使えば良いわけでもなく、
Webサーバやバージョンによってどこに何を設定するべきかは違うようなので、事前に確認しましょう。

私の場合は、本来は fullchainにするべきところを最初に cert.pem で設定してしまいました。
厄介だったのが、すぐに証明書エラーになればよかったんですが、
最初は見れてたのに、一か月後くらいに急に見れなくなった点です。
(ある日とつぜん「安全なサイトではありません」とか「表示できません」になりました)
さらに言えば、見れるブラウザ(IE、Chrome他)と見れないブラウザ(スマホのChrome、PCのFireFox)があって混乱しました。
どうやら中間証明書がWebブラウザのキャッシュ?に残っているか否かで、
たまたま見れたり見れなかったりするそうですが・・・

Webサーバ側の手順では「中間」証明書なんて解説は出てこないかもしれないので、
設定する際は気をつけて、可能であれば複数のWebブラウザで動作確認してみましょう。

ワイルドカード証明書

Certbot クライアントは、Version 0.22.0 以上が ACME v2 とワイルドカード証明書に対応
とあるので、とりあえず certbot をアップデートします。

# yum update certbot
# certbot --version
certbot 0.26.1   ←ここが 0.22.0以上であることを確認

以下、対話式で、
表示される内容をDNSに反映しながらやる作業なので、
事前にDNSを更新できる状態にしてから、certbotを実行します。

# certbot certonly --manual -d null-i.net -d *.null-i.net --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory

「このマシンのIPアドレスは記録されますよ?」(英語)
等の対話式の問いに「Y」で答えた後に、

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.null-i.net with the following value:

(ここにチャレンジコードが表示される)

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

ここで表示されたコードをDNSに反映します
(_acme-challenge.指定したドメイン、TXTタイプで、チャレンジコードを設定)
反映後にEnterで続行。
無事に成功した場合は、違うチャレンジコードでもう一回聞いてきます。
たぶん、-d で指定した数だけ繰り返すのでしょうか?
DNSを再度更新して、Enterを押します。

Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
 (以下略)

これでサブドメインをワイルドカード化した証明書が取得できました。
私の場合、既存分とは別に再取得したせいなのか、
/etc/letsencrypt/live/null-i.net-0001
のように、既存と被らない連番「0001」付きのディレクトリを作ってくれたので、
Webサーバで証明書の場所を指定する場合は、certbotが出力したPATHには注意しましょう。

あと、終わったらDNSに入れた_acme-challenge は消しちゃって良いと思います。たぶん。


以下は余談ですが、

まず、最初は 「-d *.null-i.net」 だけで作ってしまいました。
はい、「null-i.net」(サブドメイン無し)が抜けているので、両方指定してやり直しです。
つまり、www.null-i.net/index.htmlは問題ないですが、
null-i.net/index.html の証明書は無い状態(安全なサイトではありません)になってしまいました。
(ワイルドカードなんだから、こう、1つにまとめられないのかな、*null-i.netみたく)

次にやらかしたのは「--preferred-challenges dns」無しで実行してしまったので、
certbot実行後の対話が以下になってしまいました。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create a file containing just this data:

(ファイル内に書き込むチャレンジコード)

And make it available on your web server at this URL:

http://null-i.net/.well-known/acme-challenge/ランダムなファイル名

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

つまり「DNSの更新」&「well-knownの下にファイルを置く」の両方を要求されて
とても悲しい気持ちになりました。
ワイルドカード指定の分はDNS認証で、それ以外はファイル認証がデフォルトなんですかね?
何度もやり直しているうちに回数制限が近づいて、試せていませんが。
(あれ、でも certbot --help でpreferred-challenges なんて無いぞ...馬鹿には見えないオプションとかなのか?)

certbot manualコマンドを実行する時は他のオプションを付けて対話式部分を省略することもできます、お好みでどうぞ。

今までcronとかで自動更新していた環境では、manualはキツイかもしれません。
ですが、私のWebサーバはCGIやらファイアウォールやらを使っている影響で(更新作業中だけファイアウォールを解除したりする作業が発生してしまうので)、こちらの手動更新の方がはるかに楽です。
三か月に一回ですからね、メールで通知も来ますし。


(2022.05 追記)
…と思ったらメールが来ませんでした。
メール通知は期待せずに自分で利用期限を意識した方が良いと思います。(フリー証明書なのにメール通知までやってくれてる方が不思議でした)

あと、おおまかな手順は変わって無いんですが、続ける前にDNS更新が反映されるまで数秒~数分待てとかの注意文が増えてました。それだけ反映に失敗するケースが多かったんですかね?
…特にDNSキャッシュサーバのような情報をどこかで溜め込むような構成の場合は、well-knownファイルを用意する方式のほうが良いのかもしれません。


*1 検証しない、といっても大抵は初回アクセスと同じ接続先か、突然偽物のサーバにすり替わったりしていないかを検証する親切な設定になっていると思います。
*2 例として「openssl s_client -connect localhost:443 -showcerts」、繋いだ後にHTTP構文を手動で実行する訳ですが、今回は繋ぐまでが目的なので「Q」で即終了。