Conversation
Fix: - Returning group names as claim only once (unique) instead of multiple times when resolving nested groups - Handling users without an explicit group membership without null exception (happens when a user is only an implicit group member without "memberof" attributes) Added: - Adds user and group SIDs as claims which is the default on windows. This should enable writing more portable code. Todo: - For some reason the SID of builtin groups is returned as string. Only used empirical results on local machine (using a samba ad and not a windows server) to check how to interpret the string but should investigate the cause. The code for the SID parsing is derived from SID.cs of the .NET runtime library, because SID parsing is not available on Linux.
Fix problem caused by commit 7193260 where the claims cache was changed from an enumerable of strings to an enumerable of a KeyValuePair<string, string>
|
@dotnet-policy-service agree |
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Günther Foidl <gue@korporal.at>
Co-authored-by: Günther Foidl <gue@korporal.at>
Correct code formatting Co-authored-by: Günther Foidl <gue@korporal.at>
More code formatting
Fix missing new
Addresses comments with regard to the string to SID conversion. The stack allocation is rounded up to the next power of two and the conversion avoids creating temporary arrays.
Required to change from simple if statement to pattern matching
Fixed use of the wrong schema for the claim.
| } | ||
|
|
||
| var uniqueGroups = new HashSet<string>(); | ||
| if (memberof is not null) |
There was a problem hiding this comment.
When is memberof null for the top-level user entry? Did you actually see this happen? If it does, it's probably cleaner to do var memberof = userFound.Attributes["memberof"] ?? new(); Or better yet, use a static readonly empty DirectoryAttribute.
There was a problem hiding this comment.
Yes, this did happen to me. A freshly created unprivileged user does not have any memberof LDAP attributes (user created using windows RSAT tools but with a samba AD as primary DC). This led to a null exception. I will follow your recommendation and switch to using a static readonly empty DirectoryAttribute.
There was a problem hiding this comment.
I was looking at this again, but apart from memberof never being null I fail to see the advantage of using a static readonly empty DirectoryAttribute here. However, I might be missing something obvious, so please let me know if I do and I will change it right away.
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
Outdated
Show resolved
Hide resolved
| return; | ||
| } | ||
|
|
||
| retrievedClaims.Add(new KeyValuePair<string, string>(principal.RoleClaimType, groupCN)); |
There was a problem hiding this comment.
Why not add this whether or not there's any SearchResponse.Entries like before?
There was a problem hiding this comment.
What would be the scenario where a groupCN should be added to the claims despite not being found in LDAP (=> no SearchResponse.Entries)? If a trusted LDAP is used this should not be able to happen, but is there any reason you would like the claim being added regardless of finding the group in LDAP?
| @@ -129,12 +202,145 @@ private static void GetNestedGroups(LdapConnection connection, ClaimsIdentity pr | |||
| if (processedGroups.Contains(nestedGroupDN)) | |||
There was a problem hiding this comment.
Is this still necessary given we have the same check at the top of the method?
There was a problem hiding this comment.
As far as I can tell you are right, it would not be necessary. Currently this would only avoid another recursion including one more LDAP search request before returning back to this loop, but it could be removed.
| { | ||
| var filter = $"(&(objectClass=group)(sAMAccountName={groupCN}))"; // This is using ldap search query language, it is looking on the server for someUser | ||
| var searchRequest = new SearchRequest(distinguishedName, filter, SearchScope.Subtree); | ||
| var searchResponse = (SearchResponse)connection.SendRequest(searchRequest); |
There was a problem hiding this comment.
This seems mostly copied from GetNestedGroups. Can we share the logic?
| int length = 4; | ||
| ((ulong)authority).TryFormat(result.Slice(length), out int written, provider: CultureInfo.InvariantCulture); | ||
| length += written; | ||
| // Might need a check against a stack overflow, but this is directly taken from SID.cs of the .NET runtime library |
There was a problem hiding this comment.
SecurityIdentifier.ToString() includes an explanation in its comments why a stack overflow should be impossible, and I think that logic should hold since we're using the same MaxSubAuthorities of 15.
We need to be careful not to let the code go out of sync. Ideally, we could make the non-platform-specific aspects of SecurityIdentifier like its byte[]-based constructor and ToString(). That will require a runtime API proposal.
Otherwise, we might want to do some sort of source sharing. Perhaps we can use a github workflow to sync these like we do for some of our HTTP/2 and HTTP/3 logic
| var claimsCache = new MemoryCache(new MemoryCacheOptions()); | ||
| claimsCache.Set("name", new string[] { "CN=Domain Admins,CN=Users,DC=domain,DC=net" }); | ||
| var claimsList = new List<KeyValuePair<string, string>>(); | ||
| claimsList.Add(new KeyValuePair<string, string>("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "CN=Domain Admins,CN=Users,DC=domain,DC=net")); |
There was a problem hiding this comment.
Can we test caching the Sid and GroupSid claims now?
There was a problem hiding this comment.
Well this does not actually test much. It just adds the claims to the cache which is then directly copied to the claims (regardless of the claim types).
| var usid = ParseSID((byte[])userSID[0]); | ||
| if (usid is not null) | ||
| { | ||
| retrievedClaims.Add(new KeyValuePair<string, string>(ClaimTypes.Sid, usid.ToString())); |
There was a problem hiding this comment.
Is everyone who's using EnableLdap going to want Sid and GroupSid? This was requested by #31959, but it seems like most people have gotten by fine without it. And it does waste space in the cache.
Also, should this include all of the SID claims mentioned by the issue?
primarysid
primarygroupsid
groupsid
denyonlysid
There's even more that aren't listed like denyonlyprimarysid and denyonlyprimarygroupsid.
I think this should be opt-in or at the very least opt-out which will require new public API. You can use our API proposal issue template to file a new API proposal issue, or copy the template and fill it out in a new comment on #55705. We can add the api-ready-for-review label once it's ready.
There was a problem hiding this comment.
I think I figured out how to add primarygroupsid by constructing it from the RID, but I could not find much useful information about denyonlysid (unfortunately my time to look into this is quite restricted so if anyone could point me in the right direction I would be very grateful).
There was a problem hiding this comment.
I added primarygroupsidbut as mentioned before I am stuck with denyonlysid.
Since the key of the claim is a reference to a const string the string size does not need to be added to the cache size. Co-authored-by: Stephen Halter <halter73@gmail.com>
Incorporated suggestions from the review process:
- Instantiating `uniqueGroups` only when resolving nested groups
- Using target-typed new for key value pair allocations
- Using simplified `if (groupSid is { Count: 1 })` instead of two nested if's
|
Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime. |
Deriving the full SID from the RID stored in the primarygroupid field retrieved by LDAP using the SID of the user.
Added the option EnableLdapSIDClaimResolution to the public API LdapSetting to make the SID resolution on Linux using LDAP optional.
Added the option EnableLdapSIDClaimResolution to the public API LdapSetting to make the SID resolution on Linux using LDAP optional.
…mResolution Using the added EnableLdapSIDClaimResolution to make the SID claim resolution optional.
Fixed LDAP based claims
Claims retrieved from LDAP return unique group memberships with SIDs.
Description
Fix:
Added:
Todo:
The code for the SID parsing is derived from SID.cs of the .NET runtime library, because SID parsing is not available on Linux.
Fixes #55705