Exclude setting HSTS on localhost and allow for user to specify excluded domains. (#274)

This commit is contained in:
Justin Kotalik 2017-12-12 14:12:26 -08:00 committed by GitHub
parent 3488c24804
commit 892cebb27e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 134 additions and 13 deletions

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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
};
}
}

View File

@ -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);
}
}
}

View File

@ -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 =>