Skip to content
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

Does NegotiateStream support Kerberos Authentication on Linux and how? #26418

Closed
CalArabshahi opened this issue Jun 7, 2018 · 17 comments
Closed
Assignees
Labels
area-System.Net documentation Documentation bug or enhancement, does not impact product or test code os-linux Linux OS (any supported distro)
Milestone

Comments

@CalArabshahi
Copy link

@karelz and @davidsh Would you please describe/state whether .NET Core 2.1 supports Kerberos Authentication from a Linux Client against a service? IMO, there are lots of confusing statements/issues around Kerberos Authentication on Linux and macOS and no clear statement as to what works and what does not work. Please clarify. For example how does ".NET Data Provider for SQL Server" support Kerberos authentication on Linux. Does it revert back to P-Invoke calls; if yes why? We are trying to create a roadmap for our product and hope to make it work on all platforms. I think Kerberos Authentication is fundamental and it has been tested on Linux (e.g. Red-Hat) already. I hope we are missing something fundamental (e.g. how to populate the cache with a Kerberos TGT for). Thanks in advance for clarification and help with Kerberos authentication on Linux and eventually macOS. I originally posted this question in #26293 .

@davidsh
Copy link
Contributor

davidsh commented Jun 7, 2018

karelz and @davidsh Would you please describe/state whether .NET Core 2.1 supports Kerberos Authentication from a Linux Client against a service?

Can you specify what scenarios you think don't work? Are they HTTP scenarios (using HttpClient) or NegotiateStream scenarios (using NegotiateStream class). Or just SQL Client APIs?

Can you post some sample code to demonstrate what APIs you're using?

In general, we don't know of any known Kerberos issues with Linux as a client. Kerberos does requires interaction with a Kerberos server (i.e. Windows Active Directory) and the Linux client needs to be configured with Kerberos including setup against the Kerberos ticketing server, etc.

@CalArabshahi
Copy link
Author

we are using NegotiateStream; no HTTP scenarios; refer to #26293 for the sample code; karelz asked that I open a new issue; see my original post in #26293. Specifically we are writing a .NET Data Provider (similar in concept to the .NET Data Provider for SQL Server). The Data Provider must use Kerberos to authenticate and establish a connection to the Database server. This has been working on .NET Framework for years; now we are porting the Data Provider to .NET Core and it must run on Linux, macOS and Windows.

@davidsh
Copy link
Contributor

davidsh commented Jun 7, 2018

Thx for the added info. What distro of Linux did you try this on? We'd like to set up the same environment to see if we can reproduce the problem you're seeing. Please include detail such as what packages (apt-get etc.) are installed into the distro.

@wfurt
Copy link
Member

wfurt commented Jun 7, 2018

to answer some of your questions @CalArabshahi :
The PAL layer uses GSSAPI interface and that normally links with MIT kerberos libraries.

At least for HTTP with 2.1 Negotiate stream does not use cache created with kinit - but creates in-memory ticket.

@wfurt
Copy link
Member

wfurt commented Jun 7, 2018

BTW Credential Cache.DefaultNetworkCredentials may not work on Unix.
Unlike Windows domain joined system, There is really no established identity.

You can look at unit tests under src/System.Net.Security/tests and use that as example.

@CalArabshahi
Copy link
Author

CalArabshahi commented Jun 7, 2018

@wfurt I think you are stating that the following line of code from #26293 will not work on Linux

authStream.AuthenticateAsClient(CredentialCache.DefaultNetworkCredentials, "HOST/ESROOT",
       ProtectionLevel.EncryptAndSign,
       impLevel);

and that we must perform something like

        public static void GetDefaultKerberosCredentials(string username, string password)
        {
            // Fetch a Kerberos TGT which gets saved in the default cache

            SafeGssCredHandle.Create(username, password, isNtlmOnly:false).Dispose();
        }

is this correct?

@karelz karelz changed the title Question: Does .NET Core 2.1 support Kerberos Authentication on Linux and how? Does NegotiateStream support Kerberos Authentication on Linux and how? Jun 7, 2018
@karelz
Copy link
Member

karelz commented Jun 7, 2018

As I stated on #26293:

Kerberos support on Linux has likely rough edges. We tested it only on HttpClient in the sense "it is possible to make it work with proper machine setup".
We didn't have many asks beyond that - this is likely the first one.

If you can help us debug through your scenario and tell us where things don't work as expected, we can likely help.
Unfortunately, we might not be able to prioritize full investigation into all corner-cases of Kerberos support in NegotiateStream on Linux at this moment.

@davidsh
Copy link
Contributor

davidsh commented Jun 9, 2018

I did some experimenting on Linux using a multi-machine setup (Windows w/ Active Directory machines and Linux client) that I am working on (eventually to be used as our 'Enterprise-Scenario' testing environment).

I was able to get a Linux Debian-9 HttpClient to connect to a Windows IIS website using Kerberos (specified as Negotiate.Kerberos on IIS server) with both an explicit NetworkCredential and "default credentials" (CredentialCache.DefaultCredentials). In the "default credentials" case, the authentication did not work at first since I had no "kinit" done. But after doing a "kinit" with a credential from the Windows Active Directory domain, the .NET Core 2.1 app was able to use HttpClient with CredentialCache.DefaultCredentials.

Code:

var server = new Uri("http://corefx-net-iis.corefx-net.contoso.com/test/auth/kerberos/showidentity.ashx");
var handler = new HttpClientHandler();

// Use either explicit credential or default credentials
//handler.Credentials = new NetworkCredential("user1", "password", "COREFX-NET.CONTOSO.COM");
//handler.Credentials = CredentialCache.DefaultCredentials;

var client = new HttpClient(handler);
HttpResponseMessage response = await client.GetAsync(server);

Explicit Network Credential (works)

200 OK
{
  "Authenticated": "True",
  "User": "COREFX-NET\user1"
}

Default Credentials test

localadmin@squidproxy:~/dotnet/HelloWorld$ klist
klist: No credentials cache found (filename: /tmp/krb5cc_1000)

dotnet run
System.ComponentModel.Win32Exception (0x80090020): GSSAPI operation failed with error - An invalid status code was supplied (SPNEGO cannot find mechanisms to negotiate).
   at System.Net.NTAuthentication.GetOutgoingBlob(Byte[] incomingBlob, Boolean throwOnError, SecurityStatusPal& statusCode)
   at System.Net.NTAuthentication.GetOutgoingBlob(String incomingBlob)
   at System.Net.Http.AuthenticationHelper.SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, Boolean isProxyAuth, HttpConnection connection, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.AuthenticationHelper.SendWithAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, Boolean preAuthenticate, Boolean isProxyAuth, Boolean doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at HelloWorld.Program.Main(String[] args) in /home/localadmin/dotnet/HelloWorld/Program.cs:line 24

Note: there is a separate bug here because the "Win32Exception" shouldn't be the top-level exception thrown by HttpClient. It should be an HttpRequestException as top-level exception. And then inside that it could the Win32Exception or perhaps something else like AuthenticationException and then Win32Exception inside that.

Then do a "kinit" and Default Credentials (works)

localadmin@squidproxy:~/dotnet/HelloWorld$ kinit globaladmin
Password for globaladmin@COREFX-NET.CONTOSO.COM:
localadmin@squidproxy:~/dotnet/HelloWorld$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: globaladmin@COREFX-NET.CONTOSO.COM

Valid starting     Expires            Service principal
06/09/18 18:10:41  06/10/18 04:10:41  krbtgt/COREFX-NET.CONTOSO.COM@COREFX-NET.CONTOSO.COM
        renew until 06/10/18 18:10:37
localadmin@squidproxy:~/dotnet/HelloWorld$ dotnet run
Hello World!
http://corefx-net-iis.corefx-net.contoso.com/test/auth/kerberos/showidentity.ashx
200 OK
{
  "Authenticated": "True",
  "User": "COREFX-NET\globaladmin"
}

I have not yet experimented with NegotiateStream class yet but plan to try that out over the coming days.

@davidsh
Copy link
Contributor

davidsh commented Jun 10, 2018

I was able to test System.Net.Security.NegotiateStream between Windows and Linux using Kerberos. I setup the NegotiateStream server on a Windows machine (that is joined to the COREFX-NET domain). The client was on the Linux machine (also "joined" to the COREFX-NET domain). I used .NET Core 2.1 for everything.

I made sure to run the NegotiateStream server application in the context of the machine account (NT AUTHORITY\SYSTEM) since all the default SPN's (such as HOST/COREFX-NET-IIS) on Windows are only registered against the machine account. I used the PsExec utility to run the server application under that machine context.

I then used "kinit" to set up the "default credentials" for the Linux machine. I did a "kinit user2" which maps to COREFX\user2 user account on the domain.

Here are the code snippets and results. As you can see, NegotiateStream worked and used Kerberos (and not NTLM). I have not yet tried the inverse of this test (i.e. a Linux NegotiateStream server and Windows NegotiateStream client). To do that, I will need to register some new SPN's against the Linux machine account. I'll try that at some point later.

Code snippet (server)

NegotiateStream authStream = new NegotiateStream(stream, false);
authStream.AuthenticateAsServer(
    CredentialCache.DefaultNetworkCredentials,
    ProtectionLevel.EncryptAndSign,
    TokenImpersonationLevel.Identification);

Code snippet (client)

NegotiateStream authStream = new NegotiateStream(clientStream, false);
authStream.AuthenticateAsClient(
    CredentialCache.DefaultNetworkCredentials,
    "HOST/corefx-net-iis",
    ProtectionLevel.EncryptAndSign,
    TokenImpersonationLevel.Identification);

Windows Server (NegotiateStream as server)

Listening on 0.0.0.0:8080 for clients...
Client connected.
IsAuthenticated: True
IsMutuallyAuthenticated: True
IsEncrypted: True
IsSigned: True
IsServer: True
ClientIdentity.AuthenticationType: Kerberos
ClientIdentity.IsAuthenticated: True
ClientIdentity.Name: COREFX-NET\user2

Linux Client (NegotiateStream as client)

localadmin@squidproxy:~/dotnet/krbclient$ dotnet run
Client waiting for authentication...
IsAuthenticated: True
IsMutuallyAuthenticated: True
IsEncrypted: True
IsSigned: True
IsServer: False
ServerIdentity.AuthenticationType: Kerberos
ServerIdentity.IsAuthenticated: True
ServerIdentity.Name: HOST/corefx-net-iis
Sent 22 bytes.
Client closed.
localadmin@squidproxy:~/dotnet/krbclient$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: user2@COREFX-NET.CONTOSO.COM

Valid starting     Expires            Service principal
06/09/18 20:37:26  06/10/18 06:37:26  krbtgt/COREFX-NET.CONTOSO.COM@COREFX-NET.CONTOSO.COM
        renew until 06/10/18 20:37:25
06/09/18 20:37:42  06/10/18 06:37:26  HTTP/corefx-net-iis.corefx-net.contoso.com@COREFX-NET.CONTOSO.COM
        renew until 06/10/18 20:37:25
06/09/18 23:54:34  06/10/18 06:37:26  HOST/corefx-net-iis@COREFX-NET.CONTOSO.COM
        renew until 06/10/18 20:37:25

@davidsh
Copy link
Contributor

davidsh commented Jun 10, 2018

cc: @stephentoub @rmkerr @Caesar1995 in case you're curious about Kerberos authentication in HttpClient and/or NegotiateStream classes.

@davidsh
Copy link
Contributor

davidsh commented Jun 10, 2018

@CalArabshahi

@karelz and @davidsh Would you please describe/state whether .NET Core 2.1 supports Kerberos Authentication from a Linux Client against a service?

Based on the tests I've done with both HttpClient and NegotiateStream, I can say that .NET Core 2.1 supports Kerberos authentication from a Linux Client against a service (Windows server in the tests I ran).

The key things I've noticed to make it work with Kerberos are:

  • Client and server must be on separate machines. I tried to do NegotiateStream tests, for example, on Windows .NET Framework with client and server on the same machine (using localhost) and NegotiateStream would never use Kerberos. It would only use NTLM.

  • The Linux machine must be properly set up with Kerberos and "joined" to the Windows Active Directory domain using the proper machine account for the Linux client. I used Linux tools such as msktutil and net ads. Here is the output of using 'net ads info' and 'net ads status' for the Linux machine.

  • The time on the Linux machine must be in sync with the domain controller/Kerberos ticketing server. Setting up NTP and using the domain controller as the time-source is important.

localadmin@squidproxy:/usr/sbin$ ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*corefx-net-dc.c 86.77.84.80      5 u   40   64  177    0.728   -0.420   0.241
  • SPNs must be registered on the server and the server application must be run in the security context allowed for those SPNs. On Windows machines, by default, the SPNs normally will only work against the machine account and not user accounts. So, the "service" must be running under LocalSystem (NT AUTHORITY\SYSTEM) or equivalent. Using tools like "setspn -L " will tell you the registered SPNs on the domain for that machinename account. See setspn.

    Example:

C:\>setspn -L corefx-net-iis
Registered ServicePrincipalNames for CN=corefx-net-iis,CN=Computers,DC=corefx-net,DC=contoso,DC=com:
        TERMSRV/COREFX-NET-IIS
        TERMSRV/corefx-net-iis.corefx-net.contoso.com
        WSMAN/corefx-net-iis
        WSMAN/corefx-net-iis.corefx-net.contoso.com
        RestrictedKrbHost/corefx-net-iis
        HOST/corefx-net-iis
        RestrictedKrbHost/corefx-net-iis.corefx-net.contoso.com
        HOST/corefx-net-iis.corefx-net.contoso.com

Problems with any of the above configuration will result in NegotiateStream using NTLM instead of Kerberos. And in some cases, failing to authenticate at all and returning an error.

@davidsh davidsh self-assigned this Jun 10, 2018
@vaintroub
Copy link

Is not the need of SPN (must be registered, service run under machine account) too restrictive. Windows AD based servers can use UPN , e.g machine$@DOMAIN.COM, even Linux based servers that join AD can use it. GSSAPI supports that, if gss_import_name is called with GSS_C_NT_USER_NAME

Currently UPN can be used as targetName in NegotiateStream.AuthenticateAsClient() on Windows, and I hope Linux could support it too, one day.

@wfurt
Copy link
Member

wfurt commented Mar 13, 2019

Should we close this and open specific bug or feature request if something did not get answered?

@davidsh
Copy link
Contributor

davidsh commented Mar 13, 2019

Should we close this and open specific bug or feature request if something did not get answered?

No. Don't close this. It is currently marked for 'documentation' and that is part of my work. I will close this when a corresponding documentation item is create for it. Not sure where that will be yet.

@wfurt
Copy link
Member

wfurt commented Aug 20, 2019

This seems to be related to all your recent improvements @davidsh. Can we fix documentation for 5.0?

@davidsh
Copy link
Contributor

davidsh commented Sep 9, 2019

Now tracking with dotnet/docs#14310

@davidsh davidsh closed this as completed Sep 9, 2019
@johnnyreilly
Copy link

johnnyreilly commented Jan 25, 2020

I stumbled on this as I was trying to work out how to convert a Kerberos ticket obtained with Kerberos.Net to an ICredential that can be used by SignalR. See this discussion on SignalR and Windows Authentication / Kerberos in AWS:

dotnet/aspnetcore#18576 (comment)

I thought I'd ask in case anyone else had an idea as to how we could perform this leap from performing Kerberos authentication to SignalR:

var client = new KerberosClient();
var kerbCred = new KerberosPasswordCredential("user@domain.com", "userP@ssw0rd!");
await client.Authenticate(kerbCred);
var ticket = await client.GetServiceTicket("http/yoursignalr.app.com");

var connection = new HubConnectionBuilder()
                .WithUrl("https://our.server.com/hub/bub", options =>
                {
                    options.Credentials = // somehow use ticket here?
                })
                .Build();

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 5.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 16, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net documentation Documentation bug or enhancement, does not impact product or test code os-linux Linux OS (any supported distro)
Projects
None yet
Development

No branches or pull requests

7 participants