Exclude setting HSTS on localhost and allow for user to specify excluded domains. (#274)
This commit is contained in:
parent
3488c24804
commit
892cebb27e
|
|
@ -61,7 +61,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsP
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpsPolicySample", "samples\HttpsPolicySample\HttpsPolicySample.csproj", "{AC424AEE-4883-49C6-945F-2FC916B8CA1C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy.Tests", "test\Microsoft.AspNetCore.HttpsEnforcement.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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy
|
|||
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly StringValues _strictTransportSecurityValue;
|
||||
private readonly IList<string> _excludedHosts;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the HSTS middleware.
|
||||
|
|
@ -30,24 +32,20 @@ namespace Microsoft.AspNetCore.HttpsPolicy
|
|||
/// <param name="options"></param>
|
||||
public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options)
|
||||
{
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
|
||||
var hstsOptions = options.Value;
|
||||
var maxAge = Convert.ToInt64(Math.Floor(hstsOptions.MaxAge.TotalSeconds))
|
||||
.ToString(CultureInfo.InvariantCulture);
|
||||
.ToString(CultureInfo.InvariantCulture);
|
||||
var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty;
|
||||
var preload = hstsOptions.Preload ? Preload : StringSegment.Empty;
|
||||
_strictTransportSecurityValue = new StringValues($"max-age={maxAge}{includeSubdomains}{preload}");
|
||||
_excludedHosts = hstsOptions.ExcludedHosts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,12 +55,29 @@ namespace Microsoft.AspNetCore.HttpsPolicy
|
|||
/// <returns></returns>
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
if (context.Request.IsHttps)
|
||||
if (context.Request.IsHttps && !IsHostExcluded(context.Request.Host.Host))
|
||||
{
|
||||
context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue;
|
||||
}
|
||||
|
||||
return _next(context);
|
||||
return _next(context);
|
||||
}
|
||||
|
||||
private bool IsHostExcluded(string host)
|
||||
{
|
||||
if (_excludedHosts == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _excludedHosts.Count; i++)
|
||||
{
|
||||
if (string.Equals(host, _excludedHosts[i], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.HttpsPolicy
|
||||
{
|
||||
|
|
@ -35,5 +36,15 @@ namespace Microsoft.AspNetCore.HttpsPolicy
|
|||
/// to preload HSTS sites on fresh install. See https://hstspreload.org/.
|
||||
/// </remarks>
|
||||
public bool Preload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of host names that will not add the HSTS header.
|
||||
/// </summary>
|
||||
public IList<string> ExcludedHosts { get; } = new List<string>
|
||||
{
|
||||
"localhost",
|
||||
"127.0.0.1", // ipv4
|
||||
"[::1]" // ipv6
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Net.Http;
|
||||
|
|
@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
|||
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
client.BaseAddress = new Uri("https://localhost:5050");
|
||||
client.BaseAddress = new Uri("https://example.com:5050");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
|||
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
client.BaseAddress = new Uri("https://localhost:5050");
|
||||
client.BaseAddress = new Uri("https://example.com:5050");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
|
@ -113,7 +114,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
|||
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
client.BaseAddress = new Uri("https://localhost:5050");
|
||||
client.BaseAddress = new Uri("https://example.com:5050");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
|
@ -121,5 +122,98 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
|||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expected, response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("localhost")]
|
||||
[InlineData("Localhost")]
|
||||
[InlineData("LOCALHOST")]
|
||||
[InlineData("127.0.0.1")]
|
||||
[InlineData("[::1]")]
|
||||
public async Task DefaultExcludesCommonLocalhostDomains_DoesNotSetHstsHeader(string host)
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseHsts();
|
||||
app.Run(context =>
|
||||
{
|
||||
return context.Response.WriteAsync("Hello world");
|
||||
});
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
client.BaseAddress = new Uri($"https://{host}:5050");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("localhost")]
|
||||
[InlineData("127.0.0.1")]
|
||||
[InlineData("[::1]")]
|
||||
public async Task AllowLocalhostDomainsIfListIsReset_SetHstsHeader(string host)
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHsts(options =>
|
||||
{
|
||||
options.ExcludedHosts.Clear();
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseHsts();
|
||||
app.Run(context =>
|
||||
{
|
||||
return context.Response.WriteAsync("Hello world");
|
||||
});
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
client.BaseAddress = new Uri($"https://{host}:5050");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Single(response.Headers);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("example.com")]
|
||||
[InlineData("Example.com")]
|
||||
[InlineData("EXAMPLE.COM")]
|
||||
public async Task AddExcludedDomains_DoesNotAddHstsHeader(string host)
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHsts(options => {
|
||||
options.ExcludedHosts.Add(host);
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseHsts();
|
||||
app.Run(context =>
|
||||
{
|
||||
return context.Response.WriteAsync("Hello world");
|
||||
});
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
client.BaseAddress = new Uri($"https://{host}:5050");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
|||
options.IncludeSubDomains = includeSubDomains;
|
||||
options.MaxAge = TimeSpan.FromSeconds(maxAge);
|
||||
options.Preload = preload;
|
||||
options.ExcludedHosts.Clear(); // allowing localhost for testing
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
Loading…
Reference in New Issue