From 44f03ef83f1d0678b1dd95f7b021378f5b54eb7d Mon Sep 17 00:00:00 2001 From: Chris R Date: Mon, 21 Dec 2015 14:45:37 -0800 Subject: [PATCH] Restrict x-forwarded-for evalutation. Credit: pmhsfelix. Use x-forwarded-for validation to restrict other forwarders. Rename OverrideHeaderMiddleware to ForwardedHeadersMiddleware. Fix tests Fix package version. --- samples/HttpOverridesSample/Startup.cs | 5 +- samples/HttpOverridesSample/project.json | 6 +- ...sions.cs => ForwardedHeadersExtensions.cs} | 15 +- .../ForwardedHeadersMiddleware.cs | 275 +++++++++ .../ForwardedHeadersOptions.cs | 34 ++ .../IPNetwork.cs | 67 +++ .../{ => Internal}/IPEndPointParser.cs | 2 +- .../OverrideHeaderMiddleware.cs | 107 ---- .../OverrideHeaderOptions.cs | 12 - .../project.json | 3 +- .../ForwardedHeadersMiddlewareTest.cs | 522 ++++++++++++++++++ .../IPEndPointParserTest.cs | 2 +- .../IPNetworkTest.cs | 33 ++ .../OverrideHeaderMiddlewareTest.cs | 223 -------- .../project.json | 2 +- 15 files changed, 948 insertions(+), 360 deletions(-) rename src/Microsoft.AspNetCore.HttpOverrides/{OverrideHeaderExtensions.cs => ForwardedHeadersExtensions.cs} (61%) create mode 100644 src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs create mode 100644 src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs create mode 100644 src/Microsoft.AspNetCore.HttpOverrides/IPNetwork.cs rename src/Microsoft.AspNetCore.HttpOverrides/{ => Internal}/IPEndPointParser.cs (97%) delete mode 100644 src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderMiddleware.cs delete mode 100644 src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderOptions.cs create mode 100644 test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs create mode 100644 test/Microsoft.AspNetCore.HttpOverrides.Tests/IPNetworkTest.cs delete mode 100644 test/Microsoft.AspNetCore.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs diff --git a/samples/HttpOverridesSample/Startup.cs b/samples/HttpOverridesSample/Startup.cs index 473a978a55..9cacb60dc1 100644 --- a/samples/HttpOverridesSample/Startup.cs +++ b/samples/HttpOverridesSample/Startup.cs @@ -10,10 +10,9 @@ namespace HttpOverridesSample // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { - app.UseIISPlatformHandler(); - app.UseOverrideHeaders(new OverrideHeaderOptions + app.UseForwardedHeaders(new ForwardedHeadersOptions { - ForwardedOptions = ForwardedHeaders.All + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); app.UseHttpMethodOverride(); diff --git a/samples/HttpOverridesSample/project.json b/samples/HttpOverridesSample/project.json index 7550445e20..6f7515973c 100644 --- a/samples/HttpOverridesSample/project.json +++ b/samples/HttpOverridesSample/project.json @@ -4,9 +4,9 @@ "emitEntryPoint": true }, "dependencies": { + "Microsoft.AspNetCore.HttpOverrides": "1.0.0-*", "Microsoft.AspNetCore.IISPlatformHandler": "1.0.0-*", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*", - "Microsoft.AspNetCore.HttpOverrides": "0.1.0-*" + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*" }, "commands": { "web": "HttpOverridesSample" @@ -23,4 +23,4 @@ "**.user", "**.vspscc" ] -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderExtensions.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersExtensions.cs similarity index 61% rename from src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderExtensions.cs rename to src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersExtensions.cs index 8925c4bb68..df3c221996 100644 --- a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderExtensions.cs +++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -7,31 +7,30 @@ using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { - public static class OverrideHeaderExtensions + public static class ForwardedHeadersExtensions { /// /// Forwards proxied headers onto current request /// /// - /// Enables the different override options. /// - public static IApplicationBuilder UseOverrideHeaders(this IApplicationBuilder builder) + public static IApplicationBuilder UseForwardedHeaders(this IApplicationBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } - return builder.UseMiddleware(); + return builder.UseMiddleware(); } /// /// Forwards proxied headers onto current request /// /// - /// Enables the different override options. + /// Enables the different forwarding options. /// - public static IApplicationBuilder UseOverrideHeaders(this IApplicationBuilder builder, OverrideHeaderOptions options) + public static IApplicationBuilder UseForwardedHeaders(this IApplicationBuilder builder, ForwardedHeadersOptions options) { if (builder == null) { @@ -42,7 +41,7 @@ namespace Microsoft.AspNetCore.Builder throw new ArgumentNullException(nameof(options)); } - return builder.UseMiddleware(Options.Create(options)); + return builder.UseMiddleware(Options.Create(options)); } } } diff --git a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs new file mode 100644 index 0000000000..042b0887b7 --- /dev/null +++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs @@ -0,0 +1,275 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpOverrides.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.HttpOverrides +{ + public class ForwardedHeadersMiddleware + { + private const string XForwardedForHeaderName = "X-Forwarded-For"; + private const string XForwardedHostHeaderName = "X-Forwarded-Host"; + private const string XForwardedProtoHeaderName = "X-Forwarded-Proto"; + private const string XOriginalForName = "X-Original-For"; + private const string XOriginalHostName = "X-Original-Host"; + private const string XOriginalProtoName = "X-Original-Proto"; + + private readonly ForwardedHeadersOptions _options; + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ForwardedHeadersMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options) + { + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + _options = options.Value; + _logger = loggerFactory.CreateLogger(); + _next = next; + } + + public Task Invoke(HttpContext context) + { + ApplyForwarders(context); + return _next(context); + } + + public void ApplyForwarders(HttpContext context) + { + // Gather expected headers. Enabled headers must have the same number of entries. + string[] forwardedFor = null, forwardedProto = null, forwardedHost = null; + bool checkFor = false, checkProto = false, checkHost = false; + int entryCount = 0; + + if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedFor) == ForwardedHeaders.XForwardedFor) + { + checkFor = true; + forwardedFor = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName); + if (StringValues.IsNullOrEmpty(forwardedFor)) + { + return; + } + entryCount = forwardedFor.Length; + } + + if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedProto) == ForwardedHeaders.XForwardedProto) + { + checkProto = true; + forwardedProto = context.Request.Headers.GetCommaSeparatedValues(XForwardedProtoHeaderName); + if (StringValues.IsNullOrEmpty(forwardedProto)) + { + return; + } + if (checkFor && forwardedFor.Length != forwardedProto.Length) + { + _logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto."); + return; + } + entryCount = forwardedProto.Length; + } + + if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedHost) == ForwardedHeaders.XForwardedHost) + { + checkHost = true; + forwardedHost = context.Request.Headers.GetCommaSeparatedValues(XForwardedHostHeaderName); + if (StringValues.IsNullOrEmpty(forwardedHost)) + { + return; + } + if ((checkFor && forwardedFor.Length != forwardedHost.Length) + || (checkProto && forwardedProto.Length != forwardedHost.Length)) + { + _logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-Host and X-Forwarded-For or X-Forwarded-Proto."); + return; + } + entryCount = forwardedHost.Length; + } + + // Apply ForwardLimit, if any + int offset = 0; + if (_options.ForwardLimit.HasValue && entryCount > _options.ForwardLimit) + { + offset = entryCount - _options.ForwardLimit.Value; + entryCount = _options.ForwardLimit.Value; + } + + // Group the data together. + var sets = new List(entryCount); + for (int i = 0; i < entryCount; i++) + { + var set = new SetOfForwarders(); + if (checkFor) + { + set.IpAndPortText = forwardedFor[offset + i]; + } + if (checkProto) + { + set.Scheme = forwardedProto[offset + i]; + } + if (checkHost) + { + set.Host = forwardedHost[offset + i]; + } + sets.Add(set); + } + // They get processed in reverse order, right to left. + sets.Reverse(); + + // Gather initial values + var connection = context.Connection; + var request = context.Request; + var currentValues = new SetOfForwarders() + { + RemoteIpAndPort = connection.RemoteIpAddress != null ? new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort) : null, + // Host and Scheme initial values are never inspected, no need to set them here. + }; + + var checkKnownIps = _options.KnownNetworks.Count > 0 || _options.KnownProxies.Count > 0; + bool applyChanges = false; + int entriesConsumed = 0; + + foreach (var set in sets) + { + if (checkFor) + { + // For the first instance, allow remoteIp to be null for servers that don't support it natively. + if (currentValues.RemoteIpAndPort != null && checkKnownIps && !CheckKnownAddress(currentValues.RemoteIpAndPort.Address)) + { + // Stop at the first unknown remote IP, but still apply changes processed so far. + _logger.LogDebug(1, $"Unknown proxy: {currentValues.RemoteIpAndPort}"); + break; + } + if (!IPEndPointParser.TryParse(set.IpAndPortText, out set.RemoteIpAndPort)) + { + _logger.LogDebug(2, $"Failed to parse forwarded IPAddress: {currentValues.IpAndPortText}"); + return; + } + } + + if (checkProto) + { + if (string.IsNullOrEmpty(set.Scheme)) + { + _logger.LogDebug(3, $"Failed to parse forwarded scheme: {set.Scheme}"); + return; + } + } + + if (checkHost) + { + if (string.IsNullOrEmpty(set.Host)) + { + _logger.LogDebug(4, $"Failed to parse forwarded host: {set.Host}"); + return; + } + } + + applyChanges = true; + currentValues = set; + entriesConsumed++; + } + + if (applyChanges) + { + if (checkFor) + { + if (connection.RemoteIpAddress != null) + { + // Save the original + request.Headers[XOriginalForName] = new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort).ToString(); + } + if (forwardedFor.Length > entriesConsumed) + { + // Truncate the consumed header values + request.Headers[XForwardedForHeaderName] = forwardedFor.Take(forwardedFor.Length - entriesConsumed).ToArray(); + } + else + { + // All values were consumed + request.Headers.Remove(XForwardedForHeaderName); + } + connection.RemoteIpAddress = currentValues.RemoteIpAndPort.Address; + connection.RemotePort = currentValues.RemoteIpAndPort.Port; + } + + if (checkProto) + { + // Save the original + request.Headers[XOriginalProtoName] = request.Scheme; + if (forwardedProto.Length > entriesConsumed) + { + // Truncate the consumed header values + request.Headers[XForwardedProtoHeaderName] = forwardedProto.Take(forwardedProto.Length - entriesConsumed).ToArray(); + } + else + { + // All values were consumed + request.Headers.Remove(XForwardedProtoHeaderName); + } + request.Scheme = currentValues.Scheme; + } + + if (checkHost) + { + // Save the original + request.Headers[XOriginalHostName] = request.Host.ToString(); + if (forwardedHost.Length > entriesConsumed) + { + // Truncate the consumed header values + request.Headers[XForwardedHostHeaderName] = forwardedHost.Take(forwardedHost.Length - entriesConsumed).ToArray(); + } + else + { + // All values were consumed + request.Headers.Remove(XForwardedHostHeaderName); + } + request.Host = HostString.FromUriComponent(currentValues.Host); + } + } + } + + private bool CheckKnownAddress(IPAddress address) + { + if (_options.KnownProxies.Contains(address)) + { + return true; + } + foreach (var network in _options.KnownNetworks) + { + if (network.Contains(address)) + { + return true; + } + } + return false; + } + + private class SetOfForwarders + { + public string IpAndPortText; + public IPEndPoint RemoteIpAndPort; + public string Host; + public string Scheme; + } + } +} diff --git a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs new file mode 100644 index 0000000000..2802e79359 --- /dev/null +++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net; +using Microsoft.AspNetCore.HttpOverrides; + +namespace Microsoft.AspNetCore.Builder +{ + public class ForwardedHeadersOptions + { + /// + /// Identifies which forwarders should be processed. + /// + public ForwardedHeaders ForwardedHeaders { get; set; } + + /// + /// Limits the number of entries in the headers that will be processed. The default value is 1. + /// Set to null to disable the limit, but this should only be done if + /// KnownProxies or KnownNetworks are configured. + /// + public int? ForwardLimit { get; set; } = 1; + + /// + /// Addresses of known proxies to accept forwarded headers from. + /// + public IList KnownProxies { get; } = new List() { IPAddress.IPv6Loopback }; + + /// + /// Address ranges of known proxies to accept forwarded headers from. + /// + public IList KnownNetworks { get; } = new List() { new IPNetwork(IPAddress.Loopback, 8) }; + } +} diff --git a/src/Microsoft.AspNetCore.HttpOverrides/IPNetwork.cs b/src/Microsoft.AspNetCore.HttpOverrides/IPNetwork.cs new file mode 100644 index 0000000000..034e5754b6 --- /dev/null +++ b/src/Microsoft.AspNetCore.HttpOverrides/IPNetwork.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; + +namespace Microsoft.AspNetCore.HttpOverrides +{ + public class IPNetwork + { + public IPNetwork(IPAddress prefix, int prefixLength) + { + Prefix = prefix; + PrefixLength = prefixLength; + PrefixBytes = Prefix.GetAddressBytes(); + Mask = CreateMask(); + } + + public IPAddress Prefix { get; } + + private byte[] PrefixBytes { get; } + + /// + /// The CIDR notation of the subnet mask + /// + public int PrefixLength { get; } + + private byte[] Mask { get; } + + public bool Contains(IPAddress address) + { + if (Prefix.AddressFamily != address.AddressFamily) + { + return false; + } + + var addressBytes = address.GetAddressBytes(); + for (int i = 0; i < PrefixBytes.Length && Mask[i] != 0; i++) + { + if (PrefixBytes[i] != (addressBytes[i] & Mask[i])) + { + return false; + } + } + + return true; + } + + private byte[] CreateMask() + { + var mask = new byte[PrefixBytes.Length]; + int remainingBits = PrefixLength; + int i = 0; + while (remainingBits >= 8) + { + mask[i] = 0xFF; + i++; + remainingBits -= 8; + } + if (remainingBits > 0) + { + mask[i] = (byte)(0xFF << (8 - remainingBits)); + } + + return mask; + } + } +} diff --git a/src/Microsoft.AspNetCore.HttpOverrides/IPEndPointParser.cs b/src/Microsoft.AspNetCore.HttpOverrides/Internal/IPEndPointParser.cs similarity index 97% rename from src/Microsoft.AspNetCore.HttpOverrides/IPEndPointParser.cs rename to src/Microsoft.AspNetCore.HttpOverrides/Internal/IPEndPointParser.cs index 9bdd0a66cf..d33b44e72b 100644 --- a/src/Microsoft.AspNetCore.HttpOverrides/IPEndPointParser.cs +++ b/src/Microsoft.AspNetCore.HttpOverrides/Internal/IPEndPointParser.cs @@ -3,7 +3,7 @@ using System.Net; -namespace Microsoft.AspNetCore.HttpOverrides +namespace Microsoft.AspNetCore.HttpOverrides.Internal { public static class IPEndPointParser { diff --git a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderMiddleware.cs b/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderMiddleware.cs deleted file mode 100644 index e23a883031..0000000000 --- a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderMiddleware.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.HttpOverrides -{ - public class OverrideHeaderMiddleware - { - private const string XForwardedForHeaderName = "X-Forwarded-For"; - private const string XForwardedHostHeaderName = "X-Forwarded-Host"; - private const string XForwardedProtoHeaderName = "X-Forwarded-Proto"; - private const string XOriginalForName = "X-Original-For"; - private const string XOriginalHostName = "X-Original-Host"; - private const string XOriginalProtoName = "X-Original-Proto"; - private readonly OverrideHeaderOptions _options; - private readonly RequestDelegate _next; - - public OverrideHeaderMiddleware(RequestDelegate next, IOptions options) - { - if (next == null) - { - throw new ArgumentNullException(nameof(next)); - } - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - _options = options.Value; - _next = next; - } - - public Task Invoke(HttpContext context) - { - UpdateRemoteIp(context); - - UpdateHost(context); - - UpdateScheme(context); - - return _next(context); - } - - private void UpdateRemoteIp(HttpContext context) - { - if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedFor) != 0) - { - var xForwardedForHeaderValue = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName); - if (xForwardedForHeaderValue != null && xForwardedForHeaderValue.Length > 0) - { - IPEndPoint endpoint; - if (IPEndPointParser.TryParse(xForwardedForHeaderValue[0], out endpoint)) - { - var connection = context.Connection; - var remoteIP = connection.RemoteIpAddress; - if (remoteIP != null) - { - var remoteIPString = new IPEndPoint(remoteIP, connection.RemotePort).ToString(); - context.Request.Headers[XOriginalForName] = remoteIPString; - } - connection.RemoteIpAddress = endpoint.Address; - connection.RemotePort = endpoint.Port; - } - } - } - } - - private void UpdateHost(HttpContext context) - { - if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedHost) != 0) - { - var xForwardHostHeaderValue = context.Request.Headers[XForwardedHostHeaderName]; - if (!string.IsNullOrEmpty(xForwardHostHeaderValue)) - { - var hostString = context.Request.Host.ToString(); - if (!string.IsNullOrEmpty(hostString)) - { - context.Request.Headers[XOriginalHostName] = hostString; - } - context.Request.Host = HostString.FromUriComponent(xForwardHostHeaderValue); - } - } - } - - private void UpdateScheme(HttpContext context) - { - if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedProto) != 0) - { - var xForwardProtoHeaderValue = context.Request.Headers[XForwardedProtoHeaderName]; - if (!string.IsNullOrEmpty(xForwardProtoHeaderValue)) - { - if (!string.IsNullOrEmpty(context.Request.Scheme)) - { - context.Request.Headers[XOriginalProtoName] = context.Request.Scheme; - } - context.Request.Scheme = xForwardProtoHeaderValue; - } - } - } - } -} diff --git a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderOptions.cs b/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderOptions.cs deleted file mode 100644 index 011c564813..0000000000 --- a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.HttpOverrides; - -namespace Microsoft.AspNetCore.Builder -{ - public class OverrideHeaderOptions - { - public ForwardedHeaders ForwardedOptions { get; set; } - } -} diff --git a/src/Microsoft.AspNetCore.HttpOverrides/project.json b/src/Microsoft.AspNetCore.HttpOverrides/project.json index 78d4310cab..a37a16f335 100644 --- a/src/Microsoft.AspNetCore.HttpOverrides/project.json +++ b/src/Microsoft.AspNetCore.HttpOverrides/project.json @@ -1,5 +1,5 @@ { - "version": "0.1.0-*", + "version": "1.0.0-*", "compilationOptions": { "warningsAsErrors": true, "keyFile": "../../tools/Key.snk" @@ -11,6 +11,7 @@ }, "dependencies": { "Microsoft.AspNetCore.Http.Extensions": "1.0.0-*", + "Microsoft.Extensions.Logging.Abstractions": "1.0.0-*", "Microsoft.Extensions.Options": "1.0.0-*" }, "frameworks": { diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs new file mode 100644 index 0000000000..6632a1bba6 --- /dev/null +++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs @@ -0,0 +1,522 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Xunit; + +namespace Microsoft.AspNetCore.HttpOverrides +{ + public class ForwardedHeadersMiddlewareTests + { + [Fact] + public async Task XForwardedForDefaultSettingsChangeRemoteIpAndPort() + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor + }); + app.Run(context => + { + Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); + Assert.Equal(9090, context.Connection.RemotePort); + // No Original set if RemoteIpAddress started null. + Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); + // Should have been consumed and removed + Assert.False(context.Request.Headers.ContainsKey("X-Forwarded-For")); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11:9090"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Theory] + [InlineData(1, "11.111.111.11.12345", "10.0.0.1", 99)] // Invalid + public async Task XForwardedForFirstValueIsInvalid(int limit, string header, string expectedIp, int expectedPort) + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.Use((context, next) => + { + context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); + context.Connection.RemotePort = 99; + return next(); + }); + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor, + ForwardLimit = limit, + }); + app.Run(context => + { + Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); + Assert.Equal(expectedPort, context.Connection.RemotePort); + Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); + Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-For")); + Assert.Equal(header, context.Request.Headers["X-Forwarded-For"]); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.TryAddWithoutValidation("X-Forwarded-For", header); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Theory] + [InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "")] + [InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "")] + [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456")] + [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "")] + [InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "")] + [InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "10.0.0.1", 99, "12.112.112.12.23456, 11.111.111.11:12345")] // Invalid + [InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "10.0.0.1", 99, + "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345")] // Invalid + [InlineData(2, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "13.113.113.13:34567")] + [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "")] + public async Task XForwardedForForwardLimit(int limit, string header, string expectedIp, int expectedPort, string remainingHeader) + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.Use((context, next) => + { + context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); + context.Connection.RemotePort = 99; + return next(); + }); + var options = new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor, + ForwardLimit = limit, + }; + options.KnownProxies.Clear(); + options.KnownNetworks.Clear(); + app.UseForwardedHeaders(options); + app.Run(context => + { + Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); + Assert.Equal(expectedPort, context.Connection.RemotePort); + Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-For"].ToString()); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.TryAddWithoutValidation("X-Forwarded-For", header); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Theory] + [InlineData("11.111.111.11", false)] + [InlineData("127.0.0.1", true)] + [InlineData("127.0.1.1", true)] + [InlineData("::1", true)] + [InlineData("::", false)] + public async Task XForwardedForLoopback(string originalIp, bool expectForwarded) + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.Use((context, next) => + { + context.Connection.RemoteIpAddress = IPAddress.Parse(originalIp); + context.Connection.RemotePort = 99; + return next(); + }); + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor, + }); + app.Run(context => + { + if (expectForwarded) + { + Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString()); + Assert.Equal(1234, context.Connection.RemotePort); + Assert.True(context.Request.Headers.ContainsKey("X-Original-For")); + Assert.Equal(new IPEndPoint(IPAddress.Parse(originalIp), 99).ToString(), + context.Request.Headers["X-Original-For"]); + } + else + { + Assert.Equal(originalIp, context.Connection.RemoteIpAddress.ToString()); + Assert.Equal(99, context.Connection.RemotePort); + Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); + } + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.TryAddWithoutValidation("X-Forwarded-For", "10.0.0.1:1234"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Theory] + [InlineData(1, "11.111.111.11:12345", "20.0.0.1", "10.0.0.1", 99)] + [InlineData(1, "", "10.0.0.1", "10.0.0.1", 99)] + [InlineData(1, "11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)] + [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)] + [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)] + [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "11.111.111.11", 12345)] + [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "12.112.112.12", 23456)] + [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345)] + [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456)] + [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "13.113.113.13", 34567)] + [InlineData(3, "13.113.113.13:34567, 12.112.112.12;23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "10.0.0.1", 99)] // Invalid 2nd IP + [InlineData(3, "13.113.113.13;34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "10.0.0.1", 99)] // Invalid 3rd IP + public async Task XForwardedForForwardKnownIps(int limit, string header, string knownIPs, string expectedIp, int expectedPort) + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.Use((context, next) => + { + context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); + context.Connection.RemotePort = 99; + return next(); + }); + var options = new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor, + ForwardLimit = limit, + }; + foreach (var ip in knownIPs.Split(',').Select(text => IPAddress.Parse(text))) + { + options.KnownProxies.Add(ip); + } + app.UseForwardedHeaders(options); + app.Run(context => + { + Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); + Assert.Equal(expectedPort, context.Connection.RemotePort); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.TryAddWithoutValidation("X-Forwarded-For", header); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp() + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor + }); + app.Run(context => + { + Assert.Null(context.Connection.RemoteIpAddress); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "BAD-IP"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task XForwardedHostOverrideChangesRequestHost() + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedHost + }); + app.Run(context => + { + Assert.Equal("testhost", context.Request.Host.ToString()); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-Host", "testhost"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Theory] + [InlineData(0, "h1", "http")] + [InlineData(1, "", "http")] + [InlineData(1, "h1", "h1")] + [InlineData(3, "h1", "h1")] + [InlineData(1, "h2, h1", "h1")] + [InlineData(2, "h2, h1", "h2")] + [InlineData(10, "h3, h2, h1", "h3")] + public async Task XForwardedProtoOverrideChangesRequestProtocol(int limit, string header, string expected) + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedProto, + ForwardLimit = limit, + }); + app.Run(context => + { + Assert.Equal(expected, context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-Proto", header); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Theory] + [InlineData(0, "h1", "::1", "http")] + [InlineData(1, "", "::1", "http")] + [InlineData(1, "h1", "::1", "h1")] + [InlineData(3, "h1", "::1", "h1")] + [InlineData(3, "h2, h1", "::1", "http")] + [InlineData(5, "h2, h1", "::1, ::1", "h2")] + [InlineData(10, "h3, h2, h1", "::1, ::1, ::1", "h3")] + [InlineData(10, "h3, h2, h1", "::1, badip, ::1", "http")] + public async Task XForwardedProtoOverrideLimitedByXForwardedForCount(int limit, string protoHeader, string forHeader, string expected) + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, + ForwardLimit = limit, + }); + app.Run(context => + { + Assert.Equal(expected, context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-Proto", protoHeader); + req.Headers.Add("X-Forwarded-For", forHeader); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Theory] + [InlineData("", "", "::1", false, "http")] + [InlineData("h1", "", "::1", false, "http")] + [InlineData("h1", "F::", "::1", false, "h1")] + [InlineData("h1", "F::", "E::", false, "h1")] + [InlineData("", "", "::1", true, "http")] + [InlineData("h1", "", "::1", true, "http")] + [InlineData("h1", "F::", "::1", true, "h1")] + [InlineData("h1", "", "F::", true, "http")] + [InlineData("h1", "E::", "F::", true, "http")] + [InlineData("h2, h1", "", "::1", true, "http")] + [InlineData("h2, h1", "F::, D::", "::1", true, "h1")] + [InlineData("h2, h1", "E::, D::", "F::", true, "http")] + [InlineData("h2, h1", "E::, D::", "F::", true, "http")] + public async Task XForwardedProtoOverrideLimitedByLoopback(string protoHeader, string forHeader, string remoteIp, bool loopback, string expected) + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.Use((context, next) => + { + context.Connection.RemoteIpAddress = IPAddress.Parse(remoteIp); + return next(); + }); + var options = new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, + ForwardLimit = 5, + }; + if (!loopback) + { + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + } + app.UseForwardedHeaders(options); + app.Run(context => + { + Assert.Equal(expected, context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-Proto", protoHeader); + req.Headers.Add("X-Forwarded-For", forHeader); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public void AllForwardsDisabledByDefault() + { + var options = new ForwardedHeadersOptions(); + Assert.True(options.ForwardedHeaders == ForwardedHeaders.None); + Assert.Equal(1, options.ForwardLimit); + Assert.Equal(1, options.KnownNetworks.Count()); + Assert.Equal(1, options.KnownProxies.Count()); + } + + [Fact] + public async Task AllForwardsEnabledChangeRequestRemoteIpHostandProtocol() + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.All + }); + app.Run(context => + { + Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); + Assert.Equal("testhost", context.Request.Host.ToString()); + Assert.Equal("Protocol", context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11"); + req.Headers.Add("X-Forwarded-Host", "testhost"); + req.Headers.Add("X-Forwarded-Proto", "Protocol"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task AllOptionsDisabledRequestDoesntChange() + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.None + }); + app.Run(context => + { + Assert.Null(context.Connection.RemoteIpAddress); + Assert.Equal("localhost", context.Request.Host.ToString()); + Assert.Equal("http", context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11"); + req.Headers.Add("X-Forwarded-Host", "otherhost"); + req.Headers.Add("X-Forwarded-Proto", "Protocol"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task PartiallyEnabledForwardsPartiallyChangesRequest() + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto + }); + app.Run(context => + { + Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); + Assert.Equal("localhost", context.Request.Host.ToString()); + Assert.Equal("Protocol", context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11"); + req.Headers.Add("X-Forwarded-Proto", "Protocol"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + } +} diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs index 0ba8e134ee..f5106bd2a7 100644 --- a/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs +++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs @@ -4,7 +4,7 @@ using System.Net; using Xunit; -namespace Microsoft.AspNetCore.HttpOverrides +namespace Microsoft.AspNetCore.HttpOverrides.Internal { public class IPEndPointParserTests { diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPNetworkTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPNetworkTest.cs new file mode 100644 index 0000000000..b3700c583f --- /dev/null +++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPNetworkTest.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; +using Xunit; + +namespace Microsoft.AspNetCore.HttpOverrides +{ + public class IPNetworkTest + { + [Theory] + [InlineData("10.1.1.0", 8, "10.1.1.10")] + [InlineData("174.0.0.0", 7, "175.1.1.10")] + [InlineData("10.174.0.0", 15, "10.175.1.10")] + [InlineData("10.168.0.0", 14, "10.171.1.10")] + public void Contains_Positive(string prefixText, int length, string addressText) + { + var network = new IPNetwork(IPAddress.Parse(prefixText), length); + Assert.True(network.Contains(IPAddress.Parse(addressText))); + } + + [Theory] + [InlineData("10.1.0.0", 16, "10.2.1.10")] + [InlineData("174.0.0.0", 7, "173.1.1.10")] + [InlineData("10.174.0.0", 15, "10.173.1.10")] + [InlineData("10.168.0.0", 14, "10.172.1.10")] + public void Contains_Negative(string prefixText, int length, string addressText) + { + var network = new IPNetwork(IPAddress.Parse(prefixText), length); + Assert.False(network.Contains(IPAddress.Parse(addressText))); + } + } +} diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs deleted file mode 100644 index b66e57e2cd..0000000000 --- a/test/Microsoft.AspNetCore.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Xunit; - -namespace Microsoft.AspNetCore.HttpOverrides -{ - public class OverrideMiddlewareHeaderTests - { - [Fact] - public async Task XForwardedForOverrideChangesRemoteIp() - { - var assertsExecuted = false; - - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseOverrideHeaders(new OverrideHeaderOptions - { - ForwardedOptions = ForwardedHeaders.XForwardedFor - }); - app.Run(context => - { - Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); - assertsExecuted = true; - return Task.FromResult(0); - }); - }); - var server = new TestServer(builder); - - var req = new HttpRequestMessage(HttpMethod.Get, ""); - req.Headers.Add("X-Forwarded-For", "11.111.111.11"); - await server.CreateClient().SendAsync(req); - Assert.True(assertsExecuted); - } - - [Fact] - public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp() - { - var assertsExecuted = false; - - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseOverrideHeaders(new OverrideHeaderOptions - { - ForwardedOptions = ForwardedHeaders.XForwardedFor - }); - app.Run(context => - { - Assert.Null(context.Connection.RemoteIpAddress); - assertsExecuted = true; - return Task.FromResult(0); - }); - }); - var server = new TestServer(builder); - - var req = new HttpRequestMessage(HttpMethod.Get, ""); - req.Headers.Add("X-Forwarded-For", "BAD-IP"); - await server.CreateClient().SendAsync(req); - Assert.True(assertsExecuted); - } - - [Fact] - public async Task XForwardedHostOverrideChangesRequestHost() - { - var assertsExecuted = false; - - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseOverrideHeaders(new OverrideHeaderOptions - { - ForwardedOptions = ForwardedHeaders.XForwardedHost - }); - app.Run(context => - { - Assert.Equal("testhost", context.Request.Host.ToString()); - assertsExecuted = true; - return Task.FromResult(0); - }); - }); - var server = new TestServer(builder); - - var req = new HttpRequestMessage(HttpMethod.Get, ""); - req.Headers.Add("X-Forwarded-Host", "testhost"); - await server.CreateClient().SendAsync(req); - Assert.True(assertsExecuted); - } - - [Fact] - public async Task XForwardedProtoOverrideChangesRequestProtocol() - { - var assertsExecuted = false; - - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseOverrideHeaders(new OverrideHeaderOptions - { - ForwardedOptions = ForwardedHeaders.XForwardedProto - }); - app.Run(context => - { - Assert.Equal("TestProtocol", context.Request.Scheme); - assertsExecuted = true; - return Task.FromResult(0); - }); - }); - var server = new TestServer(builder); - - var req = new HttpRequestMessage(HttpMethod.Get, ""); - req.Headers.Add("X-Forwarded-Proto", "TestProtocol"); - await server.CreateClient().SendAsync(req); - Assert.True(assertsExecuted); - } - - [Fact] - public void AllForwardsDisabledByDefault() - { - var options = new OverrideHeaderOptions(); - Assert.True(options.ForwardedOptions == 0); - } - - [Fact] - public async Task AllForwardsEnabledChangeRequestRemoteIpHostandProtocol() - { - var assertsExecuted = false; - - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseOverrideHeaders(new OverrideHeaderOptions - { - ForwardedOptions = ForwardedHeaders.All - }); - app.Run(context => - { - Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); - Assert.Equal("testhost", context.Request.Host.ToString()); - Assert.Equal("Protocol", context.Request.Scheme); - assertsExecuted = true; - return Task.FromResult(0); - }); - }); - var server = new TestServer(builder); - - var req = new HttpRequestMessage(HttpMethod.Get, ""); - req.Headers.Add("X-Forwarded-For", "11.111.111.11"); - req.Headers.Add("X-Forwarded-Host", "testhost"); - req.Headers.Add("X-Forwarded-Proto", "Protocol"); - await server.CreateClient().SendAsync(req); - Assert.True(assertsExecuted); - } - - [Fact] - public async Task AllOptionsDisabledRequestDoesntChange() - { - var assertsExecuted = false; - - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseOverrideHeaders(new OverrideHeaderOptions - { - ForwardedOptions = ForwardedHeaders.None - }); - app.Run(context => - { - Assert.Null(context.Connection.RemoteIpAddress); - Assert.Equal("localhost", context.Request.Host.ToString()); - Assert.Equal("http", context.Request.Scheme); - assertsExecuted = true; - return Task.FromResult(0); - }); - }); - var server = new TestServer(builder); - - var req = new HttpRequestMessage(HttpMethod.Get, ""); - req.Headers.Add("X-Forwarded-For", "11.111.111.11"); - req.Headers.Add("X-Forwarded-Host", "otherhost"); - req.Headers.Add("X-Forwarded-Proto", "Protocol"); - await server.CreateClient().SendAsync(req); - Assert.True(assertsExecuted); - } - - [Fact] - public async Task PartiallyEnabledForwardsPartiallyChangesRequest() - { - var assertsExecuted = false; - - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseOverrideHeaders(new OverrideHeaderOptions - { - ForwardedOptions = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto - }); - app.Run(context => - { - Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); - Assert.Equal("localhost", context.Request.Host.ToString()); - Assert.Equal("Protocol", context.Request.Scheme); - assertsExecuted = true; - return Task.FromResult(0); - - }); - }); - var server = new TestServer(builder); - - var req = new HttpRequestMessage(HttpMethod.Get, ""); - req.Headers.Add("X-Forwarded-For", "11.111.111.11"); - req.Headers.Add("X-Forwarded-Proto", "Protocol"); - await server.CreateClient().SendAsync(req); - Assert.True(assertsExecuted); - } - } -} diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/project.json b/test/Microsoft.AspNetCore.HttpOverrides.Tests/project.json index 107fcfb6ab..2729c1bed4 100644 --- a/test/Microsoft.AspNetCore.HttpOverrides.Tests/project.json +++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/project.json @@ -4,7 +4,7 @@ "warningsAsErrors": true }, "dependencies": { - "Microsoft.AspNetCore.HttpOverrides": "0.1.0-*", + "Microsoft.AspNetCore.HttpOverrides": "1.0.0-*", "Microsoft.AspNetCore.TestHost": "1.0.0-*", "Microsoft.Extensions.Logging.Testing": "1.0.0-*", "xunit": "2.1.0"