38

I am working to get my .net core 1.1 application working behind a load balancer and enforcing https. I have the following setup in my Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IOptions<Auth0Settings> auth0Settings)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();


    var startupLogger = loggerFactory.CreateLogger<Startup>();


    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
        startupLogger.LogInformation("In Development");
    }
    else
    {
        startupLogger.LogInformation("NOT in development");
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMiddleware<HttpsRedirectMiddleware>();
    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
    });`
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationScheme= CookieAuthenticationDefaults.AuthenticationScheme,
            AutomaticAuthenticate = true,
            AutomaticChallenge = true,
            CookieHttpOnly = true,
            SlidingExpiration = true
        });

The HttpsRedirectMiddleware is for validating the LB has the X-Forwarded-Proto set, it does, and comes back as https as the only value. When I go to the site (https://myapp.somedomain.net), it knows I am not authenticated and redirects me to (http://myapp.somedomain.net/Account/Logon?ReturnUrl=%2f). It loses the SSL connection and switched back over to port 80 on me. The .net core documentation says to use "UseForwardedHeaders" like below, which does not work in my case. The console logger does not have any error or warnings from the middleware when this switch happens.

For a short term fix, I have put this below "UseForwardedHeaders"

    app.Use(async (context, next) =>
    {
        var xproto = context.Request.Headers["X-Forwarded-Proto"].ToString();
        if (xproto!=null && xproto.StartsWith("https", StringComparison.OrdinalIgnoreCase)){
            startupLogger.LogInformation("Switched to https");
            context.Request.Scheme = "https";
        }
        await next();

    });

The above works perfect, but is a hack. I would like to do it the correct way.

Josh
  • 2,248
  • 2
  • 19
  • 38
  • I do not fully understand the implication of clearing the networks, but after banging my head against the desk for a day trying to get a sample application running with SSL on AppHarbor this did the trick. I imagine there is a way to check the network behind the load balancer and use that as the known network when the application starts, but at least I know it has the potential to work. Thanks! – Vale Trujillo Jun 07 '17 at 23:14
  • 1
    Clearing the networks tells your application that you are ok with anyone connecting over port 80 and using the xforwarded headers which would mean it is not encrypted and could be used to force a MiTM(Main in the Middle) attack. If your firewalls are setup to only allow https(433) traffic, you should be fine, but security minded people always like extra layers of protection. Software now is being required to be secure out of the box to prevent default configuration setup attacks, thus the 127.0.0.1 default in .net core. – Josh Jun 08 '17 at 14:29

2 Answers2

79

.NET Core has a default set for the forwarded headers. It defaults to 127.0.0.1, for IIS integration.

After tracking down the source code, I found that you can clear the KnownNetworks and KnownProxies lists to accept any forwarded requests. However, it is still best to have a firewall setup or lock the known networks down to a private subnet.

var forwardingOptions = new ForwardedHeadersOptions()
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
};
forwardingOptions.KnownNetworks.Clear(); // Loopback by default, this should be temporary
forwardingOptions.KnownProxies.Clear(); // Update to include

app.UseForwardedHeaders(forwardingOptions);

Update for .NET Core 2.x: Remember setting the IP of your proxy/load balancer or the private network after debugging the issue. This prevents bypassing your proxy/load balancer and faking the Forwarded-For headers.

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;

    // Replace with IP of your proxy/load balancer
    options.KnownProxies.Add(IPAddress.Parse("192.168.1.5"));

    // 192.168.1.0/24 allows any from 192.168.1.1-254;
    options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.1.0"), 24));
});

https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.2#forwarded-headers-middleware-options

janw
  • 8,758
  • 11
  • 40
  • 62
Josh
  • 2,248
  • 2
  • 19
  • 38
  • 3
    Absolutely. Those Clear() lines made the difference for us. –  May 22 '18 at 14:28
  • 1
    @SunilBuddala You would still need to setup the KnownNetworks or KnownProxies unless you followed the NGinx tutorial which would make the site appear to be hosted similar to the IISIntegration option. – Josh Nov 08 '18 at 13:30
  • 3
    Upvoted because it helped me solve my problem, thanks! But it's worth pointing out: using `.Clear()` should *not* be a long-term solution because it implicitly trusts any caller. It's better to figure out what addresses or networks your reverse proxy will call your application on, and explicitly whitelist them. This page in the docs has more detail: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.2#forwarded-headers-middleware-options – Nate Barbettini Mar 05 '19 at 16:56
  • 1
    @NateBarbettini updated and provided new code sample and link to the document for further configuration. – Josh Mar 28 '19 at 15:11
  • 2
    Similar use case is exemplified in the [docs](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.2#forward-the-scheme-for-linux-and-non-iis-reverse-proxies). Another thing that may cause issues is the use of `UseHttpsRedirection` and `UseForwardedHeaders` middlewares in a wrong order; in my case `UseForwardedHeaders` being the first solved an issue. – Jota.Toledo Jul 13 '19 at 07:12
  • Does this solution relevant for .ner core 3? – Yaroslav Bres Oct 12 '20 at 12:04
  • @ЯрославВиталиевич same syntax, help doc below for 3.x. https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-3.1#forwarded-headers-middleware-options – Josh Oct 12 '20 at 19:49
  • For AWS load balancer, I just cleared KnownNetworks and KnownProxies, and tested that it correctly appends client IP address and port (when "Client port preservation" is enabled on the load balancer in "edit load balancer attributes"), and then HttpContext.Connection.RemoteIpAddress and RemotePort are correctly set to the client IP address and port. – xhafan Apr 12 '23 at 14:20
  • @xhafan that's a security risk. You want to list the known load balancers on your network or the header can be spoofed which can cause the wrong IP to listed in your logs. – Josh Jul 21 '23 at 15:17
14

If you are using a load balancer, it is common to have the load balance terminate the SSL connection and send the request to your application over HTTP.

This worked for me. I am using SSL termination on AWS Load Balancer.

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedProto
});

What this does is updates the Request.Scheme with the X-Forwarded-Proto header so that all redirects link generation uses the correct scheme.

X-Forwarded-Proto: The scheme from the original client and proxies.

Sunil Buddala
  • 1,123
  • 1
  • 10
  • 22
  • 3
    Downvoted because you've essentially told OP what he already knows, as shown in his question. – ColinM Mar 03 '20 at 13:11
  • 8
    The original author has `ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto` in the sample, whereas this code is different. In my experience, this is a very meaningful difference. In fact, this is the only working answer I've found. – JD Huntington Jul 02 '20 at 20:45
  • 1
    @JDHuntington can agree. Using ForwardedHeaders.All didn't work for me. Using ForwardedHeaders.XForwardedProto did the trick. – Christian Klemm Jul 03 '20 at 08:16
  • this did the trick for me in my aks cluster with nginx ingress controller in front of it as application gateway. Thanks! – Marc Ottenbacher Mar 23 '21 at 14:29
  • This should be the correct answer. Maybe someone could explain why the options object is ignored for the XForwardedProto? – Mitch Dart Jun 10 '22 at 11:02
  • @MitchDart the options are being ignored because not enough has been configured. By default, `KnownProxies` and `KnownNetworks` are configured with values in them and if you don't set them using details from your proxy layer, things will fail. Clearing works, but is not advisable for a production scenario. – al3xjohnson Apr 30 '23 at 03:43
  • This answer worked for me too (combined with clearing the KnownNetworks/KnownProxies because I don't control the load balancer CIDR). Anyone can explain why using both parameters it does not change the scheme but dropping XForwardedFor does? – Dunge Aug 08 '23 at 02:50