ASP.NET Core unable to use Forwarded Headers

I have a bit of a headscratcher of an issue here. I am using ASP.NET Core and am attempting to use the forwarded headers feature to obtain the client’s IP address, as described here. This works perfectly locally, and I can specify it to use Fly-Client-IP rather than X-Forwarded-For to simplify this determination. However, when I deploy it to Fly’s infrastructure it seems to break for some reason and I am not sure if this is something to do with Fly or with ASP.NET Core (though leaning to Fly :wink:).

I have published a simple repro of this at https://bitter-wood-2809.fly.dev, this simply returns the remote IP address as reported by ASP.NET Core. When the header specified in the config is present it should be updated with the value of that.

The source code for this project is simply created by creating a webapi project as described here and replacing the contents of Program.cs with:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.Configure<ForwardedHeadersOptions>(options => 
{
    options.ForwardedForHeaderName = "Fly-Client-IP";
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor;
});

var app = builder.Build();

app.UseForwardedHeaders();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

// app.UseHttpsRedirection();

app.MapGet("/", (HttpContext context) => context.Connection.RemoteIpAddress?.ToString());

app.Run();

During local testing it seems that the forwarded headers settings aren’t case sensitive (as I have noticed that Fly-Client-Ip with a lowercase p seems to be actually passed). Any help would be most appreciated!

Alright, so I did a little more digging here and expanded the response from the demo app to include the header values and they all come through. Case is ignored too which I was wondering if that made a difference.

app.MapGet("/", (HttpContext context) => 
{
    return new
    {
        FlyClientIpLowerP = context.Request.Headers.TryGetValue("Fly-Client-Ip", out var ipValues)
            ? string.Join(", ", ipValues)
            : "--None--",
        FlyClientIpUpperP = context.Request.Headers.TryGetValue("Fly-Client-IP", out var iPValues)
            ? string.Join(", ", iPValues)
            : "--None--",
        XForwardedFor = context.Request.Headers.TryGetValue("X-Forwarded-For", out var forwardedValues)
            ? string.Join(", ", forwardedValues)
            : "--None--",
        RemoteIp = context.Connection.RemoteIpAddress?.ToString()
    };
});

So I am quite perplexed why this isn’t working. Is Fly sending headers is some nonstandard way? Is there some edge case in the ASP.NET Core code that is being hit? :thinking:

Ah - got it!!

I enabled debug logging and found the error:

{"EventId":1,"LogLevel":"Debug","Category":"Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware","Message":"Unknown proxy: [::ffff:145.40.109.133]:51474","State":{"Message":"Unknown proxy: [::ffff:145.40.109.133]:51474","RemoteIpAndPort":"[::ffff:145.40.109.133]:51474","{OriginalFormat}":"Unknown proxy: {RemoteIpAndPort}"}}

Which corresponds with this line in ASP.NET Core.

Turns out I needed to specify KnownNetworks in the ForwardedHeaders config. :man_facepalming:

The question now becomes what range of networks specifically for the proxies I should be allowing? I found the following request which is a few years old now: ( Request: Fly IP ranges - General - Fly.io. I guess I can just allow any network, but that doesn’t seem best practice (happy to be convinced otherwise though).

Can you show the change you made? It looked from these docs that adding Fly-Client-Ip to ForwardedHeaders should work.

Regarding the networks issue, it should be safe to accept from any network. The IP you see here is from the physical host your VM runs on. Your VM is already closed to traffic from outside your organization by default.

Yep, so I needed the following ForwardedHeadersOptions:

builder.Services.Configure<ForwardedHeadersOptions>(options => 
{
    options.ForwardedForHeaderName = "Fly-Client-IP";
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor;
    options.KnownNetworks.Add(new IPNetwork(IPAddress.Any, 0)); // This needed to be added
    options.KnownNetworks.Add(new IPNetwork(IPAddress.IPv6Any, 0)); // This needed to be added
});

Yes given the app is behind your proxy I guess it’s ok to allow from any address. Some folks who are a little more security conscious than me may not want to do that.