Настройка ASP.NET Core для работы с прокси-серверами и подсистемами балансировки нагрузки

Автор: Крис Росс (Chris Ross)

В рекомендуемой конфигурации для ASP.NET Core приложение размещается с использованием модуля ASP.NET Core (ANCM) для IIS, Nginx или Apache. Прокси-серверы, подсистемы балансировки нагрузки и другие сетевые устройства часто скрывают сведения о запросах, еще не достигших целевого приложения.

  • Если прокси-сервер передает HTTPS-запросы через протокол HTTP, исходная схема (HTTPS) теряется и ее нужно дополнительно передать в заголовке.
  • Так как приложение получает запрос от прокси-сервера, а не от правильного источника в Интернете или корпоративной сети, исходный IP-адрес клиента также нужно передать в заголовке.

Эти сведения могут быть важны при обработке запросов, например для перенаправления, аутентификации, создания ссылок, оценки политик и геолокации клиентов.

Приложения, предназначенные для запуска в веб-ферме, должны читать узел ASP.NET Core в веб-ферме.

Перенаправленные заголовки

По действующему соглашению прокси-серверы передают данные в заголовках HTTP.

Верхний колонтитул Description
X-Forwarded-For (XFF) Содержит сведения о клиенте, который создал этот запрос, и обо всех предыдущих узлах в цепочке прокси-серверов. Этот параметр может содержать IP-адреса (и номера портов, если потребуется). В цепочке прокси-серверов первый параметр обозначает клиента, отправившего исходный запрос. За ним следуют идентификаторы всех последующих прокси-серверов. В списке параметров отсутствует последний прокси-сервер в цепочке. IP-адрес последнего прокси-сервера (и номер порта, если нужно) поступает в формате удаленного IP-адреса на транспортном уровне.
X-Forwarded-Proto (XFP) Значение исходной схемы передачи данных (HTTP/HTTPS). Здесь может быть указан целый список схем, если запрос прошел через несколько прокси-серверов.
X-Forwarded-Host (XFH) Исходное значение поля Host в заголовке запроса. Обычно прокси-серверы не изменяют заголовок Host. В рекомендациях Макрософт по безопасности CVE-2018-0787 представлены сведения о связанной с повышением привилегий уязвимости, которая затрагивает системы, где прокси-сервер не проверяет заголовок Host и не ограничивает его значения известными допустимыми значениями.

ПО промежуточного слоя перенаправления заголовков (ForwardedHeadersMiddleware) считывает эти заголовки и заполняет соответствующие поля в HttpContext.

Это ПО промежуточного слоя обновляет следующие сведения:

  • HttpContext.Connection.RemoteIpAddress: задайте значение заголовка X-Forwarded-For . Дополнительные параметры влияют на значение RemoteIpAddress, которое устанавливает ПО промежуточного слоя. Дополнительные сведения см. в разделе Параметры ПО промежуточного слоя перенаправления заголовков. Используемые значения удаляются из X-Forwarded-For, а старое значение HttpContext.Connection.RemoteIpAddress сохраняется в X-Original-For. Примечание. Этот процесс может повторяться несколько раз при наличии нескольких значений X-Forwarded-For/Proto/Host, что приводит к перемещению X-Original-*нескольких значений, включая исходный RemoteIpAddress/Host/Scheme.
  • HttpContext.Request.Scheme: задайте значение заголовка X-Forwarded-Proto . Используемое значение удаляется из X-Forwarded-Proto, а старое значение HttpContext.Request.Scheme сохраняется в X-Original-Proto.
  • HttpContext.Request.Host: задайте значение заголовка X-Forwarded-Host . Используемое значение удаляется из X-Forwarded-Host, а старое значение HttpContext.Request.Host сохраняется в X-Original-Host.

Дополнительные сведения об описанных выше обновлениях см. в этом описании проблемы на GitHub.

Для ПО промежуточного слоя перенаправления заголовков можно настроить параметры по умолчанию. Для параметров по умолчанию:

  • Между приложением и источником запросов существует только один прокси.
  • Для известных прокси-серверов и известных сетей настраиваются только петлевые адреса.
  • Перенаправленным заголовками заданы имена X-Forwarded-For и X-Forwarded-Proto.
  • Параметр ForwardedHeaders имеет значение ForwardedHeaders.None, в качестве значения этого параметра необходимо установить желаемые серверы пересылки, чтобы включить ПО промежуточного слоя.

Не все сетевые устройства добавляют заголовки X-Forwarded-For и X-Forwarded-Proto без дополнительной настройки. Если запросы, поступающие в приложение через прокси-сервер, не содержат эти заголовки, обратитесь к руководствам изготовителя устройства. Если устройство использует имена заголовков, отличные от X-Forwarded-For и X-Forwarded-Proto, задайте параметрам ForwardedForHeaderName и ForwardedProtoHeaderName значения, совпадающие с именами заголовков, используемыми устройством. Дополнительные сведения см. в разделах Параметры ПО промежуточного слоя перенаправления заголовков и Конфигурация для прокси-сервера, использующего другие имена заголовков.

IIS и IIS Express с модулем ASP.NET Core

ПО промежуточного слоя интеграции IIS по умолчанию включает ПО промежуточного слоя перенаправления заголовков при размещении приложения вне процесса за пределами IIS и модуля ASP.NET Core (ANCM) для IIS. При использовании модуля ASP.NET Core ПО промежуточного слоя перенаправления заголовков настраивается так, чтобы оно запускалось первым в конвейере ПО промежуточного слоя и использовало специальную ограниченную конфигурацию, так как перенаправленные заголовки создают дополнительные проблемы с доверием (например, проблему IP-спуфинга). В ПО промежуточного слоя настраивается пересылка заголовков X-Forwarded-For и X-Forwarded-Proto, и оно может работать только с одним локальным прокси-сервером. Если вам нужны другие настройки, изучите раздел Параметры ПО промежуточного слоя перенаправления заголовков.

Другие сценарии использования прокси-сервера и подсистемы балансировки нагрузки

Если интеграция IIS не используется при размещении вне процесса, ПО промежуточного слоя перенаправления заголовков не включается по умолчанию. ПО промежуточного слоя перенаправления заголовков нужно включить, чтобы приложение могло обрабатывать перенаправленные заголовки с UseForwardedHeaders. После включения ПО промежуточного слоя, если не задан параметр ForwardedHeadersOptions, для свойства ForwardedHeadersOptions.ForwardedHeaders по умолчанию устанавливается значение ForwardedHeaders.None.

В ПО промежуточного слоя с ForwardedHeadersOptions настройте перенаправление заголовков X-Forwarded-For и X-Forwarded-Proto.

Порядок ПО промежуточного слоя перенаправления заголовков

ПО промежуточного слоя перенаправления заголовков должно выполняться до другого ПО промежуточного слоя. Такой порядок гарантирует, что ПО промежуточного слоя, полагающееся на сведения о перенаправленных заголовках, может использовать значения заголовков для обработки. ПО промежуточного слоя перенаправления заголовков может выполняться после диагностики и обработки ошибок, однако его необходимо выполнить перед вызовом UseHsts.

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseForwardedHeaders();
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseForwardedHeaders();
}

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Кроме того, можно вызвать UseForwardedHeaders перед диагностикой:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Примечание.

Если класс ForwardedHeadersOptions не указан или не применяется непосредственно в методе расширения с UseForwardedHeaders, по умолчанию будут перенаправляться заголовки ForwardedHeaders.None. Свойство ForwardedHeaders должно быть настроено с заголовками для перенаправления.

Конфигурация Nginx

Сведения о перенаправлении заголовков X-Forwarded-For и X-Forwarded-Proto см. в разделе Размещение ASP.NET Core в Linux с Nginx. Дополнительные сведения см. в разделе NGINX. Использование перенаправленного заголовка.

Конфигурация Apache

X-Forwarded-For добавляется автоматически. Дополнительные сведения см. в разделе Модуль mod_proxy в Apache: заголовки обратных прокси-запросов. Сведения о том, как перенаправить заголовок X-Forwarded-Proto, см. в разделе Размещение ASP.NET Core в Linux с Apache.

Параметры ПО промежуточного слоя перенаправления заголовков

ForwardedHeadersOptions управляет поведением ПО промежуточного слоя перенаправления заголовков. В следующем примере показано изменение значений по умолчанию.

  • Ограничивает количество записей в перенаправленных заголовках до 2.
  • Добавляет известный адрес прокси сервера 127.0.10.1.
  • Изменяет имя перенаправленного заголовка с заданного по умолчанию X-Forwarded-For на X-Forwarded-For-My-Custom-Header-Name.
using System.Net;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.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";
});

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();
Вариант Описание
AllowedHosts Ограничивает узлы в заголовке X-Forwarded-Host списком указанных значений.
  • Значения сравниваются по порядковым номерам без учета регистра.
  • Номера портов указывать не нужно.
  • Если список пуст, разрешены все узлы.
  • Подстановочный знак верхнего уровня * разрешает все непустые значения узлов.
  • Подстановочные знаки поддоменов допускаются, но не могут указывать на корневой домен. Например, *.contoso.com соответствует поддомену foo.contoso.com, но не корневому домену contoso.com.
  • Имена узлов в Юникоде разрешены, но для сопоставления они преобразуются в Punycode.
  • IPv6-адреса должны включать ограничивающие квадратные скобки и иметь стандартный формат (например, [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]). IPv6-адреса не приводятся к специальным правилам регистра для логического соответствия разных форматов, и к ним не применяется канонизация.
  • Если список разрешенных узлов не будет ограничен, злоумышленник сможет подделать ссылки, создаваемые службой.
Значение по умолчанию — пустой объект IList<string>.
ForwardedForHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XForwardedForHeaderName. Этот параметр применяется в случае, если для перенаправления данных прокси-сервер или сервер пересылки не используют заголовок X-Forwarded-For, а используют другой заголовок.

Значение по умолчанию — X-Forwarded-For.
ForwardedHeaders Определяет, какие серверы пересылки будут обрабатываться. В разделе Перечисление 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, послужит мерой предосторожности (но не гарантией) для защиты от неправильной настройки прокси-серверов и вредоносных запросов, поступающих по сторонним каналам сети.

ПО промежуточного слоя перенаправления заголовков обрабатывает их в обратном порядке: справа налево. Если используется значение по умолчанию (1), обрабатывается только крайнее правое значение из заголовков, если не увеличить значение ForwardLimit.

Значение по умолчанию — 1.
KnownNetworks Диапазоны адресов известных сетей, от которых можно принимать перенаправленные заголовки. Диапазоны IP-адресов указываются в нотации CIDR.

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 выглядит так: ::ffff:10.0.0.1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress.

Значение по умолчанию — IList<IPNetwork> с одной записью для IPAddress.Loopback.
KnownProxies Адреса известных прокси-серверов, от которых можно принимать перенаправленные заголовки. Используйте KnownProxies, чтобы указать точные IP-адреса.

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 выглядит так: ::ffff:10.0.0.1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress.

Значение по умолчанию — IList<IPAddress> с одной записью для IPAddress.IPv6Loopback.
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 для всех внешних запросов, ее можно задать вручную перед использованием любого ПО промежуточного слоя:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();

В промежуточной среде или среде разработки этот код можно отключить с помощью переменной среды или другого параметра конфигурации:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Работа с базовым путем и прокси-серверами, которые изменяют путь запроса

Некоторые прокси-серверы передают путь, но добавляют к нему базовый путь приложения, который нужно удалять для правильной работы маршрутизации. ПО промежуточного слоя UsePathBaseExtensions.UsePathBase разделяет путь на два сегмента: HttpRequest.Path и базовый путь приложения HttpRequest.PathBase.

Если /foo является базовым путем приложения в пути /foo/api/1, полученном от прокси-сервера, это ПО промежуточного слоя устанавливает для Request.PathBase значение /foo, а для Request.Path значение /api/1 с помощью следующей команды:

app.UsePathBase("/foo");
// ...
app.UseRouting();

Примечание.

При использовании WebApplication (см. раздел Миграция с ASP.NET Core 5.0 на 6.0) метод app.UseRouting должен вызываться после UsePathBase, чтобы ПО промежуточного слоя для маршрутизации получало измененный путь до сопоставления маршрутов. В противном случае маршруты будут сопоставляться, прежде чем UsePathBase перезапишет путь, как описано в статьях Порядок ПО промежуточного слоя и Маршрутизация.

Исходный путь и базовый путь подставляются в прежнем виде, когда создается обратный запрос к ПО промежуточного слоя. Дополнительные сведения о порядке обработки для ПО промежуточного слоя см. в статье ПО промежуточного слоя ASP.NET Core.

Если прокси-сервер усекает путь (например, перенаправляет /foo/api/1 в /api/1), такие перенаправления и ссылки можно исправить, задав для запроса свойство PathBase:

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

Если прокси-сервер добавляет к пути данные, лишнюю часть можно отсечь с помощью StartsWithSegments и присвоить правильное значение свойству Path:

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

    return next(context);
});

Конфигурация для прокси-сервера, использующего другие имена заголовков

Если для перенаправления адреса или порта прокси-сервера и сведений об исходной схеме прокси-сервер не использует заголовки с именами X-Forwarded-For и X-Forwarded-Proto, задайте параметрам ForwardedForHeaderName и ForwardedProtoHeaderName значения, совпадающие с именами заголовков, используемыми прокси-сервером:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "HeaderNamUsedByProxy_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "HeaderNamUsedByProxy_X-Forwarded-Proto_Header";
});

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Переадресация схемы для Linux и обратных прокси-серверов не IIS

Приложения, вызывающие UseHttpsRedirection и UseHsts, помещают сайт в бесконечный цикл при развертывании в Службе приложений Azure Linux, виртуальной машине Linux в Azure или за любыми другими обратными прокси-серверами, помимо IIS. TLS завершается обратным прокси-сервером, и Kestrel не знает о правильной схеме запроса. OAuth и OIDC также не работают в этой конфигурации, поскольку создают неверные перенаправления. UseIISIntegration добавляет и настраивает ПО промежуточного слоя переадресованных заголовков при работе за IIS, но для Linux (интеграция с Apache или Nginx) нет аналогичной автоматической конфигурации.

Для переадресации схемы с прокси-сервера, когда используется не IIS, включите ПО промежуточного слоя перенаправленных заголовков, задав для ASPNETCORE_FORWARDEDHEADERS_ENABLED значение true. Предупреждение. Этот флаг использует параметры, предназначенные для облачных сред, и не включает такие функции, как KnownProxies option, для ограничения IP-адресов, с которых принимается переадресация.

Переадресация сертификатов

Azure

Сведения о настройке Службы приложений Azure для переадресации сертификатов см. в статье Configure TLS mutual authentication for Azure App Service (Настройка взаимной проверки подлинности TLS для Службы приложений Azure). Приведенные ниже инструкции применимы к настройке приложения ASP.NET Core.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
    options.CertificateHeader = "X-ARR-ClientCert");

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

Другие веб-прокси

Если вы используете прокси-сервер не IIS или маршрутизацию запросов приложений (ARR) Службы приложений Azure, настройте прокси-сервер для переадресации сертификата, полученного в заголовке HTTP.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
    options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

Если прокси-сервер не выполняет шифрование сертификата в кодировке base64 (как в случае с Nginx), задайте параметр HeaderConverter. Рассмотрим следующий пример:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
    options.HeaderConverter = (headerValue) =>
    {
        // Conversion logic to create an X509Certificate2.
        var clientCertificate = ConversionLogic.CreateAnX509Certificate2();
        return clientCertificate;
    };
});

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

Устранение неполадок

Если заголовки перенаправляются не так, как ожидалось, включите ведение журнала на уровне debug и ведение журналов HTTP-запросов. UseHttpLogging нужно вызывать после UseForwardedHeaders:

using Microsoft.AspNetCore.HttpLogging;
using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddHttpLogging(options =>
{
    options.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders;
});

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

var app = builder.Build();

app.UseForwardedHeaders();
app.UseHttpLogging();

app.Use(async (context, next) =>
{
    // Connection: RemoteIp
    app.Logger.LogInformation("Request RemoteIp: {RemoteIpAddress}",
        context.Connection.RemoteIpAddress);

    await next(context);
});

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Если в некотором заголовке содержится несколько значений, ПО промежуточного слоя перенаправления заголовков обрабатывает их в обратном порядке: справа налево. По умолчанию ForwardLimit имеет значение 1 (один), а значит обрабатывается только крайнее правое значение из заголовков, если не увеличить значение ForwardLimit.

Удаленный IP-адрес из исходного запроса должен совпадать с записью в списке KnownProxies или KnownNetworks, чтобы происходила обработка заголовков переадресации. Это позволяет избежать некоторых методов спуфинга, отклоняя запросы от ненадежных прокси-серверов пересылки. В журнале указываются адреса неизвестных обнаруженных прокси-серверов:

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

В приведенном выше примере указан прокси-сервер с адресом 10.0.0.100. Если сервер является доверенным прокси-сервером, добавьте его IP-адрес в KnownProxies (или добавьте доверенную сеть в KnownNetworks). Дополнительные сведения см. в разделе о параметрах ПО промежуточного слоя перенаправления заголовков.

using Microsoft.AspNetCore.HttpOverrides;
using System.Net;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseForwardedHeaders();
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseForwardedHeaders();
}

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Чтобы отобразить журналы, добавьте "Microsoft.AspNetCore.HttpLogging": "Information" в файл appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.HttpLogging": "Information"
    }
  }
}

Внимание

Переадресацию заголовков следует разрешить только доверенным прокси-серверам и сетям. В противном случае будут возможны атаки подмены IP-адресов.

Дополнительные ресурсы

В рекомендуемой конфигурации для ASP.NET Core приложение размещается с использованием модуля ASP.NET Core для IIS, Nginx или Apache. Прокси-серверы, подсистемы балансировки нагрузки и другие сетевые устройства часто скрывают сведения о запросах, еще не достигших целевого приложения.

  • Если прокси-сервер передает HTTPS-запросы через протокол HTTP, исходная схема (HTTPS) теряется и ее нужно дополнительно передать в заголовке.
  • Так как приложение получает запрос от прокси-сервера, а не от правильного источника в Интернете или корпоративной сети, исходный IP-адрес клиента также нужно передать в заголовке.

Эти сведения могут быть важны при обработке запросов, например для перенаправления, аутентификации, создания ссылок, оценки политик и геолокации клиентов.

Перенаправленные заголовки

По действующему соглашению прокси-серверы передают данные в заголовках HTTP.

Верхний колонтитул Description
X-Forwarded-For Содержит сведения о клиенте, который создал этот запрос, и обо всех предыдущих узлах в цепочке прокси-серверов. Этот параметр может содержать IP-адреса (и номера портов, если потребуется). В цепочке прокси-серверов первый параметр обозначает клиента, отправившего исходный запрос. За ним следуют идентификаторы всех последующих прокси-серверов. В списке параметров отсутствует последний прокси-сервер в цепочке. IP-адрес последнего прокси-сервера (и номер порта, если нужно) поступает в формате удаленного IP-адреса на транспортном уровне.
X-Forwarded-Proto Значение исходной схемы передачи данных (HTTP/HTTPS). Здесь может быть указан целый список схем, если запрос прошел через несколько прокси-серверов.
X-Forwarded-Host Исходное значение поля Host в заголовке запроса. Обычно прокси-серверы не изменяют заголовок Host. В рекомендациях Макрософт по безопасности CVE-2018-0787 представлены сведения о связанной с повышением привилегий уязвимости, которая затрагивает системы, где прокси-сервер не проверяет заголовок Host и не ограничивает его значения известными допустимыми значениями.

ПО промежуточного слоя перенаправления заголовков (ForwardedHeadersMiddleware) считывает эти заголовки и заполняет соответствующие поля HttpContext.

Это ПО промежуточного слоя обновляет следующие сведения:

  • HttpContext.Connection.RemoteIpAddress — устанавливается на основе значения заголовка X-Forwarded-For. Дополнительные параметры влияют на значение RemoteIpAddress, которое устанавливает ПО промежуточного слоя. Дополнительные сведения см. в разделе Параметры ПО промежуточного слоя перенаправления заголовков. Используемые значения удаляются из X-Forwarded-For, а старые значения сохраняются в X-Original-For. Тот же шаблон применяется к другим заголовкам Host и Proto.
  • HttpContext.Request.Scheme — устанавливается на основе значения заголовка X-Forwarded-Proto.
  • HttpContext.Request.Host — устанавливается на основе значения заголовка X-Forwarded-Host.

Дополнительные сведения об описанных выше обновлениях см. в этом описании проблемы на GitHub.

Для ПО промежуточного слоя перенаправления заголовков можно настроить параметры по умолчанию. Для параметров по умолчанию:

  • Между приложением и источником запросов существует только один прокси.
  • Для известных прокси-серверов и известных сетей настраиваются только петлевые адреса.
  • Перенаправленным заголовками заданы имена X-Forwarded-For и X-Forwarded-Proto.
  • Параметр ForwardedHeaders имеет значение ForwardedHeaders.None, в качестве значения этого параметра необходимо установить желаемые серверы пересылки, чтобы включить ПО промежуточного слоя.

Не все сетевые устройства добавляют заголовки X-Forwarded-For и X-Forwarded-Proto без дополнительной настройки. Если запросы, поступающие в приложение через прокси-сервер, не содержат эти заголовки, обратитесь к руководствам изготовителя устройства. Если устройство использует имена заголовков, отличные от X-Forwarded-For и X-Forwarded-Proto, задайте параметрам ForwardedForHeaderName и ForwardedProtoHeaderName значения, совпадающие с именами заголовков, используемыми устройством. Дополнительные сведения см. в разделах Параметры ПО промежуточного слоя перенаправления заголовков и Конфигурация для прокси-сервера, использующего другие имена заголовков.

IIS и IIS Express с модулем ASP.NET Core

ПО промежуточного слоя интеграции IIS по умолчанию включает ПО промежуточного слоя перенаправления заголовков при размещении приложения вне процесса за IIS и модулем ASP.NET Core. При использовании модуля ASP.NET Core ПО промежуточного слоя перенаправления заголовков настраивается так, чтобы оно запускалось первым в конвейере ПО промежуточного слоя и использовало специальную ограниченную конфигурацию, так как перенаправленные заголовки создают дополнительные проблемы с доверием (например, проблему IP-спуфинга). В ПО промежуточного слоя настраивается пересылка заголовков X-Forwarded-For и X-Forwarded-Proto, и оно может работать только с одним локальным прокси-сервером. Если вам нужны другие настройки, изучите раздел Параметры ПО промежуточного слоя перенаправления заголовков.

Другие сценарии использования прокси-сервера и подсистемы балансировки нагрузки

Если интеграция IIS не используется при размещении вне процесса, ПО промежуточного слоя перенаправления заголовков не включается по умолчанию. ПО промежуточного слоя перенаправления заголовков нужно включить, чтобы приложение могло обрабатывать перенаправленные заголовки с UseForwardedHeaders. После включения ПО промежуточного слоя, если не задан параметр ForwardedHeadersOptions, для свойства ForwardedHeadersOptions.ForwardedHeaders по умолчанию устанавливается значение ForwardedHeaders.None.

В ПО промежуточного слоя с ForwardedHeadersOptions настройте перенаправление заголовков X-Forwarded-For и X-Forwarded-Proto в Startup.ConfigureServices.

Порядок ПО промежуточного слоя перенаправления заголовков

ПО промежуточного слоя перенаправления заголовков должно выполняться до другого ПО промежуточного слоя. Такой порядок гарантирует, что ПО промежуточного слоя, полагающееся на сведения о перенаправленных заголовках, может использовать значения заголовков для обработки. ПО промежуточного слоя перенаправления заголовков может выполняться после диагностики и обработки ошибок, однако его необходимо выполнить перед вызовом 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?}");
    });
}

Примечание.

Если класс ForwardedHeadersOptions не указан в Startup.ConfigureServices или непосредственно в методе расширения с UseForwardedHeaders, по умолчанию будут перенаправляться заголовки ForwardedHeaders.None. Свойство ForwardedHeaders должно быть настроено с заголовками для перенаправления.

Конфигурация Nginx

Сведения о перенаправлении заголовков X-Forwarded-For и X-Forwarded-Proto см. в разделе Размещение ASP.NET Core в Linux с Nginx. Дополнительные сведения см. в разделе NGINX. Использование перенаправленного заголовка.

Конфигурация Apache

X-Forwarded-For добавляется автоматически (см. раздел Apache Module mod_proxy: заголовки обратных прокси-запросов). Сведения о том, как перенаправить заголовок X-Forwarded-Proto, см. в разделе Размещение ASP.NET Core в Linux с Apache.

Параметры ПО промежуточного слоя перенаправления заголовков

Параметр ForwardedHeadersOptions позволяет управлять поведением ПО промежуточного слоя перенаправления заголовков. В следующем примере показано изменение значений по умолчанию.

  • Ограничьте количество записей в перенаправленных заголовках до 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.
  • Имена узлов в Юникоде разрешены, но для сопоставления они преобразуются в Punycode.
  • IPv6-адреса должны включать ограничивающие квадратные скобки и иметь стандартный формат (например, [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]). IPv6-адреса не приводятся к специальным правилам регистра для логического соответствия разных форматов, и к ним не применяется канонизация.
  • Если список разрешенных узлов не будет ограничен, злоумышленник сможет подделать ссылки, создаваемые службой.
Значение по умолчанию — пустой объект IList<string>.
ForwardedForHeaderName Заголовок, заданный этим свойством, используется вместо заголовка, указанного в параметре ForwardedHeadersDefaults.XForwardedForHeaderName. Этот параметр применяется в случае, если для перенаправления данных прокси-сервер или сервер пересылки не используют заголовок X-Forwarded-For, а используют другой заголовок.

Значение по умолчанию — X-Forwarded-For.
ForwardedHeaders Определяет, какие серверы пересылки будут обрабатываться. В разделе Перечисление 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, послужит мерой предосторожности (но не гарантией) для защиты от неправильной настройки прокси-серверов и вредоносных запросов, поступающих по сторонним каналам сети.

ПО промежуточного слоя перенаправления заголовков обрабатывает их в обратном порядке: справа налево. Если используется значение по умолчанию (1), обрабатывается только крайнее правое значение из заголовков, если не увеличить значение ForwardLimit.

Значение по умолчанию — 1.
KnownNetworks Диапазоны адресов известных сетей, от которых можно принимать перенаправленные заголовки. Диапазоны IP-адресов указываются в нотации CIDR.

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 выглядит так: ::ffff:10.0.0.1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress.

Значение по умолчанию — IList<IPNetwork> с одной записью для IPAddress.Loopback.
KnownProxies Адреса известных прокси-серверов, от которых можно принимать перенаправленные заголовки. Используйте KnownProxies, чтобы указать точные IP-адреса.

Если сервер использует сокеты с двумя режимами, IPv4-адреса указываются в формате IPv6 (например, IPv4-адрес 10.0.0.1 в формате IPv6 выглядит так: ::ffff:10.0.0.1). См. статью о методе IPAddress.MapToIPv6. Определите, нужно ли использовать этот формат, ознакомившись со статьей о свойстве HttpContext.Connection.RemoteIpAddress.

Значение по умолчанию — IList<IPAddress> с одной записью для IPAddress.IPv6Loopback.
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.Path и базовый путь приложения HttpRequest.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-For и X-Forwarded-Proto, задайте параметрам ForwardedForHeaderName и ForwardedProtoHeaderName значения, совпадающие с именами заголовков, используемыми прокси-сервером:

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, виртуальной машине Linux в Azure или за любыми другими обратными прокси-серверами, помимо IIS. TLS завершается обратным прокси-сервером, и Kestrel не знает о правильной схеме запроса. OAuth и OIDC также не работают в этой конфигурации, поскольку создают неверные перенаправления. UseIISIntegration добавляет и настраивает ПО промежуточного слоя переадресованных заголовков при работе за IIS, но для Linux (интеграция с Apache или Nginx) нет аналогичной автоматической конфигурации.

Для переадресации схемы с прокси-сервера, когда используется не IIS, добавьте и настройте ПО промежуточного слоя перенаправленных заголовков. В 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 для переадресации сертификатов см. в статье Configure TLS mutual authentication for Azure App Service (Настройка взаимной проверки подлинности TLS для Службы приложений Azure). Приведенные ниже инструкции применимы к настройке приложения ASP.NET Core.

В Startup.Configure добавьте указанный ниже код перед вызовом app.UseAuthentication();:

app.UseCertificateForwarding();

Настройте ПО промежуточного слоя переадресации сертификатов, чтобы указать имя заголовка, который использует Azure. В Startup.ConfigureServices добавьте указанный ниже код, чтобы настроить заголовок, из которого ПО промежуточного слоя создает сертификат:

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

Другие веб-прокси

Если вы используете прокси-сервер не IIS или маршрутизацию запросов приложений (ARR) Службы приложений Azure, настройте прокси-сервер для переадресации сертификата, полученного в заголовке 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;
    }
});

Устранение неполадок

Если заголовки перенаправляются не так, как ожидалось, включите ведение журнала. Если журналы не содержат достаточно информации для устранения неполадок, просмотрите список заголовков в запросе, полученном сервером. Используйте встроенное ПО промежуточного слоя для записи заголовков запроса в ответ приложения или для сохранения заголовков в журнал.

Чтобы записать заголовки в ответ приложения, используйте следующее встроенное ПО промежуточного слоя на терминале сразу после вызова UseForwardedHeaders в Startup.Configure:

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}");
});

Можно сделать запись в журналы, а не в текст ответа. Запись в журналы позволяет сайту нормально работать во время отладки.

Для записи в журналы, а не в текст ответа, сделайте следующее:

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}. Если в некотором заголовке содержится несколько значений, ПО промежуточного слоя перенаправления заголовков обрабатывает их в обратном порядке: справа налево. По умолчанию ForwardLimit имеет значение 1 (один), а значит обрабатывается только крайнее правое значение из заголовков, если не увеличить значение ForwardLimit.

Удаленный IP-адрес из исходного запроса должен совпадать с записью в списке KnownProxies или KnownNetworks, чтобы происходила обработка заголовков переадресации. Это позволяет избежать некоторых методов спуфинга, отклоняя запросы от ненадежных прокси-серверов пересылки. В журнале указываются адреса неизвестных обнаруженных прокси-серверов:

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

В приведенном выше примере указан прокси-сервер с адресом 10.0.0.100. Если сервер является доверенным прокси-сервером, добавьте его IP-адрес в KnownProxies (или добавьте доверенную сеть в KnownNetworks) в файле Startup.ConfigureServices. Дополнительные сведения см. в разделе о параметрах ПО промежуточного слоя перенаправления заголовков.

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

Внимание

Переадресацию заголовков следует разрешить только доверенным прокси-серверам и сетям. В противном случае будут возможны атаки подмены IP-адресов.

Дополнительные ресурсы