Add host filtering middleware
This commit is contained in:
parent
8b58a9a091
commit
2b80c90554
|
|
@ -1,6 +1,6 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26817.0
|
VisualStudioVersion = 15.0.27130.2027
|
||||||
MinimumVisualStudioVersion = 15.0.26730.03
|
MinimumVisualStudioVersion = 15.0.26730.03
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOverrides", "src\Microsoft.AspNetCore.HttpOverrides\Microsoft.AspNetCore.HttpOverrides.csproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOverrides", "src\Microsoft.AspNetCore.HttpOverrides\Microsoft.AspNetCore.HttpOverrides.csproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -63,6 +63,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpsPolicySample", "sample
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy.Tests", "test\Microsoft.AspNetCore.HttpsPolicy.Tests\Microsoft.AspNetCore.HttpsPolicy.Tests.csproj", "{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy.Tests", "test\Microsoft.AspNetCore.HttpsPolicy.Tests\Microsoft.AspNetCore.HttpsPolicy.Tests.csproj", "{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostFilteringSample", "samples\HostFilteringSample\HostFilteringSample.csproj", "{368B00A2-992A-4B0E-9085-A8136A22922D}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5CEA6F31-A829-4A02-8CD5-EC3DDD4CC1EA}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
build\dependencies.props = build\dependencies.props
|
||||||
|
build\repo.props = build\repo.props
|
||||||
|
build\sources.props = build\sources.props
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HostFiltering.Tests", "test\Microsoft.AspNetCore.HostFiltering.Tests\Microsoft.AspNetCore.HostFiltering.Tests.csproj", "{4BC947ED-13B8-4BE6-82A4-96A48D86980B}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HostFiltering", "src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj", "{762F7276-C916-4111-A6C0-41668ABB3823}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -129,6 +142,18 @@ Global
|
||||||
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{368B00A2-992A-4B0E-9085-A8136A22922D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{368B00A2-992A-4B0E-9085-A8136A22922D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{368B00A2-992A-4B0E-9085-A8136A22922D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{368B00A2-992A-4B0E-9085-A8136A22922D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{762F7276-C916-4111-A6C0-41668ABB3823}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{762F7276-C916-4111-A6C0-41668ABB3823}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{762F7276-C916-4111-A6C0-41668ABB3823}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{762F7276-C916-4111-A6C0-41668ABB3823}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -149,6 +174,10 @@ Global
|
||||||
{4D39C29B-4EC8-497C-B411-922DA494D71B} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
|
{4D39C29B-4EC8-497C-B411-922DA494D71B} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
|
||||||
{AC424AEE-4883-49C6-945F-2FC916B8CA1C} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
{AC424AEE-4883-49C6-945F-2FC916B8CA1C} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
||||||
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE} = {8437B0F3-3894-4828-A945-A9187F37631D}
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE} = {8437B0F3-3894-4828-A945-A9187F37631D}
|
||||||
|
{368B00A2-992A-4B0E-9085-A8136A22922D} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
||||||
|
{5CEA6F31-A829-4A02-8CD5-EC3DDD4CC1EA} = {59A9B64C-E9BE-409E-89A2-58D72E2918F5}
|
||||||
|
{4BC947ED-13B8-4BE6-82A4-96A48D86980B} = {8437B0F3-3894-4828-A945-A9187F37631D}
|
||||||
|
{762F7276-C916-4111-A6C0-41668ABB3823} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158}
|
SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview2-30281</MicrosoftAspNetCoreTestHostPackageVersion>
|
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview2-30281</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||||
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
|
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
|
||||||
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationBinderPackageVersion>
|
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationBinderPackageVersion>
|
||||||
|
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationJsonPackageVersion>
|
||||||
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
|
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
|
||||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsLoggingConsolePackageVersion>
|
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="appsettings.Development.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Update="appsettings.Production.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// 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.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace HostFilteringSample
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
BuildWebHost(args).Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IWebHost BuildWebHost(string[] args)
|
||||||
|
{
|
||||||
|
var hostBuilder = new WebHostBuilder()
|
||||||
|
.ConfigureLogging((_, factory) =>
|
||||||
|
{
|
||||||
|
factory.SetMinimumLevel(LogLevel.Debug);
|
||||||
|
factory.AddConsole();
|
||||||
|
})
|
||||||
|
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||||
|
{
|
||||||
|
var env = hostingContext.HostingEnvironment;
|
||||||
|
config.AddJsonFile("appsettings.json", optional: true)
|
||||||
|
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
|
||||||
|
})
|
||||||
|
.UseKestrel()
|
||||||
|
.UseStartup<Startup>();
|
||||||
|
|
||||||
|
return hostBuilder.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:14124/",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HostFilteringSample": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://localhost:14125/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace HostFilteringSample
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public IConfiguration Config { get; }
|
||||||
|
|
||||||
|
public Startup(IConfiguration config)
|
||||||
|
{
|
||||||
|
Config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHostFiltering(options =>
|
||||||
|
{
|
||||||
|
// If this is excluded then it will fall back to the server's addresses
|
||||||
|
options.AllowedHosts = Config.GetSection("AllowedHosts").Get<List<string>>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
app.UseHostFiltering();
|
||||||
|
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello World! " + context.Request.Host);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"AllowedHosts": [ "localhost", "127.0.0.1", "[::1]" ]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"AllowedHosts": [ "example.com", "localhost" ]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// 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 Microsoft.AspNetCore.HostFiltering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the HostFiltering middleware.
|
||||||
|
/// </summary>
|
||||||
|
public static class HostFilteringBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds middleware for filtering requests by allowed host headers. Invalid requests will be rejected with a
|
||||||
|
/// 400 status code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
|
||||||
|
/// <returns>The original <see cref="IApplicationBuilder"/>.</returns>
|
||||||
|
public static IApplicationBuilder UseHostFiltering(this IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseMiddleware<HostFilteringMiddleware>();
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
// 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.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HostFiltering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A middleware used to filter requests by their Host header.
|
||||||
|
/// </summary>
|
||||||
|
public class HostFilteringMiddleware
|
||||||
|
{
|
||||||
|
// Matches Http.Sys.
|
||||||
|
private static readonly byte[] DefaultResponse = Encoding.ASCII.GetBytes(
|
||||||
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\"http://www.w3.org/TR/html4/strict.dtd\">\r\n"
|
||||||
|
+ "<HTML><HEAD><TITLE>Bad Request</TITLE>\r\n"
|
||||||
|
+ "<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=us-ascii\"></ HEAD >\r\n"
|
||||||
|
+ "<BODY><h2>Bad Request - Invalid Hostname</h2>\r\n"
|
||||||
|
+ "<hr><p>HTTP Error 400. The request hostname is invalid.</p>\r\n"
|
||||||
|
+ "</BODY></HTML>");
|
||||||
|
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<HostFilteringMiddleware> _logger;
|
||||||
|
private readonly HostFilteringOptions _options;
|
||||||
|
private IList<StringSegment> _allowedHosts;
|
||||||
|
private bool? _allowAnyNonEmptyHost;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A middleware used to filter requests by their Host header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next"></param>
|
||||||
|
/// <param name="logger"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
public HostFilteringMiddleware(RequestDelegate next, ILogger<HostFilteringMiddleware> logger,
|
||||||
|
IOptions<HostFilteringOptions> options)
|
||||||
|
{
|
||||||
|
_next = next ?? throw new ArgumentNullException(nameof(next));
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
EnsureConfigured();
|
||||||
|
|
||||||
|
if (!CheckHost(context))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 400;
|
||||||
|
if (_options.IncludeFailureMessage)
|
||||||
|
{
|
||||||
|
context.Response.ContentLength = DefaultResponse.Length;
|
||||||
|
context.Response.ContentType = "text/html";
|
||||||
|
return context.Response.Body.WriteAsync(DefaultResponse, 0, DefaultResponse.Length);
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _next(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureConfigured()
|
||||||
|
{
|
||||||
|
if (_allowAnyNonEmptyHost == true || _allowedHosts?.Count > 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowedHosts = new List<StringSegment>();
|
||||||
|
if (_options.AllowedHosts?.Count > 0 && !TryProcessHosts(_options.AllowedHosts, allowedHosts))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Wildcard detected, all requests with hosts will be allowed.");
|
||||||
|
_allowAnyNonEmptyHost = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedHosts.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No allowed hosts were configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Allowed hosts: " + string.Join("; ", allowedHosts));
|
||||||
|
_allowedHosts = allowedHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns false if any wildcards were found
|
||||||
|
private bool TryProcessHosts(IEnumerable<string> incoming, IList<StringSegment> results)
|
||||||
|
{
|
||||||
|
foreach (var entry in incoming)
|
||||||
|
{
|
||||||
|
// Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode.
|
||||||
|
var host = new HostString(entry).ToUriComponent();
|
||||||
|
|
||||||
|
if (IsTopLevelWildcard(host))
|
||||||
|
{
|
||||||
|
// Disable filtering
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!results.Contains(host, StringSegmentComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
results.Add(host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTopLevelWildcard(string host)
|
||||||
|
{
|
||||||
|
return (string.Equals("*", host, StringComparison.Ordinal) // HttpSys wildcard
|
||||||
|
|| string.Equals("[::]", host, StringComparison.Ordinal) // Kestrel wildcard, IPv6 Any
|
||||||
|
|| string.Equals("0.0.0.0", host, StringComparison.Ordinal)); // IPv4 Any
|
||||||
|
}
|
||||||
|
|
||||||
|
// This does not duplicate format validations that are expected to be performed by the host.
|
||||||
|
private bool CheckHost(HttpContext context)
|
||||||
|
{
|
||||||
|
var host = new StringSegment(context.Request.Headers[HeaderNames.Host].ToString()).Trim();
|
||||||
|
|
||||||
|
if (StringSegment.IsNullOrEmpty(host))
|
||||||
|
{
|
||||||
|
// Http/1.0 does not require the host header.
|
||||||
|
// Http/1.1 requires the header but the value may be empty.
|
||||||
|
if (!_options.AllowEmptyHosts)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"{context.Request.Protocol} request rejected due to missing or empty host header.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_logger.LogDebug($"{context.Request.Protocol} request allowed with missing or empty host header.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_allowAnyNonEmptyHost == true)
|
||||||
|
{
|
||||||
|
_logger.LogTrace($"All hosts are allowed.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HostString.MatchesAny(host, _allowedHosts))
|
||||||
|
{
|
||||||
|
_logger.LogTrace($"The host '{host}' matches an allowed host.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"The host '{host}' does not match an allowed host.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HostFiltering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options for the HostFiltering middleware
|
||||||
|
/// </summary>
|
||||||
|
public class HostFilteringOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The hosts headers that are allowed to access this site. At least one value is required.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item><description>Port numbers must be excluded.</description></item>
|
||||||
|
/// <item><description>A top level wildcard "*" allows all non-empty hosts.</description></item>
|
||||||
|
/// <item><description>Subdomain wildcards are permitted. E.g. "*.example.com" matches subdomains like foo.example.com,
|
||||||
|
/// but not the parent domain example.com.</description></item>
|
||||||
|
/// <item><description>Unicode host names are allowed but will be converted to punycode for matching.</description></item>
|
||||||
|
/// <item><description>IPv6 addresses must include their bounding brackets and be in their normalized form.</description></item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<string> AllowedHosts { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if requests without hosts are allowed. The default is true.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// HTTP/1.0 does not require a host header.
|
||||||
|
/// Http/1.1 requires a host header, but says the value may be empty.
|
||||||
|
/// </remarks>
|
||||||
|
public bool AllowEmptyHosts { get; set; } = true;
|
||||||
|
|
||||||
|
// Note if this were disabled then things like the status code middleware may try to re-execute
|
||||||
|
// the request. This is a low level protocol violation, pretty error pages should not be required.
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the 400 response should include a default message or be empty. This is enabled by default.
|
||||||
|
/// </summary>
|
||||||
|
public bool IncludeFailureMessage { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// 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 Microsoft.AspNetCore.HostFiltering;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the host filtering middleware.
|
||||||
|
/// </summary>
|
||||||
|
public static class HostFilteringServicesExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds services and options for the host filtering middleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
|
||||||
|
/// <param name="configureOptions">A delegate to configure the <see cref="HostFilteringOptions"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IServiceCollection AddHostFiltering(this IServiceCollection services, Action<HostFilteringOptions> configureOptions)
|
||||||
|
{
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
if (configureOptions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configureOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.Configure(configureOptions);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>
|
||||||
|
ASP.NET Core middleware for filtering out requests with unknown HTTP host headers.
|
||||||
|
</Description>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<PackageTags>aspnetcore</PackageTags>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
@ -11,6 +12,7 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.HttpOverrides.Internal;
|
using Microsoft.AspNetCore.HttpOverrides.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.HttpOverrides
|
namespace Microsoft.AspNetCore.HttpOverrides
|
||||||
{
|
{
|
||||||
|
|
@ -22,6 +24,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
|
||||||
private readonly ForwardedHeadersOptions _options;
|
private readonly ForwardedHeadersOptions _options;
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private bool _allowAllHosts;
|
||||||
|
private IList<StringSegment> _allowedHosts;
|
||||||
|
|
||||||
static ForwardedHeadersMiddleware()
|
static ForwardedHeadersMiddleware()
|
||||||
{
|
{
|
||||||
|
|
@ -85,6 +89,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_logger = loggerFactory.CreateLogger<ForwardedHeadersMiddleware>();
|
_logger = loggerFactory.CreateLogger<ForwardedHeadersMiddleware>();
|
||||||
_next = next;
|
_next = next;
|
||||||
|
|
||||||
|
PreProcessHosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnsureOptionNotNullorWhitespace(string value, string propertyName)
|
private static void EnsureOptionNotNullorWhitespace(string value, string propertyName)
|
||||||
|
|
@ -95,6 +101,43 @@ namespace Microsoft.AspNetCore.HttpOverrides
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PreProcessHosts()
|
||||||
|
{
|
||||||
|
if (_options.AllowedHosts == null || _options.AllowedHosts.Count == 0)
|
||||||
|
{
|
||||||
|
_allowAllHosts = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowedHosts = new List<StringSegment>();
|
||||||
|
foreach (var entry in _options.AllowedHosts)
|
||||||
|
{
|
||||||
|
// Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode.
|
||||||
|
var host = new HostString(entry).ToUriComponent();
|
||||||
|
|
||||||
|
if (IsTopLevelWildcard(host))
|
||||||
|
{
|
||||||
|
// Disable filtering
|
||||||
|
_allowAllHosts = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowedHosts.Contains(host, StringSegmentComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
allowedHosts.Add(host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_allowedHosts = allowedHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTopLevelWildcard(string host)
|
||||||
|
{
|
||||||
|
return (string.Equals("*", host, StringComparison.Ordinal) // HttpSys wildcard
|
||||||
|
|| string.Equals("[::]", host, StringComparison.Ordinal) // Kestrel wildcard, IPv6 Any
|
||||||
|
|| string.Equals("0.0.0.0", host, StringComparison.Ordinal)); // IPv4 Any
|
||||||
|
}
|
||||||
|
|
||||||
public Task Invoke(HttpContext context)
|
public Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
ApplyForwarders(context);
|
ApplyForwarders(context);
|
||||||
|
|
@ -231,7 +274,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
|
||||||
|
|
||||||
if (checkHost)
|
if (checkHost)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(set.Host) && TryValidateHost(set.Host))
|
if (!string.IsNullOrEmpty(set.Host) && TryValidateHost(set.Host)
|
||||||
|
&& (_allowAllHosts || HostString.MatchesAny(set.Host, _allowedHosts)))
|
||||||
{
|
{
|
||||||
applyChanges = true;
|
applyChanges = true;
|
||||||
currentValues.Host = set.Host;
|
currentValues.Host = set.Host;
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,22 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) };
|
public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The allowed values from x-forwarded-host. If the list is empty then all hosts are allowed.
|
||||||
|
/// Failing to restrict this these values may allow an attacker to spoof links generated by your service.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item><description>Port numbers must be excluded.</description></item>
|
||||||
|
/// <item><description>A top level wildcard "*" allows all non-empty hosts.</description></item>
|
||||||
|
/// <item><description>Subdomain wildcards are permitted. E.g. "*.example.com" matches subdomains like foo.example.com,
|
||||||
|
/// but not the parent domain example.com.</description></item>
|
||||||
|
/// <item><description>Unicode host names are allowed but will be converted to punycode for matching.</description></item>
|
||||||
|
/// <item><description>IPv6 addresses must include their bounding brackets and be in their normalized form.</description></item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<string> AllowedHosts { get; set; } = new List<string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Require the number of header values to be in sync between the different headers being processed.
|
/// Require the number of header values to be in sync between the different headers being processed.
|
||||||
/// The default is 'false'.
|
/// The default is 'false'.
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
using Microsoft.AspNetCore.HttpsPolicy;
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
|
|
@ -20,15 +18,13 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
|
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
|
||||||
/// <returns>The <see cref="IApplicationBuilder"/> for HttpsRedirection.</returns>
|
/// <returns>The <see cref="IApplicationBuilder"/> for HttpsRedirection.</returns>
|
||||||
/// <remarks>
|
|
||||||
/// HTTPS Enforcement interanlly uses the UrlRewrite middleware to redirect HTTP requests to HTTPS.
|
|
||||||
/// </remarks>
|
|
||||||
public static IApplicationBuilder UseHttpsRedirection(this IApplicationBuilder app)
|
public static IApplicationBuilder UseHttpsRedirection(this IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
if (app == null)
|
if (app == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(app));
|
throw new ArgumentNullException(nameof(app));
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverAddressFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
|
var serverAddressFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
|
||||||
if (serverAddressFeature != null)
|
if (serverAddressFeature != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HostFiltering
|
||||||
|
{
|
||||||
|
public class HostFilteringMiddlewareTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task MissingConfigThrows()
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseHostFiltering();
|
||||||
|
});
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(() => new TestServer(builder).SendAsync(_ => { }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true, 200)]
|
||||||
|
[InlineData(false, 400)]
|
||||||
|
public async Task AllowsMissingHost(bool allowed, int status)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHostFiltering(options =>
|
||||||
|
{
|
||||||
|
options.AllowEmptyHosts = allowed;
|
||||||
|
options.AllowedHosts.Add("Localhost");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Use((ctx, next) =>
|
||||||
|
{
|
||||||
|
ctx.Request.Headers.Remove(HeaderNames.Host);
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
app.UseHostFiltering();
|
||||||
|
app.Run(c =>
|
||||||
|
{
|
||||||
|
Assert.False(c.Request.Headers.TryGetValue(HeaderNames.Host, out var host));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var response = await server.CreateClient().GetAsync("/");
|
||||||
|
Assert.Equal(status, (int)response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true, 200)]
|
||||||
|
[InlineData(false, 400)]
|
||||||
|
public async Task AllowsEmptyHost(bool allowed, int status)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHostFiltering(options =>
|
||||||
|
{
|
||||||
|
options.AllowEmptyHosts = allowed;
|
||||||
|
options.AllowedHosts.Add("Localhost");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Use((ctx, next) =>
|
||||||
|
{
|
||||||
|
ctx.Request.Headers[HeaderNames.Host] = " ";
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
app.UseHostFiltering();
|
||||||
|
app.Run(c =>
|
||||||
|
{
|
||||||
|
Assert.True(c.Request.Headers.TryGetValue(HeaderNames.Host, out var host));
|
||||||
|
Assert.True(StringValues.Equals(" ", host));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
app.Run(c => Task.CompletedTask);
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var response = await server.CreateClient().GetAsync("/");
|
||||||
|
Assert.Equal(status, (int)response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("localHost", "localhost")]
|
||||||
|
[InlineData("localHost", "*")] // Any - Used by HttpSys
|
||||||
|
[InlineData("localHost", "[::]")] // IPv6 Any - This is what Kestrel reports when binding to *
|
||||||
|
[InlineData("localHost", "0.0.0.0")] // IPv4 Any
|
||||||
|
[InlineData("localhost:9090", "example.com;localHost")]
|
||||||
|
[InlineData("example.com:443", "example.com;localhost")]
|
||||||
|
[InlineData("localHost:80", "localhost;")]
|
||||||
|
[InlineData("foo.eXample.com:443", "*.exampLe.com")]
|
||||||
|
[InlineData("f.eXample.com:443", "*.exampLe.com")]
|
||||||
|
[InlineData("127.0.0.1", "127.0.0.1")]
|
||||||
|
[InlineData("127.0.0.1:443", "127.0.0.1")]
|
||||||
|
[InlineData("xn--c1yn36f:443", "xn--c1yn36f")]
|
||||||
|
[InlineData("xn--c1yn36f:443", "點看")]
|
||||||
|
[InlineData("[::ABC]", "[::aBc]")]
|
||||||
|
[InlineData("[::1]:80", "[::1]")]
|
||||||
|
public async Task AllowsSpecifiedHost(string host, string allowedHost)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHostFiltering(options =>
|
||||||
|
{
|
||||||
|
options.AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Use((ctx, next) =>
|
||||||
|
{
|
||||||
|
// TestHost's ClientHandler doesn't let you set the host header, only the host in the URI
|
||||||
|
// and that would over-normalize some of our test conditions like casing.
|
||||||
|
ctx.Request.Headers[HeaderNames.Host] = host;
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
app.UseHostFiltering();
|
||||||
|
app.Run(c => Task.CompletedTask);
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var response = await server.CreateRequest("/").GetAsync();
|
||||||
|
Assert.Equal(200, (int)response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("example.com", "localhost")]
|
||||||
|
[InlineData("localhost:9090", "example.com;")]
|
||||||
|
[InlineData(";", "example.com;localhost")]
|
||||||
|
[InlineData(";:80", "example.com;localhost")]
|
||||||
|
[InlineData(":80", "localhost")]
|
||||||
|
[InlineData(":", "localhost")]
|
||||||
|
[InlineData("example.com:443", "*.example.com")]
|
||||||
|
[InlineData(".example.com:443", "*.example.com")]
|
||||||
|
[InlineData("foo.com:443", "*.example.com")]
|
||||||
|
[InlineData("foo.example.com.bar:443", "*.example.com")]
|
||||||
|
[InlineData(".com:443", "*.com")]
|
||||||
|
// Unicode in the host shouldn't be allowed without punycode anyways. This match fails because the middleware converts
|
||||||
|
// its input to punycode.
|
||||||
|
[InlineData("點看", "點看")]
|
||||||
|
[InlineData("[::1", "[::1]")]
|
||||||
|
[InlineData("[::1:80", "[::1]")]
|
||||||
|
public async Task RejectsMismatchedHosts(string host, string allowedHost)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHostFiltering(options =>
|
||||||
|
{
|
||||||
|
options.AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Use((ctx, next) =>
|
||||||
|
{
|
||||||
|
// TestHost's ClientHandler doesn't let you set the host header, only the host in the URI
|
||||||
|
// and that would reject some of our test conditions.
|
||||||
|
ctx.Request.Headers[HeaderNames.Host] = host;
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
app.UseHostFiltering();
|
||||||
|
app.Run(c => throw new NotImplementedException("App"));
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var response = await server.CreateRequest("/").GetAsync();
|
||||||
|
Assert.Equal(400, (int)response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.TestHost;
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.HttpOverrides
|
namespace Microsoft.AspNetCore.HttpOverrides
|
||||||
|
|
@ -392,6 +394,119 @@ namespace Microsoft.AspNetCore.HttpOverrides
|
||||||
Assert.True(assertsExecuted);
|
Assert.True(assertsExecuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("localHost", "localhost")]
|
||||||
|
[InlineData("localHost", "*")] // Any - Used by HttpSys
|
||||||
|
[InlineData("localHost", "[::]")] // IPv6 Any - This is what Kestrel reports when binding to *
|
||||||
|
[InlineData("localHost", "0.0.0.0")] // IPv4 Any
|
||||||
|
[InlineData("localhost:9090", "example.com;localHost")]
|
||||||
|
[InlineData("example.com:443", "example.com;localhost")]
|
||||||
|
[InlineData("localHost:80", "localhost;")]
|
||||||
|
[InlineData("foo.eXample.com:443", "*.exampLe.com")]
|
||||||
|
[InlineData("f.eXample.com:443", "*.exampLe.com")]
|
||||||
|
[InlineData("127.0.0.1", "127.0.0.1")]
|
||||||
|
[InlineData("127.0.0.1:443", "127.0.0.1")]
|
||||||
|
[InlineData("xn--c1yn36f:443", "xn--c1yn36f")]
|
||||||
|
[InlineData("xn--c1yn36f:443", "點看")]
|
||||||
|
[InlineData("[::ABC]", "[::aBc]")]
|
||||||
|
[InlineData("[::1]:80", "[::1]")]
|
||||||
|
public async Task XForwardedHostAllowsSpecifiedHost(string host, string allowedHost)
|
||||||
|
{
|
||||||
|
bool assertsExecuted = false;
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||||
|
{
|
||||||
|
ForwardedHeaders = ForwardedHeaders.XForwardedHost,
|
||||||
|
AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
});
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
Assert.Equal(host, context.Request.Headers[HeaderNames.Host]);
|
||||||
|
assertsExecuted = true;
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var response = await server.SendAsync(ctx =>
|
||||||
|
{
|
||||||
|
ctx.Request.Headers["X-forwarded-Host"] = host;
|
||||||
|
});
|
||||||
|
Assert.True(assertsExecuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("example.com", "localhost")]
|
||||||
|
[InlineData("localhost:9090", "example.com;")]
|
||||||
|
[InlineData(";", "example.com;localhost")]
|
||||||
|
[InlineData(";:80", "example.com;localhost")]
|
||||||
|
[InlineData(":80", "localhost")]
|
||||||
|
[InlineData(":", "localhost")]
|
||||||
|
[InlineData("example.com:443", "*.example.com")]
|
||||||
|
[InlineData(".example.com:443", "*.example.com")]
|
||||||
|
[InlineData("foo.com:443", "*.example.com")]
|
||||||
|
[InlineData("foo.example.com.bar:443", "*.example.com")]
|
||||||
|
[InlineData(".com:443", "*.com")]
|
||||||
|
// Unicode in the host shouldn't be allowed without punycode anyways. This match fails because the middleware converts
|
||||||
|
// its input to punycode.
|
||||||
|
[InlineData("點看", "點看")]
|
||||||
|
[InlineData("[::1", "[::1]")]
|
||||||
|
[InlineData("[::1:80", "[::1]")]
|
||||||
|
public async Task XForwardedHostFailsMismatchedHosts(string host, string allowedHost)
|
||||||
|
{
|
||||||
|
bool assertsExecuted = false;
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||||
|
{
|
||||||
|
ForwardedHeaders = ForwardedHeaders.XForwardedHost,
|
||||||
|
AllowedHosts = new[] { allowedHost }
|
||||||
|
});
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
Assert.NotEqual<string>(host, context.Request.Headers[HeaderNames.Host]);
|
||||||
|
assertsExecuted = true;
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var response = await server.SendAsync(ctx =>
|
||||||
|
{
|
||||||
|
ctx.Request.Headers["X-forwarded-Host"] = host;
|
||||||
|
});
|
||||||
|
Assert.True(assertsExecuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task XForwardedHostStopsAtFirstUnspecifiedHost()
|
||||||
|
{
|
||||||
|
bool assertsExecuted = false;
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||||
|
{
|
||||||
|
ForwardedHeaders = ForwardedHeaders.XForwardedHost,
|
||||||
|
ForwardLimit = 10,
|
||||||
|
AllowedHosts = new[] { "bar.com", "*.foo.com" }
|
||||||
|
});
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
Assert.Equal("bar.foo.com:432", context.Request.Headers[HeaderNames.Host]);
|
||||||
|
assertsExecuted = true;
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var response = await server.SendAsync(ctx =>
|
||||||
|
{
|
||||||
|
ctx.Request.Headers["X-forwarded-Host"] = "stuff:523, bar.foo.com:432, bar.com:80";
|
||||||
|
});
|
||||||
|
Assert.True(assertsExecuted);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(0, "h1", "http")]
|
[InlineData(0, "h1", "http")]
|
||||||
[InlineData(1, "", "http")]
|
[InlineData(1, "", "http")]
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
public async Task SetOptions_DefaultsSetCorrectly()
|
public async Task SetOptions_DefaultsSetCorrectly()
|
||||||
{
|
{
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
@ -34,9 +31,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var featureCollection = new FeatureCollection();
|
var server = new TestServer(builder);
|
||||||
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
|
|
||||||
var server = new TestServer(builder, featureCollection);
|
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
@ -75,9 +70,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var featureCollection = new FeatureCollection();
|
var server = new TestServer(builder);
|
||||||
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
|
|
||||||
var server = new TestServer(builder, featureCollection);
|
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
@ -115,9 +108,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var featureCollection = new FeatureCollection();
|
var server = new TestServer(builder);
|
||||||
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
|
|
||||||
var server = new TestServer(builder, featureCollection);
|
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
@ -182,12 +173,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
public async Task SetServerAddressesFeature_SingleHttpsAddress_Success()
|
public async Task SetServerAddressesFeature_SingleHttpsAddress_Success()
|
||||||
{
|
{
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services.AddHttpsRedirection(options =>
|
|
||||||
{
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
@ -215,12 +200,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
public async Task SetServerAddressesFeature_MultipleHttpsAddresses_ThrowInMiddleware()
|
public async Task SetServerAddressesFeature_MultipleHttpsAddresses_ThrowInMiddleware()
|
||||||
{
|
{
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services.AddHttpsRedirection(options =>
|
|
||||||
{
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
@ -248,12 +227,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
public async Task SetServerAddressesFeature_MultipleHttpsAddressesWithSamePort_Success()
|
public async Task SetServerAddressesFeature_MultipleHttpsAddressesWithSamePort_Success()
|
||||||
{
|
{
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services.AddHttpsRedirection(options =>
|
|
||||||
{
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue