Pin NTFS AD operations to domain controller
This commit is contained in:
@@ -58,6 +58,7 @@ namespace C4IT.LIAM
|
||||
private const string AdditionalConfigurationTraverseBoundaryPathKey = "NtfsTraverseBoundaryPath";
|
||||
private const string AdditionalConfigurationGroupNameSanitizeReplacementKey = "NtfsGroupNameSanitizeReplacement";
|
||||
private const string AdditionalConfigurationPreserveAdGroupNameCaseKey = "PreserveNtfsAdGroupNameCase";
|
||||
private const string AdditionalConfigurationAdDomainControllersKey = "NtfsAdDomainControllers";
|
||||
public readonly cNtfsBase ntfsBase = new cNtfsBase();
|
||||
public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase();
|
||||
private readonly Dictionary<string, HashSet<string>> publishedShareCache = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -137,6 +138,7 @@ namespace C4IT.LIAM
|
||||
var LI = new cNtfsLogonInfo()
|
||||
{
|
||||
Domain = Domain,
|
||||
DomainControllers = GetAdditionalConfigurationValue(AdditionalConfigurationAdDomainControllersKey),
|
||||
User = Credential?.Identification,
|
||||
UserSecret = Credential?.Secret,
|
||||
TargetNetworkName = RootPath,
|
||||
@@ -980,6 +982,7 @@ namespace C4IT.LIAM
|
||||
{
|
||||
ConfigID = "manual",
|
||||
domainName = this.Domain,
|
||||
effectiveDomainController = activeDirectoryBase.EffectiveDomainController,
|
||||
username = this.Credential.Identification,
|
||||
password = new NetworkCredential("", this.Credential.Secret).SecurePassword,
|
||||
baseFolder = this.RootPath,
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace C4IT_IAM_SET
|
||||
public const string constApplicationDataPath = "%ProgramData%\\Consulting4IT GmbH\\LIAM";
|
||||
|
||||
public string domainName;
|
||||
public string effectiveDomainController;
|
||||
public string username;
|
||||
public SecureString password;
|
||||
private cNetworkConnection Connection;
|
||||
@@ -87,6 +88,11 @@ namespace C4IT_IAM_SET
|
||||
templates = new List<IAM_SecurityGroupTemplate>();
|
||||
}
|
||||
|
||||
private string GetAdServer()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(effectiveDomainController) ? domainName : effectiveDomainController;
|
||||
}
|
||||
|
||||
private ResultToken checkRequiredVariables()
|
||||
{
|
||||
ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
||||
@@ -302,6 +308,7 @@ namespace C4IT_IAM_SET
|
||||
{
|
||||
username = username,
|
||||
domainName = domainName,
|
||||
effectiveDomainController = effectiveDomainController,
|
||||
password = password,
|
||||
ForceStrictAdGroupNames = forceStrictAdGroupNames,
|
||||
PreserveAdGroupNameCase = preserveAdGroupNameCase
|
||||
@@ -441,7 +448,7 @@ namespace C4IT_IAM_SET
|
||||
return resultToken;
|
||||
}
|
||||
|
||||
var domainContext = new PrincipalContext(ContextType.Domain, domainName, username, new NetworkCredential("", password).Password);
|
||||
var domainContext = new PrincipalContext(ContextType.Domain, GetAdServer(), username, new NetworkCredential("", password).Password);
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, "PrincipalContext erfolgreich erstellt.");
|
||||
|
||||
// Überprüfen von newDataArea und IAM_Folders
|
||||
@@ -1005,7 +1012,7 @@ namespace C4IT_IAM_SET
|
||||
return null;
|
||||
}
|
||||
|
||||
var basePath = "LDAP://" + domainName;
|
||||
var basePath = "LDAP://" + GetAdServer();
|
||||
if (!string.IsNullOrWhiteSpace(groupOUPath))
|
||||
basePath += "/" + groupOUPath;
|
||||
|
||||
@@ -1213,6 +1220,7 @@ namespace C4IT_IAM_SET
|
||||
{
|
||||
ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
||||
resultToken.resultErrorId = 0;
|
||||
newSecurityGroups.effectiveDomainController = effectiveDomainController;
|
||||
if (Directory.Exists(newDataArea.IAM_Folders[0].technicalName))
|
||||
{
|
||||
resultToken.resultMessage = "New folder " + newDataArea.IAM_Folders[0].technicalName + " already exists";
|
||||
@@ -1602,7 +1610,7 @@ namespace C4IT_IAM_SET
|
||||
|
||||
try
|
||||
{
|
||||
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domainName, username, new NetworkCredential("", password).Password);
|
||||
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, GetAdServer(), username, new NetworkCredential("", password).Password);
|
||||
UserPrincipal user;
|
||||
user = UserPrincipal.FindByIdentity(ctx, IdentityType.Sid, (sid));
|
||||
return user;
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace C4IT_IAM_Engine
|
||||
public class SecurityGroups
|
||||
{
|
||||
public string domainName;
|
||||
public string effectiveDomainController;
|
||||
public string username;
|
||||
public SecureString password;
|
||||
public bool ForceStrictAdGroupNames;
|
||||
@@ -32,6 +33,11 @@ namespace C4IT_IAM_Engine
|
||||
{
|
||||
IAM_SecurityGroups = new List<IAM_SecurityGroup>();
|
||||
}
|
||||
|
||||
private string GetLdapServer()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(effectiveDomainController) ? domainName : effectiveDomainController;
|
||||
}
|
||||
public bool GroupsAllreadyExisting(string ouPath)
|
||||
{
|
||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||
@@ -47,7 +53,7 @@ namespace C4IT_IAM_Engine
|
||||
{
|
||||
DirectoryEntry entry = new DirectoryEntry
|
||||
{
|
||||
Path = "LDAP://" + domainName,
|
||||
Path = "LDAP://" + GetLdapServer(),
|
||||
Username = username,
|
||||
Password = new NetworkCredential("", password).Password,
|
||||
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
||||
@@ -86,7 +92,7 @@ namespace C4IT_IAM_Engine
|
||||
{
|
||||
DirectoryEntry entry = new DirectoryEntry
|
||||
{
|
||||
Path = "LDAP://" + domainName,
|
||||
Path = "LDAP://" + GetLdapServer(),
|
||||
Username = username,
|
||||
Password = new NetworkCredential("", password).Password,
|
||||
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
||||
@@ -431,7 +437,7 @@ namespace C4IT_IAM_Engine
|
||||
{
|
||||
DirectoryEntry entry = new DirectoryEntry
|
||||
{
|
||||
Path = "LDAP://" + domainName,
|
||||
Path = "LDAP://" + GetLdapServer(),
|
||||
Username = username,
|
||||
Password = new NetworkCredential("", password).Password,
|
||||
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
||||
@@ -473,7 +479,7 @@ namespace C4IT_IAM_Engine
|
||||
return null;
|
||||
}
|
||||
|
||||
var basePath = "LDAP://" + domainName;
|
||||
var basePath = "LDAP://" + GetLdapServer();
|
||||
if (!string.IsNullOrWhiteSpace(ouPath))
|
||||
basePath += "/" + ouPath;
|
||||
|
||||
@@ -528,7 +534,7 @@ namespace C4IT_IAM_Engine
|
||||
return null;
|
||||
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Reusing existing AD group '{matchedName}' via wildcard '{wildcardPattern}'.");
|
||||
return new DirectoryEntry("LDAP://" + domainName + "/" + matchedDistinguishedName, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
||||
return new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + matchedDistinguishedName, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
||||
}
|
||||
|
||||
private DirectoryEntry FindGroupEntryFromFolderAcl(string folderPath, string wildcardPattern)
|
||||
@@ -555,7 +561,7 @@ namespace C4IT_IAM_Engine
|
||||
.Cast<FileSystemAccessRule>();
|
||||
var matchedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
using (var domainContext = new PrincipalContext(ContextType.Domain, domainName, username, new NetworkCredential("", password).Password))
|
||||
using (var domainContext = new PrincipalContext(ContextType.Domain, GetLdapServer(), username, new NetworkCredential("", password).Password))
|
||||
{
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
@@ -735,7 +741,7 @@ namespace C4IT_IAM_Engine
|
||||
if (!GroupAllreadyExisting(groupName))
|
||||
{
|
||||
|
||||
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domainName + "/" + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
||||
DirectoryEntry entry = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Creating ad entry with CN / sAmAccountName: {groupName}");
|
||||
DirectoryEntry group = entry.Children.Add("CN=" + groupName, "group");
|
||||
group.Properties["sAmAccountName"].Value = groupName;
|
||||
@@ -763,7 +769,7 @@ namespace C4IT_IAM_Engine
|
||||
}
|
||||
|
||||
group.CommitChanges();
|
||||
DirectoryEntry ent = new DirectoryEntry("LDAP://" + domainName + "/" + "CN=" + groupName + "," + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
||||
DirectoryEntry ent = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + "CN=" + groupName + "," + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
||||
|
||||
var objectid = SecurityGroups.getSID(ent);
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}");
|
||||
|
||||
@@ -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;
|
||||
@@ -24,6 +25,7 @@ namespace LiamNtfs
|
||||
private cNtfsLogonInfo 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;
|
||||
|
||||
@@ -49,8 +51,12 @@ namespace LiamNtfs
|
||||
//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($"NTFS AD domain controller pinned to '{EffectiveDomainController}' for domain '{LogonInfo.Domain}'.", LogLevels.Debug);
|
||||
|
||||
var ldapPath = $"LDAP://{EffectiveDomainController}/{LogonInfo.TargetGroupPath}";
|
||||
directoryEntry = new DirectoryEntry
|
||||
{
|
||||
Path = ldapPath,
|
||||
@@ -69,6 +75,70 @@ namespace LiamNtfs
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ResolveEffectiveDomainController(cNtfsLogonInfo logonInfo)
|
||||
{
|
||||
var configuredDomainControllers = ParseDomainControllers(logonInfo?.DomainControllers);
|
||||
foreach (var domainController in configuredDomainControllers)
|
||||
{
|
||||
if (CanConnectToDomainController(domainController, logonInfo))
|
||||
return domainController;
|
||||
|
||||
LogEntry($"Configured NTFS AD 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, cNtfsLogonInfo 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(cNtfsLogonInfo 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 async Task<bool> privRelogon()
|
||||
{
|
||||
if (privLogonInfo == null)
|
||||
|
||||
@@ -334,6 +334,7 @@ namespace LiamNtfs
|
||||
public class cNtfsLogonInfo
|
||||
{
|
||||
public string Domain;
|
||||
public string DomainControllers;
|
||||
public string User;
|
||||
public string UserSecret;
|
||||
public string TargetNetworkName;
|
||||
|
||||
Reference in New Issue
Block a user