Skip to content

Commit 4e31f14

Browse files
TimHessbart-vmware
andauthored
Account for ASP.NET Core changes around proxy header handling (#1525)
* Set FORWARDEDHEADERS_ENABLED when running on Cloud Foundry * Remove UseCertificateAuthorization(options) Co-authored-by: Bart Koelman <[email protected]>
1 parent 88da899 commit 4e31f14

File tree

4 files changed

+109
-25
lines changed

4 files changed

+109
-25
lines changed

src/Configuration/src/CloudFoundry/CloudFoundryConfigurationProvider.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ private void Process()
7373

7474
LoadData("vcap:application", applicationData.GetChildren(), data);
7575
AddDiegoVariables(data);
76+
77+
// Enable evaluation of X-Forwarded headers so that ASP.NET Core works automatically behind Gorouter.
78+
// Equivalent to setting ASPNETCORE_FORWARDEDHEADERS_ENABLED to true.
79+
data["FORWARDEDHEADERS_ENABLED"] = "true";
7680
}
7781

7882
string? servicesJson = _settingsReader.ServicesJson;

src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Net;
56
using FluentAssertions.Extensions;
7+
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.AspNetCore.Hosting;
9+
using Microsoft.AspNetCore.HttpOverrides;
610
using Microsoft.Extensions.Configuration;
11+
using Microsoft.Extensions.DependencyInjection;
12+
using Microsoft.Extensions.Options;
713
using Steeltoe.Common.TestResources;
14+
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
815

916
namespace Steeltoe.Configuration.CloudFoundry.Test;
1017

@@ -216,6 +223,102 @@ public void Load_VCAP_APPLICATION_Allows_Reload_Without_Throwing_Exception()
216223
Assert.Equal("fb8fbcc6-8d58-479e-bcc7-3b4ce5a7f0ca", options.Version);
217224
}
218225

226+
[Theory]
227+
[InlineData(true)]
228+
[InlineData(false)]
229+
public async Task ForwardedHeadersOptions_unrestricted_when_running_on_CloudFoundry(bool isRunningOnCloudFoundry)
230+
{
231+
using IDisposable? scope = isRunningOnCloudFoundry ? new EnvironmentVariableScope("VCAP_APPLICATION", "{}") : null;
232+
233+
WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault();
234+
builder.AddCloudFoundryConfiguration();
235+
await using WebApplication host = builder.Build();
236+
237+
ForwardedHeadersOptions options = host.Services.GetRequiredService<IOptions<ForwardedHeadersOptions>>().Value;
238+
239+
if (isRunningOnCloudFoundry)
240+
{
241+
options.ForwardedHeaders.Should().HaveFlag(ForwardedHeaders.XForwardedFor);
242+
options.ForwardedHeaders.Should().HaveFlag(ForwardedHeaders.XForwardedProto);
243+
options.KnownNetworks.Should().BeEmpty();
244+
options.KnownProxies.Should().BeEmpty();
245+
}
246+
else
247+
{
248+
options.ForwardedHeaders.Should().NotHaveFlag(ForwardedHeaders.XForwardedFor);
249+
options.ForwardedHeaders.Should().NotHaveFlag(ForwardedHeaders.XForwardedProto);
250+
options.KnownNetworks.Should().ContainSingle().Which.Should().BeEquivalentTo(IPNetwork.Parse("127.0.0.1/8"));
251+
options.KnownProxies.Should().ContainSingle().Which.Should().Be(IPAddress.Parse("::1"));
252+
}
253+
}
254+
255+
[Theory]
256+
[InlineData(true)]
257+
[InlineData(false)]
258+
public async Task ForwardedHeadersMiddleware_updates_connection_details_when_running_on_CloudFoundry(bool isRunningOnCloudFoundry)
259+
{
260+
using IDisposable? scope = isRunningOnCloudFoundry ? new EnvironmentVariableScope("VCAP_APPLICATION", "{}") : null;
261+
262+
WebApplicationBuilder builder = WebApplication.CreateBuilder();
263+
builder.WebHost.UseKestrel().UseUrls("http://127.0.0.1:0");
264+
builder.AddCloudFoundryConfiguration();
265+
await using WebApplication host = builder.Build();
266+
bool? forwardedHeadersWereEvaluated = null;
267+
268+
host.Map("/", context =>
269+
{
270+
forwardedHeadersWereEvaluated = context.Request.IsHttps;
271+
return Task.CompletedTask;
272+
});
273+
274+
await host.StartAsync(TestContext.Current.CancellationToken);
275+
string address = host.Urls.First(url => url.StartsWith("http://", StringComparison.OrdinalIgnoreCase));
276+
277+
var client = new HttpClient();
278+
client.DefaultRequestHeaders.Add("X-Forwarded-Proto", "https");
279+
client.DefaultRequestHeaders.Add("X-Forwarded-For", "1.2.3.4");
280+
281+
HttpResponseMessage response = await client.GetAsync(new Uri(address), TestContext.Current.CancellationToken);
282+
response.StatusCode.Should().Be(HttpStatusCode.OK);
283+
284+
forwardedHeadersWereEvaluated.Should()
285+
.Be(isRunningOnCloudFoundry, $"X-Forwarded-Proto should {(isRunningOnCloudFoundry ? string.Empty : "not ")}be evaluated");
286+
287+
await host.StopAsync(TestContext.Current.CancellationToken);
288+
}
289+
290+
[Fact]
291+
public async Task ForwardedHeadersMiddleware_uses_customized_options_when_running_on_CloudFoundry()
292+
{
293+
using var vcapScope = new EnvironmentVariableScope("VCAP_APPLICATION", "{}");
294+
WebApplicationBuilder builder = WebApplication.CreateBuilder();
295+
builder.WebHost.UseKestrel().UseUrls("http://127.0.0.1:0");
296+
builder.AddCloudFoundryConfiguration();
297+
builder.Services.Configure<ForwardedHeadersOptions>(options => options.KnownProxies.Add(IPAddress.Parse("192.168.1.20")));
298+
await using WebApplication host = builder.Build();
299+
bool? forwardedHeadersWereEvaluated = null;
300+
301+
host.Map("/", context =>
302+
{
303+
forwardedHeadersWereEvaluated = context.Request.IsHttps;
304+
return Task.CompletedTask;
305+
});
306+
307+
await host.StartAsync(TestContext.Current.CancellationToken);
308+
string address = host.Urls.First(url => url.StartsWith("http://", StringComparison.OrdinalIgnoreCase));
309+
310+
var client = new HttpClient();
311+
client.DefaultRequestHeaders.Add("X-Forwarded-Proto", "https");
312+
client.DefaultRequestHeaders.Add("X-Forwarded-For", "1.2.3.4");
313+
314+
HttpResponseMessage response = await client.GetAsync(new Uri(address), TestContext.Current.CancellationToken);
315+
316+
response.StatusCode.Should().Be(HttpStatusCode.OK);
317+
forwardedHeadersWereEvaluated.Should().BeFalse("X-Forwarded-Proto should not be evaluated for unknown proxies");
318+
319+
await host.StopAsync(TestContext.Current.CancellationToken);
320+
}
321+
219322
private sealed class VcapApp
220323
{
221324
#pragma warning disable S3459 // Unassigned members should be removed

src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,25 @@
33
// See the LICENSE file in the project root for more information.
44

55
using Microsoft.AspNetCore.Builder;
6-
using Microsoft.AspNetCore.HttpOverrides;
76

87
namespace Steeltoe.Security.Authorization.Certificate;
98

109
public static class CertificateApplicationBuilderExtensions
1110
{
12-
/// <summary>
13-
/// Enables certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares. Sets ForwardedHeaders to
14-
/// <see cref="ForwardedHeaders.XForwardedProto" />.
15-
/// </summary>
16-
/// <param name="builder">
17-
/// The <see cref="IApplicationBuilder" /> to configure.
18-
/// </param>
19-
/// <returns>
20-
/// The incoming <paramref name="builder" /> so that additional calls can be chained.
21-
/// </returns>
22-
public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder builder)
23-
{
24-
return UseCertificateAuthorization(builder, new ForwardedHeadersOptions());
25-
}
26-
2711
/// <summary>
2812
/// Enables certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares.
2913
/// </summary>
3014
/// <param name="builder">
3115
/// The <see cref="IApplicationBuilder" /> to configure.
3216
/// </param>
33-
/// <param name="options">
34-
/// Custom header forwarding policy. <see cref="ForwardedHeaders.XForwardedProto" /> is added to your <see cref="ForwardedHeadersOptions" />.
35-
/// </param>
3617
/// <returns>
3718
/// The incoming <paramref name="builder" /> so that additional calls can be chained.
3819
/// </returns>
39-
public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder builder, ForwardedHeadersOptions options)
20+
public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder builder)
4021
{
4122
ArgumentNullException.ThrowIfNull(builder);
42-
ArgumentNullException.ThrowIfNull(options);
43-
44-
options.ForwardedHeaders |= ForwardedHeaders.XForwardedProto;
4523

46-
builder.UseForwardedHeaders(options);
24+
builder.UseForwardedHeaders();
4725
builder.UseCertificateForwarding();
4826
builder.UseAuthentication();
4927
builder.UseAuthorization();

src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicies.SameOrg = "sameorg" -> string!
33
const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicies.SameSpace = "samespace" -> string!
44
static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! builder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
5-
static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! builder, Microsoft.AspNetCore.Builder.ForwardedHeadersOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
65
static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions.AddOrgAndSpacePolicies(this Microsoft.AspNetCore.Authorization.AuthorizationBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
76
static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions.AddOrgAndSpacePolicies(this Microsoft.AspNetCore.Authorization.AuthorizationBuilder! builder, string? certificateHeaderName) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
87
static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameOrg(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder!

0 commit comments

Comments
 (0)