-
Notifications
You must be signed in to change notification settings - Fork 84
Adds Https Redirection and Hsts Middlewares #264
Conversation
6dbf757
to
d9f364d
Compare
Per offline feedback, |
samples/HttpsSample/Startup.cs
Outdated
.UseKestrel( | ||
options => | ||
{ | ||
options.Listen(new IPEndPoint(IPAddress.Loopback, 443), listenOptions => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Demo using a custom port.
samples/HttpsSample/Startup.cs
Outdated
|
||
public void Configure(IApplicationBuilder app) | ||
{ | ||
var httpsEnforcementOptions = new HttpsEnforcementOptions(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Options should all be moved to ConfiguredServices as services.Configure<MyOptions>(options => ...);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a helper method for this.
services.AddHttpsPolicy(o => o. ...);
/// <returns></returns> | ||
public async Task Invoke(HttpContext context) | ||
{ | ||
context.Response.OnStarting(() => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just do this before calling next? OnStarting is not required.
/// Max-age is required; defaults to 0. | ||
/// See: https://tools.ietf.org/html/rfc6797#section-6.1.1 | ||
/// </remarks> | ||
public int MaxAge { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TimeSpan.
Also, 0 means to disable HSTS, so it can't be the default.
NOTE: A max-age value of zero (i.e., "max-age=0") signals the UA to
cease regarding the host as a Known HSTS Host, including the
includeSubDomains directive (if asserted for that HSTS Host).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though https://tools.ietf.org/html/rfc6797#section-11.2 suggests 0 as the default and making users set it so that it's not using an arbitrary value.
If we require users to set it then we should make it nullable and throw if it's not set. If not then we need to come up with a non-zero default. Talk to @blowdart.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://tools.ietf.org/html/rfc6797#section-11.2 defines an alternative to a fixed second count where you would define a fixed date and then Max-Age would reflect a count-down to that date. Not sure if that's in wide use or worth implementing. Do other providers offer this variation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm in favor of having it nullable and throw if it isn't set. The fixed second count is odd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ask the PMs.
{ | ||
context.Response.OnStarting(() => | ||
{ | ||
if (context.Request.Scheme == "https") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Request.IsHttps
/// Sets the preload parameter of the Strict-Transport-Security header. | ||
/// </summary> | ||
/// <remarks> | ||
/// Preload is not part of the RFC specification, but is supported by web browsers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove the Chrome reference, just say some web browsers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RFC has it "IncludeSubDomains" but one could argue the property should be named IncludeSubdomains
, I don't know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll stick with the RFC here. 😃
public static class HttpsEnforcementExtensions | ||
{ | ||
/// <summary> | ||
/// Adds middleware for enforcing HTTPS for all HttpRequests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Including redirecting from HTTP to HTTPS and adding the HSTS header.
/// <summary> | ||
/// The status code to be used for Url Redirection | ||
/// </summary> | ||
public int StatusCode { get; set; } = 301; // TODO throw ArgumentOutOfRangeException from UrlRewrite redirect rule? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the constant
/// <summary> | ||
/// Whether to use HTTP Strict-Transport-Security (HSTS) on all HTTPS requests. | ||
/// </summary> | ||
public bool EnforceHsts { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SendHsts? SetHsts? The HSTS enforcement happens on the client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Off to a good start!
samples/HttpsSample/Startup.cs
Outdated
|
||
public void Configure(IApplicationBuilder app) | ||
{ | ||
var httpsEnforcementOptions = new HttpsEnforcementOptions(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a helper method for this.
services.AddHttpsPolicy(o => o. ...);
throw new ArgumentNullException(nameof(app)); | ||
} | ||
|
||
return app.UseHsts(new HstsOptions()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HstsOptions here should come from DI.
namespace Microsoft.AspNetCore.HttpsEnforcement | ||
{ | ||
/// <summary> | ||
/// Enables Http Strict Transport Security (HSTS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: HTTP
(casing)
{ | ||
throw new ArgumentNullException(nameof(app)); | ||
} | ||
var httpsEnforcementOptions = new HttpsEnforcementOptions(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should come from DI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to figure out how we will flow the custom port into this middle-ware for development scenarios.
My standing suggestion is a top level config key / environment variable HttpsPort that would be set by VS when launching the process. VS is also the one setting URLs at startup. |
} | ||
var httpsEnforcementOptions = new HttpsPolicyOptions(); | ||
|
||
return app.UseHttpsPolicy(httpsEnforcementOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Policy
/// <summary> | ||
/// The status code to be used for Url Redirection | ||
/// </summary> | ||
public int StatusCode { get; set; } = StatusCodes.Status301MovedPermanently; // TODO throw ArgumentOutOfRangeException from UrlRewrite redirect rule? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's ok as it is. I don't think we need to over protect against a non redirect status code. I would change the property name and the doc comment to reflect that is a redirect status code. Property would be RedirectStatusCode. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes RedirectStatusCode.
/// <summary> | ||
/// Options for using HSTS | ||
/// </summary> | ||
public HstsOptions HstsOptions { get; set; } // TODO should this be initialized at all? We could remove the bool if so. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy with this being initialized by default (to whatever defaults we choose) and for it being null to have the meaning of SetHsts = false. That way there can't be invalid combinations (like the user initializes the setting and forgets to set the flag or viceversa). I also think we follow this pattern in other places. @Tratcher do you have anything against my proposal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! It's almost there!
@@ -65,5 +46,9 @@ public static IApplicationBuilder UseHttpsPolicy(this IApplicationBuilder app, H | |||
|
|||
return app; | |||
} | |||
|
|||
public static IApplicationBuilder UseHttpsPolicy(this IApplicationBuilder app, bool enableHsts) | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why empty? Do we need a comment explaining it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry redesign for this. I need to do a bit of work still!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, OK, sorry didn't notice the [WIP] in the title.
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.AspNetCore.HttpsPolicy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Builder namespace
var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty; | ||
var preload = hstsOptions.Preload ? Preload : StringSegment.Empty; | ||
|
||
_nameValueHeaderValue = new StringValues($"max-age={hstsOptions.MaxAge}{includeSubdomains}{preload}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_stsValue
{ | ||
context.Response.Headers[HeaderNames.StrictTransportSecurity] = _nameValueHeaderValue; | ||
} | ||
await _next(context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no async/await required
/// Max-age is required; defaults to 0. | ||
/// See: https://tools.ietf.org/html/rfc6797#section-6.1.1 | ||
/// </remarks> | ||
public int MaxAge { get; set; } // TODO set this to a different default in dev/not dev? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0 cannot be the default, that has a specific meaning in the spec. Ask the PMs for a specific value. 1 day? 1 week?
We may not need a separate value for dev as the templates won't have this enabled by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shirhatti as I know you were opinionated on having 0 as the default.
public class HttpsPolicyOptions | ||
{ | ||
/// <summary> | ||
/// Whether to use HTTP Strict-Transport-Security (HSTS) on all HTTPS requests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
responses
/// <summary> | ||
/// The status code to be used for Url Redirection | ||
/// </summary> | ||
public int StatusCode { get; set; } = StatusCodes.Status301MovedPermanently; // TODO throw ArgumentOutOfRangeException from UrlRewrite redirect rule? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes RedirectStatusCode.
public int StatusCode { get; set; } = StatusCodes.Status301MovedPermanently; // TODO throw ArgumentOutOfRangeException from UrlRewrite redirect rule? | ||
|
||
/// <summary> | ||
/// The TLS port to be added to the redirected URL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default port (443) will be used if not provided.
[InlineData(302, 5050, 0, true, true, "max-age=0; includeSubDomains; preload", "https://localhost:5050/")] | ||
public async Task SetOptions_AddHstsMiddlewareAndEnableInPolicy(int statusCode, int? tlsPort, int maxAge, bool includeSubDomains, bool preload, string expectedHstsHeader, string expectedUrl) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove empty line
.Configure(app => | ||
{ | ||
app.UseHttpsPolicy(); | ||
app.UseHsts(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought UseHttpsPolicy would add Hsts if needed, why is this line here. Is that not the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is the case. This is just showing that you can still add Hsts again after calling UseHttps.
|
||
var response = await client.SendAsync(request); | ||
|
||
// By default, we will not redirect if we only use the Hsts Middleware. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is confusing, I'm not sure what you are trying to convey here, but it seems out of context
|
||
var response = await client.SendAsync(request); | ||
|
||
// By default, we will not redirect if we only use the Hsts Middleware. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You copy pasted this comment below and doesn't make sense there. I don't think this adds a lot of value here, so I would just remove it.
We should consider making HttpsPolicy a shared options collection with RedirectToHttpsOptions and HstsOptions. To configure the HttpPolicy, if we require the max-age to be set, users will have to call services.Configure. |
/// See: https://tools.ietf.org/html/rfc6797#section-6.1.1 | ||
/// </remarks> | ||
public int MaxAge { get; set; } | ||
public int MaxAge { get; set; } = 2592000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a TimeSpan so people don't have to do the math themselves.
samples/HttpsPolicySample/Startup.cs
Outdated
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.Configure<HttpsRedirectionOptions>(options => { | ||
options.RedirectStatusCode = 301; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use StatusCodes.Status301MovedPermanently
/// <returns></returns> | ||
public Task Invoke(HttpContext context) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've got a blank line here.
{ | ||
context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue; | ||
} | ||
return _next(context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should have a blank line before the return statement.
samples/HttpsPolicySample/Startup.cs
Outdated
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.Configure<HttpsRedirectionOptions>(options => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the samples to use the helper methods
var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty; | ||
var preload = hstsOptions.Preload ? Preload : StringSegment.Empty; | ||
|
||
_strictTransportSecurityValue = new StringValues($"max-age={hstsOptions.MaxAge.TotalSeconds}{includeSubdomains}{preload}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nvrm realized I realized that security has max age as nullable.
/// </summary> | ||
public class HttpsRedirectionOptions | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra line
<PackageTags>aspnetcore;https;hsts</PackageTags> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is Http.Extensions needed for?
The current Options usage looks fine. |
.UseUrls("https://*:5050") | ||
.ConfigureServices(services => | ||
{ | ||
services.Configure<HstsOptions>(options => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the usages on the tests to use the service collection extensions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two sets of tests, one that uses Configure and AddHsts(). I can remove these tests if you would like 😄
.UseUrls("https://*:5050", "http://*:5050") | ||
.ConfigureServices(services => | ||
{ | ||
services.Configure<HttpsRedirectionOptions>(options => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too
var builder = new WebHostBuilder() | ||
.ConfigureServices(services => | ||
{ | ||
services.Configure<HttpsRedirectionOptions>(options => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Final cleanups, but it's good to go
Implementation of #258.
Final items:
cc @danroth27 @shirhatti @muratg