プロキシ サーバーとロード バランサーを使用するために ASP.NET Core を構成する

作成者: Chris Ross

ASP.NET Core の推奨される構成では、アプリは IIS/ASP.NET Core モジュール、Nginx、または Apache を使ってホストされます。 プロキシ サーバー、ロード バランサー、および他のネットワーク アプライアンスにより、要求に関する情報が、アプリに到達する前にわからなくなることがよくあります。

  • HTTPS 要求が HTTP によってプロキシされると、元のスキーム (HTTPS) は失われ、ヘッダーで転送される必要があります。
  • アプリは、インターネット上または社内ネットワーク上の本来の送信元ではなく、プロキシから要求を受信するため、送信元クライアントの IP アドレスもヘッダーで転送される必要があります。

この情報は、リダイレクト、認証、リンクの生成、ポリシーの評価、クライアントの位置情報など、要求の処理で重要になる可能性があります。

転送されるヘッダー

慣例によりは、プロキシは HTTP ヘッダーで情報を転送します。

Header 説明
X-Forwarded-For 要求を開始したクライアントと、プロキシ チェーン内の後続のプロキシに関する情報を保持します。 このパラメーターは、IP アドレス (および、必要に応じてポート番号) を含むことがあります。 プロキシ サーバーのチェーンにおいては、最初のパラメーターが、要求を最初に行ったクライアントを示します。 その後に、後続のプロキシの識別子が指定されています。 チェーン内の最後のプロキシは、パラメーターのリストには含まれません。 最後のプロキシの IP アドレスとオプションのポート番号は、トランスポート層のリモート IP アドレスとして入手できます。
X-Forwarded-Proto 開始スキーム (HTTP/HTTPS) の値です。 要求が複数のプロキシを通過している場合、値はスキームのリストである場合もあります。
X-Forwarded-Host ホスト ヘッダー フィールドの元の値です。 通常、プロキシはホスト ヘッダーを変更しません。 プロキシにおいてホスト ヘッダーが既知の適切な値であることが検証されない、または既知の適切な値に制限されないシステムに影響を与える、特権の昇格脆弱性については、マイクロソフト セキュリティ アドバイザリ CVE-2018-0787 をご覧ください。

Forwarded Headers Middleware (ForwardedHeadersMiddleware) によって、これらのヘッダーが読み取られ、HttpContext 上の関連するフィールドに入力されます。

ミドルウェアの更新:

上記に関する詳細については、こちらの GitHub イシューを参照してください。

Forwarded Headers Middleware の既定の設定は構成できます。 既定の設定の場合:

  • アプリと要求のソースの間には、"1 つのプロキシ" だけが存在します。
  • 既知のプロキシと既知のネットワークに対しては、ループバック アドレスのみが構成されています。
  • 転送されるヘッダーには X-Forwarded-ForX-Forwarded-Proto の名前が付けられます。
  • ForwardedHeaders の値は ForwardedHeaders.None です。ミドルウェアを有効にするには、目的のフォワーダーをここで設定する必要があります。

すべてのネットワーク アプライアンスが追加構成なしで X-Forwarded-For および X-Forwarded-Proto ヘッダーを追加するわけではありません。 プロキシされた要求がアプリに届いたときにこれらのヘッダーが含まれていない場合は、アプライアンスの製造元のガイダンスを参照してください。 アプライアンスが X-Forwarded-ForX-Forwarded-Proto 以外の名前を使用している場合は、アプライアンスで使用されるヘッダー名と一致するように ForwardedForHeaderNameForwardedProtoHeaderName のオプションを設定してください。 詳細については、「Forwarded Headers Middleware のオプション」と「別のヘッダー名を使用するプロキシの構成」を参照してください。

IIS/IIS Express と ASP.NET Core モジュール

アプリが IIS と ASP.NET Core モジュールの背後でアウト プロセスでホストされている場合、IIS Integration Middleware によって Forwarded Headers Middleware が既定で有効にされます。 転送されるヘッダーの信頼に関する問題のため (たとえば、IP スプーフィング)、Forwarded Headers Middleware はアクティブ化されると最初に、ASP.NET Core モジュールに固有の制限された構成を使って、ミドルウェア パイプライン内で実行します。 ミドルウェアは、X-Forwarded-For および X-Forwarded-Proto ヘッダーを転送するように構成され、単一の localhost プロキシに制限されます。 追加の構成が必要な場合は、「Forwarded Headers Middleware のオプション」をご覧ください。

プロキシ サーバーとロード バランサーの他のシナリオ

アウト プロセスでホストされている場合に IIS Integration が使われていない場合は、Forwarded Headers Middleware は既定で有効になりません。 UseForwardedHeaders を含む転送されたヘッダーをアプリで処理するためには、Forwarded Headers Middleware を有効にする必要があります。 ミドルウェアを有効にした後、ミドルウェアに対して ForwardedHeadersOptions が指定されていない場合の既定の ForwardedHeadersOptions.ForwardedHeadersForwardedHeaders.None です。

ForwardedHeadersOptions でミドルウェアを構成して、Startup.ConfigureServicesX-Forwarded-For および X-Forwarded-Proto ヘッダーを転送します。

Forwarded Headers Middleware の順序

Forwarded Headers Middleware は、他のミドルウェアの前に実行する必要があります。 この順序により、転送されるヘッダー情報に依存するミドルウェアが処理にヘッダー値を使用できます。 Forwarded Headers Middleware は、診断とエラー処理の後に実行できますが、UseHsts を呼び出す前に実行する必要があります。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders =
                ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseForwardedHeaders();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseForwardedHeaders();
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

または、診断の前に UseForwardedHeaders を呼び出します。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseForwardedHeaders();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

注意

ForwardedHeadersOptionsStartup.ConfigureServices において指定されていない場合、または UseForwardedHeaders を使って拡張メソッドに直接渡されない場合、転送される既定のヘッダーは ForwardedHeaders.None です。 転送するヘッダーで ForwardedHeaders プロパティが構成されている必要があります。

Nginx の構成

X-Forwarded-For および X-Forwarded-Proto ヘッダーを転送する場合は、Nginx 搭載の Linux で ASP.NET Core をホストする を参照してください。 詳細については、「NGINX:Using the Forwarded header」 (NGINX: 転送されるヘッダーの使用) を参照してください。

Apache の構成

X-Forwarded-For は自動的に追加されます (「Apache Module mod_proxy:Reverse Proxy Request Headers」 (Apache モジュール mod_proxy: リバース プロキシがヘッダーを要求する) を参照)。 X-Forwarded-Proto ヘッダーを転送する方法については、Apache 搭載の Linux で ASP.NET Core をホストする を参照してください。

Forwarded Headers Middleware のオプション

ForwardedHeadersOptions は、Forwarded Headers Middleware の動作を制御します。 既定の値の変更例を次に示します。

  • 転送されるヘッダーのエントリ数を 2 に制限します。
  • 127.0.10.1 の既知のプロキシ アドレスを追加します。
  • 転送されるヘッダーの名前を既定の X-Forwarded-For から X-Forwarded-For-My-Custom-Header-Name に変更します。
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
    options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});
オプション 説明
AllowedHosts X-Forwarded-Host ヘッダーによるホストを指定した値に制限します。
  • 値は、大文字と小文字を無視して序数で比較されます。
  • ポート番号は除外されている必要があります。
  • リストが空の場合、すべてのホストが許可されます。
  • 最上位のワイルドカード * は、すべての空でないホストを許可します。
  • サブドメインのワイルドカードは許可されますが、ルート ドメインとは一致しません。 たとえば、*.contoso.com はサブドメイン foo.contoso.com と一致しますが、ルート ドメイン contoso.com とは一致しません。
  • Unicode のホスト名は許可されますが、一致のために Punycode に変換されます。
  • IPv6 アドレスは境界の角かっこを含む必要があり、従来の形式 (例: [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]) になっている必要があります。 IPv6 アドレスは、異なる形式間で論理的な等価性を調べるために特別な大文字小文字にはされず、正規化は行われません。
  • 許可されるホストの制限に失敗すると、サービスによって生成されたリンクを攻撃者が偽装する可能性があります。
既定値は空の IList<string> です。
ForwardedForHeaderName ForwardedHeadersDefaults.XForwardedForHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。 このオプションは、プロキシ/フォワーダーが X-Forwarded-For ヘッダーを使用していないが、情報の転送のためにその他のヘッダーを使用している場合に使用されます。

既定値は、X-Forwarded-For です。
ForwardedHeaders 処理する必要があるフォワーダーを識別します。 適用されるフィールドの一覧については、「ForwardedHeaders Enum」(ForwardedHeaders 列挙型) をご覧ください。 このプロパティに割り当てられる標準値は、ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto です。

既定値は ForwardedHeaders.None です。
ForwardedHostHeaderName ForwardedHeadersDefaults.XForwardedHostHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。 このオプションは、プロキシ/フォワーダーが X-Forwarded-Host ヘッダーを使用していないが、情報の転送のためにその他のヘッダーを使用している場合に使用されます。

既定値は、X-Forwarded-Host です。
ForwardedProtoHeaderName ForwardedHeadersDefaults.XForwardedProtoHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。 このオプションは、プロキシ/フォワーダーが X-Forwarded-Proto ヘッダーを使用していないが、情報の転送のためにその他のヘッダーを使用している場合に使用されます。

既定値は、X-Forwarded-Proto です。
ForwardLimit 処理されるヘッダー内のエントリの数を制限します。 制限を無効にするには null に設定しますが、これは KnownProxies または KnownNetworks が構成されている場合にのみ行う必要があります。 null 以外の値を設定することは、正しく構成されていないプロキシや、ネットワーク上のサイドチャネルから届く悪意のある要求から保護するための予防策となります (ただし保証はできません)。

Forwarded Headers Middleware では、ヘッダーが右から左の逆の順序で処理されます。 規定値 (1) を使う場合は、ForwardLimit の値を増やさない限りヘッダーの右端にある値のみが処理されます。

既定値は、1 です。
KnownNetworks 受け付けるヘッダーの転送元である既知のネットワークのアドレス範囲です。 クラスレス ドメイン間ルーティング (CIDR) の表記を使って、IP の範囲を指定します。

サーバーがデュアル モードのソケットを使用している場合、IPv4 アドレスは IPv6 形式で提供されます (たとえば、IPv4 での 10.0.0.1 は IPv6 で ::ffff:10.0.0.1 と表現されます)。 「IPAddress.MapToIPv6」をご覧ください。 この形式が必要かどうかを判断するには、「HttpContext.Connection.RemoteIpAddress」をご覧ください。

既定値は、IPAddress.Loopback の単一エントリを含む IList<IPNetwork> です。
KnownProxies 受け付けるヘッダーの転送元である既知のプロキシのアドレスです。 IP アドレスの正確な一致を指定するには、KnownProxies を使います。

サーバーがデュアル モードのソケットを使用している場合、IPv4 アドレスは IPv6 形式で提供されます (たとえば、IPv4 での 10.0.0.1 は IPv6 で ::ffff:10.0.0.1 と表現されます)。 「IPAddress.MapToIPv6」をご覧ください。 この形式が必要かどうかを判断するには、「HttpContext.Connection.RemoteIpAddress」をご覧ください。

既定値は、IPAddress.IPv6Loopback の単一エントリを含む IList<IPAddress> です。
OriginalForHeaderName ForwardedHeadersDefaults.XOriginalForHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。

既定値は、X-Original-For です。
OriginalHostHeaderName ForwardedHeadersDefaults.XOriginalHostHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。

既定値は、X-Original-Host です。
OriginalProtoHeaderName ForwardedHeadersDefaults.XOriginalProtoHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。

既定値は、X-Original-Proto です。
RequireHeaderSymmetry 処理対象の ForwardedHeadersOptions.ForwardedHeaders の間でヘッダー値の数が同期していることを要求します。

ASP.NET Core 1.x での既定値は true です。 ASP.NET Core 2.0 以降での既定値は false です。

シナリオとユース ケース

転送されるヘッダーを追加することができず、すべての要求が安全な場合

アプリにプロキシされる要求に、転送されるヘッダーを追加できない場合があります。 すべてのパブリック外部要求が HTTPS を使うことをプロキシが強制している場合、すべての種類のミドルウェアを使う前に、Startup.Configure でスキームを手動で設定できます。

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
    return next();
});

このコードは、開発環境またはステージング環境の環境変数または他の構成設定によって、無効にすることができます。

要求のパスを変更するパス ベースとプロキシを処理する

一部のプロキシでは、パスはそのまま渡されますが、ルーティングが正しく機能するためには削除する必要のあるアプリ ベース パスが含まれます。 UsePathBaseExtensions.UsePathBase ミドルウェアは、パスを HttpRequest.PathHttpRequest.PathBase へのアプリ ベース パスに分割します。

/foo/foo/api/1 として渡されるプロキシ パスのアプリ ベース パスである場合、ミドルウェアは次のコマンドで Request.PathBase/fooに、Request.Path/api/1 に設定します。

app.UsePathBase("/foo");

ミドルウェアが逆方向にもう一度呼び出されると、元のパスとパス ベースが再度適用されます。 ミドルウェアの処理の順序について詳しくは、ASP.NET Core のミドルウェア を参照してください。

プロキシがパスをトリミングする場合は (たとえば、/foo/api/1/api/1 に転送)、要求の PathBase プロパティを設定することによって、リダイレクトとリンクを修正します。

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/foo");
    return next();
});

プロキシがパス データを追加している場合は、StartsWithSegments を使用して Path プロパティに割り当てることで、パスの一部を破棄してリダイレクトとリンクを修正します。

app.Use((context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next();
});

別のヘッダー名を使用するプロキシの構成

プロキシがプロキシ アドレス/ポートおよび開始スキーム情報の転送に名前が X-Forwarded-ForX-Forwarded-Proto のヘッダーを使用しない場合、プロキシで使用されるヘッダー名と一致するように、ForwardedForHeaderNameForwardedProtoHeaderName のオプションを設定してください。

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-Proto_Header";
});

Linux および非 IIS のリバース プロキシのスキームを転送する

UseHttpsRedirection および UseHsts を呼び出すアプリが、Azure Linux App Service、Azure Linux 仮想マシン (VM)、または IIS 以外の他のリバース プロキシの背後に展開されている場合、サイトは無限ループに陥ります。 TLS はリバース プロキシによって終了され、Kestrel では正しい要求スキームが認識されません。 OAuth と OIDC もこの構成では正しくないリダイレクトを生成するため、失敗します。 UseIISIntegration は IIS の背後で実行される場合、Forwarded Headers Middleware を追加して構成しますが、Linux 用の対応する自動設定 (Apache または Nginx 統合) はありません。

非 IIS シナリオでプロキシからスキームを転送するには、Forwarded Headers Middleware を追加して構成します。 Startup.ConfigureServices で、次のコードを使用します。

// using Microsoft.AspNetCore.HttpOverrides;

if (string.Equals(
    Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"), 
    "true", StringComparison.OrdinalIgnoreCase))
{
    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | 
            ForwardedHeaders.XForwardedProto;
        // Only loopback proxies are allowed by default.
        // Clear that restriction because forwarders are enabled by explicit 
        // configuration.
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
}

証明書の転送

Azure

証明書の転送用に Azure App Service を構成するには、「Azure App Service に対する TLS 相互認証の構成」を参照してください。 次のガイダンスは、ASP.NET Core アプリの構成に関するものです。

Startup.Configure で、app.UseAuthentication(); の呼び出しの前に次のコードを追加します。

app.UseCertificateForwarding();

Azure で使用されるヘッダー名を指定するように、証明書転送ミドルウェアを構成します。 Startup.ConfigureServices に次のコードを追加し、ミドルウェアによる証明書作成の基になるヘッダーを構成します。

services.AddCertificateForwarding(options =>
    options.CertificateHeader = "X-ARR-ClientCert");

他の Web プロキシ

使用されているプロキシが、IIS でも、Azure App Service のアプリケーション要求ルーティング処理 (ARR) でもない場合は、HTTP ヘッダーで受け取った証明書を転送するように、プロキシを構成します。 Startup.Configure で、app.UseAuthentication(); の呼び出しの前に次のコードを追加します。

app.UseCertificateForwarding();

ヘッダー名を指定するように証明書の転送ミドルウェアを構成します。 Startup.ConfigureServices に次のコードを追加し、ミドルウェアによる証明書作成の基になるヘッダーを構成します。

services.AddCertificateForwarding(options =>
    options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");

プロキシで証明書が base64 エンコードではない場合は (Nginx の場合と同様)、HeaderConverter オプションを設定します。 Startup.ConfigureServices での次の例を検討してください。

services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
    options.HeaderConverter = (headerValue) => 
    {
        var clientCertificate = 
           /* some conversion logic to create an X509Certificate2 */
        return clientCertificate;
    }
});

トラブルシューティング

ヘッダーが意図したとおりに転送されない場合は、ログ を有効にします。 ログで問題のトラブルシューティングに十分な情報が提供されない場合は、サーバーが受信した要求ヘッダーを列挙します。 インライン ミドルウェアを使用し、アプリ応答に要求ヘッダーを書き込んだり、ヘッダーをログに記録したりします。

アプリの応答にヘッダーを書き込むには、Startup.Configure 内の UseForwardedHeaders の呼び出しの直後に、次の端末のインライン ミドルウェアを配置します。

app.Run(async (context) =>
{
    context.Response.ContentType = "text/plain";

    // Request method, scheme, and path
    await context.Response.WriteAsync(
        $"Request Method: {context.Request.Method}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Scheme: {context.Request.Scheme}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Path: {context.Request.Path}{Environment.NewLine}");

    // Headers
    await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");

    foreach (var header in context.Request.Headers)
    {
        await context.Response.WriteAsync($"{header.Key}: " +
            $"{header.Value}{Environment.NewLine}");
    }

    await context.Response.WriteAsync(Environment.NewLine);

    // Connection: RemoteIp
    await context.Response.WriteAsync(
        $"Request RemoteIp: {context.Connection.RemoteIpAddress}");
});

応答本文ではなく、ログに書き込むことができます。 ログに書き込むことで、デバッグしている間、サイトは正常に機能できます。

応答本文ではなく、ログに書き込むには:

  • Startup でログを作成する」に説明されているように、ILogger<Startup>Startup クラスに挿入します。
  • Startup.Configure 内で UseForwardedHeaders を呼び出した直後に次のインライン ミドルウェアを配置します。
app.Use(async (context, next) =>
{
    // Request method, scheme, and path
    _logger.LogDebug("Request Method: {Method}", context.Request.Method);
    _logger.LogDebug("Request Scheme: {Scheme}", context.Request.Scheme);
    _logger.LogDebug("Request Path: {Path}", context.Request.Path);

    // Headers
    foreach (var header in context.Request.Headers)
    {
        _logger.LogDebug("Header: {Key}: {Value}", header.Key, header.Value);
    }

    // Connection: RemoteIp
    _logger.LogDebug("Request RemoteIp: {RemoteIpAddress}", 
        context.Connection.RemoteIpAddress);

    await next();
});

処理時、X-Forwarded-{For|Proto|Host} 値は X-Original-{For|Proto|Host} に移動されます。 指定されたヘッダーに複数の値がある場合、Forwarded Headers Middleware では右から左の逆の順序でヘッダーが処理されます。 既定の ForwardLimit1 です。そのため、ForwardLimit の値を増やさない限り、ヘッダーの右端にある値のみが処理されます。

Forwarded Headers が処理される前に、要求の元のリモート IP アドレスが KnownProxies リストまたは KnownNetworks リストのエントリと一致する必要があります。 これにより、信頼されないプロキシからの転送を受け付けないことで、ヘッダーのスプーフィングを制限します。 不明なプロキシが検出されると、ログにプロキシのアドレスが示されます。

September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321

前の例では、10.0.0.100 がプロキシ サーバーです。 サーバーが信頼されているプロキシであれば、Startup.ConfigureServicesKnownProxies にサーバーの IP アドレスを追加します (あるいは、信頼されているネットワークを KnownNetworks に追加します)。 詳細については、「Forwarded Headers Middleware のオプション」セクションをご覧ください。

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

重要

信頼されているプロキシとネットワークにのみ、ヘッダーの転送を許可します。 それ以外に許可すると、IP なりすまし攻撃が可能になります。

その他の技術情報

ASP.NET Core の推奨される構成では、アプリは IIS/ASP.NET Core モジュール、Nginx、または Apache を使ってホストされます。 プロキシ サーバー、ロード バランサー、および他のネットワーク アプライアンスにより、要求に関する情報が、アプリに到達する前にわからなくなることがよくあります。

  • HTTPS 要求が HTTP によってプロキシされると、元のスキーム (HTTPS) は失われ、ヘッダーで転送される必要があります。
  • アプリは、インターネット上または社内ネットワーク上の本来の送信元ではなく、プロキシから要求を受信するため、送信元クライアントの IP アドレスもヘッダーで転送される必要があります。

この情報は、リダイレクト、認証、リンクの生成、ポリシーの評価、クライアントの位置情報など、要求の処理で重要になる可能性があります。

転送されるヘッダー

慣例によりは、プロキシは HTTP ヘッダーで情報を転送します。

Header 説明
X-Forwarded-For 要求を開始したクライアントと、プロキシ チェーン内の後続のプロキシに関する情報を保持します。 このパラメーターは、IP アドレス (および、必要に応じてポート番号) を含むことがあります。 プロキシ サーバーのチェーンにおいては、最初のパラメーターが、要求を最初に行ったクライアントを示します。 その後に、後続のプロキシの識別子が指定されています。 チェーン内の最後のプロキシは、パラメーターのリストには含まれません。 最後のプロキシの IP アドレスとオプションのポート番号は、トランスポート層のリモート IP アドレスとして入手できます。
X-Forwarded-Proto 開始スキーム (HTTP/HTTPS) の値です。 要求が複数のプロキシを通過している場合、値はスキームのリストである場合もあります。
X-Forwarded-Host ホスト ヘッダー フィールドの元の値です。 通常、プロキシはホスト ヘッダーを変更しません。 プロキシにおいてホスト ヘッダーが既知の適切な値であることが検証されない、または既知の適切な値に制限されないシステムに影響を与える、特権の昇格脆弱性については、マイクロソフト セキュリティ アドバイザリ CVE-2018-0787 をご覧ください。

Microsoft.AspNetCore.HttpOverrides パッケージの Forwarded Headers Middleware は、これらのヘッダーを読み取り、HttpContext の関連するフィールドに入力します。

ミドルウェアの更新:

Forwarded Headers Middleware の既定の設定は構成できます。 既定の設定は次のとおりです。

  • アプリと要求のソースの間には、"1 つのプロキシ" だけが存在します。
  • 既知のプロキシと既知のネットワークに対しては、ループバック アドレスのみが構成されています。
  • 転送されるヘッダーには X-Forwarded-ForX-Forwarded-Proto の名前が付けられます。

すべてのネットワーク アプライアンスが追加構成なしで X-Forwarded-For および X-Forwarded-Proto ヘッダーを追加するわけではありません。 プロキシされた要求がアプリに届いたときにこれらのヘッダーが含まれていない場合は、アプライアンスの製造元のガイダンスを参照してください。 アプライアンスが X-Forwarded-ForX-Forwarded-Proto 以外の名前を使用している場合は、アプライアンスで使用されるヘッダー名と一致するように ForwardedForHeaderNameForwardedProtoHeaderName のオプションを設定してください。 詳細については、「Forwarded Headers Middleware のオプション」と「別のヘッダー名を使用するプロキシの構成」を参照してください。

IIS/IIS Express と ASP.NET Core モジュール

アプリが IIS と ASP.NET Core モジュールの背後でアウト プロセスでホストされている場合、IIS Integration Middleware によって Forwarded Headers Middleware が既定で有効にされます。 転送されるヘッダーの信頼に関する問題のため (たとえば、IP スプーフィング)、Forwarded Headers Middleware はアクティブ化されると最初に、ASP.NET Core モジュールに固有の制限された構成を使って、ミドルウェア パイプライン内で実行します。 ミドルウェアは、X-Forwarded-For および X-Forwarded-Proto ヘッダーを転送するように構成され、単一の localhost プロキシに制限されます。 追加の構成が必要な場合は、「Forwarded Headers Middleware のオプション」をご覧ください。

プロキシ サーバーとロード バランサーの他のシナリオ

アウト プロセスでホストされている場合に IIS Integration が使われていない場合は、Forwarded Headers Middleware は既定で有効になりません。 UseForwardedHeaders を含む転送されたヘッダーをアプリで処理するためには、Forwarded Headers Middleware を有効にする必要があります。 ミドルウェアを有効にした後、ミドルウェアに対して ForwardedHeadersOptions が指定されていない場合の既定の ForwardedHeadersOptions.ForwardedHeadersForwardedHeaders.None です。

ForwardedHeadersOptions でミドルウェアを構成して、Startup.ConfigureServicesX-Forwarded-For および X-Forwarded-Proto ヘッダーを転送します。 他のミドルウェアを呼び出す前に、Startup.Configure 内で UseForwardedHeaders メソッドを呼び出します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = 
            ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseForwardedHeaders();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();
    // In ASP.NET Core 1.x, replace the following line with: app.UseIdentity();
    app.UseAuthentication();
    app.UseMvc();
}

注意

ForwardedHeadersOptionsStartup.ConfigureServices において指定されていない場合、または UseForwardedHeaders を使って拡張メソッドに直接渡されない場合、転送される既定のヘッダーは ForwardedHeaders.None です。 転送するヘッダーで ForwardedHeaders プロパティが構成されている必要があります。

Nginx の構成

X-Forwarded-For および X-Forwarded-Proto ヘッダーを転送する場合は、Nginx 搭載の Linux で ASP.NET Core をホストする を参照してください。 詳細については、「NGINX:Using the Forwarded header」 (NGINX: 転送されるヘッダーの使用) を参照してください。

Apache の構成

X-Forwarded-For は自動的に追加されます (「Apache Module mod_proxy:Reverse Proxy Request Headers」 (Apache モジュール mod_proxy: リバース プロキシがヘッダーを要求する) を参照)。 X-Forwarded-Proto ヘッダーを転送する方法については、Apache 搭載の Linux で ASP.NET Core をホストする を参照してください。

Forwarded Headers Middleware のオプション

ForwardedHeadersOptions は、Forwarded Headers Middleware の動作を制御します。 既定の値の変更例を次に示します。

  • 転送されるヘッダーのエントリ数を 2 に制限します。
  • 127.0.10.1 の既知のプロキシ アドレスを追加します。
  • 転送されるヘッダーの名前を既定の X-Forwarded-For から X-Forwarded-For-My-Custom-Header-Name に変更します。
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
    options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});
オプション 説明
AllowedHosts X-Forwarded-Host ヘッダーによるホストを指定した値に制限します。
  • 値は、大文字と小文字を無視して序数で比較されます。
  • ポート番号は除外されている必要があります。
  • リストが空の場合、すべてのホストが許可されます。
  • 最上位のワイルドカード * は、すべての空でないホストを許可します。
  • サブドメインのワイルドカードは許可されますが、ルート ドメインとは一致しません。 たとえば、*.contoso.com はサブドメイン foo.contoso.com と一致しますが、ルート ドメイン contoso.com とは一致しません。
  • Unicode のホスト名は許可されますが、一致のために Punycode に変換されます。
  • IPv6 アドレスは境界の角かっこを含む必要があり、従来の形式 (例: [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]) になっている必要があります。 IPv6 アドレスは、異なる形式間で論理的な等価性を調べるために特別な大文字小文字にはされず、正規化は行われません。
  • 許可されるホストの制限に失敗すると、サービスによって生成されたリンクを攻撃者が偽装する可能性があります。
既定値は空の IList<string> です。
ForwardedForHeaderName ForwardedHeadersDefaults.XForwardedForHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。 このオプションは、プロキシ/フォワーダーが X-Forwarded-For ヘッダーを使用していないが、情報の転送のためにその他のヘッダーを使用している場合に使用されます。

既定値は、X-Forwarded-For です。
ForwardedHeaders 処理する必要があるフォワーダーを識別します。 適用されるフィールドの一覧については、「ForwardedHeaders Enum」(ForwardedHeaders 列挙型) をご覧ください。 このプロパティに割り当てられる標準値は、ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto です。

既定値は ForwardedHeaders.None です。
ForwardedHostHeaderName ForwardedHeadersDefaults.XForwardedHostHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。 このオプションは、プロキシ/フォワーダーが X-Forwarded-Host ヘッダーを使用していないが、情報の転送のためにその他のヘッダーを使用している場合に使用されます。

既定値は、X-Forwarded-Host です。
ForwardedProtoHeaderName ForwardedHeadersDefaults.XForwardedProtoHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。 このオプションは、プロキシ/フォワーダーが X-Forwarded-Proto ヘッダーを使用していないが、情報の転送のためにその他のヘッダーを使用している場合に使用されます。

既定値は、X-Forwarded-Proto です。
ForwardLimit 処理されるヘッダー内のエントリの数を制限します。 制限を無効にするには null に設定しますが、これは KnownProxies または KnownNetworks が構成されている場合にのみ行う必要があります。 null 以外の値を設定することは、正しく構成されていないプロキシや、ネットワーク上のサイドチャネルから届く悪意のある要求から保護するための予防策となります (ただし保証はできません)。

Forwarded Headers Middleware では、ヘッダーが右から左の逆の順序で処理されます。 規定値 (1) を使う場合は、ForwardLimit の値を増やさない限りヘッダーの右端にある値のみが処理されます。

既定値は、1 です。
KnownNetworks 受け付けるヘッダーの転送元である既知のネットワークのアドレス範囲です。 クラスレス ドメイン間ルーティング (CIDR) の表記を使って、IP の範囲を指定します。

サーバーがデュアル モードのソケットを使用している場合、IPv4 アドレスは IPv6 形式で提供されます (たとえば、IPv4 での 10.0.0.1 は IPv6 で ::ffff:10.0.0.1 と表現されます)。 「IPAddress.MapToIPv6」をご覧ください。 この形式が必要かどうかを判断するには、「HttpContext.Connection.RemoteIpAddress」をご覧ください。 詳細については、「IPv6 アドレスとして表される IPv4 アドレスの構成」セクションをご覧ください。

既定値は、IPAddress.Loopback の単一エントリを含む IList<IPNetwork> です。
KnownProxies 受け付けるヘッダーの転送元である既知のプロキシのアドレスです。 IP アドレスの正確な一致を指定するには、KnownProxies を使います。

サーバーがデュアル モードのソケットを使用している場合、IPv4 アドレスは IPv6 形式で提供されます (たとえば、IPv4 での 10.0.0.1 は IPv6 で ::ffff:10.0.0.1 と表現されます)。 「IPAddress.MapToIPv6」をご覧ください。 この形式が必要かどうかを判断するには、「HttpContext.Connection.RemoteIpAddress」をご覧ください。 詳細については、「IPv6 アドレスとして表される IPv4 アドレスの構成」セクションをご覧ください。

既定値は、IPAddress.IPv6Loopback の単一エントリを含む IList<IPAddress> です。
OriginalForHeaderName ForwardedHeadersDefaults.XOriginalForHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。

既定値は、X-Original-For です。
OriginalHostHeaderName ForwardedHeadersDefaults.XOriginalHostHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。

既定値は、X-Original-Host です。
OriginalProtoHeaderName ForwardedHeadersDefaults.XOriginalProtoHeaderName によって指定されているヘッダーではなく、このプロパティによって指定されているヘッダーを使います。

既定値は、X-Original-Proto です。
RequireHeaderSymmetry 処理対象の ForwardedHeadersOptions.ForwardedHeaders の間でヘッダー値の数が同期していることを要求します。

ASP.NET Core 1.x での既定値は true です。 ASP.NET Core 2.0 以降での既定値は false です。

シナリオとユース ケース

転送されるヘッダーを追加することができず、すべての要求が安全な場合

アプリにプロキシされる要求に、転送されるヘッダーを追加できない場合があります。 すべてのパブリック外部要求が HTTPS を使うことをプロキシが強制している場合、すべての種類のミドルウェアを使う前に、Startup.Configure でスキームを手動で設定できます。

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
    return next();
});

このコードは、開発環境またはステージング環境の環境変数または他の構成設定によって、無効にすることができます。

要求のパスを変更するパス ベースとプロキシを処理する

一部のプロキシでは、パスはそのまま渡されますが、ルーティングが正しく機能するためには削除する必要のあるアプリ ベース パスが含まれます。 UsePathBaseExtensions.UsePathBase ミドルウェアは、パスを HttpRequest.PathHttpRequest.PathBase へのアプリ ベース パスに分割します。

/foo/foo/api/1 として渡されるプロキシ パスのアプリ ベース パスである場合、ミドルウェアは次のコマンドで Request.PathBase/fooに、Request.Path/api/1 に設定します。

app.UsePathBase("/foo");

ミドルウェアが逆方向にもう一度呼び出されると、元のパスとパス ベースが再度適用されます。 ミドルウェアの処理の順序について詳しくは、ASP.NET Core のミドルウェア を参照してください。

プロキシがパスをトリミングする場合は (たとえば、/foo/api/1/api/1 に転送)、要求の PathBase プロパティを設定することによって、リダイレクトとリンクを修正します。

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/foo");
    return next();
});

プロキシがパス データを追加している場合は、StartsWithSegments を使用して Path プロパティに割り当てることで、パスの一部を破棄してリダイレクトとリンクを修正します。

app.Use((context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next();
});

別のヘッダー名を使用するプロキシの構成

プロキシがプロキシ アドレス/ポートおよび開始スキーム情報の転送に名前が X-Forwarded-ForX-Forwarded-Proto のヘッダーを使用しない場合、プロキシで使用されるヘッダー名と一致するように、ForwardedForHeaderNameForwardedProtoHeaderName のオプションを設定してください。

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-Proto_Header";
});

IPv6 アドレスとして表される IPv4 アドレスの構成

サーバーがデュアル モードのソケットを使用している場合、IPv4 アドレスは IPv6 形式で提供されます (たとえば、IPv4 での 10.0.0.1 は IPv6 で ::ffff:10.0.0.1 または ::ffff:a00:1 と表現されます)。 「IPAddress.MapToIPv6」をご覧ください。 この形式が必要かどうかを判断するには、「HttpContext.Connection.RemoteIpAddress」をご覧ください。

次の例では、転送されるヘッダーを提供するネットワーク アドレスを、IPv6 形式の KnownNetworks リストに追加します。

IPv4 アドレス: 10.11.12.1/8

変換された IPv6 アドレス: ::ffff:10.11.12.1
変換されたプレフィックス長:104

16 進数形式でアドレスを指定することもできます (10.11.12.1 は IPv6 で ::ffff:0a0b:0c01 として表されます)。 IPv4 アドレスを IPv6 に変換するときは、CIDR プレフィックス長 (例では 8) に 96 を足して、追加の ::ffff: IPv6 プレフィックスを構成します (8 + 96 = 104)。

// To access IPNetwork and IPAddress, add the following namespaces:
// using System.Net;
// using Microsoft.AspNetCore.HttpOverrides;
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownNetworks.Add(new IPNetwork(
        IPAddress.Parse("::ffff:10.11.12.1"), 104));
});

Linux および非 IIS のリバース プロキシのスキームを転送する

UseHttpsRedirection および UseHsts を呼び出すアプリが、Azure Linux App Service、Azure Linux 仮想マシン (VM)、または IIS 以外の他のリバース プロキシの背後に展開されている場合、サイトは無限ループに陥ります。 TLS はリバース プロキシによって終了され、Kestrel では正しい要求スキームが認識されません。 OAuth と OIDC もこの構成では正しくないリダイレクトを生成するため、失敗します。 UseIISIntegration は IIS の背後で実行される場合、Forwarded Headers Middleware を追加して構成しますが、Linux 用の対応する自動設定 (Apache または Nginx 統合) はありません。

非 IIS シナリオでプロキシからスキームを転送するには、Forwarded Headers Middleware を追加して構成します。 Startup.ConfigureServices で、次のコードを使用します。

// using Microsoft.AspNetCore.HttpOverrides;

if (string.Equals(
    Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"), 
    "true", StringComparison.OrdinalIgnoreCase))
{
    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | 
            ForwardedHeaders.XForwardedProto;
        // Only loopback proxies are allowed by default.
        // Clear that restriction because forwarders are enabled by explicit 
        // configuration.
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
}

トラブルシューティング

ヘッダーが意図したとおりに転送されない場合は、ログ を有効にします。 ログで問題のトラブルシューティングに十分な情報が提供されない場合は、サーバーが受信した要求ヘッダーを列挙します。 インライン ミドルウェアを使用し、アプリ応答に要求ヘッダーを書き込んだり、ヘッダーをログに記録したりします。

アプリの応答にヘッダーを書き込むには、Startup.Configure 内の UseForwardedHeaders の呼び出しの直後に、次の端末のインライン ミドルウェアを配置します。

app.Run(async (context) =>
{
    context.Response.ContentType = "text/plain";

    // Request method, scheme, and path
    await context.Response.WriteAsync(
        $"Request Method: {context.Request.Method}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Scheme: {context.Request.Scheme}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Path: {context.Request.Path}{Environment.NewLine}");

    // Headers
    await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");

    foreach (var header in context.Request.Headers)
    {
        await context.Response.WriteAsync($"{header.Key}: " +
            $"{header.Value}{Environment.NewLine}");
    }

    await context.Response.WriteAsync(Environment.NewLine);

    // Connection: RemoteIp
    await context.Response.WriteAsync(
        $"Request RemoteIp: {context.Connection.RemoteIpAddress}");
});

応答本文ではなく、ログに書き込むことができます。 ログに書き込むことで、デバッグしている間、サイトは正常に機能できます。

応答本文ではなく、ログに書き込むには:

  • Startup でログを作成する」に説明されているように、ILogger<Startup>Startup クラスに挿入します。
  • Startup.Configure 内で UseForwardedHeaders を呼び出した直後に次のインライン ミドルウェアを配置します。
app.Use(async (context, next) =>
{
    // Request method, scheme, and path
    _logger.LogDebug("Request Method: {Method}", context.Request.Method);
    _logger.LogDebug("Request Scheme: {Scheme}", context.Request.Scheme);
    _logger.LogDebug("Request Path: {Path}", context.Request.Path);

    // Headers
    foreach (var header in context.Request.Headers)
    {
        _logger.LogDebug("Header: {Key}: {Value}", header.Key, header.Value);
    }

    // Connection: RemoteIp
    _logger.LogDebug("Request RemoteIp: {RemoteIpAddress}", 
        context.Connection.RemoteIpAddress);

    await next();
});

処理時、X-Forwarded-{For|Proto|Host} 値は X-Original-{For|Proto|Host} に移動されます。 指定されたヘッダーに複数の値がある場合、Forwarded Headers Middleware では右から左の逆の順序でヘッダーが処理されます。 既定の ForwardLimit1 です。そのため、ForwardLimit の値を増やさない限り、ヘッダーの右端にある値のみが処理されます。

Forwarded Headers が処理される前に、要求の元のリモート IP アドレスが KnownProxies リストまたは KnownNetworks リストのエントリと一致する必要があります。 これにより、信頼されないプロキシからの転送を受け付けないことで、ヘッダーのスプーフィングを制限します。 不明なプロキシが検出されると、ログにプロキシのアドレスが示されます。

September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321

前の例では、10.0.0.100 がプロキシ サーバーです。 サーバーが信頼されているプロキシであれば、Startup.ConfigureServicesKnownProxies にサーバーの IP アドレスを追加します (あるいは、信頼されているネットワークを KnownNetworks に追加します)。 詳細については、「Forwarded Headers Middleware のオプション」セクションをご覧ください。

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

重要

信頼されているプロキシとネットワークにのみ、ヘッダーの転送を許可します。 それ以外に許可すると、IP なりすまし攻撃が可能になります。

その他の技術情報