Azure App ServiceでホストされているASP.NET Coreアプリケーションのデータ保護の構成



# デプロイスロットをスワップするとすべてのユーザーがログアウトされてしまう問題
Azure App ServiceでホストされているASP.NET Coreアプリケーションでデプロイスロットを切り替えると、ログインしていたすべてのユーザーがログアウトされてしまうという問題に遭遇しました。

調べてみると、Microsoftのドキュメントに下記のような記載がありました。

https://learn.microsoft.com/ja-jp/aspnet/core/security/data-protection/configuration/default-settings?source=recommendations&view=aspnetcore-6.0

> アプリが Azure Apps でホストされている場合、キーは %HOME%ASP.NET_DataProtection-Keys フォルダーに保持されます。 このフォルダーはネットワーク ストレージにバックアップされ、アプリをホストしているすべてのマシンで同期されています。
> - 保存中のキーは保護されていません。
> - DataProtection-Keys フォルダーから、単一のデプロイ スロットのアプリのすべてのインスタンスにキー リングが提供されます。
> - ステージングや運用などの別のデプロイ スロットでは、キー リングが共有されません。 ステージングから運用へのスワップや A/B テストの使用など、デプロイ スロットをスワップすると、データ保護を使っているアプリからは、前のスロット内のキー リングを使って格納されたデータを復号化できなくなります。 その結果、標準の ASP.NET Core cookie 認証を使うアプリでは、データ保護を使って cookie が保護されているため、ユーザーがログアウトされます。 スロットに依存しないキー リングが必要な場合は、Azure Blob Storage、Azure Key Vault、SQL ストア、Redis キャッシュなどの外部キー リング プロバイダーを使います。

データ保護キーは、認証cookieの保護やCSRFトークン生成時のキーとして利用されるものですが、Asp.NETCoreアプリの既定の設定ではデプロイスロット間で共有されない場所に保管されるためにスロットの切り替え後に認証cookieが復号できずにログアウトされてしまうようです。

# Azure Blob Storage と Azure Key Vaultを利用したデータ保護キーの構成
上記の問題は、異なるデプロイスロット間で共有できる場所にデータ保護キーを保管することで解決できるようです。

保護キーの保管先はデータベースやRedisキャッシュなども選択できますが、今回はAzure Blob Storageを利用することとします。
また、保護キー自体を保護するための暗号化キーをAzure Key Vaultで管理するようにします。

https://learn.microsoft.com/ja-jp/aspnet/core/security/data-protection/configuration/overview?source=recommendations&view=aspnetcore-6.0

## コードの修正
まずは、ソースコード側でデータ保護の構成を行います。

下記ライブラリをNuGetでインストール後、DataProtectionのミドルウェアを下記の様に構成します。
- Azure.Extensions.AspNetCore.DataProtection.Blobs
- Azure.Extensions.AspNetCore.DataProtection.Keys
- Azure.Identity

```csharp
using Microsoft.AspNetCore.DataProtection;
using Azure.Identity;
using Azure.Storage.Blobs;

public class Startup
{
    //....

    public void ConfigureServices(IServiceCollection services)
    {
        //....
        //アプリケーション設定からBlobコンテナのURLとKey Vaultのキー識別子を取得
        var blobContainerUri = configuration["DataProtectionBlobContainerUri"];
        var keyIdentifier = configuration["DataProtectionKeyIdentifier"];
        //Blobコンテナクライアントを作成
        var blobContainer = new BlobContainerClient(
                new Uri(blobContainerUri), new DefaultAzureCredential());
        blobContainer.CreateIfNotExists();
        var blobClient = blobContainer.GetBlobClient("keys.xml");
        //サービスに登録
        services
            .AddDataProtection()
            .PersistKeysToAzureBlobStorage(blobClient)
            .ProtectKeysWithAzureKeyVault(new Uri(keyIdentifier), new DefaultAzureCredential());
     }
}
```

## Azure StrageのBlobコンテナの作成 
下記の手順でデータ保護キーを格納するためのBlobコンテナを作成します。

### Strageアカウントの作成
まずはAzureポータルにログインし、 Azure Strageアカウントを作成します。

<img width="600" alt="create strage account" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/0881f75b-0ea9-06a5-ca36-0ce249ce4c0d.png">

- Standard(汎用v2アカウント)
- geo冗長ストレージ(GRS)

### Blobコンテナの作成
作成したストレージアカウントにコンテナを追加します。

<img width="400" alt="create blob container" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/22445b0e-1356-3d17-b034-66831f03898d.png">

- パブリックアクセスレベルは「プライベート」とします。

### BlobコンテナのURLを取得
作成したコンテナのプロパティを開き、URLをコピーしておきます。（後ほどこのURLをAppSerciceのアプリケーション設定に登録します。）
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/19a0f63d-b84f-28a7-7735-3f3c41d44f33.png">

## Key VaultでKeyを作成する
次に、データ保護キー自体を保護ずるためのキーをAzure Key Vaultで作成します。

### Key Vaultコンテナの作成
<img width=600 art="create key container" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/b0f7b059-6fb6-2c18-ad34-6c49eee38ce7.png">

### キーの作成
キーの種類、サイズを指定してキーを作成します。

<img width=600 art="create key" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/307976be-c309-511f-13a1-2a6071072189.png">

### キー識別子の取得
作成したキーを選択後、バージョンを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/cea2f93f-4702-5812-81e9-e0537419a643.png)

ここで表示されるキー識別子をコーピーしておきます。（後ほどこのURLをAppSerciceのアプリケーション設定に登録します。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/dbd93b59-2786-4317-118b-556ff760ef00.png)

また、許可された操作は下記のみにチェックが入っている状態に変更して「保存」します。
- キーを折り返す
- キーの折り返しを解除

これで、データの保管先の設定ができました。次に、AppSerciceがこれらのリソースにアクセスできるように設定する必要があります。

## App Sercice からデータ保護のリソースにアクセスする
※　ここでは、既にApp Serciceが作成されていて、Asp.NETCoreアプリがデプロイされていることを前提としています。

### App Serviceのシステム割り当てマネージドIDを有効にする
Azure ポータルで対象のApp Serciceを開きます。左のメニューでIDを選択し、「システム割り当て済み」タブで「状態」をオンに設定します。

<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/6c48e673-6782-017d-a091-b709dc2ba28c.png">

### Blobコンテナのアクセス制御でApp ServiceのマネージドIDに対しロールを割り当てる
Azureポータルで、作成したStrage Blobコンテナを開き、左のメニューで「アクセス制御（IAM）」を選択し、「ロールの割り当て」タブをクリックします。
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/2e5d3b22-1257-7f27-9fbb-482c6f030811.png">

ロールの割り当て追加で「ストレージBLOBデータ共同作成者」のロールを選択します。
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/572e798a-245b-268c-2a52-55e3bb0c4f2a.png">
アクセスの割当先を「マネージドID」にして、メンバーを選択するをクリック。マネージドIDのプルダウンで対象のAppServiceを選択して割り当てを追加します。
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/8497e948-5480-f0a7-1511-9f42240cb4b9.png">

### Keyコンテナのアクセス制御でApp ServiceのマネージドIDに対しロールを割り当てる
KeyVaultのKeyコンテナに対しても同様にロールを割り当てます。

作成したKeyVaultのコンテナで「アクセス構成」を選択し、アクセス許可モデルを「Azureロールベースのアクセス制御」に設定後、「アクセス制御（IAM）」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/0bf85f43-38c6-df49-a4d8-c93797261b0d.png)

ロールは「キーコンテナー暗号化ユーザー」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/5212d9ae-5ba5-3ece-b9b1-36091d6a9279.png)

アクセスの割当先を「マネージドID」にして、メンバーを選択するをクリック。マネージドIDのプルダウンで対象のAppServiceを選択して割り当てを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/c9e3e089-cb05-73d6-8d0a-d29107287632.png)

### App Serviceのアプリケーション設定にBlobコンテナのURLとKeyコンテナのキー識別子を設定する

ソースコードを修正した際、アプリケーション設定（Configuration）から下記情報を取得するようにしていました。


|名前|値|
|:-|:-|
|DataProtectionBlobContainerUri|{BlobコンテナのURL}|
|DataProtectionKeyIdentifier|{Key VaultのKey識別子}|

この値をApp Serviceのアプリケーション設定に登録します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/4152f249-26a8-75de-7fe1-cdeea2ee1643.png">

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/499e0cac-90bb-682f-7934-20b759d459e6.png">

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/a2e2cbfc-f50b-a0bb-12c8-be9a424c6180.png">

## 確認 
これで一通りの設定が完了しました。
設定したApp ServiceのWebページにアクセス後、Keyコンテナにキーファイルが保存されていればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/dc02e046-b1da-1a60-01c1-4e406107e0c3.png)

# 参照

https://docs.microsoft.com/ja-jp/aspnet/core/security/data-protection/configuration/overview

https://docs.microsoft.com/ja-jp/aspnet/core/security/data-protection/configuration/default-settings

https://docs.microsoft.com/ja-jp/aspnet/core/security/data-protection/implementation/key-storage-providers

https://docs.microsoft.com/ja-jp/azure/app-service/scenario-secure-app-access-storage