Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8394d1e67e | ||
|
|
0c16e8a5b1 | ||
|
|
a9b4cfe10b | ||
|
|
723eae1018 | ||
|
|
0cad46ddef | ||
|
|
ea0517d1dc |
@@ -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();
|
||||||
|
|||||||
@@ -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 + "))";
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ namespace C4IT.LIAM
|
|||||||
private const string AdditionalConfigurationTraverseBoundaryPathKey = "NtfsTraverseBoundaryPath";
|
private const string AdditionalConfigurationTraverseBoundaryPathKey = "NtfsTraverseBoundaryPath";
|
||||||
private const string AdditionalConfigurationGroupNameSanitizeReplacementKey = "NtfsGroupNameSanitizeReplacement";
|
private const string AdditionalConfigurationGroupNameSanitizeReplacementKey = "NtfsGroupNameSanitizeReplacement";
|
||||||
private const string AdditionalConfigurationPreserveAdGroupNameCaseKey = "PreserveNtfsAdGroupNameCase";
|
private const string AdditionalConfigurationPreserveAdGroupNameCaseKey = "PreserveNtfsAdGroupNameCase";
|
||||||
|
private const string AdditionalConfigurationAdDomainControllersKey = "NtfsAdDomainControllers";
|
||||||
public readonly cNtfsBase ntfsBase = new cNtfsBase();
|
public readonly cNtfsBase ntfsBase = new cNtfsBase();
|
||||||
public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase();
|
public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase();
|
||||||
private readonly Dictionary<string, HashSet<string>> publishedShareCache = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
|
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()
|
var LI = new cNtfsLogonInfo()
|
||||||
{
|
{
|
||||||
Domain = Domain,
|
Domain = Domain,
|
||||||
|
DomainControllers = GetAdditionalConfigurationValue(AdditionalConfigurationAdDomainControllersKey),
|
||||||
User = Credential?.Identification,
|
User = Credential?.Identification,
|
||||||
UserSecret = Credential?.Secret,
|
UserSecret = Credential?.Secret,
|
||||||
TargetNetworkName = RootPath,
|
TargetNetworkName = RootPath,
|
||||||
@@ -980,6 +982,7 @@ namespace C4IT.LIAM
|
|||||||
{
|
{
|
||||||
ConfigID = "manual",
|
ConfigID = "manual",
|
||||||
domainName = this.Domain,
|
domainName = this.Domain,
|
||||||
|
effectiveDomainController = activeDirectoryBase.EffectiveDomainController,
|
||||||
username = this.Credential.Identification,
|
username = this.Credential.Identification,
|
||||||
password = new NetworkCredential("", this.Credential.Secret).SecurePassword,
|
password = new NetworkCredential("", this.Credential.Secret).SecurePassword,
|
||||||
baseFolder = this.RootPath,
|
baseFolder = this.RootPath,
|
||||||
@@ -1347,72 +1350,110 @@ namespace C4IT.LIAM
|
|||||||
var traverseNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Traverse);
|
var traverseNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Traverse);
|
||||||
foreach (FileSystemAccessRule rule in ACLs)
|
foreach (FileSystemAccessRule rule in ACLs)
|
||||||
{
|
{
|
||||||
if (rule.IdentityReference.Value == "S-1-1-0")
|
var aclSid = rule.IdentityReference.Value;
|
||||||
|
if (aclSid == "S-1-1-0")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
GroupPrincipal grp = GroupPrincipal.FindByIdentity(Provider.activeDirectoryBase.adContext, IdentityType.Sid, rule.IdentityReference.Value);
|
GroupPrincipal grp = GroupPrincipal.FindByIdentity(Provider.activeDirectoryBase.adContext, IdentityType.Sid, aclSid);
|
||||||
if (grp == null)
|
if (grp == null)
|
||||||
continue;
|
|
||||||
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Try matching: {grp.Name}");
|
|
||||||
if (Regex.IsMatch(grp.SamAccountName, ownerNamingConvention.Wildcard, RegexOptions.IgnoreCase))
|
|
||||||
{
|
{
|
||||||
this.OwnerGroupIdentifier = rule.IdentityReference.Value;
|
DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' could not be resolved to an AD group. Naming convention matching skipped.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var samAccountName = grp.SamAccountName ?? string.Empty;
|
||||||
|
if (string.IsNullOrWhiteSpace(samAccountName))
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{grp.Name}', but no sAMAccountName is available. Naming convention matching skipped.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Regex.IsMatch(samAccountName, ownerNamingConvention.Wildcard, RegexOptions.IgnoreCase))
|
||||||
|
{
|
||||||
|
this.OwnerGroupIdentifier = aclSid;
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Owner naming convention '{ownerNamingConvention.Wildcard}'.");
|
||||||
if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
|
if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
|
||||||
{
|
{
|
||||||
var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
|
var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
|
||||||
var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
|
var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
|
||||||
var ownerNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && i.Scope == eLiamAccessRoleScopes.Global);
|
var ownerNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && i.Scope == eLiamAccessRoleScopes.Global);
|
||||||
|
var matchedGlobalGroup = false;
|
||||||
|
|
||||||
foreach (var memberItem in res)
|
foreach (var memberItem in res ?? new cADCollectionBase())
|
||||||
{
|
{
|
||||||
var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
|
var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
|
||||||
if (Regex.IsMatch(SecurityGroup.TechnicalName, ownerNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
|
if (Regex.IsMatch(SecurityGroup.TechnicalName, ownerNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
|
||||||
this.OwnerGroupIdentifier = SecurityGroup.UID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Regex.IsMatch(grp.SamAccountName, writeNamingConvention.Wildcard, RegexOptions.IgnoreCase))
|
|
||||||
{
|
{
|
||||||
this.WriteGroupIdentifier = rule.IdentityReference.Value;
|
this.OwnerGroupIdentifier = SecurityGroup.UID;
|
||||||
|
matchedGlobalGroup = true;
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Owner ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchedGlobalGroup)
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Owner ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{ownerNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Regex.IsMatch(samAccountName, writeNamingConvention.Wildcard, RegexOptions.IgnoreCase))
|
||||||
|
{
|
||||||
|
this.WriteGroupIdentifier = aclSid;
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Write naming convention '{writeNamingConvention.Wildcard}'.");
|
||||||
if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
|
if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
|
||||||
{
|
{
|
||||||
var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
|
var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
|
||||||
var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
|
var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
|
||||||
var writeNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && i.Scope == eLiamAccessRoleScopes.Global);
|
var writeNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && i.Scope == eLiamAccessRoleScopes.Global);
|
||||||
|
var matchedGlobalGroup = false;
|
||||||
|
|
||||||
foreach (var memberItem in res)
|
foreach (var memberItem in res ?? new cADCollectionBase())
|
||||||
{
|
{
|
||||||
var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
|
var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
|
||||||
if (Regex.IsMatch(SecurityGroup.TechnicalName, writeNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
|
if (Regex.IsMatch(SecurityGroup.TechnicalName, writeNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
|
||||||
this.WriteGroupIdentifier = SecurityGroup.UID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Regex.IsMatch(grp.SamAccountName, readNamingConvention.Wildcard, RegexOptions.IgnoreCase))
|
|
||||||
{
|
{
|
||||||
this.ReadGroupIdentifier = rule.IdentityReference.Value;
|
this.WriteGroupIdentifier = SecurityGroup.UID;
|
||||||
|
matchedGlobalGroup = true;
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Write ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchedGlobalGroup)
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Write ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{writeNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Regex.IsMatch(samAccountName, readNamingConvention.Wildcard, RegexOptions.IgnoreCase))
|
||||||
|
{
|
||||||
|
this.ReadGroupIdentifier = aclSid;
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Read naming convention '{readNamingConvention.Wildcard}'.");
|
||||||
if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
|
if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
|
||||||
{
|
{
|
||||||
var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
|
var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
|
||||||
var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
|
var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
|
||||||
var readNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && i.Scope == eLiamAccessRoleScopes.Global);
|
var readNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && i.Scope == eLiamAccessRoleScopes.Global);
|
||||||
|
var matchedGlobalGroup = false;
|
||||||
|
|
||||||
foreach (var memberItem in res)
|
foreach (var memberItem in res ?? new cADCollectionBase())
|
||||||
{
|
{
|
||||||
var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
|
var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
|
||||||
if (Regex.IsMatch(SecurityGroup.TechnicalName, readNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
|
if (Regex.IsMatch(SecurityGroup.TechnicalName, readNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
|
||||||
this.ReadGroupIdentifier = SecurityGroup.UID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Regex.IsMatch(grp.SamAccountName, traverseNamingConvention.Wildcard, RegexOptions.IgnoreCase))
|
|
||||||
{
|
{
|
||||||
this.TraverseGroupIdentifier = rule.IdentityReference.Value;
|
this.ReadGroupIdentifier = SecurityGroup.UID;
|
||||||
|
matchedGlobalGroup = true;
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Read ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchedGlobalGroup)
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Read ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{readNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Regex.IsMatch(samAccountName, traverseNamingConvention.Wildcard, RegexOptions.IgnoreCase))
|
||||||
|
{
|
||||||
|
this.TraverseGroupIdentifier = aclSid;
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Traverse naming convention '{traverseNamingConvention.Wildcard}'.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"No match for: {grp.Name}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}', but did not match Owner/Write/Read/Traverse naming conventions.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace C4IT_IAM_SET
|
|||||||
public const string constApplicationDataPath = "%ProgramData%\\Consulting4IT GmbH\\LIAM";
|
public const string constApplicationDataPath = "%ProgramData%\\Consulting4IT GmbH\\LIAM";
|
||||||
|
|
||||||
public string domainName;
|
public string domainName;
|
||||||
|
public string effectiveDomainController;
|
||||||
public string username;
|
public string username;
|
||||||
public SecureString password;
|
public SecureString password;
|
||||||
private cNetworkConnection Connection;
|
private cNetworkConnection Connection;
|
||||||
@@ -87,6 +88,11 @@ namespace C4IT_IAM_SET
|
|||||||
templates = new List<IAM_SecurityGroupTemplate>();
|
templates = new List<IAM_SecurityGroupTemplate>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetAdServer()
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(effectiveDomainController) ? domainName : effectiveDomainController;
|
||||||
|
}
|
||||||
|
|
||||||
private ResultToken checkRequiredVariables()
|
private ResultToken checkRequiredVariables()
|
||||||
{
|
{
|
||||||
ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
||||||
@@ -302,6 +308,7 @@ namespace C4IT_IAM_SET
|
|||||||
{
|
{
|
||||||
username = username,
|
username = username,
|
||||||
domainName = domainName,
|
domainName = domainName,
|
||||||
|
effectiveDomainController = effectiveDomainController,
|
||||||
password = password,
|
password = password,
|
||||||
ForceStrictAdGroupNames = forceStrictAdGroupNames,
|
ForceStrictAdGroupNames = forceStrictAdGroupNames,
|
||||||
PreserveAdGroupNameCase = preserveAdGroupNameCase
|
PreserveAdGroupNameCase = preserveAdGroupNameCase
|
||||||
@@ -441,7 +448,7 @@ namespace C4IT_IAM_SET
|
|||||||
return resultToken;
|
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.");
|
DefaultLogger.LogEntry(LogLevels.Debug, "PrincipalContext erfolgreich erstellt.");
|
||||||
|
|
||||||
// Überprüfen von newDataArea und IAM_Folders
|
// Überprüfen von newDataArea und IAM_Folders
|
||||||
@@ -1005,7 +1012,7 @@ namespace C4IT_IAM_SET
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var basePath = "LDAP://" + domainName;
|
var basePath = "LDAP://" + GetAdServer();
|
||||||
if (!string.IsNullOrWhiteSpace(groupOUPath))
|
if (!string.IsNullOrWhiteSpace(groupOUPath))
|
||||||
basePath += "/" + groupOUPath;
|
basePath += "/" + groupOUPath;
|
||||||
|
|
||||||
@@ -1213,6 +1220,7 @@ namespace C4IT_IAM_SET
|
|||||||
{
|
{
|
||||||
ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
||||||
resultToken.resultErrorId = 0;
|
resultToken.resultErrorId = 0;
|
||||||
|
newSecurityGroups.effectiveDomainController = effectiveDomainController;
|
||||||
if (Directory.Exists(newDataArea.IAM_Folders[0].technicalName))
|
if (Directory.Exists(newDataArea.IAM_Folders[0].technicalName))
|
||||||
{
|
{
|
||||||
resultToken.resultMessage = "New folder " + newDataArea.IAM_Folders[0].technicalName + " already exists";
|
resultToken.resultMessage = "New folder " + newDataArea.IAM_Folders[0].technicalName + " already exists";
|
||||||
@@ -1602,7 +1610,7 @@ namespace C4IT_IAM_SET
|
|||||||
|
|
||||||
try
|
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;
|
UserPrincipal user;
|
||||||
user = UserPrincipal.FindByIdentity(ctx, IdentityType.Sid, (sid));
|
user = UserPrincipal.FindByIdentity(ctx, IdentityType.Sid, (sid));
|
||||||
return user;
|
return user;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace C4IT_IAM_Engine
|
|||||||
public class SecurityGroups
|
public class SecurityGroups
|
||||||
{
|
{
|
||||||
public string domainName;
|
public string domainName;
|
||||||
|
public string effectiveDomainController;
|
||||||
public string username;
|
public string username;
|
||||||
public SecureString password;
|
public SecureString password;
|
||||||
public bool ForceStrictAdGroupNames;
|
public bool ForceStrictAdGroupNames;
|
||||||
@@ -32,6 +33,11 @@ namespace C4IT_IAM_Engine
|
|||||||
{
|
{
|
||||||
IAM_SecurityGroups = new List<IAM_SecurityGroup>();
|
IAM_SecurityGroups = new List<IAM_SecurityGroup>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetLdapServer()
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(effectiveDomainController) ? domainName : effectiveDomainController;
|
||||||
|
}
|
||||||
public bool GroupsAllreadyExisting(string ouPath)
|
public bool GroupsAllreadyExisting(string ouPath)
|
||||||
{
|
{
|
||||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||||
@@ -47,7 +53,7 @@ namespace C4IT_IAM_Engine
|
|||||||
{
|
{
|
||||||
DirectoryEntry entry = new DirectoryEntry
|
DirectoryEntry entry = new DirectoryEntry
|
||||||
{
|
{
|
||||||
Path = "LDAP://" + domainName,
|
Path = "LDAP://" + GetLdapServer(),
|
||||||
Username = username,
|
Username = username,
|
||||||
Password = new NetworkCredential("", password).Password,
|
Password = new NetworkCredential("", password).Password,
|
||||||
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
||||||
@@ -86,7 +92,7 @@ namespace C4IT_IAM_Engine
|
|||||||
{
|
{
|
||||||
DirectoryEntry entry = new DirectoryEntry
|
DirectoryEntry entry = new DirectoryEntry
|
||||||
{
|
{
|
||||||
Path = "LDAP://" + domainName,
|
Path = "LDAP://" + GetLdapServer(),
|
||||||
Username = username,
|
Username = username,
|
||||||
Password = new NetworkCredential("", password).Password,
|
Password = new NetworkCredential("", password).Password,
|
||||||
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
||||||
@@ -431,7 +437,7 @@ namespace C4IT_IAM_Engine
|
|||||||
{
|
{
|
||||||
DirectoryEntry entry = new DirectoryEntry
|
DirectoryEntry entry = new DirectoryEntry
|
||||||
{
|
{
|
||||||
Path = "LDAP://" + domainName,
|
Path = "LDAP://" + GetLdapServer(),
|
||||||
Username = username,
|
Username = username,
|
||||||
Password = new NetworkCredential("", password).Password,
|
Password = new NetworkCredential("", password).Password,
|
||||||
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
||||||
@@ -473,7 +479,7 @@ namespace C4IT_IAM_Engine
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var basePath = "LDAP://" + domainName;
|
var basePath = "LDAP://" + GetLdapServer();
|
||||||
if (!string.IsNullOrWhiteSpace(ouPath))
|
if (!string.IsNullOrWhiteSpace(ouPath))
|
||||||
basePath += "/" + ouPath;
|
basePath += "/" + ouPath;
|
||||||
|
|
||||||
@@ -528,7 +534,7 @@ namespace C4IT_IAM_Engine
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Reusing existing AD group '{matchedName}' via wildcard '{wildcardPattern}'.");
|
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)
|
private DirectoryEntry FindGroupEntryFromFolderAcl(string folderPath, string wildcardPattern)
|
||||||
@@ -555,7 +561,7 @@ namespace C4IT_IAM_Engine
|
|||||||
.Cast<FileSystemAccessRule>();
|
.Cast<FileSystemAccessRule>();
|
||||||
var matchedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
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)
|
foreach (var rule in rules)
|
||||||
{
|
{
|
||||||
@@ -735,7 +741,7 @@ namespace C4IT_IAM_Engine
|
|||||||
if (!GroupAllreadyExisting(groupName))
|
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}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Creating ad entry with CN / sAmAccountName: {groupName}");
|
||||||
DirectoryEntry group = entry.Children.Add("CN=" + groupName, "group");
|
DirectoryEntry group = entry.Children.Add("CN=" + groupName, "group");
|
||||||
group.Properties["sAmAccountName"].Value = groupName;
|
group.Properties["sAmAccountName"].Value = groupName;
|
||||||
@@ -763,7 +769,7 @@ namespace C4IT_IAM_Engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
group.CommitChanges();
|
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);
|
var objectid = SecurityGroups.getSID(ent);
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}");
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -24,6 +25,7 @@ namespace LiamNtfs
|
|||||||
private cNtfsLogonInfo privLogonInfo = null;
|
private cNtfsLogonInfo 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;
|
||||||
|
|
||||||
@@ -49,8 +51,12 @@ namespace LiamNtfs
|
|||||||
//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($"NTFS AD 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,
|
||||||
@@ -69,6 +75,70 @@ namespace LiamNtfs
|
|||||||
return false;
|
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()
|
private async Task<bool> privRelogon()
|
||||||
{
|
{
|
||||||
if (privLogonInfo == null)
|
if (privLogonInfo == null)
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ namespace LiamNtfs
|
|||||||
public class cNtfsLogonInfo
|
public class cNtfsLogonInfo
|
||||||
{
|
{
|
||||||
public string Domain;
|
public string Domain;
|
||||||
|
public string DomainControllers;
|
||||||
public string User;
|
public string User;
|
||||||
public string UserSecret;
|
public string UserSecret;
|
||||||
public string TargetNetworkName;
|
public string TargetNetworkName;
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ setlocal EnableDelayedExpansion
|
|||||||
set "ProductName=C4IT Light Identity Access Management"
|
set "ProductName=C4IT Light Identity Access Management"
|
||||||
set "SignTool=..\..\Common Code\Tools\signtool.exe"
|
set "SignTool=..\..\Common Code\Tools\signtool.exe"
|
||||||
set "TimeStamp=http://rfc3161timestamp.globalsign.com/advanced"
|
set "TimeStamp=http://rfc3161timestamp.globalsign.com/advanced"
|
||||||
|
set "SignCertificateSubject=Consulting4IT GmbH"
|
||||||
|
set "SignCertificateEmail=info@consulting4it.de"
|
||||||
|
|
||||||
|
if defined LIAM_SIGN_CERT_SUBJECT (
|
||||||
|
set "SignCertificateSubject=%LIAM_SIGN_CERT_SUBJECT%"
|
||||||
|
)
|
||||||
|
|
||||||
REM Alle passenden Dateien in einer Variablen sammeln
|
REM Alle passenden Dateien in einer Variablen sammeln
|
||||||
set "FileList="
|
set "FileList="
|
||||||
@@ -13,6 +19,11 @@ for %%F in (".\bin\Release\Liam*.dll") do (
|
|||||||
|
|
||||||
REM SignTool mit allen gesammelten Dateien aufrufen
|
REM SignTool mit allen gesammelten Dateien aufrufen
|
||||||
echo Signing all matching files at once...
|
echo Signing all matching files at once...
|
||||||
call "%SignTool%" sign /a /tr %TimeStamp% /td SHA256 /fd SHA256 /d "%ProductName%" !FileList!
|
echo Expected signer: %SignCertificateSubject% ^<%SignCertificateEmail%^>
|
||||||
|
if defined LIAM_SIGN_CERT_THUMBPRINT (
|
||||||
|
call "%SignTool%" sign /sha1 "%LIAM_SIGN_CERT_THUMBPRINT%" /tr %TimeStamp% /td SHA256 /fd SHA256 /d "%ProductName%" !FileList!
|
||||||
|
) else (
|
||||||
|
call "%SignTool%" sign /n "%SignCertificateSubject%" /tr %TimeStamp% /td SHA256 /fd SHA256 /d "%ProductName%" !FileList!
|
||||||
|
)
|
||||||
|
|
||||||
pause
|
pause
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ setlocal EnableDelayedExpansion
|
|||||||
set "ProductName=C4IT Light Identity Access Management"
|
set "ProductName=C4IT Light Identity Access Management"
|
||||||
set "SignTool=..\..\Common Code\Tools\signtool.exe"
|
set "SignTool=..\..\Common Code\Tools\signtool.exe"
|
||||||
set "TimeStamp=http://rfc3161timestamp.globalsign.com/advanced"
|
set "TimeStamp=http://rfc3161timestamp.globalsign.com/advanced"
|
||||||
|
set "SignCertificateSubject=Consulting4IT GmbH"
|
||||||
|
set "SignCertificateEmail=info@consulting4it.de"
|
||||||
|
|
||||||
|
if defined LIAM_SIGN_CERT_SUBJECT (
|
||||||
|
set "SignCertificateSubject=%LIAM_SIGN_CERT_SUBJECT%"
|
||||||
|
)
|
||||||
|
|
||||||
set "FileList="
|
set "FileList="
|
||||||
|
|
||||||
@@ -24,6 +30,11 @@ if not defined FileList (
|
|||||||
)
|
)
|
||||||
|
|
||||||
echo Signing all matching files at once...
|
echo Signing all matching files at once...
|
||||||
call "%SignTool%" sign /a /tr %TimeStamp% /td SHA256 /fd SHA256 /d "%ProductName%" !FileList!
|
echo Expected signer: %SignCertificateSubject% ^<%SignCertificateEmail%^>
|
||||||
|
if defined LIAM_SIGN_CERT_THUMBPRINT (
|
||||||
|
call "%SignTool%" sign /sha1 "%LIAM_SIGN_CERT_THUMBPRINT%" /tr %TimeStamp% /td SHA256 /fd SHA256 /d "%ProductName%" !FileList!
|
||||||
|
) else (
|
||||||
|
call "%SignTool%" sign /n "%SignCertificateSubject%" /tr %TimeStamp% /td SHA256 /fd SHA256 /d "%ProductName%" !FileList!
|
||||||
|
)
|
||||||
|
|
||||||
pause
|
pause
|
||||||
|
|||||||
73
README.md
Normal file
73
README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# LIAM
|
||||||
|
|
||||||
|
## AdditionalParameters / AdditionalConfiguration
|
||||||
|
|
||||||
|
Provider-spezifische Zusatzparameter werden in Matrix42 am DataArea-Collector ueber das Fragment `C4IT_GCC_DataArea_Collector_AdditionalAttributes` gepflegt. Pro Parameter wird ein Eintrag mit `Name` und `Value` angelegt.
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
| Name | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| `EnsureNtfsPermissionGroups` | `1` |
|
||||||
|
| `NtfsAdDomainControllers` | `dc01.contoso.local,dc02.contoso.local` |
|
||||||
|
|
||||||
|
Im Diagnose-JSON erscheinen diese Werte unter `AdditionalConfiguration`. Parameternamen werden ohne Beachtung der Gross-/Kleinschreibung gelesen. Boolean-Werte sind in der Regel aktiv, wenn der Wert `true`, `1` oder `yes` ist. Leere oder nicht vorhandene Werte deaktivieren den jeweiligen Schalter, sofern unten nichts anderes beschrieben ist.
|
||||||
|
|
||||||
|
### Allgemein
|
||||||
|
|
||||||
|
| Parameter | Provider | Werte | Wirkung |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `WhatIf` | Workflow / alle Provider, soweit unterstuetzt | `true`, `1`, `yes` | Aktiviert den Simulationsmodus fuer Workflow-Pfade, die WhatIf unterstuetzen. Aktionen werden dann vorbereitet und protokolliert, aber nicht dauerhaft ausgefuehrt. |
|
||||||
|
|
||||||
|
### NTFS
|
||||||
|
|
||||||
|
| Parameter | Werte | Wirkung |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `EnsureNtfsPermissionGroups` | `true`, `1`, `yes` | Stellt beim Auslesen von NTFS-Ordnern automatisch fehlende AD-Berechtigungsgruppen und NTFS-ACLs sicher. |
|
||||||
|
| `EnsureNtfsPermissionGroupsForShares` | `true`, `1`, `yes` | Erweitert das automatische Ensure auf Share-DataAreas. Ohne diesen Parameter wird das automatische Ensure nur fuer Ordner ausgefuehrt. |
|
||||||
|
| `AllowManualNtfsPermissionEnsureForShares` | `true`, `1`, `yes` | Erlaubt die manuelle Ensure-Aktivitaet auch fuer Share-DataAreas. |
|
||||||
|
| `NtfsIncludePaths` | Pfadliste, getrennt mit `;`, | oder Zeilenumbruechen | Beschraenkt die NTFS-Verarbeitung auf passende Pfade. Unterstuetzt relative Pfade unterhalb des RootPath, absolute UNC-Pfade und einfache Wildcards mit `*`. Wenn der Parameter leer ist, sind alle Pfade eingeschlossen. |
|
||||||
|
| `NtfsExcludePaths` | Pfadliste, getrennt mit `;`, | oder Zeilenumbruechen | Schliesst passende Pfade von der NTFS-Verarbeitung aus. Excludes gewinnen gegen Includes. Unterstuetzt relative Pfade, absolute UNC-Pfade und einfache Wildcards mit `*`. |
|
||||||
|
| `NtfsTraverseBoundaryPath` | Relativer oder absoluter Pfad | Setzt eine Traverse-Grenze fuer die Traverse-Gruppenverarbeitung. Damit koennen Traverse-Gruppen ueber den eigentlichen Einsprung hinaus bis zu einer definierten Ebene sichergestellt werden. |
|
||||||
|
| `NtfsGroupNameSanitizeReplacement` | Zeichenfolge, z. B. `_`, `.`, leer, `none`, `remove`, `<empty>` | Legt fest, womit ungueltige Zeichen in dynamischen gruppennamenrelevanten Pfadbestandteilen ersetzt werden. Standard ist `_`. Mit leerem Wert oder `none`/`remove`/`<empty>` werden ungueltige Zeichen entfernt und Pfadsegmente ohne Trennzeichen verbunden. |
|
||||||
|
| `PreserveNtfsAdGroupNameCase` | `true`, `1`, `yes` | Unterbindet das automatische Uppercase fuer generierte NTFS-AD-Gruppennamen. Ohne diesen Parameter werden generierte Gruppennamen wie bisher in Grossbuchstaben erzeugt. |
|
||||||
|
| `ForceStrictAdGroupNames` | `true`, `1`, `yes` | Erzwingt strikte AD-Gruppennamen. Wildcard-/ACL-basierte Wiederverwendung abweichender bestehender Gruppen wird damit eingeschraenkt; es werden nur exakt passende konfigurierte oder generierte Namen verwendet. |
|
||||||
|
| `NtfsAdDomainControllers` | Kommagetrennte DC-Liste, z. B. `dc01.contoso.local,dc02.contoso.local` | Pinnt NTFS-AD-Operationen auf einen Domain Controller. Der erste erreichbare DC wird verwendet. Wenn kein Eintrag erreichbar ist oder der Parameter fehlt, wird der PDC Emulator verwendet; danach faellt der Code auf die normale Domain-Locator-Logik zurueck. Der ausgewaehlte DC wird im Debug-Log protokolliert. |
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
|
||||||
|
```text
|
||||||
|
EnsureNtfsPermissionGroups=1
|
||||||
|
EnsureNtfsPermissionGroupsForShares=1
|
||||||
|
NtfsIncludePaths=Finance\*;HR\Reports
|
||||||
|
NtfsExcludePaths=*\_archive\*
|
||||||
|
NtfsTraverseBoundaryPath=\\fileserver\file_shares
|
||||||
|
NtfsGroupNameSanitizeReplacement=.
|
||||||
|
PreserveNtfsAdGroupNameCase=1
|
||||||
|
NtfsAdDomainControllers=dc01.contoso.local,dc02.contoso.local
|
||||||
|
```
|
||||||
|
|
||||||
|
### Active Directory
|
||||||
|
|
||||||
|
| Parameter | Werte | Wirkung |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `AdDomainControllers` | Kommagetrennte DC-Liste, z. B. `dc01.contoso.local,dc02.contoso.local` | Pinnt Active-Directory-Provider-Operationen auf einen Domain Controller. Der erste erreichbare DC wird verwendet. Wenn kein Eintrag erreichbar ist oder der Parameter fehlt, wird der PDC Emulator verwendet; danach faellt der Code auf die normale Domain-Locator-Logik zurueck. Der ausgewaehlte DC wird im Debug-Log protokolliert. |
|
||||||
|
| `ActiveDirectoryDomainControllers` | Kommagetrennte DC-Liste | Alias/Fallback fuer `AdDomainControllers`, wenn `AdDomainControllers` nicht gesetzt ist. |
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AdDomainControllers=dc01.contoso.local,dc02.contoso.local
|
||||||
|
```
|
||||||
|
|
||||||
|
### Microsoft Teams
|
||||||
|
|
||||||
|
| Parameter | Werte | Wirkung |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `WithoutPrivateChannels` | `true`, `1` | Private Channels werden beim Teams-Provider nicht beruecksichtigt. |
|
||||||
|
|
||||||
|
### Hinweise zur Pfad- und Listen-Syntax
|
||||||
|
|
||||||
|
`NtfsIncludePaths` und `NtfsExcludePaths` verwenden `;`, | oder Zeilenumbrueche als Trenner. Kommas sind dort Teil des Werts. Die Domain-Controller-Parameter verwenden dagegen eine kommagetrennte Prioritaetsliste.
|
||||||
|
|
||||||
|
Pfadfilter koennen relativ zum konfigurierten NTFS-RootPath oder als absolute UNC-Pfade angegeben werden. Einfache Wildcards mit `*` sind moeglich.
|
||||||
@@ -269,6 +269,32 @@ ACL_share2.test33_O
|
|||||||
|
|
||||||
`PreserveNtfsAdGroupNameCase=1` unterbindet die bisher automatische Grossschreibung der erzeugten AD-Gruppennamen. Ohne diesen Schalter bleibt das bisherige Verhalten erhalten und die generierten CN-/sAMAccountName-Werte werden in Grossbuchstaben erzeugt.
|
`PreserveNtfsAdGroupNameCase=1` unterbindet die bisher automatische Grossschreibung der erzeugten AD-Gruppennamen. Ohne diesen Schalter bleibt das bisherige Verhalten erhalten und die generierten CN-/sAMAccountName-Werte werden in Grossbuchstaben erzeugt.
|
||||||
|
|
||||||
|
### 12. Domain-Controller-Pinning fuer direkte AD-Readbacks
|
||||||
|
|
||||||
|
Der NTFS-Provider pinnt seine AD-Zugriffe auf einen konkreten Domain Controller, damit neu angelegte Gruppen unmittelbar im gleichen oder in einem direkt folgenden Workflow-Lauf wieder gefunden werden koennen. Ohne Pinning kann Windows bei getrennten WF-Instanzen unterschiedliche DCs auswaehlen; bei langen AD-Replikationsintervallen waeren frisch angelegte Gruppen dann auf dem zweiten DC noch nicht sichtbar.
|
||||||
|
|
||||||
|
Standardverhalten ohne Konfiguration:
|
||||||
|
|
||||||
|
- der Provider ermittelt automatisch den PDC Emulator der Domain
|
||||||
|
- alle NTFS-AD-Zugriffe innerhalb des Provider-Laufs verwenden diesen DC
|
||||||
|
- der verwendete DC wird einmal beim AD-Logon im Debug-Log ausgegeben
|
||||||
|
|
||||||
|
Optional kann ueber `AdditionalConfiguration` eine priorisierte, kommagetrennte DC-Liste gesetzt werden:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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
|
## Matching-Regeln
|
||||||
|
|
||||||
Empfohlene Semantik:
|
Empfohlene Semantik:
|
||||||
|
|||||||
Reference in New Issue
Block a user