Skip to content

Commit b670246

Browse files
authored
Add ForwardedHeaders to CreateDefaultBuilder #4135 (#10273)
1 parent 8b99354 commit b670246

14 files changed

+283
-67
lines changed

src/DefaultBuilder/samples/SampleApp/DefaultBuilder.SampleApp.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
<Project Sdk="Microsoft.NET.Sdk.Web">
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
44
<TargetFramework>netcoreapp3.0</TargetFramework>
55
<UserSecretsId>aspnetcore-MetaPackagesSampleApp-20170406180413</UserSecretsId>
6+
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
67
</PropertyGroup>
78

89
<ItemGroup>

src/DefaultBuilder/samples/SampleApp/Program.cs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -16,19 +16,16 @@ public class Program
1616
{
1717
public static void Main(string[] args)
1818
{
19-
HelloWorld();
20-
21-
CustomUrl();
22-
23-
CustomRouter();
24-
25-
CustomApplicationBuilder();
26-
27-
StartupClass(args);
28-
29-
HostBuilderWithWebHost(args);
19+
CreateHostBuilder(args).Build().Run();
3020
}
3121

22+
public static IHostBuilder CreateHostBuilder(string[] args) =>
23+
Host.CreateDefaultBuilder(args)
24+
.ConfigureWebHostDefaults(webBuilder =>
25+
{
26+
webBuilder.UseStartup<Startup>();
27+
});
28+
3229
private static void HelloWorld()
3330
{
3431
using (WebHost.Start(context => context.Response.WriteAsync("Hello, World!")))

src/DefaultBuilder/samples/SampleApp/Startup.cs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.Linq;
6+
using System.Threading.Tasks;
47
using Microsoft.AspNetCore.Builder;
58
using Microsoft.AspNetCore.Hosting;
69
using Microsoft.AspNetCore.Http;
10+
using Microsoft.AspNetCore.Http.Extensions;
11+
using Microsoft.AspNetCore.HttpOverrides;
12+
using Microsoft.Extensions.Configuration;
713
using Microsoft.Extensions.DependencyInjection;
814

915
namespace SampleApp
@@ -15,12 +21,50 @@ public void ConfigureServices(IServiceCollection services)
1521

1622
}
1723

18-
public void Configure(IApplicationBuilder app)
24+
public void Configure(IApplicationBuilder app, IConfiguration config)
1925
{
2026
app.Run(async (context) =>
2127
{
22-
await context.Response.WriteAsync($"Hello from {nameof(Startup)}!");
28+
await context.Response.WriteAsync($"Hello from {context.Request.GetDisplayUrl()}\r\n");
29+
await context.Response.WriteAsync("\r\n");
30+
31+
await context.Response.WriteAsync("Headers:\r\n");
32+
foreach (var header in context.Request.Headers)
33+
{
34+
await context.Response.WriteAsync($"{header.Key}: {header.Value}\r\n");
35+
}
36+
await context.Response.WriteAsync("\r\n");
37+
38+
await context.Response.WriteAsync("Connection:\r\n");
39+
await context.Response.WriteAsync("RemoteIp: " + context.Connection.RemoteIpAddress + "\r\n");
40+
await context.Response.WriteAsync("RemotePort: " + context.Connection.RemotePort + "\r\n");
41+
await context.Response.WriteAsync("LocalIp: " + context.Connection.LocalIpAddress + "\r\n");
42+
await context.Response.WriteAsync("LocalPort: " + context.Connection.LocalPort + "\r\n");
43+
await context.Response.WriteAsync("ClientCert: " + context.Connection.ClientCertificate + "\r\n");
44+
await context.Response.WriteAsync("\r\n");
45+
46+
await context.Response.WriteAsync("Environment Variables:\r\n");
47+
var vars = Environment.GetEnvironmentVariables();
48+
foreach (var key in vars.Keys.Cast<string>().OrderBy(key => key, StringComparer.OrdinalIgnoreCase))
49+
{
50+
var value = vars[key];
51+
await context.Response.WriteAsync($"{key}: {value}\r\n");
52+
}
53+
await context.Response.WriteAsync("\r\n");
54+
55+
await context.Response.WriteAsync("Config:\r\n");
56+
await ShowConfig(context.Response, config);
57+
await context.Response.WriteAsync("\r\n");
2358
});
2459
}
60+
61+
private static async Task ShowConfig(HttpResponse response, IConfiguration config)
62+
{
63+
foreach (var pair in config.GetChildren())
64+
{
65+
await response.WriteAsync($"{pair.Path}: {pair.Value}\r\n");
66+
await ShowConfig(response, pair);
67+
}
68+
}
2569
}
2670
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"AllowedHosts": "example.com;localhost",
3+
"Kestrel": {
4+
"EndPoints": {
5+
"Http": {
6+
"Url": "http://localhost:5005"
7+
},
8+
"Https": {
9+
"Url": "https://localhost:5006"
10+
}
11+
12+
// To enable HTTPS using a certificate file, set the path to a .pfx file in
13+
// the "Path" property below and configure the password in user secrets.
14+
// The "Password" property should be set in user secrets.
15+
//"HttpsInlineCertFile": {
16+
// "Url": "http://localhost:5005"
17+
// "Certificate": {
18+
// "Path": "<path to .pfx file>",
19+
// "Password: "<cert password>"
20+
// }
21+
//},
22+
23+
//"HttpsInlineCertStore": {
24+
// "Url": "http://localhost:5005"
25+
// "Certificate": {
26+
// "Subject": "",
27+
// "Store": "",
28+
// "Location": "",
29+
// "AllowInvalid": "" // Set to "true" to allow invalid certificates (e.g. expired)
30+
// }
31+
//},
32+
33+
// This uses the cert defined under Certificates/Default or the development cert.
34+
//"HttpsDefaultCert": {
35+
// "Url": "http://localhost:5005"
36+
//}
37+
}
38+
},
39+
"Certificates": {
40+
//"Default": {
41+
// "Path": "<file>",
42+
// "Password": "<password>"
43+
//},
44+
45+
// From cert store:
46+
//"Default": {
47+
// "Subject": "",
48+
// "Store": "",
49+
// "Location": "",
50+
// "AllowInvalid": "" // Set to "true" to allow invalid certificates (e.g. expired certificates)
51+
//}
52+
}
53+
}
Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,2 @@
11
{
2-
"AllowedHosts": "example.com;localhost",
3-
"Kestrel": {
4-
"EndPoints": {
5-
"Http": {
6-
"Url": "http://localhost:5005"
7-
}
8-
9-
// To enable HTTPS using a certificate file, set the path to a .pfx file in
10-
// the "Path" property below and configure the password in user secrets.
11-
// The "Password" property should be set in user secrets.
12-
//"HttpsInlineCertFile": {
13-
// "Url": "http://localhost:5005"
14-
// "Certificate": {
15-
// "Path": "<path to .pfx file>",
16-
// "Password: "<cert password>"
17-
// }
18-
//},
19-
20-
//"HttpsInlineCertStore": {
21-
// "Url": "http://localhost:5005"
22-
// "Certificate": {
23-
// "Subject": "",
24-
// "Store": "",
25-
// "Location": "",
26-
// "AllowInvalid": "" // Set to "true" to allow invalid certificates (e.g. expired)
27-
// }
28-
//},
29-
30-
// This uses the cert defined under Certificates/Default or the development cert.
31-
//"HttpsDefaultCert": {
32-
// "Url": "http://localhost:5005"
33-
//}
34-
}
35-
},
36-
"Certificates": {
37-
//"Default": {
38-
// "Path": "<file>",
39-
// "Password": "<password>"
40-
//},
41-
42-
// From cert store:
43-
//"Default": {
44-
// "Subject": "",
45-
// "Store": "",
46-
// "Location": "",
47-
// "AllowInvalid": "" // Set to "true" to allow invalid certificates (e.g. expired certificates)
48-
//}
49-
}
502
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting;
7+
8+
namespace Microsoft.AspNetCore
9+
{
10+
internal class ForwardedHeadersStartupFilter : IStartupFilter
11+
{
12+
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
13+
{
14+
return app =>
15+
{
16+
app.UseForwardedHeaders();
17+
next(app);
18+
};
19+
}
20+
}
21+
}

src/DefaultBuilder/src/GenericHostBuilderExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using Microsoft.AspNetCore.Hosting;
33
using Microsoft.AspNetCore;
44

@@ -15,6 +15,8 @@ public static class GenericHostBuilderExtensions
1515
/// <remarks>
1616
/// The following defaults are applied to the <see cref="IWebHostBuilder"/>:
1717
/// use Kestrel as the web server and configure it using the application's configuration providers,
18+
/// adds the HostFiltering middleware,
19+
/// adds the ForwardedHeaders middleware if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,
1820
/// and enable IIS integration.
1921
/// </remarks>
2022
/// <param name="builder">The <see cref="IHostBuilder" /> instance to configure</param>

src/DefaultBuilder/src/WebHost.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.HostFiltering;
99
using Microsoft.AspNetCore.Hosting;
1010
using Microsoft.AspNetCore.Http;
11+
using Microsoft.AspNetCore.HttpOverrides;
1112
using Microsoft.AspNetCore.Routing;
1213
using Microsoft.Extensions.Configuration;
1314
using Microsoft.Extensions.DependencyInjection;
@@ -124,6 +125,8 @@ private static IWebHost StartWith(string url, Action<IServiceCollection> configu
124125
/// load <see cref="IConfiguration"/> from User Secrets when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development' using the entry assembly,
125126
/// load <see cref="IConfiguration"/> from environment variables,
126127
/// configure the <see cref="ILoggerFactory"/> to log to the console and debug output,
128+
/// adds the HostFiltering middleware,
129+
/// adds the ForwardedHeaders middleware if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,
127130
/// and enable IIS integration.
128131
/// </remarks>
129132
/// <returns>The initialized <see cref="IWebHostBuilder"/>.</returns>
@@ -142,6 +145,8 @@ public static IWebHostBuilder CreateDefaultBuilder() =>
142145
/// load <see cref="IConfiguration"/> from environment variables,
143146
/// load <see cref="IConfiguration"/> from supplied command line args,
144147
/// configure the <see cref="ILoggerFactory"/> to log to the console and debug output,
148+
/// adds the HostFiltering middleware,
149+
/// adds the ForwardedHeaders middleware if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,
145150
/// and enable IIS integration.
146151
/// </remarks>
147152
/// <param name="args">The command line args.</param>
@@ -224,6 +229,20 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder)
224229

225230
services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
226231

232+
if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))
233+
{
234+
services.Configure<ForwardedHeadersOptions>(options =>
235+
{
236+
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
237+
// Only loopback proxies are allowed by default. Clear that restriction because forwarders are
238+
// being enabled by explicit configuration.
239+
options.KnownNetworks.Clear();
240+
options.KnownProxies.Clear();
241+
});
242+
243+
services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
244+
}
245+
227246
services.AddRouting();
228247
})
229248
.UseIIS()

src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<ItemGroup>
88
<Reference Include="Microsoft.AspNetCore" />
9+
<Reference Include="Microsoft.AspNetCore.TestHost" />
910
</ItemGroup>
1011

1112
</Project>

src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
using System.Linq;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using Microsoft.AspNetCore.Builder;
1112
using Microsoft.AspNetCore.HostFiltering;
1213
using Microsoft.AspNetCore.Hosting;
1314
using Microsoft.AspNetCore.Routing;
15+
using Microsoft.AspNetCore.TestHost;
1416
using Microsoft.AspNetCore.Testing;
1517
using Microsoft.Extensions.Configuration;
1618
using Microsoft.Extensions.DependencyInjection;
@@ -57,6 +59,35 @@ public async Task WebHostConfiguration_HostFilterOptionsAreReloadable()
5759
Assert.Contains("NewHost", options.AllowedHosts);
5860
}
5961

62+
[Fact]
63+
public async Task WebHostConfiguration_EnablesForwardedHeadersFromConfig()
64+
{
65+
using var host = WebHost.CreateDefaultBuilder()
66+
.ConfigureAppConfiguration(configBuilder =>
67+
{
68+
configBuilder.AddInMemoryCollection(new[]
69+
{
70+
new KeyValuePair<string, string>("FORWARDEDHEADERS_ENABLED", "true" ),
71+
});
72+
})
73+
.UseTestServer()
74+
.Configure(app =>
75+
{
76+
Assert.True(app.Properties.ContainsKey("ForwardedHeadersAdded"), "Forwarded Headers");
77+
app.Run(context =>
78+
{
79+
Assert.Equal("https", context.Request.Scheme);
80+
return Task.CompletedTask;
81+
});
82+
}).Build();
83+
84+
await host.StartAsync();
85+
var client = host.GetTestClient();
86+
client.DefaultRequestHeaders.Add("x-forwarded-proto", "https");
87+
var result = await client.GetAsync("http://localhost/");
88+
result.EnsureSuccessStatusCode();
89+
}
90+
6091
[Fact]
6192
public void CreateDefaultBuilder_RegistersRouting()
6293
{

src/Hosting/TestHost/ref/Microsoft.AspNetCore.TestHost.netcoreapp3.0.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public static partial class WebHostBuilderExtensions
4848
{
4949
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureTestContainer<TContainer>(this Microsoft.AspNetCore.Hosting.IWebHostBuilder webHostBuilder, System.Action<TContainer> servicesConfiguration) { throw null; }
5050
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureTestServices(this Microsoft.AspNetCore.Hosting.IWebHostBuilder webHostBuilder, System.Action<Microsoft.Extensions.DependencyInjection.IServiceCollection> servicesConfiguration) { throw null; }
51+
public static System.Net.Http.HttpClient GetTestClient(this Microsoft.AspNetCore.Hosting.IWebHost host) { throw null; }
52+
public static Microsoft.AspNetCore.TestHost.TestServer GetTestServer(this Microsoft.AspNetCore.Hosting.IWebHost host) { throw null; }
5153
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, string solutionRelativePath, string solutionName = "*.sln") { throw null; }
5254
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, string solutionRelativePath, string applicationBasePath, string solutionName = "*.sln") { throw null; }
5355
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseTestServer(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder) { throw null; }

0 commit comments

Comments
 (0)