From e9c90c0380bcd70890a4309618149095b42b2769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB?= Date: Fri, 19 Jun 2026 00:16:51 +0400 Subject: [PATCH 1/2] fix: resolve forwarded headers section --- README.md | 6 +++--- .../ForwardedHeadersConfiguration.cs | 16 ++++++++++++++- .../ServiceCollectionExtensions.cs | 2 +- .../ForwardedHeadersConfigurationTests.cs | 20 +++++++++++++++++++ .../ServiceCollectionExtensionsTests.cs | 19 ++++++++++++++++++ 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 101456b..abe1e1a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ using PANiXiDA.Core.Presentation.Http.DependencyInjection; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddHttp(builder.Configuration.GetSection("ForwardedHeaders")); +builder.Services.AddHttp(builder.Configuration); var app = builder.Build(); @@ -68,7 +68,7 @@ ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto ``` -Additional values can be bound from the standard ASP.NET Core `ForwardedHeadersOptions` model by passing a configuration section to `AddHttp`. +Additional values can be bound from the standard ASP.NET Core `ForwardedHeadersOptions` model by adding a `ForwardedHeaders` section to the application configuration. ```json { @@ -88,7 +88,7 @@ For advanced scenarios, configure `ForwardedHeadersOptions` directly after `AddH ```csharp using Microsoft.AspNetCore.HttpOverrides; -builder.Services.AddHttp(builder.Configuration.GetSection("ForwardedHeaders")); +builder.Services.AddHttp(builder.Configuration); builder.Services.Configure(options => { diff --git a/src/PANiXiDA.Core.Presentation.Http/Configurations/ForwardedHeadersConfiguration.cs b/src/PANiXiDA.Core.Presentation.Http/Configurations/ForwardedHeadersConfiguration.cs index 987110f..4b4fffd 100644 --- a/src/PANiXiDA.Core.Presentation.Http/Configurations/ForwardedHeadersConfiguration.cs +++ b/src/PANiXiDA.Core.Presentation.Http/Configurations/ForwardedHeadersConfiguration.cs @@ -7,6 +7,8 @@ namespace PANiXiDA.Core.Presentation.Http.Configurations; internal static class ForwardedHeadersConfiguration { + private const string SectionName = "ForwardedHeaders"; + internal static IServiceCollection AddForwardedHeadersConfiguration( this IServiceCollection services, IConfiguration? configuration) @@ -21,7 +23,7 @@ internal static IServiceCollection AddForwardedHeadersConfiguration( if (configuration is not null) { - services.Configure(configuration); + services.Configure(ResolveConfiguration(configuration)); } return services; @@ -33,4 +35,16 @@ internal static WebApplication UseForwardedHeadersConfiguration(this WebApplicat return app; } + + private static IConfiguration ResolveConfiguration(IConfiguration configuration) + { + IConfigurationSection forwardedHeadersSection = configuration.GetSection(SectionName); + + if (forwardedHeadersSection.GetChildren().Any()) + { + return forwardedHeadersSection; + } + + return configuration; + } } diff --git a/src/PANiXiDA.Core.Presentation.Http/DependencyInjection/ServiceCollectionExtensions.cs b/src/PANiXiDA.Core.Presentation.Http/DependencyInjection/ServiceCollectionExtensions.cs index ee37c40..bb8c0df 100644 --- a/src/PANiXiDA.Core.Presentation.Http/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/PANiXiDA.Core.Presentation.Http/DependencyInjection/ServiceCollectionExtensions.cs @@ -19,7 +19,7 @@ public static class ServiceCollectionExtensions /// Registers the default HTTP presentation services, including API versioning, OpenAPI, Problem Details, exception handling, validation, health checks, and forwarded headers. /// /// The application service collection. - /// The forwarded headers configuration section, or to use defaults. + /// The application configuration. The standard ForwardedHeaders section is used when present. Pass a custom forwarded headers section or to use defaults. /// The original service collection for further configuration. public static IServiceCollection AddHttp( this IServiceCollection services, diff --git a/tests/PANiXiDA.Core.Presentation.Http.UnitTests/Configurations/ForwardedHeadersConfigurationTests.cs b/tests/PANiXiDA.Core.Presentation.Http.UnitTests/Configurations/ForwardedHeadersConfigurationTests.cs index 65ebdaf..02b4a68 100644 --- a/tests/PANiXiDA.Core.Presentation.Http.UnitTests/Configurations/ForwardedHeadersConfigurationTests.cs +++ b/tests/PANiXiDA.Core.Presentation.Http.UnitTests/Configurations/ForwardedHeadersConfigurationTests.cs @@ -51,6 +51,26 @@ public void AddForwardedHeadersConfiguration_ShouldBindStandardOptionsFromConfig options.AllowedHosts.ShouldBe(["api.example.test"]); } + [Fact(DisplayName = "ForwardedHeaders configuration binds standard section from root configuration")] + public void AddForwardedHeadersConfiguration_ShouldBindStandardSectionFromRootConfiguration() + { + var services = new ServiceCollection(); + var configuration = CreateConfiguration(new Dictionary + { + ["ForwardedHeaders:ForwardedHeaders"] = nameof(ForwardedHeaders.XForwardedProto), + ["ForwardedHeaders:ForwardLimit"] = "3", + ["ForwardedHeaders:AllowedHosts:0"] = "api.example.test" + }); + + services.AddForwardedHeadersConfiguration(configuration); + + var options = CreateOptions(services); + + options.ForwardedHeaders.ShouldBe(ForwardedHeaders.XForwardedProto); + options.ForwardLimit.ShouldBe(3); + options.AllowedHosts.ShouldBe(["api.example.test"]); + } + [Fact(DisplayName = "ForwardedHeaders configuration accepts a custom section")] public void AddForwardedHeadersConfiguration_ShouldBindStandardOptionsFromCustomSection() { diff --git a/tests/PANiXiDA.Core.Presentation.Http.UnitTests/DependencyInjection/ServiceCollectionExtensionsTests.cs b/tests/PANiXiDA.Core.Presentation.Http.UnitTests/DependencyInjection/ServiceCollectionExtensionsTests.cs index dc7452b..2fb5d8c 100644 --- a/tests/PANiXiDA.Core.Presentation.Http.UnitTests/DependencyInjection/ServiceCollectionExtensionsTests.cs +++ b/tests/PANiXiDA.Core.Presentation.Http.UnitTests/DependencyInjection/ServiceCollectionExtensionsTests.cs @@ -48,6 +48,25 @@ public void AddHttp_ShouldApplyForwardedHeadersConfiguration() options.ForwardLimit.ShouldBe(4); } + [Fact(DisplayName = "AddHttp applies ForwardedHeaders section from root configuration")] + public void AddHttp_ShouldApplyForwardedHeadersSectionFromRootConfiguration() + { + var services = new ServiceCollection(); + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["ForwardedHeaders:" + nameof(ForwardedHeadersOptions.ForwardLimit)] = "6" + }) + .Build(); + + services.AddHttp(configuration); + + using var serviceProvider = services.BuildServiceProvider(); + var options = serviceProvider.GetRequiredService>().Value; + + options.ForwardLimit.ShouldBe(6); + } + [Fact(DisplayName = "AddHttp registers health check services")] public void AddHttp_ShouldRegisterHealthCheckServices() { From a4bc26bafcab41d8d5bc5a1f3235679c822855d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB?= Date: Fri, 19 Jun 2026 00:21:08 +0400 Subject: [PATCH 2/2] fix: trust forwarded headers by default --- README.md | 8 +++++--- .../Configurations/ForwardedHeadersConfiguration.cs | 2 ++ .../Configurations/ForwardedHeadersConfigurationTests.cs | 5 ++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index abe1e1a..b09a00c 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto ``` +The package also clears the default loopback-only `KnownIPNetworks` and `KnownProxies` restrictions so applications behind Kubernetes ingress or Gateway API proxies can process forwarded headers without per-service proxy registration. + Additional values can be bound from the standard ASP.NET Core `ForwardedHeadersOptions` model by adding a `ForwardedHeaders` section to the application configuration. ```json @@ -83,17 +85,17 @@ Additional values can be bound from the standard ASP.NET Core `ForwardedHeadersO } ``` -For advanced scenarios, configure `ForwardedHeadersOptions` directly after `AddHttp`. +For stricter trust boundaries, configure `ForwardedHeadersOptions` directly after `AddHttp`. ```csharp using Microsoft.AspNetCore.HttpOverrides; +using System.Net; builder.Services.AddHttp(builder.Configuration); builder.Services.Configure(options => { - options.KnownProxies.Clear(); - options.KnownIPNetworks.Clear(); + options.KnownProxies.Add(IPAddress.Parse("10.0.0.10")); }); ``` diff --git a/src/PANiXiDA.Core.Presentation.Http/Configurations/ForwardedHeadersConfiguration.cs b/src/PANiXiDA.Core.Presentation.Http/Configurations/ForwardedHeadersConfiguration.cs index 4b4fffd..c9d0490 100644 --- a/src/PANiXiDA.Core.Presentation.Http/Configurations/ForwardedHeadersConfiguration.cs +++ b/src/PANiXiDA.Core.Presentation.Http/Configurations/ForwardedHeadersConfiguration.cs @@ -19,6 +19,8 @@ internal static IServiceCollection AddForwardedHeadersConfiguration( ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto; + options.KnownIPNetworks.Clear(); + options.KnownProxies.Clear(); }); if (configuration is not null) diff --git a/tests/PANiXiDA.Core.Presentation.Http.UnitTests/Configurations/ForwardedHeadersConfigurationTests.cs b/tests/PANiXiDA.Core.Presentation.Http.UnitTests/Configurations/ForwardedHeadersConfigurationTests.cs index 02b4a68..77a259d 100644 --- a/tests/PANiXiDA.Core.Presentation.Http.UnitTests/Configurations/ForwardedHeadersConfigurationTests.cs +++ b/tests/PANiXiDA.Core.Presentation.Http.UnitTests/Configurations/ForwardedHeadersConfigurationTests.cs @@ -14,7 +14,6 @@ public sealed class ForwardedHeadersConfigurationTests public void AddForwardedHeadersConfiguration_ShouldUseDefaultHeaders() { var services = new ServiceCollection(); - var defaultOptions = new ForwardedHeadersOptions(); var result = services.AddForwardedHeadersConfiguration(configuration: null); @@ -25,8 +24,8 @@ public void AddForwardedHeadersConfiguration_ShouldUseDefaultHeaders() ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto); - options.KnownIPNetworks.Count.ShouldBe(defaultOptions.KnownIPNetworks.Count); - options.KnownProxies.Count.ShouldBe(defaultOptions.KnownProxies.Count); + options.KnownIPNetworks.ShouldBeEmpty(); + options.KnownProxies.ShouldBeEmpty(); } [Fact(DisplayName = "ForwardedHeaders configuration binds standard options from configuration")]