Pin Active Directory provider to domain controller
This commit is contained in:
@@ -30,6 +30,8 @@ namespace C4IT.LIAM
|
||||
public class cLiamProviderAD : cLiamProviderBase
|
||||
{
|
||||
public static Guid adModuleId = new Guid("e820a625-0653-ee11-b886-00155d300101");
|
||||
private const string AdditionalConfigurationAdDomainControllersKey = "AdDomainControllers";
|
||||
private const string AdditionalConfigurationActiveDirectoryDomainControllersKey = "ActiveDirectoryDomainControllers";
|
||||
public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase();
|
||||
private readonly ADServiceGroupCreator _serviceGroupCreator;
|
||||
|
||||
@@ -76,6 +78,7 @@ namespace C4IT.LIAM
|
||||
var LI = new cADLogonInfo()
|
||||
{
|
||||
Domain = Domain,
|
||||
DomainControllers = GetConfiguredDomainControllers(),
|
||||
User = Credential?.Identification,
|
||||
UserSecret = Credential?.Secret,
|
||||
TargetGroupPath = this.GroupPath
|
||||
@@ -95,6 +98,26 @@ namespace C4IT.LIAM
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetConfiguredDomainControllers()
|
||||
{
|
||||
var value = GetAdditionalConfigurationValue(AdditionalConfigurationAdDomainControllersKey);
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
return value;
|
||||
|
||||
return GetAdditionalConfigurationValue(AdditionalConfigurationActiveDirectoryDomainControllersKey);
|
||||
}
|
||||
|
||||
private string GetAdditionalConfigurationValue(string key)
|
||||
{
|
||||
if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
|
||||
return string.Empty;
|
||||
|
||||
if (!AdditionalConfiguration.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue))
|
||||
return string.Empty;
|
||||
|
||||
return rawValue.Trim();
|
||||
}
|
||||
|
||||
public override async Task<List<cLiamDataAreaBase>> getDataAreasAsync(int Depth = -1)
|
||||
{
|
||||
var CM = MethodBase.GetCurrentMethod();
|
||||
@@ -368,4 +391,4 @@ namespace C4IT.LIAM
|
||||
this.scope = secGroup.Scope.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace LiamAD
|
||||
public class cADLogonInfo
|
||||
{
|
||||
public string Domain;
|
||||
public string DomainControllers;
|
||||
public string User;
|
||||
public string UserSecret;
|
||||
public string TargetGroupPath;
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace LiamAD
|
||||
{
|
||||
private readonly cLiamProviderAD _provider;
|
||||
private readonly cActiveDirectoryBase _adBase;
|
||||
private readonly string _ldapRoot;
|
||||
private readonly string _user;
|
||||
private readonly string _password;
|
||||
public enum ADGroupType
|
||||
@@ -31,11 +30,25 @@ namespace LiamAD
|
||||
{
|
||||
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||||
_adBase = provider.activeDirectoryBase;
|
||||
_ldapRoot = $"LDAP://{provider.Domain}/{provider.GroupPath}";
|
||||
_user = provider.Credential.Identification;
|
||||
_password = new System.Net.NetworkCredential(_user, provider.Credential.Secret).Password;
|
||||
}
|
||||
|
||||
private string GetLdapServer()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_adBase.EffectiveDomainController) ? _provider.Domain : _adBase.EffectiveDomainController;
|
||||
}
|
||||
|
||||
private string GetLdapRoot()
|
||||
{
|
||||
return $"LDAP://{GetLdapServer()}/{_provider.GroupPath}";
|
||||
}
|
||||
|
||||
private string GetLdapDomainRoot()
|
||||
{
|
||||
return $"LDAP://{GetLdapServer()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt oder findet beide AD-Gruppen (Member & Owner) für einen Service.
|
||||
/// Neu mit: gruppenbereich (Scope) und gruppentyp (für Member-Gruppe).
|
||||
@@ -115,7 +128,7 @@ namespace LiamAD
|
||||
if (sidList == null) return;
|
||||
|
||||
// Basis für die Suche: komplette Domäne, nicht nur der OU-Pfad
|
||||
string domainRoot = $"LDAP://{_provider.Domain}";
|
||||
string domainRoot = GetLdapDomainRoot();
|
||||
using (var root = new DirectoryEntry(domainRoot, _user, _password, AuthenticationTypes.Secure))
|
||||
using (var grpSearch = new DirectorySearcher(root))
|
||||
{
|
||||
@@ -185,7 +198,7 @@ namespace LiamAD
|
||||
|
||||
private string GetSid(string name)
|
||||
{
|
||||
using (var root = new DirectoryEntry(_ldapRoot, _user, _password, AuthenticationTypes.Secure))
|
||||
using (var root = new DirectoryEntry(GetLdapRoot(), _user, _password, AuthenticationTypes.Secure))
|
||||
using (var ds = new DirectorySearcher(root))
|
||||
{
|
||||
ds.Filter = $"(&(objectCategory=group)(sAMAccountName={name}))";
|
||||
@@ -219,7 +232,7 @@ namespace LiamAD
|
||||
{
|
||||
if (!GroupExists(groupName))
|
||||
{
|
||||
using (var root = new DirectoryEntry(_ldapRoot, _user, _password, AuthenticationTypes.Secure))
|
||||
using (var root = new DirectoryEntry(GetLdapRoot(), _user, _password, AuthenticationTypes.Secure))
|
||||
{
|
||||
var grp = root.Children.Add("CN=" + groupName, "group");
|
||||
grp.Properties["sAMAccountName"].Value = groupName;
|
||||
@@ -261,7 +274,7 @@ namespace LiamAD
|
||||
|
||||
private string GetDistinguishedName(string name)
|
||||
{
|
||||
using (var root = new DirectoryEntry(_ldapRoot, _user, _password, AuthenticationTypes.Secure))
|
||||
using (var root = new DirectoryEntry(GetLdapRoot(), _user, _password, AuthenticationTypes.Secure))
|
||||
using (var ds = new DirectorySearcher(root))
|
||||
{
|
||||
ds.Filter = "(&(objectClass=group)(sAMAccountName=" + name + "))";
|
||||
@@ -285,4 +298,4 @@ namespace LiamAD
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.DirectoryServices;
|
||||
using System.DirectoryServices.ActiveDirectory;
|
||||
using System.DirectoryServices.AccountManagement;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -25,6 +26,7 @@ namespace LiamAD
|
||||
private cADLogonInfo privLogonInfo = null;
|
||||
public PrincipalContext adContext = null;
|
||||
public DirectoryEntry directoryEntry = null;
|
||||
public string EffectiveDomainController { get; private set; } = null;
|
||||
public Exception LastException { get; private set; } = null;
|
||||
public string LastErrorMessage { get; private set; } = null;
|
||||
|
||||
@@ -50,8 +52,12 @@ namespace LiamAD
|
||||
//TODO: remove dummy delay?
|
||||
await Task.Delay(0);
|
||||
ResetError();
|
||||
adContext = new PrincipalContext(ContextType.Domain, LogonInfo.Domain, LogonInfo.User, new NetworkCredential("", LogonInfo.UserSecret).Password);
|
||||
var ldapPath = $"LDAP://{LogonInfo.Domain}/{LogonInfo.TargetGroupPath}";
|
||||
var adServer = ResolveEffectiveDomainController(LogonInfo);
|
||||
adContext = new PrincipalContext(ContextType.Domain, adServer, LogonInfo.User, new NetworkCredential("", LogonInfo.UserSecret).Password);
|
||||
EffectiveDomainController = adContext.ConnectedServer ?? adServer;
|
||||
LogEntry($"AD provider domain controller pinned to '{EffectiveDomainController}' for domain '{LogonInfo.Domain}'.", LogLevels.Debug);
|
||||
|
||||
var ldapPath = $"LDAP://{EffectiveDomainController}/{LogonInfo.TargetGroupPath}";
|
||||
directoryEntry = new DirectoryEntry
|
||||
{
|
||||
Path = ldapPath,
|
||||
@@ -70,6 +76,78 @@ namespace LiamAD
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ResolveEffectiveDomainController(cADLogonInfo logonInfo)
|
||||
{
|
||||
var configuredDomainControllers = ParseDomainControllers(logonInfo?.DomainControllers);
|
||||
foreach (var domainController in configuredDomainControllers)
|
||||
{
|
||||
if (CanConnectToDomainController(domainController, logonInfo))
|
||||
return domainController;
|
||||
|
||||
LogEntry($"Configured AD provider domain controller '{domainController}' is not reachable. Trying next candidate.", LogLevels.Warning);
|
||||
}
|
||||
|
||||
var pdc = TryGetPdcRoleOwner(logonInfo);
|
||||
if (!string.IsNullOrWhiteSpace(pdc))
|
||||
return pdc;
|
||||
|
||||
LogEntry($"Could not determine PDC emulator for domain '{logonInfo?.Domain}'. Falling back to domain locator.", LogLevels.Warning);
|
||||
return logonInfo?.Domain;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ParseDomainControllers(string domainControllers)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(domainControllers))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return domainControllers
|
||||
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(i => i.Trim())
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i));
|
||||
}
|
||||
|
||||
private bool CanConnectToDomainController(string domainController, cADLogonInfo logonInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var context = new PrincipalContext(ContextType.Domain, domainController, logonInfo.User, new NetworkCredential("", logonInfo.UserSecret).Password))
|
||||
return !string.IsNullOrWhiteSpace(context.ConnectedServer);
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E, LogLevels.Debug);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string TryGetPdcRoleOwner(cADLogonInfo logonInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var credentials = new DirectoryContext(
|
||||
DirectoryContextType.Domain,
|
||||
logonInfo.Domain,
|
||||
logonInfo.User,
|
||||
new NetworkCredential("", logonInfo.UserSecret).Password);
|
||||
|
||||
using (var domain = Domain.GetDomain(credentials))
|
||||
return domain?.PdcRoleOwner?.Name;
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E, LogLevels.Debug);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetAdServer()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(EffectiveDomainController))
|
||||
return EffectiveDomainController;
|
||||
|
||||
return privLogonInfo?.Domain;
|
||||
}
|
||||
|
||||
private async Task<bool> privRelogon()
|
||||
{
|
||||
if (privLogonInfo == null)
|
||||
@@ -193,7 +271,7 @@ namespace LiamAD
|
||||
string managedByDn = k.Properties["managedBy"][0].ToString();
|
||||
|
||||
// Erstellen eines DirectoryEntry-Objekts für den managedBy-DN
|
||||
using (DirectoryEntry managedByEntry = new DirectoryEntry($"LDAP://{managedByDn}"))
|
||||
using (DirectoryEntry managedByEntry = new DirectoryEntry($"LDAP://{GetAdServer()}/{managedByDn}", privLogonInfo.User, new NetworkCredential("", privLogonInfo.UserSecret).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing))
|
||||
{
|
||||
if (managedByEntry.Properties.Contains("objectSid") && managedByEntry.Properties["objectSid"].Count > 0)
|
||||
{
|
||||
@@ -513,4 +591,4 @@ namespace LiamAD
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +287,14 @@ NtfsAdDomainControllers=dc01.imagoverum.com,dc02.imagoverum.com
|
||||
|
||||
Der Provider testet die Eintraege in Reihenfolge. Der erste erreichbare DC wird verwendet. Wenn kein konfigurierter DC erreichbar ist, faellt der Provider auf den automatisch ermittelten PDC Emulator zurueck. Wenn auch dieser nicht ermittelt werden kann, wird wie bisher die Domain selbst verwendet und damit wieder der Windows-DC-Locator genutzt.
|
||||
|
||||
Die gleiche Logik gilt fuer den ActiveDirectory-Provider. Dort koennen die DCs ueber `AdDomainControllers` oder alternativ `ActiveDirectoryDomainControllers` gesetzt werden:
|
||||
|
||||
```text
|
||||
AdDomainControllers=dc01.imagoverum.com,dc02.imagoverum.com
|
||||
```
|
||||
|
||||
Auch hier wird ohne Konfiguration automatisch der PDC Emulator verwendet. Der tatsaechlich verwendete DC wird beim AD-Logon im Debug-Log ausgegeben.
|
||||
|
||||
## Matching-Regeln
|
||||
|
||||
Empfohlene Semantik:
|
||||
|
||||
Reference in New Issue
Block a user