dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.91k stars 4.63k forks source link

NTLM authentication not working in Linux based environment #101058

Open jondoe69420 opened 4 months ago

jondoe69420 commented 4 months ago

Description

I need to send an http request to a server which uses Windows authentication. I wrote the following code to try and send just any request to my server.

var handler = new HttpClientHandler 
{
    PreAuthenticate = true,
    UseProxy = false,
    UseDefaultCredentials = false,
    Credentials = new CredentialCache
    {
        {
            new Uri("http://otherapi"), "Negotiate", new NetworkCredential("username", "password")
        }
    }
}

using var client = new HttpClient(handler);
var stringContent = new StringContent("{}", Encoding.UTF8, "application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, "http://otherapi/api/endpoint");
request.Content = stringContent;
using var res = await client.SendAsync(request);
Console.WriteLine($"{res.StatusCode}");

When running the above code on a Windows machine, everything works as expected, and BadRequest gets printed to the console ( which is what I expect to happen when sending an invalid json request ).

The problem is, I need to run this code on a Linux based machine, and when running it on an Ubuntu 22 machine (as well as the official aspnet docker image), it fails and prints out to the console Unauthorized, which means the windows authentication didn't work.

I also tried it with

Credentials = new CredentialCache
    {
        {
            new Uri("http://otherapi"), "NTLM", new NetworkCredential("username", "password")
        }
    }

with no success.

I opened up Wireshark on my windows machine to try and figure out whats going on. I saw that the client initially sends a POST request to the server, and then the server responds with a 401, and the following headers

WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM

After that I saw that the client sent the same request again, this time with an Authorization header, which from what I understand, is the correct behaviour. After that the request continues as expected.

The problem is, when I open Wireshark on my Ubuntu machine, I see that after the server responded with the first 401, the client just stops sending requests for some reason.

Reproduction Steps

Run the following code in a Linux machine

var handler = new HttpClientHandler 
{
    PreAuthenticate = true,
    UseProxy = false,
    UseDefaultCredentials = false,
    Credentials = new CredentialsCache
    {
        {
            new Uri("http://otherapi"), "Negotiate", new NetworkCredential("username", "password")
        }
    }
}

using var client = new HttpClient(handler);
var stringContent = new StringContent("{}", Encoding.UTF8, "application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, "http://otherapi/api/endpoint");
request.Content = stringContent;
using var res = await client.SendAsync(request);
Console.WriteLine($"{res.StatusCode}");

Expected behavior

BadRequest printed out on both Linux and Windows

Actual behavior

Unauthorized printed out on Linux, BadRequest printed out on Windows.

Regression?

No response

Known Workarounds

No response

Configuration

I tried both .NET 7 and .NET 8

Ran on Windows 10 Linux was ran on Ubuntu 22 and the official aspnet docker image

Other information

No response

jondoe69420 commented 4 months ago

After digging a little deeper, I read online that on Linux, dotnet uses libcurl under the hood. I tried the running the following on my Ubuntu machine

curl --negotiate -u username:password http://otherapi --data '' -i --verbose

I got a 401, and looking at the output I saw the line

gss_init_sec_context(): failed: No credentials were supplied, or the credentials were unavailable or inaccessible. SPNEGO cannot find mechanisms to negotiate.

So I changed the command to

curl --ntlm -u username:password http://otherapi --data '' -i

And that seemed to work, I got back a 403 Forbidden. Unfortunately, changing Negotiate to NTLM in my CredentialCache didn't solve the issue.

huoyaoyuan commented 4 months ago

I read online that on Linux, dotnet uses libcurl under the hood.

It was used for early versions. Higher versions manipulate raw sockets directly.

I believe NTLM is just unimplemented as a Windows-only scheme.

jondoe69420 commented 4 months ago

@huoyaoyuan So there is no support for NTLM authentication in .NET on Linux?

huoyaoyuan commented 4 months ago

So there is no support for NTLM authentication in .NET on Linux?

I guess. Please confirm with official response from network team.

MihaZupan commented 4 months ago

Do you see the same behavior if you enable AppContext.SetSwitch("System.Net.Security.UseManagedNtlm", true);?

If so, can you run your repro project with debug-level tracing enabled, like shown in https://github.com/dotnet/runtime/issues/100231#issuecomment-2049541268?

rzikm commented 4 months ago

I believe NTLM is just unimplemented as a Windows-only scheme.

NTLM is supported on linux/OSX via libgssapi

Testing with UseManagedNtlm sounds like a good step for debugging. Additionally, it would be great if you collected and shared with us packet captures.

cc @filipnavara as subject expert.

filipnavara commented 4 months ago

The official documentation states that the gss-ntlmssp package must be installed on the system. The error above (SPNEGO cannot find mechanisms to negotiate.) is consistent with the package not being installed. Furthermore, on Ubuntu 22.04 there's a conflict between the gss-ntlmssp package and OpenSSL 3 configuration that was already discussed multiple times in other issues (eg. https://github.com/dotnet/runtime/issues/67353#issuecomment-2062210418). I am not familiar with the official docker images and whether they include the OpenSSL 3 workarounds/fixes.

Trying System.Net.Security.UseManagedNtlm is a reasonable workaround too...

wfurt commented 4 months ago

Note that the gss-ntlmssp is fixed/updated in Ubuntu 23+. One option is to upgrade or use different distribution as base OS.

amitpatel158 commented 4 months ago

Encountered similar issue while using NTLM Authentication for http proxy. Used named HttpClient with HttpClientHandler & WebProxy (Populated Address & Credentials properties only)

Error: HttpRequestException: The proxy tunnel request to proxy 'http://xxxxxxxxx:xxxx/' failed with status code '407'"

Base os: RHEL-8 Minimal (linux) Application: NET8 based MicroService After applying workaround System.Net.Security.UseManagedNtim It worked as expected

kasperk81 commented 3 months ago
 private static bool UseManagedNtlm { get; } = 
     AppContext.TryGetSwitch("System.Net.Security.UseManagedNtlm", out bool useManagedNtlm) ? 
     useManagedNtlm : !OperatingSystem.IsWindows();

is perhaps the desired state going forward

filipnavara commented 3 months ago

is perhaps the desired state going forward

The managed NTLM only implements the client-side part. That's not necessarily desirable on Linux. I am hesitant to make it the default everywhere non-Windows but not completely ruling it out either.

kasperk81 commented 3 months ago

@filipnavara, can it be split on client/server boundary? for client always use managed implementation on a platform (except perhaps windows), keeping server IsSupported separate

wfurt commented 3 months ago

I'm wondering if the switch may be smarter and for example detect cases when the NTLM gssapi module is missing or not functional (because of OpneSSL 3) Since NTLM is really not that secure and considered legacy, I'm not sure either if we should flip the switch as it can possibly impact Kerberos as well. (I could be wrong but the test coverage is somewhat weak IMHO).

And is there good place where we can put some remarks about this @rzikm and @filipnavara. I certainly feel we can document the option better and outline some of the caveats more - but I just don't know where to put it.

antonfirsov commented 3 months ago

Triage: marking as documentation issue for 9.0 based on https://github.com/dotnet/runtime/issues/101058#issuecomment-2121535782, otherwise it looks like a duplicate of #100231.

dotnet-policy-service[bot] commented 3 months ago

This issue has been automatically marked no-recent-activity because it has not had any activity for 14 days. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will remove no-recent-activity.

GuyOrShay commented 2 months ago

Hi , there is any updates ?

wfurt commented 1 month ago

what is your specific issue @GuyOrShay ? There are several workarounds here and this documentation only issue at this point.