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