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

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


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

詳細は Let's Encrypt(https://letsencrypt.jp/)で日本語で解説されており、
「Let's Encrypt の使い方」から、具体例に沿ってインストールできます。
ここでは参考適度に大まかな流れと、私が引っかかったところをメモしておきます。


おおまかな流れ

まずはインストールします。
私の環境は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サーバの設定を変えたりしているので、しばらくは手動更新で様子を見ています。

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コマンド*1等で証明書の詳細を確認すると見れるのですが、
私の場合は「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コマンドを実行する時は、
他の親切なサイトはもっとオプションをつけて実行することで、
対話式入力の手間を極力削減する方法で解説して下さっています。
特に仕事(客先や商用環境)でやる場合は、いちいち対話式で入力するよりは
それらのオプションをつけて実行した方が早いし安全だと思います。
私は勉強目的もあるので、あえて対話式とか選んじゃったりなんかしてますが。
(そして英語の文章を読んで分かったフリをして満足します)

私のWebサーバはCGIやらファイアウォールやらを使っている影響で、
ファイル認証よりも、DNS認証で手動更新の方がはるかに楽です。manualコマンド万歳
ただ、今までcronとかで自動更新していた環境では、manualはキツイかもしれません。

ちなみに、
letsenctypt.jpでも「ACME はまだ最終 RFC ではありません」と解説されているので、
また取得方法とか更新があるかもしれませんね。
つまり、このメモが賞味期限切れになるのが早い予感がします。


*1 例として「openssl s_client -connect localhost:443 -showcerts」、繋いだ後にHTTP構文を手動で実行する訳ですが、今回は繋ぐまでが目的なので「Q」で即終了。

  最終更新のRSS