Allow regex reuse for NTFS ensure groups

This commit is contained in:
Meik
2026-03-13 14:36:23 +01:00
parent d28cfe008c
commit 32021dcfd8
3 changed files with 123 additions and 8 deletions

View File

@@ -563,7 +563,8 @@ namespace C4IT.LIAM
groupReadTag = GetRequiredCustomTag("Filesystem_GroupReadTag"), groupReadTag = GetRequiredCustomTag("Filesystem_GroupReadTag"),
groupTraverseTag = GetRequiredCustomTag("Filesystem_GroupTraverseTag"), groupTraverseTag = GetRequiredCustomTag("Filesystem_GroupTraverseTag"),
groupDLTag = requiresDomainLocalTag ? GetRequiredCustomTag("Filesystem_GroupDomainLocalTag") : string.Empty, groupDLTag = requiresDomainLocalTag ? GetRequiredCustomTag("Filesystem_GroupDomainLocalTag") : string.Empty,
groupGTag = GetRequiredCustomTag("Filesystem_GroupGlobalTag") groupGTag = GetRequiredCustomTag("Filesystem_GroupGlobalTag"),
allowExistingGroupWildcardMatch = IsAdditionalConfigurationEnabled("EnsureNtfsPermissionGroupsAllowRegexMatch")
}; };
foreach (var template in BuildSecurityGroupTemplates()) foreach (var template in BuildSecurityGroupTemplates())
@@ -572,6 +573,19 @@ namespace C4IT.LIAM
return engine; return engine;
} }
private bool IsAdditionalConfigurationEnabled(string key)
{
if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
return false;
if (!AdditionalConfiguration.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue))
return false;
return rawValue.Equals("true", StringComparison.OrdinalIgnoreCase)
|| rawValue.Equals("1", StringComparison.OrdinalIgnoreCase)
|| rawValue.Equals("yes", StringComparison.OrdinalIgnoreCase);
}
private IEnumerable<IAM_SecurityGroupTemplate> BuildSecurityGroupTemplates() private IEnumerable<IAM_SecurityGroupTemplate> BuildSecurityGroupTemplates()
{ {
var templates = new List<IAM_SecurityGroupTemplate>(); var templates = new List<IAM_SecurityGroupTemplate>();

View File

@@ -51,6 +51,7 @@ namespace C4IT_IAM_SET
public ICollection<string> ownerUserSids; public ICollection<string> ownerUserSids;
public ICollection<string> readerUserSids; public ICollection<string> readerUserSids;
public ICollection<string> writerUserSids; public ICollection<string> writerUserSids;
public bool allowExistingGroupWildcardMatch;
public int ReadACLPermission = 0x200A9; public int ReadACLPermission = 0x200A9;
public int WriteACLPermission = 0x301BF; public int WriteACLPermission = 0x301BF;
@@ -144,6 +145,7 @@ namespace C4IT_IAM_SET
newSecurityGroups.username = username; newSecurityGroups.username = username;
newSecurityGroups.domainName = domainName; newSecurityGroups.domainName = domainName;
newSecurityGroups.password = password; newSecurityGroups.password = password;
newSecurityGroups.AllowExistingGroupWildcardMatch = allowExistingGroupWildcardMatch;
try try
{ {
// ImpersonationHelper.Impersonate(domainName, username, new NetworkCredential("", password).Password, delegate // ImpersonationHelper.Impersonate(domainName, username, new NetworkCredential("", password).Password, delegate
@@ -274,7 +276,8 @@ namespace C4IT_IAM_SET
{ {
username = username, username = username,
domainName = domainName, domainName = domainName,
password = password password = password,
AllowExistingGroupWildcardMatch = allowExistingGroupWildcardMatch
}; };
} }
@@ -909,9 +912,8 @@ namespace C4IT_IAM_SET
else else
users = null; users = null;
var groupAlreadyExists = newSecurityGroups.GroupAllreadyExisting(newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper());
newSecurityGroups.EnsureADGroup(groupOUPath, newSecurityGroups.IAM_SecurityGroups[i], users); newSecurityGroups.EnsureADGroup(groupOUPath, newSecurityGroups.IAM_SecurityGroups[i], users);
if (groupAlreadyExists) if (newSecurityGroups.IAM_SecurityGroups[i].ReusedExistingEntry)
resultToken.reusedGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name); resultToken.reusedGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name);
else else
resultToken.createdGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name); resultToken.createdGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name);

View File

@@ -23,6 +23,7 @@ namespace C4IT_IAM_Engine
public string domainName; public string domainName;
public string username; public string username;
public SecureString password; public SecureString password;
public bool AllowExistingGroupWildcardMatch;
public List<IAM_SecurityGroup> IAM_SecurityGroups; public List<IAM_SecurityGroup> IAM_SecurityGroups;
public string rootUID; public string rootUID;
@@ -208,6 +209,7 @@ namespace C4IT_IAM_Engine
{ {
Name = ownerGlobal.NamingTemplate, Name = ownerGlobal.NamingTemplate,
description = ownerGlobal.DescriptionTemplate, description = ownerGlobal.DescriptionTemplate,
WildcardPattern = ownerGlobal.WildcardTemplate,
technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath, technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath,
targetTyp = (int)IAM_TargetType.FileSystem, targetTyp = (int)IAM_TargetType.FileSystem,
@@ -221,6 +223,7 @@ namespace C4IT_IAM_Engine
{ {
Name = writeGlobal.NamingTemplate, Name = writeGlobal.NamingTemplate,
description = writeGlobal.DescriptionTemplate, description = writeGlobal.DescriptionTemplate,
WildcardPattern = writeGlobal.WildcardTemplate,
technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath, technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath,
targetTyp = (int)IAM_TargetType.FileSystem, targetTyp = (int)IAM_TargetType.FileSystem,
@@ -234,6 +237,7 @@ namespace C4IT_IAM_Engine
{ {
Name = readGlobal.NamingTemplate, Name = readGlobal.NamingTemplate,
description = readGlobal.DescriptionTemplate, description = readGlobal.DescriptionTemplate,
WildcardPattern = readGlobal.WildcardTemplate,
technicalName = "CN=" + readGlobal.NamingTemplate + "," + ouPath, technicalName = "CN=" + readGlobal.NamingTemplate + "," + ouPath,
targetTyp = (int)IAM_TargetType.FileSystem, targetTyp = (int)IAM_TargetType.FileSystem,
@@ -251,6 +255,7 @@ namespace C4IT_IAM_Engine
{ {
Name = ownerDL.NamingTemplate, Name = ownerDL.NamingTemplate,
description = ownerDL.DescriptionTemplate, description = ownerDL.DescriptionTemplate,
WildcardPattern = ownerDL.WildcardTemplate,
technicalName = "CN=" + ownerDL.NamingTemplate + "," + ouPath, technicalName = "CN=" + ownerDL.NamingTemplate + "," + ouPath,
targetTyp = (int)IAM_TargetType.FileSystem, targetTyp = (int)IAM_TargetType.FileSystem,
@@ -265,6 +270,7 @@ namespace C4IT_IAM_Engine
{ {
Name = writeDL.NamingTemplate, Name = writeDL.NamingTemplate,
description = writeDL.DescriptionTemplate, description = writeDL.DescriptionTemplate,
WildcardPattern = writeDL.WildcardTemplate,
technicalName = "CN=" + writeDL.NamingTemplate + "," + ouPath, technicalName = "CN=" + writeDL.NamingTemplate + "," + ouPath,
targetTyp = (int)IAM_TargetType.FileSystem, targetTyp = (int)IAM_TargetType.FileSystem,
@@ -279,6 +285,7 @@ namespace C4IT_IAM_Engine
{ {
Name = readDL.NamingTemplate, Name = readDL.NamingTemplate,
description = readDL.DescriptionTemplate, description = readDL.DescriptionTemplate,
WildcardPattern = readDL.WildcardTemplate,
technicalName = "CN=" + readDL.NamingTemplate + "," + ouPath, technicalName = "CN=" + readDL.NamingTemplate + "," + ouPath,
targetTyp = (int)IAM_TargetType.FileSystem, targetTyp = (int)IAM_TargetType.FileSystem,
@@ -293,6 +300,7 @@ namespace C4IT_IAM_Engine
secGroup.description = secGroup.description.ReplaceLoopTag(0); secGroup.description = secGroup.description.ReplaceLoopTag(0);
secGroup.Name = secGroup.Name.ReplaceLoopTag(loop); secGroup.Name = secGroup.Name.ReplaceLoopTag(loop);
secGroup.technicalName = secGroup.technicalName.ReplaceLoopTag(loop); secGroup.technicalName = secGroup.technicalName.ReplaceLoopTag(loop);
secGroup.WildcardPattern = secGroup.WildcardPattern.ReplaceLoopTag(loop);
DefaultLogger.LogEntry(LogLevels.Debug, $"Security group generated: {secGroup.technicalName}"); DefaultLogger.LogEntry(LogLevels.Debug, $"Security group generated: {secGroup.technicalName}");
} }
} }
@@ -398,6 +406,92 @@ namespace C4IT_IAM_Engine
return groupEntry; return groupEntry;
} }
private DirectoryEntry FindGroupEntryByWildcard(string ouPath, string wildcardPattern)
{
if (string.IsNullOrWhiteSpace(wildcardPattern))
return null;
Regex wildcardRegex;
try
{
wildcardRegex = new Regex(wildcardPattern, RegexOptions.IgnoreCase);
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
return null;
}
var basePath = "LDAP://" + domainName;
if (!string.IsNullOrWhiteSpace(ouPath))
basePath += "/" + ouPath;
DirectoryEntry entry = new DirectoryEntry
{
Path = basePath,
Username = username,
Password = new NetworkCredential("", password).Password,
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
};
DirectorySearcher search = new DirectorySearcher(entry)
{
Filter = "(objectClass=group)"
};
search.PageSize = 100000;
search.PropertiesToLoad.Add("sAMAccountName");
search.PropertiesToLoad.Add("distinguishedName");
string matchedName = null;
string matchedDistinguishedName = null;
var matchCount = 0;
foreach (SearchResult result in search.FindAll())
{
if (!result.Properties.Contains("sAMAccountName") || result.Properties["sAMAccountName"].Count == 0)
continue;
var samAccountName = result.Properties["sAMAccountName"][0]?.ToString();
if (string.IsNullOrWhiteSpace(samAccountName) || !wildcardRegex.IsMatch(samAccountName))
continue;
matchCount++;
if (matchCount > 1)
{
DefaultLogger.LogEntry(LogLevels.Warning, $"Multiple AD groups matched wildcard '{wildcardPattern}' in '{basePath}'. Regex-based reuse is skipped.");
search.Dispose();
entry.Dispose();
return null;
}
matchedName = samAccountName;
matchedDistinguishedName = result.Properties.Contains("distinguishedName") && result.Properties["distinguishedName"].Count > 0
? result.Properties["distinguishedName"][0]?.ToString()
: null;
}
search.Dispose();
entry.Dispose();
if (string.IsNullOrWhiteSpace(matchedDistinguishedName))
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);
}
private void ApplyExistingGroup(IAM_SecurityGroup secGroup, DirectoryEntry existingGroup)
{
secGroup.ReusedExistingEntry = true;
secGroup.UID = getSID(existingGroup);
if (existingGroup.Properties.Contains("sAMAccountName") && existingGroup.Properties["sAMAccountName"].Count > 0)
secGroup.Name = existingGroup.Properties["sAMAccountName"][0]?.ToString();
if (existingGroup.Properties.Contains("distinguishedName") && existingGroup.Properties["distinguishedName"].Count > 0)
secGroup.technicalName = existingGroup.Properties["distinguishedName"][0]?.ToString();
}
private static bool HasMember(PropertyValueCollection members, string distinguishedName) private static bool HasMember(PropertyValueCollection members, string distinguishedName)
{ {
foreach (var member in members) foreach (var member in members)
@@ -450,13 +544,16 @@ namespace C4IT_IAM_Engine
LogMethodBegin(MethodBase.GetCurrentMethod()); LogMethodBegin(MethodBase.GetCurrentMethod());
try try
{ {
secGroup.ReusedExistingEntry = false;
var existingGroup = FindGroupEntry(secGroup.Name); var existingGroup = FindGroupEntry(secGroup.Name);
if (existingGroup == null && AllowExistingGroupWildcardMatch)
existingGroup = FindGroupEntryByWildcard(ouPath, secGroup.WildcardPattern);
if (existingGroup == null) if (existingGroup == null)
return CreateADGroup(ouPath, secGroup, users); return CreateADGroup(ouPath, secGroup, users);
AddMissingMembers(existingGroup, secGroup, users); AddMissingMembers(existingGroup, secGroup, users);
var objectid = getSID(existingGroup); ApplyExistingGroup(secGroup, existingGroup);
secGroup.UID = objectid;
return existingGroup; return existingGroup;
} }
catch (Exception E) catch (Exception E)
@@ -475,6 +572,7 @@ namespace C4IT_IAM_Engine
LogMethodBegin(MethodBase.GetCurrentMethod()); LogMethodBegin(MethodBase.GetCurrentMethod());
try try
{ {
secGroup.ReusedExistingEntry = false;
if (!GroupAllreadyExisting(secGroup.Name.ToUpper())) if (!GroupAllreadyExisting(secGroup.Name.ToUpper()))
{ {
@@ -518,9 +616,8 @@ namespace C4IT_IAM_Engine
DirectoryEntry e = FindGroupEntry(secGroup.Name); DirectoryEntry e = FindGroupEntry(secGroup.Name);
if (e == null) if (e == null)
return null; return null;
var objectid = getSID(e);
secGroup.UID = objectid;
AddMissingMembers(e, secGroup, users); AddMissingMembers(e, secGroup, users);
ApplyExistingGroup(secGroup, e);
return e; return e;
} }
return null; return null;
@@ -588,6 +685,8 @@ namespace C4IT_IAM_Engine
public string UID; public string UID;
public string Parent = ""; public string Parent = "";
public string description; public string description;
public string WildcardPattern;
public bool ReusedExistingEntry;
public List<IAM_SecurityGroup> memberGroups; public List<IAM_SecurityGroup> memberGroups;
public string Name; public string Name;
public string technicalName; public string technicalName;