-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Regression in SPN generation in .NET 5.0 for non-standard HTTP ports #53193
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @dotnet/ncl Issue DetailsIt appears that The inclusion of the port in the SPN is a thing IE could be configured to do (the original KB is lost to time, but it's referenced many places, e.g. https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/internet-explorer-behaviors-with-kerberos-authentication/ba-p/396428). To the best of my knowledge, no browser includes the port by default. Chrome has an option (again, off-by-default) to include the port - https://www.chromium.org/developers/design-documents/http-authentication (see "Kerberos SPN Generation"). Perhaps there was also something that could be done to configure .NET Framework to do this also, but again, it is not the default. My guess is that this change broke fewer things than one might expect because, in a pure Windows shop, assuming security hasn't been tightened, you get an auto-magic downgrade to NTLM after the KDC returns an error because a principal with a port number in it does not exist. If NTLM is unsupported for one reason or another (Linux stuff in the mix, you care about security and have disabled NTLM) the fallback won't happen and things just don't work. Given the following test program, I observe consistent behavior in .NET 4.8 and .NET Core 3.1, but .NET 5.0 diverges. My test below only has Windows output, but this regression is also observed in Linux .NET Core clients. This is blocking our ability to port several legacy codebases to .NET Core or upgrade from .NET Core 3.1. Client code: using System;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace SpnPortDemo
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine($"Running under: {RuntimeInformation.FrameworkDescription}");
RunConsoleApp("klist", "purge");
try
{
var client = new HttpClient(new HttpClientHandler {UseDefaultCredentials = true});
Console.WriteLine("Sending HTTP GET to " + args[0]);
using (var response = await client.GetAsync(args[0]))
{
Console.WriteLine($"Status: {response.StatusCode}");
Console.WriteLine("Body:");
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
catch (HttpRequestException e)
{
Console.WriteLine(e);
}
RunConsoleApp("klist");
}
private static void RunConsoleApp(string file, string args = null)
{
var psi = new ProcessStartInfo
{
FileName = file,
Arguments = args,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
};
Console.WriteLine();
Console.WriteLine($"Output from: {file} {args}");
var proc = Process.Start(psi);
Console.WriteLine(proc.StandardOutput.ReadToEnd());
Console.WriteLine();
}
}
} Dummy (Windows only) server code (run as SYSTEM using using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace ListenerTest
{
class Program
{
static int Main(string[] args)
{
if (args.Length != 1 || !ushort.TryParse(args[0], out var port))
{
Console.WriteLine("Failed to parse port");
return 1;
}
var listener = new HttpListener();
listener.Prefixes.Add($"http://+:{port}/");
listener.AuthenticationSchemes = AuthenticationSchemes.Negotiate;
Console.WriteLine($"Starting listener on port {port}");
listener.Start();
Task.Run(() =>
{
try
{
while (true)
{
var ctx = listener.GetContext();
var bytes = Encoding.UTF8.GetBytes($"{ctx.User.Identity.AuthenticationType}: {ctx.User.Identity.Name}");
ctx.Response.ContentLength64 = bytes.Length;
ctx.Response.OutputStream.Write(bytes, 0, bytes.Length);
ctx.Response.OutputStream.Close();
}
}
catch (Exception e)
{
if (!listener.IsListening)
return;
Console.WriteLine($"Error: {e}");
}
});
Console.ReadLine();
return 0;
}
}
} .NET 4.8 output - Kerberos works (note client and server must be separate boxes or you'll get NTLM anyway):
.NET Core 3.1, again Kerberos works
But in .NET 5.0, Kerberos fails. A packet capture shows the TGS-REQ for
I'm sure there are environments that do depend on the port in the SPN, so simply reverting might cause problems for them. .NET Core should be changed to either do it conditionally in the same manner as .NET Framework (assuming the framework checked/inherited behavior from some system level setting), or to expose a configurable knob for people that need it to opt in.
|
Triage: Technically duplicate of #51701, but we may want to rethink this one ... |
Sorry, didn't notice that existing issue. That linked issue mentions that this is conforming to a spec. Which spec is that? https://datatracker.ietf.org/doc/html/rfc4559 makes no mention of including a port. That RFC is basically a retcon of IE behavior (came 6 years after IE shipped the feature), and IE doesn't do this by default. Chrome and Firefox don't do this, nor does curl. |
Thanks for addressing this, much appreciated. |
It appears that
SocketsHttpHandler
was changed in .NET 5.0 (#40860) to unconditionally include the port in the Kerberos service principal name when the HTTP service runs on a non-default port. The intent of that change appears to have been to emulate the behavior of .NET Framework, but it actually created a divergence from .NET Framework behavior, at least for environments that expect out-of-the-box default behavior (no port in SPN).The inclusion of the port in the SPN is a thing IE could be configured to do (the original KB is lost to time, but it's referenced many places, e.g. https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/internet-explorer-behaviors-with-kerberos-authentication/ba-p/396428). To the best of my knowledge, no browser includes the port by default. Chrome has an option (again, off-by-default) to include the port - https://www.chromium.org/developers/design-documents/http-authentication (see "Kerberos SPN Generation"). Perhaps there was also something that could be done to configure .NET Framework to do this also, but again, it is not the default.
My guess is that this change broke fewer things than one might expect because, in a pure Windows shop, assuming security hasn't been tightened, you get an auto-magic downgrade to NTLM after the KDC returns an error because a principal with a port number in it does not exist. If NTLM is unsupported for one reason or another (Linux stuff in the mix, you care about security and have disabled NTLM) the fallback won't happen and things just don't work.
Given the following test program, I observe consistent behavior in .NET 4.8 and .NET Core 3.1, but .NET 5.0 diverges. My test below only has Windows output, but this regression is also observed in Linux .NET Core clients. This is blocking our ability to port several legacy codebases to .NET Core or upgrade from .NET Core 3.1.
Client code:
Dummy (Windows only) server code (run as SYSTEM using
psexec -sid cmd
).NET 4.8 output - Kerberos works (note client and server must be separate boxes or you'll get NTLM anyway):
.NET Core 3.1, again Kerberos works
But in .NET 5.0, Kerberos fails. A packet capture shows the TGS-REQ for
HTTP/testhost.domain.com:12345
, which isn't found, which then triggers a fallback to NTLM. My dummy server/test environment here has NTLM enabled, so things "work". Our production environment does not support does not support NTLM.I'm sure there are environments that do depend on the port in the SPN, so simply reverting might cause problems for them. .NET Core should be changed to either do it conditionally in the same manner as .NET Framework (assuming the framework checked/inherited behavior from some system level setting), or to expose a configurable knob for people that need it to opt in.
The text was updated successfully, but these errors were encountered: