App Service 証明書の有効期限を Azure Functions を使用して監視する(2022年12月版)

# はじめに
以前、「[App Service 証明書の有効期限を Azure Functions を使用して監視する](https://qiita.com/shogo-ohe/items/b25e3a322b6f32ce1530)」という記事を書きましたが、2022 年 12 月時点においても、App Service 証明書の期限切れを通知する機能は提供されていません。
Azure Functions [ランタイムバージョン 3.x のサポート終了](https://jpazpaas.github.io/blog/2022/06/24/retire-functionsv2v3-MT12-HD0-faq.html) に伴い、ランタイムバージョン 4.x への対応とコードの修正を行ったので改めて紹介します。 

App Service 証明書を含む、Azure 上で証明書をご利用頂く場合、Key Vault に保管し、各サービスからは Key Vault よりインポート頂くのが一般的なご利用方法の一つとなります。<br />
例: [App Service で証明書をご利用頂く場合](https://learn.microsoft.com/ja-jp/azure/app-service/configure-ssl-certificate?tabs=apex%2Cportal#buy-and-import-app-service-certificate)、[Key Vault の場合](https://docs.microsoft.com/ja-jp/azure/application-gateway/configure-keyvault-ps)

Azure 上では [App Service 証明書](https://docs.microsoft.com/ja-jp/azure/app-service/configure-ssl-certificate) という TLS/SSL 証明書の発行サービスでは証明書をご購入頂くことが出来、[自動更新をオン](https://learn.microsoft.com/ja-jp/azure/app-service/configure-ssl-certificate?tabs=apex%2Cportal#renew-app-service-certificate) にするとことで、有効期限を特に気にする必要がなくなります。
一方で、App Service 証明書 (=GoDaddy) の発行ポリシ－では、[過去 395 日間にドメインの確認が必要になった](https://learn.microsoft.com/ja-jp/azure/app-service/configure-ssl-certificate?tabs=apex%2Cportal#renew-app-service-certificate) こともあり、確実に証明書を更新していくためには有効期限切れの時期をある程度把握しておく必要があります(*1)。
*1…正確にはメールでの通知は行われますが、宛先は固定されています。example.com に対する証明書の発行を要求した場合、以下の 5 つのアドレスに通知されますが、一般的に企業のドメイン/DNS 管理とアプリケーションの管理は分掌の関係で参照できないので、通知が受け取れないというのが正しい表現のように思えます。
・webmaster@example.com
・hostmaster@example.com
・postmaster@example.com
・admin@example.com
・administrator@example.com

そこで今回は、Azure Functions を使用して証明書の有効期限をチェックし、メールで通知する仕組みをサンプルコードとともにご紹介しようと思います。


# 証明書管理の概要
仕組みとしてはシンプルで、Azure Functions から Key Vault SDK でアクセスし、格納されている証明書の有効期限を評価します。
評価結果を SendGrid を使用して、メールで通知します。
Functions の [Timer トリガー](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-timer?tabs=csharp) を使用することで、週一回や月に1度など定期的に実行することで有効期限の状況を確認することが出来ます。

処理の流れとしては以下の通り。
![diagram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/fe5a3f5f-ed1e-66b5-0076-97a7b7eae2c4.jpeg)
(1) Azure Functions から KeyVault へアクセス。証明書 (シークレット) の有効期限を確認します
(2) 有効期限情報を 有効、期限切れ間近、有効期限切れ の3種類に分類し、メールを作成
(3) SendGrid 経由でメール通知


この仕組みの構築には、以下のリソースが必要です。
- Azure Key Vault (監視対象)
- Azure Functions
  - 今回は [Runtime v4](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-versions), JavaScript/Node.js 16 LTS を使用
  - 従量課金プランを使用しましたが、既に Web Apps などをご利用頂いている場合、App Service Plan を間借りして実行することも可能です
- SendGrid Free 100 (2022) プラン - 100通/日 まで無料


# リソースの準備
それぞれのリソースを準備します。既に作成済のリソースがある場合、そちらを流用頂いても問題ありません。
各リソースの作成とアクセスに必要な情報の獲得が主な作業内容です。

## SendGrid の準備
SendGrid アカウントを作成し、API Key の取得、ならびに送信元として使用するメールアドレスの登録 (Sender Verification) を行います。

SendGrid アカウントの作成については、以下のドキュメントを参照して下さい。
参考: [Create a Twilio SendGrid account](https://docs.sendgrid.com/for-developers/partners/microsoft-azure-2021#create-a-twilio-sendgrid-account)

API Key の取得については、以下のドキュメントを参照して下さい。
参考: [API keys](https://docs.sendgrid.com/for-developers/partners/microsoft-azure-2021#api-keys) <br />
注意点として、SendGrid API Key は1度しか表示されません。もし作成時に記録していない場合は作り直しとなりますので注意して下さい。

得られた SendGrid API Key は、後ほどアプリケーション設定に以下の名前で登録します。
```AzureWebJobsSendGridApiKey = SendGrid API Key の値 (SG.xxxx...)```


最後に送信元として使用するメールアドレスの登録 (Sender Identity) です。SendGrid では指定されたメールアドレスを送信元とするメールを送信することが出来ます。しかし、そのままでは他人へのなりすましなどの問題があるため、事前のメールアドレスの登録と確認が行われています。

Sendgrid アカウント作成直後の場合、送信元として使用するメールアドレスは登録されていません。
左上の Sendgrid アイコンから [Setup Guide] を開くか、[Settings] > [Sender Authentication] から送信元として使うメールアドレスを登録します。
![sendgrid-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/8ef24532-28ca-ad4d-d304-8574b41c7b1c.jpeg)

![sendgrid-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/d709ca8d-3067-3fc1-728a-3e2ea5ff03fc.jpeg)


送信元として使用するメールアドレスや送信者情報を入力します。
<!-- ![sendgrid-5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/42e2fe81-a352-7b90-ad3c-8cbb9e9be421.jpeg) -->
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/42e2fe81-a352-7b90-ad3c-8cbb9e9be421.jpeg" width=420em>

入力が完了し、[Create] ボタンを押下すると、送信元として指定したメールアドレスに確認のメールが送付されます。
<!-- !![sendgrid-6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/d4a35c29-0c31-c886-929a-26c3f683d04c.jpeg) -->
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/d4a35c29-0c31-c886-929a-26c3f683d04c.jpeg" width=400em>




指定されたメールアドレスには、確認のメールが届いているので、身に覚えがあれば [Verify Single Sender] で指定されたリンクをクリックします。
<!-- ![sendgrid-7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/2f799123-cc60-c18b-e7b0-585bdd2f61b3.jpeg) -->
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/2f799123-cc60-c18b-e7b0-585bdd2f61b3.jpeg" width=560em>


Sendgrid 上にジャンプし、"Congratulations! Your sender identity has been successfully verified." のように確認完了メッセージが表示されれば完了です。
![sendgrid-8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/d73b319c-cca0-a9dc-1f24-d9998db2a6b0.jpeg)

これで、指定したメールアドレスは、Sendgrid を使用する際の送信元として使用する事が出来ます。

## Key Vault の準備
Key Vault リソースや証明書は既に存在することを前提とします。

もし無い場合は [Key Vault リソースを作成](https://learn.microsoft.com/ja-jp/azure/key-vault/certificates/quick-create-portal) し、[証明書] の [作成/インポート] から適当な自己署名証明書を作成して下さい。
有効期限も変更しておくことで動作テストが可能になります。


## Azure Functions リソースの作成
Azure Functions を実行するリソースを作成します。リソースの作成方法はいくつかありますが、Node.js で実行される Functions を作成したいので、JavaScript の実行される ランタイム スタック で作成します。ホスティング方法は [消費量 (サーバーレス)] を指定します。リソースの作成方法は以下のドキュメントを参照して下さい。

- [Azure Portal で初めての関数を作成する](
https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-create-function-app-portal)
- [クイックスタート: Visual Studio Code を使用して Azure に JavaScript 関数を作成する](https://docs.microsoft.com/ja-jp/azure/azure-functions/create-first-function-vs-code-node)


使用するホスティングプランとして、App Service Plan を使用することも出来ますが、アイドル状態が続くと自動的にサイトが停止してしまうため、[構成] > [全般設定] にて、[常時接続] がオンになっている必要があります。この設定は App Service Plan の Basic 以上の価格プランでのみ有効化できるため、注意が必要です。詳細は以下のドキュメントを参照して下さい。

- [Azure Functions の専用ホスティング プラン - 常時接続](https://learn.microsoft.com/ja-jp/azure/azure-functions/dedicated-plan#always-on)
- [Investigating and reporting issues with timer triggered functions not firing](https://github.com/Azure/azure-functions-host/wiki/Investigating-and-reporting-issues-with-timer-triggered-functions-not-firing)


## 環境変数の設定
Azure Functions に SendGrid の API キーなどを登録します。こうすることでコード内から環境依存の情報を排除することが出来、API キーなどの変更をポータルから行うことが出来るようになります。
ポータルの [構成] > [アプリケーション設定] より、以下のアプリケーション設定を登録します。 

| 名前                         | 値 | 
|:-----------------------------|:------------|
| KEY_VAULT_NAME               | 監視対象とする KeyVault のリソース名                             |
| EXPIRATION_THRESHOLD_INDAYS  | 証明書の有効期限を警告対象とする残り日数の閾値                    |
| AzureWebJobsSendGridApiKey   | SendGrid の API キー                                            |
| SendGrid_email_from          | SendGrid でメールを送信する際の送信元として使うメールアドレス      |
| SendGrid_email_to             | SendGrid でメールを送信する際の宛先 (通知先) として使うメールアドレス     |

[高度な編集] をクリックすることで、登録する際の、JSON 形式でまとめて登録することが出来ます。サンプルを記載するので、[高度な編集] で既存の設定を開いて追記する際に活用下さい (末尾に追加する場合、要素を区切る "," が必要です)。

```json
    {
      "name": "AzureWebJobsSendGridApiKey",
      "value": "",
      "slotSetting": false
    },
    {
      "name": "EXPIRATION_THRESHOLD_INDAYS",
      "value": "",
      "slotSetting": false
    },
    {
      "name": "KEYVAULT_NAME",
      "value": "",
      "slotSetting": false
    },
    {
      "name": "SendGrid_email_from",
      "value": "",
      "slotSetting": false
    },
    {
      "name": "SendGrid_email_to",
      "value": "",
      "slotSetting": false
    }
```

## Azure Functions のマネージド ID の有効化
Azure Functions 上のコードから Azure Key Vault にアクセスするための認証手段が必要です。 ここではマネージド ID を使用します。

手順は以下の通りです。

1. Azure Functions で [システム割り当てのマネージド ID](https://learn.microsoft.com/ja-jp/azure/app-service/overview-managed-identity?tabs=portal%2Chttp) を有効化 する
2. Azure Key Vault の [アクセスポリシーで Functions からのアクセス権を付与する](https://learn.microsoft.com/ja-jp/azure/active-directory/managed-identities-azure-resources/tutorial-windows-vm-access-nonaad#grant-access) する

アクセスポリシーで必要な許可は、シークレットの一覧と取得 (Get, List) と証明書の一覧と取得 (Get, List) です。



# アプリケーションのデプロイ
アプリケーションは GitHub の以下のレポジトリで公開しています。git clone などでお手元でコピーして下さい。
https://github.com/ShogoOhe47/azure-keyvault-secret-expirationdate-checker

お手元で実行頂く際、local.settings.json にアプリケーション設定同様の環境変数を設定する必要があります。サンプルは [local.settings.sample.json](https://github.com/ShogoOhe47/azure-keyvault-secret-expirationdate-checker/blob/main/local.settings.sample.json) にありますので、こちらを参考にして下さい。

アプリケーションのローカルでの実行方法、並びにデプロイ方法は以下のドキュメントをご参照下さい。

- [クイックスタート: コマンド ラインから Azure に JavaScript 関数を作成する - 関数をローカルで実行する](https://learn.microsoft.com/ja-jp/azure/azure-functions/create-first-function-cli-node?tabs=azure-cli%2Cbrowser#run-the-function-locally)<br />
  ```func start```
- [クイックスタート: コマンド ラインから Azure に JavaScript 関数を作成する - Azure に関数プロジェクトをデプロイする](https://learn.microsoft.com/ja-jp/azure/azure-functions/create-first-function-cli-node?tabs=azure-cli%2Cbrowser#deploy-the-function-project-to-azure)<br />
  ```func azure functionapp publish <APP_NAME>```


タイマートリガーの関数が実行されるタイミングは [function.json](https://github.com/ShogoOhe47/azure-keyvault-secret-expirationdate-checker/blob/main/kv-expirationdate-checker/function.json) の "schedule" で指定します。

既定値では以下のようになっています。
- "schedule": "0 0 0 * * Tuesday" (火曜日の 00:00 UTC/09:00 JST に実行)
- "runOnStartup": true (通常スケジュールに加え、Azure Functions インスタンス変更などで再起動したタイミングでも実行。週1回しか動作しないため、動作テスト時には必要。)

実運用では週1回程度、多くても1日1回 (App Service 証明書側の自動更新も1日1回のみ実行のため) で十分と考えられます。以下を参考に調整を行って下さい。

- [NCRONTAB 式](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-timer?tabs=csharp#ncrontab-expressions)

```
// function.json, "schedule" 書き換え案
"0 50 23 * * *"    // UTC 毎日 23:50, 日本時間 8:50 に実行
"0 50 23 * * Mon"  // UTC 毎週月曜日 23:50, 日本時間 毎週火曜日 8:50 に実行
```

# 実行結果の例
正常に実行できると以下のようなメールが届きます。
シークレットと証明書、有効期限切れが近い (EXPIRATION_THRESHOLD_INDAYS で指定した日数以内に切れる)、既に有効期限が過ぎている、有効などが分かれて表示されます。
App Service 証明書のリソース ID や証明書の Common Name なども記載されているので、対象リソースを探す際の参考にしてください。
<!-- ![mailsample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/88e1b230-ebaa-5dea-e6fd-9daddb22438c.jpeg) -->
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/88e1b230-ebaa-5dea-e6fd-9daddb22438c.jpeg" width=560em>

メッセージの記載方法などはコードを変更することで対応できます。


# 最後に
App Service 証明書の有効期限の監視を行いたいというお問い合わせが多く、こちらのサンプルが参考となれば幸いです。

作者は JavaScript の素人なので、収集→仕分け→加工→書き込みと、[愚直にそのままコードに](https://github.com/ShogoOhe47/azure-keyvault-secret-expirationdate-checker/blob/main/kv-expirationdate-checker/index.js#L124) 書いています。
もう少し改善の余地があるかも知れませんが、コッソリ教えて貰えると嬉しいです。