From 11aea5e6bc895f19810d770e7fc226c681567039 Mon Sep 17 00:00:00 2001 From: Daniel Espinosa Date: Thu, 19 Dec 2024 09:45:44 -0800 Subject: [PATCH 1/6] updated to accept forwarded headers --- src/Aspire.Dashboard/DashboardWebApplication.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Aspire.Dashboard/DashboardWebApplication.cs b/src/Aspire.Dashboard/DashboardWebApplication.cs index 24f1a7a7df..1d273aeaa4 100644 --- a/src/Aspire.Dashboard/DashboardWebApplication.cs +++ b/src/Aspire.Dashboard/DashboardWebApplication.cs @@ -28,6 +28,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -267,6 +268,11 @@ public DashboardWebApplication( options.Cookie.Name = DashboardAntiForgeryCookieName; }); + builder.Services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.All; + }); + _app = builder.Build(); _dashboardOptionsMonitor = _app.Services.GetRequiredService>(); From 86de0917a9fd76f2bfc07bf7a340091aba187dfd Mon Sep 17 00:00:00 2001 From: Daniel Espinosa Date: Thu, 19 Dec 2024 10:16:34 -0800 Subject: [PATCH 2/6] added check if developer has enabled forwarded headers --- src/Aspire.Dashboard/DashboardWebApplication.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Aspire.Dashboard/DashboardWebApplication.cs b/src/Aspire.Dashboard/DashboardWebApplication.cs index 1d273aeaa4..2b963c6c25 100644 --- a/src/Aspire.Dashboard/DashboardWebApplication.cs +++ b/src/Aspire.Dashboard/DashboardWebApplication.cs @@ -268,10 +268,13 @@ public DashboardWebApplication( options.Cookie.Name = DashboardAntiForgeryCookieName; }); - builder.Services.Configure(options => + if (string.Equals(builder.Configuration["FORWARDEDHEADERS_ENABLED"], "true", StringComparison.OrdinalIgnoreCase)) { - options.ForwardedHeaders = ForwardedHeaders.All; - }); + builder.Services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.All; + }); + } _app = builder.Build(); From 3e1c620ab459852c2f0fdf9d8f5585375f7dd6b1 Mon Sep 17 00:00:00 2001 From: Daniel Espinosa Date: Thu, 19 Dec 2024 10:19:03 -0800 Subject: [PATCH 3/6] changed if statement to match other if statements --- src/Aspire.Dashboard/DashboardWebApplication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Dashboard/DashboardWebApplication.cs b/src/Aspire.Dashboard/DashboardWebApplication.cs index 2b963c6c25..8ac0c3c0ad 100644 --- a/src/Aspire.Dashboard/DashboardWebApplication.cs +++ b/src/Aspire.Dashboard/DashboardWebApplication.cs @@ -268,7 +268,7 @@ public DashboardWebApplication( options.Cookie.Name = DashboardAntiForgeryCookieName; }); - if (string.Equals(builder.Configuration["FORWARDEDHEADERS_ENABLED"], "true", StringComparison.OrdinalIgnoreCase)) + if (builder.Configuration.GetBool("ASPNETCORE_FORWARDEDHEADERS_ENABLED") ?? false) { builder.Services.Configure(options => { From 3bc4ede2463854e3f0b159261ac7f6cb4d7b61bf Mon Sep 17 00:00:00 2001 From: Daniel Espinosa Date: Sat, 21 Dec 2024 14:04:21 -0800 Subject: [PATCH 4/6] forward only host and protocol headers --- src/Aspire.Dashboard/DashboardWebApplication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Dashboard/DashboardWebApplication.cs b/src/Aspire.Dashboard/DashboardWebApplication.cs index 8ac0c3c0ad..5107903cc1 100644 --- a/src/Aspire.Dashboard/DashboardWebApplication.cs +++ b/src/Aspire.Dashboard/DashboardWebApplication.cs @@ -272,7 +272,7 @@ public DashboardWebApplication( { builder.Services.Configure(options => { - options.ForwardedHeaders = ForwardedHeaders.All; + options.ForwardedHeaders = ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto; }); } From 2b5051b750e292c5f831e430c5f38850b5459bef Mon Sep 17 00:00:00 2001 From: Daniel Espinosa Date: Mon, 23 Dec 2024 16:18:31 -0800 Subject: [PATCH 5/6] added simple unit tests for forwardedheaders --- .../ForwardedHeadersMiddlewareTests.cs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/Aspire.Dashboard.Tests/Middleware/ForwardedHeadersMiddlewareTests.cs diff --git a/tests/Aspire.Dashboard.Tests/Middleware/ForwardedHeadersMiddlewareTests.cs b/tests/Aspire.Dashboard.Tests/Middleware/ForwardedHeadersMiddlewareTests.cs new file mode 100644 index 0000000000..aa876a3fb6 --- /dev/null +++ b/tests/Aspire.Dashboard.Tests/Middleware/ForwardedHeadersMiddlewareTests.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Aspire.Dashboard.Tests.Middleware; + +public class ForwardedHeadersMiddlewareTests +{ + [Fact] + public async Task Validate_ForwardedHeaders_MapToHttpRequest() + { + using var host = await SetUpHostAsync(true); + var client = host.GetTestClient(); + client.DefaultRequestHeaders.Add("X-Forwarded-Host", "example.com"); + client.DefaultRequestHeaders.Add("X-Forwarded-Proto", "https"); + + var response = await client.GetAsync("/"); + var responseContent = await response.Content.ReadAsStringAsync(); + + Assert.NotNull(responseContent); + Assert.Equal("https://example.com", responseContent); + } + + // Create a test that doesn't use the ForwardedHeaders middleware to ensure that the test fails. + [Fact] + public async Task Validate_ForwardedHeaders_NotMapToHttpRequest() + { + using var host = await SetUpHostAsync(); + var client = host.GetTestClient(); + client.DefaultRequestHeaders.Add("X-Forwarded-Host", "example.com"); + client.DefaultRequestHeaders.Add("X-Forwarded-Proto", "https"); + var response = await client.GetAsync("/"); + var responseContent = await response.Content.ReadAsStringAsync(); + + Assert.NotNull(responseContent); + Assert.Equal("http://localhost", responseContent); + } + + private static async Task SetUpHostAsync(bool mapForwardedHeaders = false) + { + return await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder + .UseTestServer() + .ConfigureServices(services => + { + services.AddRouting(); + if (mapForwardedHeaders) + { + services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto; + }); + } + }) + .Configure(app => + { + app.UseRouting(); + if (mapForwardedHeaders) + { + app.UseForwardedHeaders(); + } + + app.Run(async context => + { + await context.Response.WriteAsync($"{context.Request.Scheme}://{context.Request.Host}"); + }); + }); + }) + .StartAsync(); + } +} From 87fe61834437446b5efb99e27c526a3651bd9783 Mon Sep 17 00:00:00 2001 From: Daniel Espinosa Date: Mon, 23 Dec 2024 16:42:02 -0800 Subject: [PATCH 6/6] Added integration test --- .../Integration/StartupTests.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs b/tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs index ad3b9e5ab1..33e3ac138d 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs @@ -8,6 +8,7 @@ using Aspire.Hosting; using Google.Protobuf; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -713,6 +714,25 @@ public async Task Configuration_CorsNoOtlpHttpEndpoint_Error() s => Assert.Contains(DashboardConfigNames.DashboardOtlpHttpUrlName.ConfigKey, s)); } + [Fact] + public async Task Configuration_ForwardedHeaders_OverrideDefaults() + { + // Arrange & Act + await using var app = IntegrationTestHelpers.CreateDashboardWebApplication(testOutputHelper, + additionalConfiguration: data => + { + data["ASPNETCORE_FORWARDEDHEADERS_ENABLED"] = "true"; + }, + clearLogFilterRules: false); + + // Assert + await app.StartAsync().DefaultTimeout(); + + var options = app.Services.GetRequiredService>(); + + Assert.Equal(ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, options.Value.ForwardedHeaders); + } + private static void AssertIPv4OrIPv6Endpoint(Func endPointAccessor) { // Check that the address is IPv4 or IPv6 any.