-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use LDAP support from DirectoryServices.Protocols for RBAC claim reso…
…lution on Linux for Negotiate (#25075)
- Loading branch information
John Luo
authored
Aug 25, 2020
1 parent
c2f0331
commit 098be5f
Showing
17 changed files
with
435 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
src/Security/Authentication/Negotiate/src/Events/LdapContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using Microsoft.AspNetCore.Http; | ||
|
||
namespace Microsoft.AspNetCore.Authentication.Negotiate | ||
{ | ||
/// <summary> | ||
/// State for the RetrieveLdapClaims event. | ||
/// </summary> | ||
public class LdapContext : ResultContext<NegotiateOptions> | ||
{ | ||
/// <summary> | ||
/// Creates a new <see cref="LdapContext"/>. | ||
/// </summary> | ||
/// <param name="context"></param> | ||
/// <param name="scheme"></param> | ||
/// <param name="options"></param> | ||
/// <param name="settings"></param> | ||
public LdapContext( | ||
HttpContext context, | ||
AuthenticationScheme scheme, | ||
NegotiateOptions options, | ||
LdapSettings settings) | ||
: base(context, scheme, options) | ||
{ | ||
LdapSettings = settings; | ||
} | ||
|
||
/// <summary> | ||
/// The LDAP settings to use for the RetrieveLdapClaims event. | ||
/// </summary> | ||
public LdapSettings LdapSettings { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.DirectoryServices.Protocols; | ||
using System.Linq; | ||
using System.Security.Claims; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.AspNetCore.Authentication.Negotiate | ||
{ | ||
internal static class LdapAdapter | ||
{ | ||
public static async Task RetrieveClaimsAsync(LdapSettings settings, ClaimsIdentity identity, ILogger logger) | ||
{ | ||
var user = identity.Name; | ||
var userAccountName = user.Substring(0, user.IndexOf('@')); | ||
var distinguishedName = settings.Domain.Split('.').Select(name => $"dc={name}").Aggregate((a, b) => $"{a},{b}"); | ||
|
||
var filter = $"(&(objectClass=user)(sAMAccountName={userAccountName}))"; // This is using ldap search query language, it is looking on the server for someUser | ||
var searchRequest = new SearchRequest(distinguishedName, filter, SearchScope.Subtree, null); | ||
var searchResponse = (SearchResponse) await Task<DirectoryResponse>.Factory.FromAsync( | ||
settings.LdapConnection.BeginSendRequest, | ||
settings.LdapConnection.EndSendRequest, | ||
searchRequest, | ||
PartialResultProcessing.NoPartialResultSupport, | ||
null); | ||
|
||
if (searchResponse.Entries.Count > 0) | ||
{ | ||
if (searchResponse.Entries.Count > 1) | ||
{ | ||
logger.LogWarning($"More than one response received for query: {filter} with distinguished name: {distinguishedName}"); | ||
} | ||
|
||
var userFound = searchResponse.Entries[0]; //Get the object that was found on ldap | ||
var memberof = userFound.Attributes["memberof"]; // You can access ldap Attributes with Attributes property | ||
|
||
foreach (var group in memberof) | ||
{ | ||
// Example distinguished name: CN=TestGroup,DC=KERB,DC=local | ||
var groupDN = $"{Encoding.UTF8.GetString((byte[])group)}"; | ||
var groupCN = groupDN.Split(',')[0].Substring("CN=".Length); | ||
|
||
if (!settings.IgnoreNestedGroups) | ||
{ | ||
GetNestedGroups(settings.LdapConnection, identity, distinguishedName, groupCN, logger); | ||
} | ||
else | ||
{ | ||
AddRole(identity, groupCN); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
logger.LogWarning($"No response received for query: {filter} with distinguished name: {distinguishedName}"); | ||
} | ||
} | ||
|
||
private static void GetNestedGroups(LdapConnection connection, ClaimsIdentity principal, string distinguishedName, string groupCN, ILogger logger) | ||
{ | ||
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, System.DirectoryServices.Protocols.SearchScope.Subtree, null); | ||
var searchResponse = (SearchResponse)connection.SendRequest(searchRequest); | ||
|
||
if (searchResponse.Entries.Count > 0) | ||
{ | ||
if (searchResponse.Entries.Count > 1) | ||
{ | ||
logger.LogWarning($"More than one response received for query: {filter} with distinguished name: {distinguishedName}"); | ||
} | ||
|
||
var group = searchResponse.Entries[0]; //Get the object that was found on ldap | ||
string name = group.DistinguishedName; | ||
AddRole(principal, name); | ||
|
||
var memberof = group.Attributes["memberof"]; // You can access ldap Attributes with Attributes property | ||
if (memberof != null) | ||
{ | ||
foreach (var member in memberof) | ||
{ | ||
var groupDN = $"{Encoding.UTF8.GetString((byte[])member)}"; | ||
var nestedGroupCN = groupDN.Split(',')[0].Substring("CN=".Length); | ||
GetNestedGroups(connection, principal, distinguishedName, nestedGroupCN, logger); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private static void AddRole(ClaimsIdentity identity, string role) | ||
{ | ||
identity.AddClaim(new Claim(identity.RoleClaimType, role)); | ||
} | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
...Security/Authentication/Negotiate/src/Internal/NegotiateOptionsValidationStartupFilter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.AspNetCore.Authentication.Negotiate.Internal | ||
{ | ||
internal class NegotiateOptionsValidationStartupFilter : IStartupFilter | ||
{ | ||
private readonly string _authenticationScheme; | ||
|
||
public NegotiateOptionsValidationStartupFilter(string authenticationScheme) | ||
{ | ||
_authenticationScheme = authenticationScheme; | ||
} | ||
|
||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) | ||
{ | ||
return builder => | ||
{ | ||
// Resolve NegotiateOptions on startup to trigger post configuration and bind LdapConnection if needed | ||
var options = builder.ApplicationServices.GetRequiredService<IOptionsMonitor<NegotiateOptions>>().Get(_authenticationScheme); | ||
next(builder); | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.DirectoryServices.Protocols; | ||
|
||
namespace Microsoft.AspNetCore.Authentication.Negotiate | ||
{ | ||
/// <summary> | ||
/// Options class for configuring LDAP connections on Linux | ||
/// </summary> | ||
public class LdapSettings | ||
{ | ||
/// <summary> | ||
/// Configure whether LDAP connection should be used to resolve claims. | ||
/// This is mainly used on Linux. | ||
/// </summary> | ||
public bool EnableLdapClaimResolution { get; set; } | ||
|
||
/// <summary> | ||
/// The domain to use for the LDAP connection. This is a mandatory setting. | ||
/// </summary> | ||
/// <example> | ||
/// DOMAIN.com | ||
/// </example> | ||
public string Domain { get; set; } | ||
|
||
/// <summary> | ||
/// The machine account name to use when opening the LDAP connection. | ||
/// If this is not provided, the machine wide credentials of the | ||
/// domain joined machine will be used. | ||
/// </summary> | ||
public string MachineAccountName { get; set; } | ||
|
||
/// <summary> | ||
/// The machine account password to use when opening the LDAP connection. | ||
/// This must be provided if a <see cref="MachineAccountName"/> is provided. | ||
/// </summary> | ||
public string MachineAccountPassword { get; set; } | ||
|
||
/// <summary> | ||
/// This option indicates whether nested groups should be ignored when | ||
/// resolving Roles. The default is false. | ||
/// </summary> | ||
public bool IgnoreNestedGroups { get; set; } | ||
|
||
/// <summary> | ||
/// The <see cref="LdapConnection"/> to be used to retrieve role claims. | ||
/// If no explicit connection is provided, an LDAP connection will be | ||
/// automatically created based on the <see cref="Domain"/>, | ||
/// <see cref="MachineAccountName"/> and <see cref="MachineAccountPassword"/> | ||
/// options. If provided, this connection will be used and the | ||
/// <see cref="Domain"/>, <see cref="MachineAccountName"/> and | ||
/// <see cref="MachineAccountPassword"/> options will not be used to create | ||
/// the <see cref="LdapConnection"/>. | ||
/// </summary> | ||
public LdapConnection LdapConnection { get; set; } | ||
|
||
public void Validate() | ||
{ | ||
if (EnableLdapClaimResolution) | ||
{ | ||
if (string.IsNullOrEmpty(Domain)) | ||
{ | ||
throw new ArgumentException($"{nameof(EnableLdapClaimResolution)} is set to true but {nameof(Domain)} is not set."); | ||
} | ||
|
||
if (string.IsNullOrEmpty(MachineAccountName) && !string.IsNullOrEmpty(MachineAccountPassword)) | ||
{ | ||
throw new ArgumentException($"{nameof(MachineAccountPassword)} should only be specified when {nameof(MachineAccountName)} is configured."); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.