タイトルの通りなんですが、App Service 向けに自動で Let's Encrypt の証明書を更新してくれる Azure Functions を作りました。Durable Functions と ACMESharp Core を使っています。
とりあえずベータリリース的な感じで出してみることにしました。
これまでも Site Extensions と WebJob を使って Let's Encrypt の証明書を更新してくれるものはありましたが、1 サイトに 1 つ仕込む必要があり、結構な確率で失敗することが多かったので不便だと思ってました。サービスプリンシパルを作って設定する必要があったのも面倒でした。
なので、今回の azure-appservice-letsencrypt は以下のような問題を解決するために作りました。
- 1 つの Azure Function で複数の証明書を更新できるように
- サービスプリンシパルではなく Managed Service Identity を利用
- とある API 呼び出しが失敗してもリトライで継続できるように
- モニタリングが容易に行えるように
Let's Encrypt で証明書を発行するために使う ACME は状態を保持したまま、発行フローを書く必要があったので Durable Functions に最適な場面だと考えていました。
Azure Function をデプロイ
適当に Consumption プランで Azure Functions を作成して、GitHub からクローンしたプロジェクトをデプロイしておきます。*1デプロイすると、Functions が大量に表示されるはずです。
そして Application settings から必要なキーを追加していきます。以下のキーを登録する必要があります。
- LetsEncrypt:Contacts
- LetsEncrypt:ResourceGroupName
- LetsEncrypt:SubscriptionId
この中でも LetsEncrypt:ResourceGroupName
は将来的には必要なくなりますが、今は Azure Management API 側の不具合で仕方なくリソースグループを貰う必要が出ています。
これで基本的な設定は完了です。上で指定したメールアドレスは Let's Encrypt のアカウント用なので、証明書の期限が近くなってくるとメールが届いたりします。
Managed Service Identity を有効化
Azure Function の設定が終わったので、Managed Service Identity を有効化していきます。Platform features から選んで、On にするだけなので簡単です。
有効化し終わったら、証明書を更新したい App Service が含まれているリソースグループにて Website Contributor 権限を付与します。この辺りに関しては前に書いた記事があるので、そっちを参照してください。
これで Functions が Managed Service Identity を使って Azure Management API を叩けるようになりました。準備も完了したので、実際に適当な App Service を用意して証明書を発行してみます。
新しく証明書を発行する
手持ちのドメインはほぼ HTTPS 化が完了していたので、テストのために利用価値のないドメインを App Service に追加して証明書の新規発行を試します。
新規発行のために AddCertificate_HttpStart
という Function を用意しているので、この Function にパラメータを与えて実行すれば証明書の発行とバインドまで行います。
パラメータは JSON で以下のように渡します。
{ "ResourceGroupName": "RESOURCEGROUP_NAME", "SiteName": "APPSERVICE_NAME", "Domain": "DOMAIN_NAME" }
存在しない App Service や追加されていないドメインの場合は、ログを出して処理が終了します。
簡単に実行するためのページなどは用意できていないので、適当なクライアントで上の Function をキックしてあげれば、証明書の発行処理が開始されます。
実際に上のリクエストを実行して、20 秒ぐらい待つと App Service に新しく SSL バインドが追加されていることが確認できます。新規の場合は SNI SSL 固定になっています。
Azure Portal はキャッシュが結構行われているので、リロードしないと表示されないことが多いです。
実際に HTTPS でアクセスしてみると、ちゃんと Let's Encrypt で発行された証明書が確認できます。
既に 4 ドメインの証明書更新をこの Function 1 つだけで行っているので、サイト単位で WebJob を使う方法に比べて非常に簡単になりました。
ちなみに ACME Challenge の応答に仮想アプリケーションを使っているので、未検証ですが Azure Functions に対しても適用出来るはずです。
期限が近い証明書を更新する
デフォルトで RenewCertificates_Timer
という Function が 1 日に 1 回動作するようになっています。
その時に証明書の発行者名が Let's Encrypt Authority になっていて、期限まで 30 日を切っているものに対して更新を行います。まだ更新に関しては検証が足りていないので、バグが残っている可能性があります。
期限切れの証明書を削除する処理はまだ用意できていないので、今は手動で消す必要がありますが、今後は更新と同様にタイマーなどで削除するように対応予定です。
*1:今は Deploy to Azure Button を使ってもデプロイ出来ます。