しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure App Service 向けに Let's Encrypt 周りの自動化を行う Azure Functions を作った

タイトルの通りなんですが、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 が大量に表示されるはずです。

f:id:shiba-yan:20180805151502p:plain

そして Application settings から必要なキーを追加していきます。以下のキーを登録する必要があります。

  • LetsEncrypt:Contacts
  • LetsEncrypt:ResourceGroupName
  • LetsEncrypt:SubscriptionId

この中でも LetsEncrypt:ResourceGroupName は将来的には必要なくなりますが、今は Azure Management API 側の不具合で仕方なくリソースグループを貰う必要が出ています。

f:id:shiba-yan:20180805151737p:plain

これで基本的な設定は完了です。上で指定したメールアドレスは Let's Encrypt のアカウント用なので、証明書の期限が近くなってくるとメールが届いたりします。

Managed Service Identity を有効化

Azure Function の設定が終わったので、Managed Service Identity を有効化していきます。Platform features から選んで、On にするだけなので簡単です。

f:id:shiba-yan:20180805152328p:plain

有効化し終わったら、証明書を更新したい App Service が含まれているリソースグループにて Website Contributor 権限を付与します。この辺りに関しては前に書いた記事があるので、そっちを参照してください。

これで Functions が Managed Service Identity を使って Azure Management API を叩けるようになりました。準備も完了したので、実際に適当な App Service を用意して証明書を発行してみます。

新しく証明書を発行する

手持ちのドメインはほぼ HTTPS 化が完了していたので、テストのために利用価値のないドメインを App Service に追加して証明書の新規発行を試します。

f:id:shiba-yan:20180805154448p:plain

新規発行のために AddCertificate_HttpStart という Function を用意しているので、この Function にパラメータを与えて実行すれば証明書の発行とバインドまで行います。

パラメータは JSON で以下のように渡します。

{
    "ResourceGroupName": "RESOURCEGROUP_NAME",
    "SiteName": "APPSERVICE_NAME",
    "Domain": "DOMAIN_NAME"
}

存在しない App Service や追加されていないドメインの場合は、ログを出して処理が終了します。

簡単に実行するためのページなどは用意できていないので、適当なクライアントで上の Function をキックしてあげれば、証明書の発行処理が開始されます。

f:id:shiba-yan:20180805155004p:plain

実際に上のリクエストを実行して、20 秒ぐらい待つと App Service に新しく SSL バインドが追加されていることが確認できます。新規の場合は SNI SSL 固定になっています。

Azure Portal はキャッシュが結構行われているので、リロードしないと表示されないことが多いです。

f:id:shiba-yan:20180805161319p:plain

実際に HTTPS でアクセスしてみると、ちゃんと Let's Encrypt で発行された証明書が確認できます。

f:id:shiba-yan:20180805161329p:plain

既に 4 ドメインの証明書更新をこの Function 1 つだけで行っているので、サイト単位で WebJob を使う方法に比べて非常に簡単になりました。

ちなみに ACME Challenge の応答に仮想アプリケーションを使っているので、未検証ですが Azure Functions に対しても適用出来るはずです。

期限が近い証明書を更新する

デフォルトで RenewCertificates_Timer という Function が 1 日に 1 回動作するようになっています。

f:id:shiba-yan:20180805160837p:plain

その時に証明書の発行者名が Let's Encrypt Authority になっていて、期限まで 30 日を切っているものに対して更新を行います。まだ更新に関しては検証が足りていないので、バグが残っている可能性があります。

期限切れの証明書を削除する処理はまだ用意できていないので、今は手動で消す必要がありますが、今後は更新と同様にタイマーなどで削除するように対応予定です。

*1:今は Deploy to Azure Button を使ってもデプロイ出来ます。