Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -68,7 +68,9 @@ ForwardedHeaders.XForwardedHost |
ForwardedHeaders.XForwardedProto
```

Additional values can be bound from the standard ASP.NET Core `ForwardedHeadersOptions` model by passing a configuration section to `AddHttp`.
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
{
Expand All @@ -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.GetSection("ForwardedHeaders"));
builder.Services.AddHttp(builder.Configuration);

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Clear();
options.KnownIPNetworks.Clear();
options.KnownProxies.Add(IPAddress.Parse("10.0.0.10"));
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -17,11 +19,13 @@ internal static IServiceCollection AddForwardedHeadersConfiguration(
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedHost |
ForwardedHeaders.XForwardedProto;
options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
});

if (configuration is not null)
{
services.Configure<ForwardedHeadersOptions>(configuration);
services.Configure<ForwardedHeadersOptions>(ResolveConfiguration(configuration));
}

return services;
Expand All @@ -33,4 +37,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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
/// <param name="services">The application service collection.</param>
/// <param name="configuration">The forwarded headers configuration section, or <see langword="null"/> to use defaults.</param>
/// <param name="configuration">The application configuration. The standard <c>ForwardedHeaders</c> section is used when present. Pass a custom forwarded headers section or <see langword="null"/> to use defaults.</param>
/// <returns>The original service collection for further configuration.</returns>
public static IServiceCollection AddHttp(
this IServiceCollection services,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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")]
Expand All @@ -51,6 +50,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<string, string?>
{
["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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string?>
{
["ForwardedHeaders:" + nameof(ForwardedHeadersOptions.ForwardLimit)] = "6"
})
.Build();

services.AddHttp(configuration);

using var serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetRequiredService<IOptions<ForwardedHeadersOptions>>().Value;

options.ForwardLimit.ShouldBe(6);
}

[Fact(DisplayName = "AddHttp registers health check services")]
public void AddHttp_ShouldRegisterHealthCheckServices()
{
Expand Down