ASP.NET MVC Core2でステートレスな構成を作るための設定

こんにちは。beaglesoftの真鍋です。

ASP.NET Core で構築しているレポートシステムの開発が大分すすんで本番環境で稼働させられるところまで来ました。本番環境を構築して社内で利用してみるといろいろな課題画見えてきます。そんな中でちょっと直面した問題とその対応方法をまとめてみました。

利用している構成

  • dotnet core 2.1.301
  • Microsoft.AspNetCore.App 2.1.1

概要

ASP.NET MVC Coreで複数台構成の環境で運用するときに、CSRFトークンなど共通のキー情報により生成される情報はロードバランサーのSticky Sessionを利用する必要があります。

これはリクエストごとに生成されるキー情報がサーバーごとに異なるため、セッションを発行元と紐付ける必要があるためです。そのため、ロードバランサーがCookieから転送するサーバーを決定しセッションごとにサーバーを固定化します。

この構成の場合、ECSなどのステートフルな構成を前提としている運用ではコンテナのライフサイクルとリクエストのライフサイクルが一致しないためうまく運用ができません。

そのため、サーバー群で使用するキー情報を共有してリクエストごとに生成されるキー情報をどのサーバーでも同様に扱えるようにすることでステートレスな構成とすることができます。

ステートレスな構成とするためには、各サーバーが利用するキー情報を1カ所の共有ストレージに安全に保存する必要がありますが、その方法を実現するために提供されているライブラリがASP.NET Core Data Protectionとなります。

ASP.NET Core Data Protection | Microsoft Docs

また、ASP.NET Core antiforgeryについては下記の通りASP.NET Core Data Protectionを利用していることが明記されています。

ASP.NET Core implements antiforgery using ASP.NET Core Data Protection. The data protection stack must be configured to work in a server farm. See Configuring data protection for more information.

Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core | Microsoft Docs

設定方法

今回はRedisにキー情報を保存することにしました。具体的な設定方法は以下の通りとなります。

Key storage providers in ASP.NET Core | Microsoft Docs

CSRFを利用する場合に、トークン生成のキー情報を複数台構成のサーバーで共有するためには以下の設定を行います。

using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis.Extensions.Core;
using StackExchange.Redis.Extensions.Core.Configuration;
using StackExchange.Redis.Extensions.Newtonsoft;
using System;
using Microsoft.AspNetCore.DataProtection;
using StackExchange.Redis;

namespace net.girafa
{
    public partial class Startup
    {
        private void InitializeRedisCache(IServiceCollection services)
        {
            var redisHost = Configuration["RedisCache:host"];
            
            ...
            var redis = ConnectionMultiplexer.Connect(redisHost);
            services.AddDataProtection().PersistKeysToRedis(redis, "DataProtection-Keys");
        }
    }
}

このように設定することでRedisには以下の情報が生成されるようになります。(RedisCLIで確認しています。)

127.0.0.1:6379> keys DataProtection-Keys
1) "DataProtection-Keys"

127.0.0.1:6379> type DataProtection-Keys
list

127.0.0.1:6379> lrange DataProtection-Keys 0 1
1) "<key id=\"9b0ac354-...\" version=\"1\"><creationDate>2018-08-26T10:44:32.112617Z</creationDate><activationDate>2018-08-26T10:44:32.084794Z</activationDate><expirationDate>2018-11-24T10:44:32.084794Z</expirationDate><descriptor deserializerType=\"Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=2.1.1.0, Culture=neutral, PublicKeyToken=..\"><descriptor><encryption algorithm=\"AES_256_CBC\" /><validation algorithm=\"HMACSHA256\" /><masterKey p4:requiresEncryption=\"true\" xmlns:p4=\"http://schemas.asp.net/2015/03/dataProtection\"><!-- Warning: the key below is in an unencrypted form. --><value>0pp...5A==</value></masterKey></descriptor></descriptor></key>"

動作確認

動作確認はECS上でALBのStickeySessionをオフにしてから2台のコンテナを用意して実行しました。1台にリクエストが来ていることを確認してからそのコンテナを停止して処理を継続できることを確認します。

上記設定を行うことで実際に処理を継続できることを確認しました。

参考

以下のドキュメントを参考にしました。

asp.net core - How do I handle ValidateAntiForgeryToken across linux servers - Stack Overflow

ASP.NET Core Data Protection | Microsoft Docs

Key storage providers in ASP.NET Core | Microsoft Docs

Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core | Microsoft Docs

Effective C# 6.0/7.0

Effective C# 6.0/7.0