How to automatically determine KnownNetworks for ASP.NET Core application running in Kubernetes with an in-cluster reverse proxy?

11/9/2018

I'm running an ASP.NET Core API in Kubernetes behind a reverse proxy that's sending X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers.

I've found that I need to use UseForwardedHeaders() to accept the values from the proxy, so I've written the following code:

var forwardedOptions = new ForwardedHeadersOptions()
{
    ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.All
};
forwardedOptions.KnownNetworks.Add(new IPNetwork(IPAddress.Parse(configuration["network:address"]), int.Parse(configuration["network:cidrMask"])));
app.UseForwardedHeaders(forwardedOptions);

I'm running my API and reverse proxy within Kubernetes, and the API is only visible in the cluster. Because of this, I'm not worried about somebody on the cluster network spoofing the headers. What I would like to do is to automatically detect the cluster's internal subnet and add this to the KnownNetworks list. Is this possible? If so, how?

-- John
asp.net-core
c#
kubernetes

1 Answer

11/9/2018

I've created a method that calculates the start in the range and the CIDR subnet mask for each active interface:

private static IEnumerable<IPNetwork> GetNetworks(NetworkInterfaceType type)
{

    foreach (var item in NetworkInterface.GetAllNetworkInterfaces()
        .Where(n => n.NetworkInterfaceType == type && n.OperationalStatus == OperationalStatus.Up)  // get all operational networks of a given type
        .Select(n => n.GetIPProperties())   // get the IPs
        .Where(n => n.GatewayAddresses.Any())) // where the IPs have a gateway defined
    {
        var ipInfo = item.UnicastAddresses.FirstOrDefault(i => i.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork); // get the first cluster-facing IP address
        if (ipInfo == null) { continue; }

        // convert the mask to bits
        var maskBytes = ipInfo.IPv4Mask.GetAddressBytes();
        if (!BitConverter.IsLittleEndian)
        {
            Array.Reverse(maskBytes);
        }
        var maskBits = new BitArray(maskBytes);

        // count the number of "true" bits to get the CIDR mask
        var cidrMask = maskBits.Cast<bool>().Count(b => b); 

        // convert my application's ip address to bits
        var ipBytes = ipInfo.Address.GetAddressBytes();
        if (!BitConverter.IsLittleEndian)
        {
            Array.Reverse(maskBytes);
        }
        var ipBits = new BitArray(ipBytes);

        // and the bits with the mask to get the start of the range
        var maskedBits = ipBits.And(maskBits);

        // Convert the masked IP back into an IP address
        var maskedIpBytes = new byte[4];
        maskedBits.CopyTo(maskedIpBytes, 0);
        if (!BitConverter.IsLittleEndian)
        {
            Array.Reverse(maskedIpBytes);
        }
        var rangeStartIp = new IPAddress(maskedIpBytes);

        // return the start IP and CIDR mask
        yield return new IPNetwork(rangeStartIp, cidrMask);
    }
}

Examples:

  • 192.168.1.33 with mask 255.255.255.252 returns 192.168.1.32/30
  • 10.50.28.77 with mask 255.252.0.0 returns 10.50.0.0/14

I've then changed my options code to look like this:

var forwardedOptions = new ForwardedHeadersOptions()
{
    ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.All
};
foreach (var network in GetNetworks(NetworkInterfaceType.Ethernet))
{
    forwardedOptions.KnownNetworks.Add(network);
}
app.UseForwardedHeaders(forwardedOptions);
-- John
Source: StackOverflow