dotnet / runtime

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

[API Proposal]: Provide a way to get delegated credentials handle (linux client -> linux server scenario) #104385

Open deryaza opened 4 months ago

deryaza commented 4 months ago

Background and motivation

From what I understand, the delegated credentials are retrieved in the last parameter of this invocation: https://github.com/dotnet/runtime/blob/main/src/native/libs/System.Net.Security.Native/pal_gssapi.c#L415C1-L426C1

So, as I can tell there is no way to use those since they are discarded.

API Proposal

Make RemoteIdentity property return something like:

namespace System.Security.Principal
{
    public class GssDelegatedIdentity : GenericIdentity
    {
        public GssDelegatedIdentity(string name, string type, SafeHandle credentialsHandle) : base(name, type)
        {
            CredentialsHandle = credentialsHandle;
        }

        public SafeHandle CredentialsHandle { get; }
    }
}

and options to accept:

namespace System.Net.Security
{
    public class NegotiateAuthenticationClientOptions
    {
+        public IIdentity? DelegatedIdentity { get; set; }
    }
}

API Usage


NegotiateAuthentication negotiateAuthentication = new(new NegotiateAuthenticationServerOptions()
{
    RequiredImpersonationLevel = TokenImpersonationLevel.Delegation,
});

// auth loop

IIdentity negotiateAuthenticationRemoteIdentity = negotiateAuthentication.RemoteIdentity;
if (negotiateAuthenticationRemoteIdentity is not GssDelegatedIdentity gssIdentity)
{
    throw new();
}

NegotiateAuthentication clientAuth = new(new NegotiateAuthenticationClientOptions()
{
    AllowedImpersonationLevel = TokenImpersonationLevel.Delegation,
    TargetName = "foo",
    DelegatedIdentity = gss
});

// auth loop

Alternative Designs

Probably instead of adding NegotiateAuthenticationClientOptions.DelegatedIdentity Thread.CurrentPrincipal could be used, but I think it's not as good probably.

Risks

No response

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

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

rzikm commented 4 months ago

Triage: we usually don't add features that are available only on particular platform unless we have a strong justification. I will tentatively put this to future for now, we might consider adding this if there are enough upvotes.

deryaza commented 4 months ago

This is sad :(

While this API is designed for a specific platform, it will still offer functionality similar to WindowsIdentity on other platforms which still provides a bit more consistent experience across different environments. Plus, if we are talking about delegation, this API (that can be changed) will provide an abstract way of using WindowsIdentity delegation functionality abstracting it to IIdentity (because no need to cast it to WindowsIdentity and calling RunImpersonated method, just assigning it to DelegatedIdentity in NegotiateAuthenticationClientOptions). @rzikm

wfurt commented 2 weeks ago

It seems like only interesting part of the IIdentity is Name. Do you know if we can consistently get it across supported platforms @filipnavara ? I'm not convinced we should expose any of the internal handles. But maybe I misunderstood the ask. Certainly the RunImpersonated will not work on Unix AFAIK.

deryaza commented 2 weeks ago

Perhaps me including GssDelegatedIdentity was a bit confusing. For my scenario, it's acceptable to make it internal to dotnet. My main request is to implement the capability to pass DelegatedIdentity, enabling the use of a delegated handle for another authentication request. For instance, in npgsql, there currently isn't a way to pass client credentials (be it Linux, Windows, or other) that have been delegated to a server (Linux), which then authenticates to a PostgreSQL database on behalf of the client. For reference npgsql code: https://github.com/npgsql/npgsql/blob/main/src/Npgsql/Internal/NpgsqlConnector.Auth.cs#L332-L363

wfurt commented 2 weeks ago

I'm not sure we can produce something that can be used independently for another authentication. Can Kerberos.Net do something like that @SteveSyfuhs? (and if so would that also work for NTLM?)

SteveSyfuhs commented 2 weeks ago

If I'm understanding correctly the intent is to just make it so when a middlebox has received an authenticated context via Negotiate, let the stack then delegate that identity across the wire downstream. E.g. the canonical SQL Delegation model. I suspect you'd want to hide all of the implementation details and very much make it something explicit like [await] identity.RunImpersonated([async] () => { /* next hop off box as impersonated user */ }) otherwise this leads to some funny ASP.NET request lifetime behaviors.

Incidentally, yes, Kerberos.NET does handle this but it needs to control the acceptance of the Kerberos AP-REQ, otherwise it can't get the necessary keys to trigger the delegation: https://github.com/dotnet/Kerberos.NET/blob/790b08f45e7c7cfd48f0e14cabe188f90825aa0d/Bruce/CommandLine/KerberosConstrainedDelegationCommand.cs#L77-L114

That makes it slightly less generic for things like third party APIs wanting to call into the core framework to do auth, unless the framework APIs expose some interfaces to BYO-auth.

NTLM has never supported delegation (that's why back in the day you'd get errors that anonymous auth failed when trying to connect to e.g. SQL, because browser->web negotiated NTLM instead of Kerberos).

deryaza commented 2 weeks ago

I suspect you'd want to hide all of the implementation details and very much make it something explicit like [await] identity.RunImpersonated([async] () => { /* next hop off box as impersonated user */ }) otherwise this leads to some funny ASP.NET request lifetime behaviors.

The thing is that linux can't impersonate user on level of a thread, so the only way to wire downstream is to pass delegated_cred_handle to gss_init_sec_context. (https://www.gnu.org/software/gss/reference/gss-api.html#gss-init-sec-context and last parameter of https://www.gnu.org/software/gss/reference/gss-api.html#gss-accept-sec-context)

(identity.RunImpersonated is working now on windows if you upcast Identity to WindowsIdentity)

SteveSyfuhs commented 2 weeks ago

Sure, I'm being handwavy about the implementation specifically. My point was that API-wise it's probably unwise to build something that looks like it will have an unbounded lifetime. That's how the Windows implementation works today and in order to make that identity available across an entire session the token is bound to the TCP socket (or was, I don't know how ASP.NET handles it these days). That has big perf consequences so it's better to be explicit about scope. Alternatively, the Kerberos.NET library can be used such that any intermediate identity can be stored in an application session and be rehydrated as necessary for anything needed on the wire.