Skip to content
This repository has been archived by the owner on Dec 20, 2018. It is now read-only.

Adds Https Redirection and Hsts Middlewares #264

Merged
merged 14 commits into from
Oct 13, 2017
Merged

Conversation

jkotalik
Copy link
Contributor

@jkotalik jkotalik commented Oct 3, 2017

Implementation of #258.
Final items:

  • Verify which browsers respect the Strict-Transport-Security and check behavior on browser.
  • Naming
  • Any design/ usability changes

cc @danroth27 @shirhatti @muratg

@jkotalik jkotalik force-pushed the jkotalik/httpsMiddleware branch from 6dbf757 to d9f364d Compare October 3, 2017 02:11
@jkotalik
Copy link
Contributor Author

jkotalik commented Oct 3, 2017

Per offline feedback,
HttpsEnforcement -> HttpsPolicy for everything?

.UseKestrel(
options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 443), listenOptions =>
Copy link
Member

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.


public void Configure(IApplicationBuilder app)
{
var httpsEnforcementOptions = new HttpsEnforcementOptions();
Copy link
Member

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 => ...);

Copy link
Member

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(() =>
Copy link
Member

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; }
Copy link
Member

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).

Copy link
Member

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.

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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")
Copy link
Member

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
Copy link
Member

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.

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.

Copy link
Contributor Author

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.
Copy link
Member

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?
Copy link
Member

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; }
Copy link
Member

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.

Copy link
Member

@javiercn javiercn left a 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!


public void Configure(IApplicationBuilder app)
{
var httpsEnforcementOptions = new HttpsEnforcementOptions();
Copy link
Member

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());
Copy link
Member

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)
Copy link
Member

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();
Copy link
Member

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

Copy link
Member

@javiercn javiercn left a 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.

@Tratcher
Copy link
Member

Tratcher commented Oct 4, 2017

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);
Copy link
Member

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?
Copy link
Member

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?

Copy link
Member

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.
Copy link
Member

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?

Copy link
Member

@javiercn javiercn left a 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)
{
Copy link
Contributor

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?

Copy link
Contributor Author

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!

Copy link
Contributor

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
Copy link
Member

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}");
Copy link
Member

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);
Copy link
Member

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?
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline with @javiercn. 0 isn't a valid default, and using a nullable would unnecessarily complicate the user-experience. We just need to come up with a good default. Take it up with @blowdart over email if needs be.

Copy link
Contributor Author

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.
Copy link
Member

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?
Copy link
Member

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.
Copy link
Member

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)
{

Copy link
Member

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();
Copy link
Member

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?

Copy link
Contributor Author

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.
Copy link
Member

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.
Copy link
Member

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.

@jkotalik
Copy link
Contributor Author

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;
Copy link
Member

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.

public void ConfigureServices(IServiceCollection services)
{
services.Configure<HttpsRedirectionOptions>(options => {
options.RedirectStatusCode = 301;

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)
{

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

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.

// 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 => {
Copy link
Member

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

@jkotalik
Copy link
Contributor Author

@javiercn @Tratcher We need to talk about how configuration options will be propagated to the middleware. Aka does this middleware need to do anything different or will configuration call app.UseHsts() or app.HttpsRedirection().

var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty;
var preload = hstsOptions.Preload ? Preload : StringSegment.Empty;

_strictTransportSecurityValue = new StringValues($"max-age={hstsOptions.MaxAge.TotalSeconds}{includeSubdomains}{preload}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

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
{

Copy link
Member

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" />
Copy link
Member

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?

@Tratcher
Copy link
Member

The current Options usage looks fine.

@jkotalik jkotalik changed the title [WIP] Adds HttpsEnforcement and Hsts Middleware Adds HttpsEnforcement and Hsts Middleware Oct 13, 2017
@jkotalik jkotalik changed the title Adds HttpsEnforcement and Hsts Middleware Adds Https Redirection and Hsts Middlewares Oct 13, 2017
.UseUrls("https://*:5050")
.ConfigureServices(services =>
{
services.Configure<HstsOptions>(options => {
Copy link
Member

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

Copy link
Contributor Author

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 =>
Copy link
Member

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 =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here

Copy link
Member

@javiercn javiercn left a 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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants