Let's Encrypt 証明書の自動更新システムを作る

この記事は はてなエンジニア Advent Calendar 2018 11日目の記事です.

こんにちは,システムプラットフォーム部でSREをしているid:cohalzです.

はてなでは証明書を自動更新してくれる仕組みを作っており,今回はその紹介をします.

はてなの証明書自動更新といえば,はてなブログの独自ドメインにおける証明書自動更新システムのことを思い浮かべる人もいるかも知れません.

今回紹介するのは,そのシステムとは違う,開発チーム用に向けて作成したシステムとなります.

ここではブログの方のシステムについて紹介は行いませんが,少し前にGeekOut様にてはてなブログのHTTPS化に関する記事が公開されましたのでそちらをご覧ください.

geek-out.jp

ブログのシステムと何が違うのか

まずはじめに,何故ブログと別のシステムを作成したかについて説明します.

大きな違いはシステムで使う認証方式および保存場所が違うことです.

Let's Encrypt における認証方式には以下の2つが存在します.

  • HTTPによる認証(HTTP-01)
  • DNSによる認証(DNS-01)

今回のシステムはDNS-01を認証形式に選んでいます.

DNS-01を選んだ理由としては,証明書を取得するためのwebサーバが不要なのと,Route 53のレコードを書き換えるだけで済むことから選びました.

Route 53のレコードを書き換えるのもcliやAPIが揃っているため自動化しやすいというのもありました.

保存場所については今回S3を選択しました.

S3を選択した理由としては,可用性が高いのは勿論のこと,暗号化やバケットポリシーなど機密性も保証しやすかったためです.

ブログのシステムでは認証方式にHTTP-01,保存場所にDynamoDBを用いており,利用用途によって認証方式・サービスを使い分けています.

システムを作成した経緯

次に,システムを作成した経緯について説明します.

はてなでは以前からLet's Encrypt証明書を利用しており,3ヶ月ごとに証明書を更新していました.

Let's Encrypt証明書をクライアントから取得し,ACMもしくは証明書ストアサーバにアップロードするという手順を行っていました.

これにはいくつか問題がありました.

  • 入れ替えの作業が煩雑で間違えやすい
  • 管理する証明書が増えるだけ,作業量も増える

そんな中,ACM自身で証明書を発行する方法にDNS検証が加わりました.

AWS Certificate Manager: DNS を使用した、証明書のより簡単な検証

これにより,Route 53 を利用してドメインを管理している場合には,コンソールから数回操作するだけで,ACM上において証明書の取得・更新が行われるようになりました.

ただし,ACMの証明書はALBやELBに対して使うことが前提です.

はてなでは,ALBやELBを用いていないサービスおよび環境もあるため,利用場所を問わないLet's Encryptの証明書は変わらず必要になってきます.

また,Let's Encryptにてワイルドカード証明書のサポートが始まるタイミングでもあり,Let's Encrypt証明書の自動更新システムを作成することにしました.

2018年1月にワイルドカード証明書の発行を開始 - Let's Encrypt 総合ポータル

なぜワイルドカード証明書が必要なのか

ここまでLet's Encryptの話をしてきましたが,なぜワイルドカード証明書が必要なのかという話をします.

理由はいくつかありますが,はてなでは主に2つの理由によりワイルドカード証明書を利用しています.

証明書管理の手間を削減

ドメインにつき一つの証明書を取得する場合では,管理するドメインが多くなった際にコストがその分増加します.

ワイルドカード証明書では,その1枚の証明書を管理すれば良いため管理が楽になります.

ドメイン情報の隠蔽

現在,証明書を取得するとCertificate Transparencyによりcrt.shのようなサイトでログが残り,証明書を取得したドメインが検索可能になります.

crt.sh

そのため,特に開発環境やステージング環境では,開発用のドメインの存在が外部に漏れないように,ワイルドカード証明書でカバーされるドメインで環境構築を行う事が望ましいです.

certbotとhookを使う方式

以上の課題から,ワイルドカード証明書を自動取得・更新するシステムの構築を始めました.

最初に試したのはcertbotとhookを使ったものでした.

certbotとはcliでLet's Encrypt証明書の取得できるクライアントのことです.

certbot.eff.org

この,certbotにはドメイン認証の際にhookとしてスクリプトを渡すことができます.

User Guide — Certbot 0.29.0.dev0 documentation

そのhookとしてDNSのレコードセットを変更するスクリプトを指定してあげることで動作します.

certbotとhookのスクリプトをバッチ用サーバに配布*1し,実行することで実現しました.

cron等で定期実行後,バッチ用サーバに保存された証明書をサーバに配布,という流れになります.

しかし,いくつかの課題点が見えてきました.

  • スクリプトを動かすサーバの保守・可用性
  • エラーや通知のハンドリング

以上から,システムの見直し・再実装を行いました.

AWS Lambdaで動作する証明書更新システム

上で挙げた課題を解決するために,AWS Lambdaを採用しました.(以下Lambdaと表記します)

Lambdaで実装を始める前に問題点を洗い出し,設計思想をあらかじめ構築しました.

例えば以下の通りです.

  • 手作業を可能な限り減らし,自動化を目指す
  • システムから最新の証明書を証明書ストアに保存する
  • 可用性の高い証明書ストアを利用する
  • 成功・失敗ともにSlack通知を行う

システムの構成図は下のようになります.

f:id:cohalz:20181210164438p:plain
今回作成したシステムの構成図

流れは大きく4つに別れています.

  1. CloudWatch Logsから証明書を取得したいドメインをLambdaに送信
  2. Lambdaからcertbotを通してLet's EncryptサーバとRoute 53のAPIと通信をし,証明書を取得
  3. 取得した証明書をS3バケットに保存
  4. 保存完了後,Slackに通知を行う(エラー時も通知を行う)

ストアから証明書をダウンロードし更新する部分については,各チームで事情が異なるためシステム上に組み込むということは行いませんでした.

cronなどを用いて,S3からのダウンロードとnginxをreloadする等のシェルスクリプトを実行すれば完全に自動化が可能になります.

以上のシステムを開発チームに使ってもらい,フィードバックからさらなる改善を行いました.

  • CloudWatch Logsに入力するJSONの入力チェックを行う
  • 証明書保存先バケットへの書き込みが可能か事前にチェックする
  • 無制限に書き込みが出来ないよう,LambdaからアクセスできるバケットをIAMで制限する
  • 証明書をSSE-KMSで暗号化し,ストアにアクセスできるユーザをKMSによって制限する

Slackにはこのように通知され,どんなドメインが更新されてどこに保存されたのかが通知されるようになっています.

f:id:cohalz:20181210104507p:plain
実行開始と終了時に通知されます

今回作成したシステムのうち,黒い枠線で囲った範囲をGitHubにて公開しました.

github.com

内部で使っているシステムですが,チーム内にOSS化をしたいと言ったところ快諾をいただきました.

はてなでは,過去にLet's Encryptへの寄付を行ったこともあります.

developer.hatenastaff.com

寄付や今回のOSS化のように,利用しているOSSやコミュニティへ様々な形で支援をしていきたいと考えています.

はてなでは,運用上の課題をシステム開発により解決していく/OSSやコミュニティへ支援をしていくSREを募集しています.

hatenacorp.jp

*1:配布はdroot https://blog.yuuk.io/entry/droot を用いています