Pin Active Directory provider to domain controller
This commit is contained in:
@@ -30,6 +30,8 @@ namespace C4IT.LIAM
|
|||||||
public class cLiamProviderAD : cLiamProviderBase
|
public class cLiamProviderAD : cLiamProviderBase
|
||||||
{
|
{
|
||||||
public static Guid adModuleId = new Guid("e820a625-0653-ee11-b886-00155d300101");
|
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();
|
public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase();
|
||||||
private readonly ADServiceGroupCreator _serviceGroupCreator;
|
private readonly ADServiceGroupCreator _serviceGroupCreator;
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ namespace C4IT.LIAM
|
|||||||
var LI = new cADLogonInfo()
|
var LI = new cADLogonInfo()
|
||||||
{
|
{
|
||||||
Domain = Domain,
|
Domain = Domain,
|
||||||
|
DomainControllers = GetConfiguredDomainControllers(),
|
||||||
User = Credential?.Identification,
|
User = Credential?.Identification,
|
||||||
UserSecret = Credential?.Secret,
|
UserSecret = Credential?.Secret,
|
||||||
TargetGroupPath = this.GroupPath
|
TargetGroupPath = this.GroupPath
|
||||||
@@ -95,6 +98,26 @@ namespace C4IT.LIAM
|
|||||||
return false;
|
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)
|
public override async Task<List<cLiamDataAreaBase>> getDataAreasAsync(int Depth = -1)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
@@ -368,4 +391,4 @@ namespace C4IT.LIAM
|
|||||||
this.scope = secGroup.Scope.ToString();
|
this.scope = secGroup.Scope.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ namespace LiamAD
|
|||||||
public class cADLogonInfo
|
public class cADLogonInfo
|
||||||
{
|
{
|
||||||
public string Domain;
|
public string Domain;
|
||||||
|
public string DomainControllers;
|
||||||
public string User;
|
public string User;
|
||||||
public string UserSecret;
|
public string UserSecret;
|
||||||
public string TargetGroupPath;
|
public string TargetGroupPath;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace LiamAD
|
|||||||
{
|
{
|
||||||
private readonly cLiamProviderAD _provider;
|
private readonly cLiamProviderAD _provider;
|
||||||
private readonly cActiveDirectoryBase _adBase;
|
private readonly cActiveDirectoryBase _adBase;
|
||||||
private readonly string _ldapRoot;
|
|
||||||
private readonly string _user;
|
private readonly string _user;
|
||||||
private readonly string _password;
|
private readonly string _password;
|
||||||
public enum ADGroupType
|
public enum ADGroupType
|
||||||
@@ -31,11 +30,25 @@ namespace LiamAD
|
|||||||
{
|
{
|
||||||
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
|
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||||||
_adBase = provider.activeDirectoryBase;
|
_adBase = provider.activeDirectoryBase;
|
||||||
_ldapRoot = $"LDAP://{provider.Domain}/{provider.GroupPath}";
|
|
||||||
_user = provider.Credential.Identification;
|
_user = provider.Credential.Identification;
|
||||||
_password = new System.Net.NetworkCredential(_user, provider.Credential.Secret).Password;
|
_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>
|
/// <summary>
|
||||||
/// Erstellt oder findet beide AD-Gruppen (Member & Owner) für einen Service.
|
/// Erstellt oder findet beide AD-Gruppen (Member & Owner) für einen Service.
|
||||||
/// Neu mit: gruppenbereich (Scope) und gruppentyp (für Member-Gruppe).
|
/// Neu mit: gruppenbereich (Scope) und gruppentyp (für Member-Gruppe).
|
||||||
@@ -115,7 +128,7 @@ namespace LiamAD
|
|||||||
if (sidList == null) return;
|
if (sidList == null) return;
|
||||||
|
|
||||||
// Basis für die Suche: komplette Domäne, nicht nur der OU-Pfad
|
// 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 root = new DirectoryEntry(domainRoot, _user, _password, AuthenticationTypes.Secure))
|
||||||
using (var grpSearch = new DirectorySearcher(root))
|
using (var grpSearch = new DirectorySearcher(root))
|
||||||
{
|
{
|
||||||
@@ -185,7 +198,7 @@ namespace LiamAD
|
|||||||
|
|
||||||
private string GetSid(string name)
|
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))
|
using (var ds = new DirectorySearcher(root))
|
||||||
{
|
{
|
||||||
ds.Filter = $"(&(objectCategory=group)(sAMAccountName={name}))";
|
ds.Filter = $"(&(objectCategory=group)(sAMAccountName={name}))";
|
||||||
@@ -219,7 +232,7 @@ namespace LiamAD
|
|||||||
{
|
{
|
||||||
if (!GroupExists(groupName))
|
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");
|
var grp = root.Children.Add("CN=" + groupName, "group");
|
||||||
grp.Properties["sAMAccountName"].Value = groupName;
|
grp.Properties["sAMAccountName"].Value = groupName;
|
||||||
@@ -261,7 +274,7 @@ namespace LiamAD
|
|||||||
|
|
||||||
private string GetDistinguishedName(string name)
|
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))
|
using (var ds = new DirectorySearcher(root))
|
||||||
{
|
{
|
||||||
ds.Filter = "(&(objectClass=group)(sAMAccountName=" + name + "))";
|
ds.Filter = "(&(objectClass=group)(sAMAccountName=" + name + "))";
|
||||||
@@ -285,4 +298,4 @@ namespace LiamAD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.DirectoryServices;
|
using System.DirectoryServices;
|
||||||
|
using System.DirectoryServices.ActiveDirectory;
|
||||||
using System.DirectoryServices.AccountManagement;
|
using System.DirectoryServices.AccountManagement;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -25,6 +26,7 @@ namespace LiamAD
|
|||||||
private cADLogonInfo privLogonInfo = null;
|
private cADLogonInfo privLogonInfo = null;
|
||||||
public PrincipalContext adContext = null;
|
public PrincipalContext adContext = null;
|
||||||
public DirectoryEntry directoryEntry = null;
|
public DirectoryEntry directoryEntry = null;
|
||||||
|
public string EffectiveDomainController { get; private set; } = null;
|
||||||
public Exception LastException { get; private set; } = null;
|
public Exception LastException { get; private set; } = null;
|
||||||
public string LastErrorMessage { get; private set; } = null;
|
public string LastErrorMessage { get; private set; } = null;
|
||||||
|
|
||||||
@@ -50,8 +52,12 @@ namespace LiamAD
|
|||||||
//TODO: remove dummy delay?
|
//TODO: remove dummy delay?
|
||||||
await Task.Delay(0);
|
await Task.Delay(0);
|
||||||
ResetError();
|
ResetError();
|
||||||
adContext = new PrincipalContext(ContextType.Domain, LogonInfo.Domain, LogonInfo.User, new NetworkCredential("", LogonInfo.UserSecret).Password);
|
var adServer = ResolveEffectiveDomainController(LogonInfo);
|
||||||
var ldapPath = $"LDAP://{LogonInfo.Domain}/{LogonInfo.TargetGroupPath}";
|
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
|
directoryEntry = new DirectoryEntry
|
||||||
{
|
{
|
||||||
Path = ldapPath,
|
Path = ldapPath,
|
||||||
@@ -70,6 +76,78 @@ namespace LiamAD
|
|||||||
return false;
|
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()
|
private async Task<bool> privRelogon()
|
||||||
{
|
{
|
||||||
if (privLogonInfo == null)
|
if (privLogonInfo == null)
|
||||||
@@ -193,7 +271,7 @@ namespace LiamAD
|
|||||||
string managedByDn = k.Properties["managedBy"][0].ToString();
|
string managedByDn = k.Properties["managedBy"][0].ToString();
|
||||||
|
|
||||||
// Erstellen eines DirectoryEntry-Objekts für den managedBy-DN
|
// 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)
|
if (managedByEntry.Properties.Contains("objectSid") && managedByEntry.Properties["objectSid"].Count > 0)
|
||||||
{
|
{
|
||||||
@@ -513,4 +591,4 @@ namespace LiamAD
|
|||||||
return null;
|
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.
|
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
|
## Matching-Regeln
|
||||||
|
|
||||||
Empfohlene Semantik:
|
Empfohlene Semantik:
|
||||||
|
|||||||
Reference in New Issue
Block a user