From 0e7eaded375e481e4e9ba296e0c9d428a0928b73 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Fri, 8 Mar 2024 17:04:32 -0800 Subject: [PATCH] Enable forwarded headers middleware when publishing projects by default Fixes #290 --- .../DisableForwardedHeadersAnnotation.cs | 12 +++++ .../ProjectResourceBuilderExtensions.cs | 28 +++++++++++- .../ManifestGenerationTests.cs | 44 +++++++++++++++++++ .../ProjectResourceTests.cs | 3 +- 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 src/Aspire.Hosting/ApplicationModel/DisableForwardedHeadersAnnotation.cs diff --git a/src/Aspire.Hosting/ApplicationModel/DisableForwardedHeadersAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/DisableForwardedHeadersAnnotation.cs new file mode 100644 index 0000000000..f76703584e --- /dev/null +++ b/src/Aspire.Hosting/ApplicationModel/DisableForwardedHeadersAnnotation.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Type = {GetType().Name,nq}")] +internal sealed class DisableForwardedHeadersAnnotation : IResourceAnnotation +{ + +} diff --git a/src/Aspire.Hosting/Extensions/ProjectResourceBuilderExtensions.cs b/src/Aspire.Hosting/Extensions/ProjectResourceBuilderExtensions.cs index 3bf42ad93e..cf5245dac4 100644 --- a/src/Aspire.Hosting/Extensions/ProjectResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/Extensions/ProjectResourceBuilderExtensions.cs @@ -13,6 +13,8 @@ namespace Aspire.Hosting; /// public static class ProjectResourceBuilderExtensions { + private const string AspNetCoreForwaredHeadersEnabledVariableName = "ASPNETCORE_FORWARDEDHEADERS_ENABLED"; + /// /// Adds a .NET project to the application model. By default, this will exist in a Projects namespace. e.g. Projects.MyProject. /// If the project is not in a Projects namespace, make sure a project reference is added from the AppHost project to the target project. @@ -92,6 +94,19 @@ private static IResourceBuilder WithProjectDefaults(this IResou builder.WithOtlpExporter(); builder.ConfigureConsoleLogs(); + var projectResource = builder.Resource; + + if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode) + { + builder.WithEnvironment(context => + { + if (!projectResource.Annotations.OfType().Any()) + { + context.EnvironmentVariables[AspNetCoreForwaredHeadersEnabledVariableName] = "true"; + } + }); + } + if (excludeLaunchProfile) { builder.WithAnnotation(new ExcludeLaunchProfileAnnotation()); @@ -103,8 +118,6 @@ private static IResourceBuilder WithProjectDefaults(this IResou builder.WithAnnotation(new LaunchProfileAnnotation(launchProfileName)); } - var projectResource = builder.Resource; - if (builder.ApplicationBuilder.ExecutionContext.IsRunMode) { // Automatically add EndpointAnnotation to project resources based on ApplicationUrl set in the launch profile. @@ -208,6 +221,17 @@ public static IResourceBuilder ExcludeLaunchProfile(this IResou throw new InvalidOperationException("This API is replaced by the AddProject overload that accepts a launchProfileName. Null means exclude launch profile. Method will be removed by GA."); } + /// + /// Configures the project to disable forwarded headers when being published. + /// + /// The project resource builder. + /// A reference to the . + public static IResourceBuilder DisableForwadedHeadersOnPublish(this IResourceBuilder builder) + { + builder.WithAnnotation(ResourceAnnotationMutationBehavior.Replace); + return builder; + } + private static bool IsKestrelHttp2ConfigurationPresent(ProjectResource projectResource) { var projectMetadata = projectResource.GetProjectMetadata(); diff --git a/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs b/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs index 9f0845c0d8..67d86082c1 100644 --- a/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs +++ b/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs @@ -136,6 +136,50 @@ public void ExcludeLaunchProfileOmitsBindings() "Service has no bindings because they weren't populated from the launch profile."); } + [Fact] + public void ProjectResourceEmitsForwardedHeadersEnvironmentVariableByDefault() + { + var appBuilder = DistributedApplication.CreateBuilder(new DistributedApplicationOptions + { Args = GetManifestArgs(), DisableDashboard = true, AssemblyName = typeof(ManifestGenerationTests).Assembly.FullName }); + + appBuilder.AddProject("servicea", launchProfileName: null); + + appBuilder.Services.AddKeyedSingleton("manifest"); + + var program = appBuilder.Build(); + var publisher = program.Services.GetManifestPublisher(); + + program.Run(); + + var resources = publisher.ManifestDocument.RootElement.GetProperty("resources"); + + Assert.Equal("true", + resources.GetProperty("servicea").GetProperty("env").GetProperty("ASPNETCORE_FORWARDEDHEADERS_ENABLED").GetString()); + } + + [Fact] + public void DisableForwardedHeadersOmitsEnvironmentVariable() + { + var appBuilder = DistributedApplication.CreateBuilder(new DistributedApplicationOptions + { Args = GetManifestArgs(), DisableDashboard = true, AssemblyName = typeof(ManifestGenerationTests).Assembly.FullName }); + + appBuilder.AddProject("servicea", launchProfileName: null) + .DisableForwadedHeadersOnPublish(); + + appBuilder.Services.AddKeyedSingleton("manifest"); + + var program = appBuilder.Build(); + var publisher = program.Services.GetManifestPublisher(); + + program.Run(); + + var resources = publisher.ManifestDocument.RootElement.GetProperty("resources"); + + Assert.False( + resources.GetProperty("servicea").GetProperty("env").TryGetProperty("ASPNETCORE_FORWARDEDHEADERS_ENABLED", out _), + "Service has no ASPNETCORE_FORWARDEDHEADERS_ENABLED environment variable because it was disabled."); + } + [Fact] public void EnsureContainerWithEndpointsEmitsContainerPort() { diff --git a/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs b/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs index b7e15f8a05..1ff75cc237 100644 --- a/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs +++ b/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs @@ -199,7 +199,8 @@ public async Task VerifyManifest() "path": "net8.0/another-path", "env": { "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", - "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true" + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", + "ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true" }, "bindings": { "http": {