diff --git a/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs b/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs index 8109ca39b323..e96819550a48 100644 --- a/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs +++ b/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs @@ -24,7 +24,24 @@ public void Configure(ForwardedHeadersOptions options) return; } - options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + var forwardedHeaders = _configuration["ForwardedHeaders_Headers"]; + if (string.IsNullOrEmpty(forwardedHeaders)) + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + } + else + { + var headers = ForwardedHeaders.None; + foreach (var headerName in forwardedHeaders.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + if (Enum.TryParse(headerName, true, out var headerValue)) + { + headers |= headerValue; + } + } + options.ForwardedHeaders = headers; + } + // Only loopback proxies are allowed by default. Clear that restriction because forwarders are // being enabled by explicit configuration. #pragma warning disable ASPDEPR005 // KnownNetworks is obsolete @@ -32,5 +49,23 @@ public void Configure(ForwardedHeadersOptions options) #pragma warning restore ASPDEPR005 // KnownNetworks is obsolete options.KnownIPNetworks.Clear(); options.KnownProxies.Clear(); + + var knownNetworks = _configuration["ForwardedHeaders_KnownIPNetworks"]?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []; + foreach (var network in knownNetworks) + { + if (System.Net.IPNetwork.TryParse(network, out var ipNetwork)) + { + options.KnownIPNetworks.Add(ipNetwork); + } + } + + var knownProxies = _configuration["ForwardedHeaders_KnownProxies"]?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []; + foreach (var proxy in knownProxies) + { + if (System.Net.IPAddress.TryParse(proxy, out var ipAddress)) + { + options.KnownProxies.Add(ipAddress); + } + } } } diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs index 8205e1459a08..75b977724d90 100644 --- a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs @@ -6,10 +6,11 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HostFiltering; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.TestHost; -using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -83,6 +84,81 @@ public async Task WebHostConfiguration_EnablesForwardedHeadersFromConfig() result.EnsureSuccessStatusCode(); } + [Fact] + public async Task WebHostConfiguration_EnablesForwardedHeaders_CustomHeaders_FromConfig() + { + using var host = WebHost.CreateDefaultBuilder() + .ConfigureAppConfiguration(configBuilder => + { + configBuilder.AddInMemoryCollection(new[] + { + new KeyValuePair("FORWARDEDHEADERS_ENABLED", "true" ), + new KeyValuePair("FORWARDEDHEADERS_HEADERS", "All" ), + }); + }) + .UseTestServer() + .Configure(app => + { + Assert.True(app.Properties.ContainsKey("ForwardedHeadersAdded"), "Forwarded Headers"); + app.Run(context => + { + Assert.Equal("https", context.Request.Scheme); + Assert.Equal("/test", context.Request.PathBase.Value); + return Task.CompletedTask; + }); + }).Build(); + + await host.StartAsync(); + var client = host.GetTestClient(); + client.DefaultRequestHeaders.Add("x-forwarded-proto", "https"); + client.DefaultRequestHeaders.Add("x-forwarded-prefix", "/test"); + var result = await client.GetAsync("http://localhost/"); + result.EnsureSuccessStatusCode(); + } + + [Fact] + public async Task WebHostConfiguration_EnablesForwardedHeaders_CustomConfig() + { + using var host = WebHost.CreateDefaultBuilder() + .ConfigureAppConfiguration(configBuilder => + { + configBuilder.AddInMemoryCollection(new[] + { + new KeyValuePair("FORWARDEDHEADERS_ENABLED", "true" ), + new KeyValuePair("FORWARDEDHEADERS_HEADERS", "XForwardedHost,XForwardedProto,XForwardedFor123" ), + new KeyValuePair("ForwardedHeaders_KnownIPNetworks", "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"), + new KeyValuePair("ForwardedHeaders_KnownProxies", "127.0.0.1") + }); + }) + .UseTestServer() + .Configure(app => + { + Assert.True(app.Properties.ContainsKey("ForwardedHeadersAdded"), "Forwarded Headers"); + app.Run(context => + { + Assert.Equal("https", context.Request.Scheme); + return Task.CompletedTask; + }); + }) + .Build(); + await host.StartAsync(); + var client = host.GetTestClient(); + client.DefaultRequestHeaders.Add("x-forwarded-proto", "https"); + var result = await client.GetAsync("http://localhost/"); + result.EnsureSuccessStatusCode(); + + var forwardedHeadersOptions = host.Services.GetRequiredService>().Value; + Assert.NotNull(forwardedHeadersOptions); + Assert.Equal( + ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto, + forwardedHeadersOptions.ForwardedHeaders + ); + Assert.NotEmpty(forwardedHeadersOptions.KnownIPNetworks); + Assert.Contains(forwardedHeadersOptions.KnownIPNetworks, network => network.Contains(System.Net.IPAddress.Parse("192.168.0.123"))); + Assert.NotEmpty(forwardedHeadersOptions.KnownProxies); + Assert.Contains(forwardedHeadersOptions.KnownProxies, System.Net.IPAddress.IsLoopback); + } + [Fact] public void CreateDefaultBuilder_RegistersRouting() {