Skip to content

Commit 0db1646

Browse files
Add AppContext switch in patch release to opt-out of breaking behavior change in ForwardedHeaders middleware. (#62687)
* Add AppContext switch in patch release to opt-out of breaking behavior change in ForwardedHeaders middleware. * env
1 parent 6bb647e commit 0db1646

File tree

2 files changed

+147
-6
lines changed

2 files changed

+147
-6
lines changed

src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class ForwardedHeadersMiddleware
2121
private readonly ForwardedHeadersOptions _options;
2222
private readonly RequestDelegate _next;
2323
private readonly ILogger _logger;
24+
private readonly bool _ignoreUnknownProxiesWithoutFor;
2425
private bool _allowAllHosts;
2526
private IList<StringSegment>? _allowedHosts;
2627

@@ -63,6 +64,18 @@ public ForwardedHeadersMiddleware(RequestDelegate next, ILoggerFactory loggerFac
6364
_logger = loggerFactory.CreateLogger<ForwardedHeadersMiddleware>();
6465
_next = next;
6566

67+
if (AppContext.TryGetSwitch("Microsoft.AspNetCore.HttpOverrides.IgnoreUnknownProxiesWithoutFor", out var enabled)
68+
&& enabled)
69+
{
70+
_ignoreUnknownProxiesWithoutFor = true;
71+
}
72+
73+
if (Environment.GetEnvironmentVariable("MICROSOFT_ASPNETCORE_HTTPOVERRIDES_IGNORE_UNKNOWN_PROXIES_WITHOUT_FOR") is string env
74+
&& (env.Equals("true", StringComparison.OrdinalIgnoreCase) || env.Equals("1")))
75+
{
76+
_ignoreUnknownProxiesWithoutFor = true;
77+
}
78+
6679
PreProcessHosts();
6780
}
6881

@@ -220,15 +233,20 @@ public void ApplyForwarders(HttpContext context)
220233
for (; entriesConsumed < sets.Length; entriesConsumed++)
221234
{
222235
var set = sets[entriesConsumed];
223-
// For the first instance, allow remoteIp to be null for servers that don't support it natively.
224-
if (currentValues.RemoteIpAndPort != null && checkKnownIps && !CheckKnownAddress(currentValues.RemoteIpAndPort.Address))
236+
// Opt-out of breaking change behavior where we now always check KnownProxies and KnownNetworks
237+
// It used to be guarded by the ForwardedHeaders.XForwardedFor flag, but now we always check it.
238+
if (!_ignoreUnknownProxiesWithoutFor || checkFor)
225239
{
226-
// Stop at the first unknown remote IP, but still apply changes processed so far.
227-
if (_logger.IsEnabled(LogLevel.Debug))
240+
// For the first instance, allow remoteIp to be null for servers that don't support it natively.
241+
if (currentValues.RemoteIpAndPort != null && checkKnownIps && !CheckKnownAddress(currentValues.RemoteIpAndPort.Address))
228242
{
229-
_logger.LogDebug(1, "Unknown proxy: {RemoteIpAndPort}", currentValues.RemoteIpAndPort);
243+
// Stop at the first unknown remote IP, but still apply changes processed so far.
244+
if (_logger.IsEnabled(LogLevel.Warning))
245+
{
246+
_logger.LogWarning(1, "Unknown proxy: {RemoteIpAndPort}", currentValues.RemoteIpAndPort);
247+
}
248+
break;
230249
}
231-
break;
232250
}
233251

234252
if (checkFor)

src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Hosting;
88
using Microsoft.AspNetCore.Http;
99
using Microsoft.AspNetCore.TestHost;
10+
using Microsoft.DotNet.RemoteExecutor;
1011
using Microsoft.Extensions.DependencyInjection;
1112
using Microsoft.Extensions.Hosting;
1213

@@ -1035,6 +1036,128 @@ public async Task IgnoreXForwardedHeadersFromUnknownProxy(ForwardedHeaders forwa
10351036
}
10361037
}
10371038

1039+
[Theory]
1040+
[InlineData(ForwardedHeaders.XForwardedFor)]
1041+
[InlineData(ForwardedHeaders.XForwardedHost)]
1042+
[InlineData(ForwardedHeaders.XForwardedProto)]
1043+
[InlineData(ForwardedHeaders.XForwardedPrefix)]
1044+
public void AppContextDoesNotValidateUnknownProxyWithoutForwardedFor(ForwardedHeaders forwardedHeaders)
1045+
{
1046+
RemoteExecutor.Invoke(static async (forwardedHeadersName) =>
1047+
{
1048+
Assert.True(Enum.TryParse<ForwardedHeaders>(forwardedHeadersName, out var forwardedHeaders));
1049+
AppContext.SetSwitch("Microsoft.AspNetCore.HttpOverrides.IgnoreUnknownProxiesWithoutFor", true);
1050+
using var host = new HostBuilder()
1051+
.ConfigureWebHost(webHostBuilder =>
1052+
{
1053+
webHostBuilder
1054+
.UseTestServer()
1055+
.Configure(app =>
1056+
{
1057+
var options = new ForwardedHeadersOptions
1058+
{
1059+
ForwardedHeaders = forwardedHeaders
1060+
};
1061+
app.UseForwardedHeaders(options);
1062+
});
1063+
}).Build();
1064+
1065+
await host.StartAsync();
1066+
1067+
var server = host.GetTestServer();
1068+
1069+
var context = await server.SendAsync(c =>
1070+
{
1071+
c.Request.Headers["X-Forwarded-For"] = "11.111.111.11";
1072+
c.Request.Headers["X-Forwarded-Host"] = "testhost";
1073+
c.Request.Headers["X-Forwarded-Proto"] = "Protocol";
1074+
c.Request.Headers["X-Forwarded-Prefix"] = "/pathbase";
1075+
c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
1076+
c.Connection.RemotePort = 99;
1077+
});
1078+
1079+
if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedFor))
1080+
{
1081+
// X-Forwarded-For ignored since 10.0.0.1 isn't in KnownProxies
1082+
Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString());
1083+
}
1084+
if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedHost))
1085+
{
1086+
Assert.Equal("testhost", context.Request.Host.ToString());
1087+
}
1088+
if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedProto))
1089+
{
1090+
Assert.Equal("Protocol", context.Request.Scheme);
1091+
}
1092+
if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedPrefix))
1093+
{
1094+
Assert.Equal("/pathbase", context.Request.PathBase);
1095+
}
1096+
return RemoteExecutor.SuccessExitCode;
1097+
}, forwardedHeaders.ToString()).Dispose();
1098+
}
1099+
1100+
[Theory]
1101+
[InlineData(ForwardedHeaders.XForwardedFor)]
1102+
[InlineData(ForwardedHeaders.XForwardedHost)]
1103+
[InlineData(ForwardedHeaders.XForwardedProto)]
1104+
[InlineData(ForwardedHeaders.XForwardedPrefix)]
1105+
public void EnvVariableDoesNotValidateUnknownProxyWithoutForwardedFor(ForwardedHeaders forwardedHeaders)
1106+
{
1107+
RemoteExecutor.Invoke(static async (forwardedHeadersName) =>
1108+
{
1109+
Assert.True(Enum.TryParse<ForwardedHeaders>(forwardedHeadersName, out var forwardedHeaders));
1110+
Environment.SetEnvironmentVariable("MICROSOFT_ASPNETCORE_HTTPOVERRIDES_IGNORE_UNKNOWN_PROXIES_WITHOUT_FOR", "true");
1111+
using var host = new HostBuilder()
1112+
.ConfigureWebHost(webHostBuilder =>
1113+
{
1114+
webHostBuilder
1115+
.UseTestServer()
1116+
.Configure(app =>
1117+
{
1118+
var options = new ForwardedHeadersOptions
1119+
{
1120+
ForwardedHeaders = forwardedHeaders
1121+
};
1122+
app.UseForwardedHeaders(options);
1123+
});
1124+
}).Build();
1125+
1126+
await host.StartAsync();
1127+
1128+
var server = host.GetTestServer();
1129+
1130+
var context = await server.SendAsync(c =>
1131+
{
1132+
c.Request.Headers["X-Forwarded-For"] = "11.111.111.11";
1133+
c.Request.Headers["X-Forwarded-Host"] = "testhost";
1134+
c.Request.Headers["X-Forwarded-Proto"] = "Protocol";
1135+
c.Request.Headers["X-Forwarded-Prefix"] = "/pathbase";
1136+
c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
1137+
c.Connection.RemotePort = 99;
1138+
});
1139+
1140+
if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedFor))
1141+
{
1142+
// X-Forwarded-For ignored since 10.0.0.1 isn't in KnownProxies
1143+
Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString());
1144+
}
1145+
if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedHost))
1146+
{
1147+
Assert.Equal("testhost", context.Request.Host.ToString());
1148+
}
1149+
if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedProto))
1150+
{
1151+
Assert.Equal("Protocol", context.Request.Scheme);
1152+
}
1153+
if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedPrefix))
1154+
{
1155+
Assert.Equal("/pathbase", context.Request.PathBase);
1156+
}
1157+
return RemoteExecutor.SuccessExitCode;
1158+
}, forwardedHeaders.ToString()).Dispose();
1159+
}
1160+
10381161
[Fact]
10391162
public async Task PartiallyEnabledForwardsPartiallyChangesRequest()
10401163
{

0 commit comments

Comments
 (0)