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

Question about using NegotiateStream in linux and TokenImpersonationLevel #80846

Closed
deryaza opened this issue Jan 19, 2023 · 20 comments
Closed
Labels
area-System.Net.Security help wanted [up-for-grabs] Good issue for external contributors os-linux Linux OS (any supported distro)
Milestone

Comments

@deryaza
Copy link

deryaza commented Jan 19, 2023

Is the check in this line still valid? I don't know about impersonation, but delegation should work, and it works for us via gssapi (linux client -> windows server scenario).

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs#L36

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 19, 2023
@ghost
Copy link

ghost commented Jan 19, 2023

Tagging subscribers to this area: @dotnet/ncl, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details

Is the check in this line still valid? I don't know about impersonation, but delegation should work, and it works for us via gssapi (linux client -> windows server scenario).

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs#L36

Author: deryaza
Assignees: -
Labels:

area-System.Net.Security

Milestone: -

@wfurt
Copy link
Member

wfurt commented Jan 19, 2023

It may be possible. It is there probably since the initial Linux port. I'm looking at #28460 at the moment and I was planning to do more testing with Linux.

The difficult part may be testing. We have some tests with Linux KDC but I'm not sure if/how we would construct test automation. If you can share some details @deryaza it would be useful.

@wfurt wfurt removed the untriaged New issue has not been triaged by the area owner label Jan 19, 2023
@wfurt wfurt added this to the Future milestone Jan 19, 2023
@wfurt wfurt added the os-linux Linux OS (any supported distro) label Jan 19, 2023
@deryaza
Copy link
Author

deryaza commented Jan 20, 2023

I just want to clarify that my "is it still valid" (or is it actually not supported) question was because some time ago, when I investigated the possibility, I used System.Net.Security code as a reference to get an idea of the native functions that could be called. After that and with some tries, I was able to get the user token and impersonate the user on the server.

So that just me being curious :) Is there some concerns why NegotiateStream or the new api (that calls the same function, iirc) are limited to identification? I alse remembered seeing some flags mappings that mention delegation.

Actually, after posting this, I tried to build from source and extend the check to delegation and it worked. I even got WindowsIdentity as an Identity property of NegotiateStream.

So yeah, I'm just asking why validation is still like this (I could guess that it's because lack of testing?).

@wfurt
Copy link
Member

wfurt commented Jan 20, 2023

I think it is lack of demand (you are really first one IMHO) and lack of testing infrastructure. I think we would be happy to take community PR.
cc: @filipnavara

@filipnavara
Copy link
Member

There's definitely a lack of testing for this but the underlying code for Unix (Linux/macOS) seems to be there.

@ryannewington
Copy link

Just adding another voice to this ask

The restriction to identification in this file

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs#L36

prevents the use of impersonation on linux systems.

internal static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel)
{
    if (impersonationLevel != TokenImpersonationLevel.Identification)
    {
        throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(),
            SR.net_auth_supported_impl_levels);
    }
}

This is blocking us from being able to support linux clients talking to Windows servers and using Kerberos authentication where impersonation is required. As this library is widely used, it also impacts other services like WCP's net.tcp stream.

By contrast, NegotiateStreamPal.Windows.cs contains

internal static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel)
{
    if (impersonationLevel != TokenImpersonationLevel.Identification &&
        impersonationLevel != TokenImpersonationLevel.Impersonation &&
        impersonationLevel != TokenImpersonationLevel.Delegation)
    {
        throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(), SR.net_auth_supported_impl_levels);
    }
}

Another side note is that currently the error message returned by the Unix PAL incorrectly reports the following message in it's exception text

The supported values are Identification, Impersonation or Delegation.

The message is incorrect, as currently only Identification is allowed.

I'm certainly happy to contribute to resolving this issue, if there is a way forward here.

@wfurt wfurt added the help wanted [up-for-grabs] Good issue for external contributors label Feb 15, 2023
@wfurt
Copy link
Member

wfurt commented Feb 15, 2023

yes, we would take contributions @ryannewington. As I mentioned above lack of testing and low priority is probably main reason for the gap.

You can look at https://github.com/dotnet/runtime/tree/main/src/libraries/Common/tests/System/Net/EnterpriseTests/setup to see if we can somehow integrate it with current Docker tests.

We would only add Delegation, right? e.g. no Impersonation on Unix?

@ryannewington
Copy link

Ok! Great! So there's a few dimensions to this to consider

Linux client to Windows Server

First, let's talk about the linux client to windows server scenario. This is going to be the most common use case.

The client sets the TokenImpersonationLevel on the token it sends to the server, which specifies how the windows server can use the token. The action itself isn't performed client side, it's a flag to say what the server is permitted to do with it's identity. Eg 'you can identify me (Identification)', 'you can impersonate me locally (Impersonation)' or 'you can impersonate me on other servers (Delegation)'.

This scenario is broken in my case, because i can't give permission for the Windows server to impersonate me with the access token I provide when coming from Linux.

I think in the client scenario, there's no consequence to having the same checks as Windows, that is, allow the client to use any of the TokenImpersonationLevels supported by NegotiateStream, as windows does.

For testing, this would be as simple as pointing the linux NegotiateStream client at the same Server as the Windows NegotiateClient, and validating that the server receives the correct impersonation level as specified client side.

(FWIW I've patched NegotiateStream and confirmed it does work as expected from Linux)

Windows or Linux Client to Linux server scenario

This one has a little more nuance to it.

When i set the TokenImpersonationLevel on a server, I'm saying 'the client must present me with this minimum impersonation level'. Impersonation levels are cumulative, in that an Impersonation token allows for both Identification and 'impersonation on the local system'. Delegation allows for Identification, and local Impersonation.

While impersonation may not apply to a linux system, delegation technically can, and delegation (with impersonation levels being cumulative), includes the right of impersonation.

If I set my Linux server-side 'minimum impersonation level' to Identification, I can still be sent an Impersonation token by the client.

Since the token I receive is really dependent on the client at the end of the day, perhaps it's best left to the developer to work out what they want to support? There doesn't seem to be much to gain from restricting the token types you can receive on the Linux side. And given that dotnet provides no mechanism to impersonate an identity on Linux, having an impersonation token in your possession doesn't appear to be too great of a problem.

I think it's also fair to say that if a developer wanted to use the delegation token, they'd have some work to do. This is also normally handled on Windows by the WindowsIdentity class's impersonation function. However, it doesn't rule out the possibility of doing it, perhaps with using something like Kerberos.NET.

That all being said, I think the likelihood of someone wanting to run a NegotiateStream server on Linux is low.

Options for a way forward

  1. Modify the ValidateImpersonationLevel function in the Unix PAL to match windows (or move it back into the shared PAL). This would allow NegotiateStream clients on any OS to correctly specify their impersonation level. It would also allow NegotiateStream servers on any platform to request client impersonation levels of any type, but the onus would be on the developer as to what they would do with that. Since NegotiateStream is a low level construct, and doesn't actually use the impersonation token itself in any way, the behavior can be left implementation-specific?

  2. Add an isServer parameter to the ValidateImpersonationLevel function, ignored in the Windows PAL, but used in the Unix PAL to enable the additional levels when working as a client, but leave servers set to Identification only as they are today.

Test updates

I've had a look at the existing test cases, and there doesn't appear to be any Impersonation or Delegation token level tests at all, even for Windows, but I might just not be seeing them?

Would we be looking at adding additional test cases validating the expected impersonation levels to:
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs

or adding additional tests here?
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/tests/EnterpriseTests/NegotiateStreamLoopbackTest.cs

I guess it depends if need to validate that a) The TokenImpersonationLevel is correctly negotiated between both ends. This can be done with a StreamToStream test. Or b) we actually want to try to impersonate/delegate the resulting access token? But it doesn't look like the tests go as far as this today (and seems out of scope for the NegotiateStream's capabilities anyway) .

@wfurt
Copy link
Member

wfurt commented Feb 16, 2023

To the Linux client. While I agree in principal this is server's job there may be practical limits.
https://datatracker.ietf.org/doc/rfc5896/ talks about OK_AS_DELEGATE but I did not see anything about impersonation. (may be just hidden somewhere else) We will need to pass the given desire to GSSAPI and that may be limiting factor.

The functional tests you linked have only NTLM tests. To run Kerberos, you obviously need KDC. That was done as manual tests in the past and the EnterpriseTests is attempt to build on ad-hoc in container. And yes, there are currently no test for this.

Windows is even more complicated. AFAIK you cannot run DC in container and our automated test infrastructure does not have capability to proved test server. That really leaves only ad hoc manual tests in place.

@filipnavara did great job in 7.0 to pull in Kerberos.Net so we can do more detailed tests. But that still does not have coverage in this are.

@wfurt
Copy link
Member

wfurt commented Feb 16, 2023

BTW I had private conversation about this with @SteveSyfuhs and it would be great if he can chime in publicly.

@SteveSyfuhs
Copy link

Level setting: https://syfuhs.net/understanding-identity-delegation

In Windows you have an ability to authenticate a user, which translates to identifying a user, impersonating a user, or delegating a user. Each ability requires increased privileges across the resource. We all understand the authentication step. Identifying the user is nothing special; it just means you're given a username, SID, and group membership information the popped out of the authenticated ticket. This requires no special machine privileges. Impersonation means the process can act as that user on that single machine, meaning when you access local OS resources, you do so as that user. This requires explicit impersonation privileges granted by the machine and is security critical. Lastly, you have delegation of the user, which grants the process the right to act as that user on the network such as when you're querying a database or file share or whatever. This requires permission at the Active Directory level, granting the host process the right to delegate users to specific other resources.

All of this comes from the NT Token for that user, where the hosting process has an implied privilege associated with that token. They that hold the [NT] token can identify or impersonate the user. It is up to the Windows system to make that decision, not the process. Whether you can impersonate that user is stamped on the token, so verifying if you can do that should really just be a matter of checking the token, nothing else. The hard part is determining if you can delegate the user. The only way you can determine that is by actually trying it, and seeing if the far side accepted it.

The reason all of this works this way is because it uses Kerberos S4U (or worse, unconstrained delegation, which is like 5th or 6th on the list of technologies I'm trying to kill), and Windows hides all that machinery from you with the simple Identify|Impersonate token levels. Because this is protocol-based, the ability to impersonate or delegate on Linux or Windows is rather up to the machine and directory, not the app developer.

So, in summary, I think my recommendation is don't optimistically check impersonation levels or things like that, and just handle the errors the underlying system returns instead because the OS is still the ultimate arbiter (perhaps that's obvious, and I'm missing the salient point). GSS-wise I don't know if the Linux Kerberos libraries support S4U -- it's a Microsoft-specific protocol. It's much safer than unconstrained delegation, which is basically what the OK_AS_DELEGATE flag is triggering. Kerberos.NET does support S4U and it's rather trivial to use, but that obviously leads to its own set of challenges. I think I implemented basic S4U support in the KDC too, so if you want to extend the test harness to try it out, that is probably easy enough to do too.

@ryannewington
Copy link

@SteveSyfuhs thank you for that explanation. Always great to hear you explain things so well.

Is the presence of the Delegation flag in an access token exclusively tied to unconstrained delegation/ok-as-delegate being enabled?

From my testing, it seems you can do constrained delegation with only Impersonation flag set. Is that correct?

@SteveSyfuhs
Copy link

SteveSyfuhs commented Feb 16, 2023 via email

@wfurt
Copy link
Member

wfurt commented Feb 28, 2023

It so happened I was able to get my hands on setup with IIS and HttpClient on Windows calling app that needs delegation. On Linux, even if GSS_C_DELEG_FLAG was passed to GSSAPI, I still could not make it work. Perhaps the difference is spnego provider vs kerberos but there are certainly functional issue on Unix.

@ryannewington
Copy link

I'll retest this scenario in my lab, but I'm 95% sure I tested this scenario successfully. Was it linux client -> windows server?

I have a very simple native negotiatestream -> negotiatestream setup, to rule out any IIS or config issues.

I've had to use ModMono to overwrite the ValidateImpersonationLevel at runtime on the linux side to get it working. But Impersonation and (im 95% sure) Delegation work when AD is configured appropriately.

@filipnavara
Copy link
Member

.NET 7 Preview 7 removed the ValidateImpersonationLevel restriction. If you can retest once it is released it would be appreciated.

@ryannewington
Copy link

@filipnavara Happy to do this! I assume you mean .NET 8 Preview 7?

@filipnavara
Copy link
Member

Yes, .NET 8 Preview 7, right 🤦‍♂️

@deryaza
Copy link
Author

deryaza commented Aug 14, 2023

sorry for silence, will try to provide some info today/tomorrow

@deryaza
Copy link
Author

deryaza commented Aug 15, 2023

It works just fine, thanks!

The only difference that I think could be documented somewhere is that negotiatestream now disposes remote identity on it self disposal. I will close this issue, if @ryannewington had some troubles, you can reopen it or proceed the way you want.

@deryaza deryaza closed this as completed Aug 15, 2023
@karelz karelz modified the milestones: Future, 9.0.0 Aug 29, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Sep 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Security help wanted [up-for-grabs] Good issue for external contributors os-linux Linux OS (any supported distro)
Projects
None yet
Development

No branches or pull requests

6 participants