Safeguard NTFS AD group reuse
This commit is contained in:
@@ -144,6 +144,8 @@ namespace C4IT.LIAM
|
|||||||
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";
|
private const string AdditionalConfigurationAdDomainControllersKey = "NtfsAdDomainControllers";
|
||||||
|
private const string AdditionalConfigurationAdGroupReuseModeKey = "NtfsAdGroupReuseMode";
|
||||||
|
private const string AdditionalConfigurationAdGroupMarkerBackfillKey = "NtfsAdGroupMarkerBackfill";
|
||||||
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);
|
||||||
@@ -1394,6 +1396,8 @@ namespace C4IT.LIAM
|
|||||||
engine.CanManagePermissionsForPath = IsPermissionManagedFolderPath;
|
engine.CanManagePermissionsForPath = IsPermissionManagedFolderPath;
|
||||||
engine.CanManageTraversePermissionsForPath = IsTraversePermissionManagedPath;
|
engine.CanManageTraversePermissionsForPath = IsTraversePermissionManagedPath;
|
||||||
engine.forceStrictAdGroupNames = IsAdditionalConfigurationEnabled("ForceStrictAdGroupNames");
|
engine.forceStrictAdGroupNames = IsAdditionalConfigurationEnabled("ForceStrictAdGroupNames");
|
||||||
|
engine.adGroupReuseMode = GetAdGroupReuseMode();
|
||||||
|
engine.adGroupMarkerBackfill = IsAdditionalConfigurationEnabled(AdditionalConfigurationAdGroupMarkerBackfillKey);
|
||||||
engine.groupNameSanitizeReplacement = GetAdditionalConfigurationValueOrDefault(
|
engine.groupNameSanitizeReplacement = GetAdditionalConfigurationValueOrDefault(
|
||||||
AdditionalConfigurationGroupNameSanitizeReplacementKey,
|
AdditionalConfigurationGroupNameSanitizeReplacementKey,
|
||||||
Helper.DefaultGroupNameSanitizeReplacement);
|
Helper.DefaultGroupNameSanitizeReplacement);
|
||||||
@@ -1458,6 +1462,25 @@ namespace C4IT.LIAM
|
|||||||
return rawValue == null ? string.Empty : rawValue.Trim();
|
return rawValue == null ? string.Empty : rawValue.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SecurityGroupReuseMode GetAdGroupReuseMode()
|
||||||
|
{
|
||||||
|
var value = GetAdditionalConfigurationValue(AdditionalConfigurationAdGroupReuseModeKey);
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return SecurityGroupReuseMode.Safe;
|
||||||
|
|
||||||
|
if (value.Equals("Safe", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return SecurityGroupReuseMode.Safe;
|
||||||
|
|
||||||
|
if (value.Equals("Name", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
LogEntry("NtfsAdGroupReuseMode=Name is active. Existing AD groups may be reused by name without ACL/marker validation.", LogLevels.Warning);
|
||||||
|
return SecurityGroupReuseMode.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEntry($"AdditionalConfiguration '{AdditionalConfigurationAdGroupReuseModeKey}' has invalid value '{value}'. Defaulting to Safe.", LogLevels.Warning);
|
||||||
|
return SecurityGroupReuseMode.Safe;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetEffectiveTraverseBoundaryPath()
|
private string GetEffectiveTraverseBoundaryPath()
|
||||||
{
|
{
|
||||||
var configuredBoundaryPath = GetAdditionalConfigurationValue(AdditionalConfigurationTraverseBoundaryPathKey);
|
var configuredBoundaryPath = GetAdditionalConfigurationValue(AdditionalConfigurationTraverseBoundaryPathKey);
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ namespace C4IT_IAM_SET
|
|||||||
public Func<string, bool> CanManageTraversePermissionsForPath;
|
public Func<string, bool> CanManageTraversePermissionsForPath;
|
||||||
public string traverseBoundaryPath;
|
public string traverseBoundaryPath;
|
||||||
public bool forceStrictAdGroupNames;
|
public bool forceStrictAdGroupNames;
|
||||||
|
public SecurityGroupReuseMode adGroupReuseMode = SecurityGroupReuseMode.Safe;
|
||||||
|
public bool adGroupMarkerBackfill;
|
||||||
public string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement;
|
public string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement;
|
||||||
public bool preserveAdGroupNameCase;
|
public bool preserveAdGroupNameCase;
|
||||||
public bool WhatIf;
|
public bool WhatIf;
|
||||||
@@ -323,7 +325,9 @@ namespace C4IT_IAM_SET
|
|||||||
effectiveDomainController = effectiveDomainController,
|
effectiveDomainController = effectiveDomainController,
|
||||||
password = password,
|
password = password,
|
||||||
ForceStrictAdGroupNames = forceStrictAdGroupNames,
|
ForceStrictAdGroupNames = forceStrictAdGroupNames,
|
||||||
PreserveAdGroupNameCase = preserveAdGroupNameCase
|
PreserveAdGroupNameCase = preserveAdGroupNameCase,
|
||||||
|
ReuseMode = adGroupReuseMode,
|
||||||
|
MarkerBackfill = adGroupMarkerBackfill
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,7 +938,9 @@ namespace C4IT_IAM_SET
|
|||||||
Name = traverseNameTemplate.ReplaceLoopTag(loop),
|
Name = traverseNameTemplate.ReplaceLoopTag(loop),
|
||||||
description = traverseDescriptionTemplate.ReplaceLoopTag(loop),
|
description = traverseDescriptionTemplate.ReplaceLoopTag(loop),
|
||||||
technicalName = "CN=" + traverseNameTemplate.ReplaceLoopTag(loop) + "," + groupOUPath,
|
technicalName = "CN=" + traverseNameTemplate.ReplaceLoopTag(loop) + "," + groupOUPath,
|
||||||
Scope = traverseGroupTemplate.Scope
|
securityGroupType = SecurityGroupType.Traverse,
|
||||||
|
Scope = traverseGroupTemplate.Scope,
|
||||||
|
MarkerPath = parent.FullName
|
||||||
};
|
};
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Erstellte TraverseGroup: {newTraverseGroup.Name} (Loop: {loop})");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Erstellte TraverseGroup: {newTraverseGroup.Name} (Loop: {loop})");
|
||||||
loop++;
|
loop++;
|
||||||
|
|||||||
@@ -26,9 +26,13 @@ namespace C4IT_IAM_Engine
|
|||||||
public SecureString password;
|
public SecureString password;
|
||||||
public bool ForceStrictAdGroupNames;
|
public bool ForceStrictAdGroupNames;
|
||||||
public bool PreserveAdGroupNameCase;
|
public bool PreserveAdGroupNameCase;
|
||||||
|
public SecurityGroupReuseMode ReuseMode = SecurityGroupReuseMode.Safe;
|
||||||
|
public bool MarkerBackfill;
|
||||||
|
|
||||||
public List<IAM_SecurityGroup> IAM_SecurityGroups;
|
public List<IAM_SecurityGroup> IAM_SecurityGroups;
|
||||||
public string rootUID;
|
public string rootUID;
|
||||||
|
private const string MarkerAttributeName = "info";
|
||||||
|
private const string MarkerPrefix = "LIAM;Provider=Ntfs;";
|
||||||
public SecurityGroups()
|
public SecurityGroups()
|
||||||
{
|
{
|
||||||
IAM_SecurityGroups = new List<IAM_SecurityGroup>();
|
IAM_SecurityGroups = new List<IAM_SecurityGroup>();
|
||||||
@@ -269,9 +273,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = ownerGlobal.WildcardTemplate,
|
WildcardPattern = ownerGlobal.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Owner,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)ownerACLPermission,
|
rights = (FileSystemRights)ownerACLPermission,
|
||||||
Scope = GroupScope.Global
|
Scope = GroupScope.Global,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
IAM_SecurityGroups.Add(osecGroup);
|
IAM_SecurityGroups.Add(osecGroup);
|
||||||
|
|
||||||
@@ -283,9 +289,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = writeGlobal.WildcardTemplate,
|
WildcardPattern = writeGlobal.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Write,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)writeACLPermission,
|
rights = (FileSystemRights)writeACLPermission,
|
||||||
Scope = GroupScope.Global
|
Scope = GroupScope.Global,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
IAM_SecurityGroups.Add(wsecGroup);
|
IAM_SecurityGroups.Add(wsecGroup);
|
||||||
|
|
||||||
@@ -297,9 +305,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = readGlobal.WildcardTemplate,
|
WildcardPattern = readGlobal.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + readGlobal.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + readGlobal.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Read,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)readACLPermission,
|
rights = (FileSystemRights)readACLPermission,
|
||||||
Scope = GroupScope.Global
|
Scope = GroupScope.Global,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
IAM_SecurityGroups.Add(rsecGroup);
|
IAM_SecurityGroups.Add(rsecGroup);
|
||||||
|
|
||||||
@@ -315,9 +325,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = ownerDL.WildcardTemplate,
|
WildcardPattern = ownerDL.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + ownerDL.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + ownerDL.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Owner,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)ownerACLPermission,
|
rights = (FileSystemRights)ownerACLPermission,
|
||||||
Scope = GroupScope.Local
|
Scope = GroupScope.Local,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
osecDLGroup.memberGroups.Add(osecGroup);
|
osecDLGroup.memberGroups.Add(osecGroup);
|
||||||
IAM_SecurityGroups.Add(osecDLGroup);
|
IAM_SecurityGroups.Add(osecDLGroup);
|
||||||
@@ -330,9 +342,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = writeDL.WildcardTemplate,
|
WildcardPattern = writeDL.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + writeDL.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + writeDL.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Write,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)writeACLPermission,
|
rights = (FileSystemRights)writeACLPermission,
|
||||||
Scope = GroupScope.Local
|
Scope = GroupScope.Local,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
wsecDLGroup.memberGroups.Add(wsecGroup);
|
wsecDLGroup.memberGroups.Add(wsecGroup);
|
||||||
IAM_SecurityGroups.Add(wsecDLGroup);
|
IAM_SecurityGroups.Add(wsecDLGroup);
|
||||||
@@ -345,9 +359,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = readDL.WildcardTemplate,
|
WildcardPattern = readDL.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + readDL.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + readDL.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Read,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)readACLPermission,
|
rights = (FileSystemRights)readACLPermission,
|
||||||
Scope = GroupScope.Local
|
Scope = GroupScope.Local,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
rsecDLGroup.memberGroups.Add(rsecGroup);
|
rsecDLGroup.memberGroups.Add(rsecGroup);
|
||||||
IAM_SecurityGroups.Add(rsecDLGroup);
|
IAM_SecurityGroups.Add(rsecDLGroup);
|
||||||
@@ -661,6 +677,168 @@ namespace C4IT_IAM_Engine
|
|||||||
group.CommitChanges();
|
group.CommitChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string NormalizeMarkerPath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetMarkerScope(GroupScope scope)
|
||||||
|
{
|
||||||
|
return scope == GroupScope.Local ? "DomainLocal" : "Global";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildMarker(IAM_SecurityGroup secGroup)
|
||||||
|
{
|
||||||
|
return $"{MarkerPrefix}Path={NormalizeMarkerPath(secGroup.MarkerPath)};Role={secGroup.securityGroupType};Scope={GetMarkerScope(secGroup.Scope)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> ParseMarker(string marker)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (string.IsNullOrWhiteSpace(marker) || !marker.StartsWith(MarkerPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
foreach (var part in marker.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
var separatorIndex = part.IndexOf('=');
|
||||||
|
if (separatorIndex <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result[part.Substring(0, separatorIndex).Trim()] = part.Substring(separatorIndex + 1).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetLiamMarkers(string info)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(info))
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
|
||||||
|
return info
|
||||||
|
.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(i => i.Trim())
|
||||||
|
.Where(i => i.StartsWith(MarkerPrefix, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsMatchingMarker(string marker, IAM_SecurityGroup secGroup)
|
||||||
|
{
|
||||||
|
var values = ParseMarker(marker);
|
||||||
|
if (values.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return values.TryGetValue("Provider", out var provider)
|
||||||
|
&& string.Equals(provider, "Ntfs", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& values.TryGetValue("Path", out var markerPath)
|
||||||
|
&& string.Equals(NormalizeMarkerPath(markerPath), NormalizeMarkerPath(secGroup.MarkerPath), StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& values.TryGetValue("Role", out var role)
|
||||||
|
&& string.Equals(role, secGroup.securityGroupType.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& values.TryGetValue("Scope", out var scope)
|
||||||
|
&& string.Equals(scope, GetMarkerScope(secGroup.Scope), StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryReadInfoAttribute(DirectoryEntry group, out string info)
|
||||||
|
{
|
||||||
|
info = string.Empty;
|
||||||
|
if (group == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
group.RefreshCache(new[] { MarkerAttributeName });
|
||||||
|
if (group.Properties.Contains(MarkerAttributeName) && group.Properties[MarkerAttributeName].Count > 0)
|
||||||
|
info = group.Properties[MarkerAttributeName].Value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception E)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, $"AD group marker attribute '{MarkerAttributeName}' cannot be read for '{group.Path}'. Marker-based reuse is not available. {E.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasMatchingMarker(DirectoryEntry group, IAM_SecurityGroup secGroup)
|
||||||
|
{
|
||||||
|
if (!TryReadInfoAttribute(group, out var info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return GetLiamMarkers(info).Any(marker => IsMatchingMarker(marker, secGroup));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryWriteMarker(DirectoryEntry group, IAM_SecurityGroup secGroup, bool onlyIfNoLiamMarker)
|
||||||
|
{
|
||||||
|
if (group == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!TryReadInfoAttribute(group, out var info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var existingMarkers = GetLiamMarkers(info).ToList();
|
||||||
|
if (existingMarkers.Any(marker => IsMatchingMarker(marker, secGroup)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (onlyIfNoLiamMarker && existingMarkers.Count > 0)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, $"AD group '{secGroup.Name}' already has a LIAM marker, but it does not match path '{secGroup.MarkerPath}' and role '{secGroup.securityGroupType}'. Marker backfill is skipped.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var marker = BuildMarker(secGroup);
|
||||||
|
var newInfo = string.IsNullOrWhiteSpace(info)
|
||||||
|
? marker
|
||||||
|
: info.TrimEnd('\r', '\n') + Environment.NewLine + marker;
|
||||||
|
group.Properties[MarkerAttributeName].Value = newInfo;
|
||||||
|
group.CommitChanges();
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AD group marker written to '{secGroup.Name}': {marker}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception E)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, $"AD group marker attribute '{MarkerAttributeName}' cannot be written for '{secGroup.Name}'. {E.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirectoryEntry ValidateExistingGroupReuse(DirectoryEntry existingGroup, IAM_SecurityGroup secGroup, string folderPath, bool linkedFromFolderAcl)
|
||||||
|
{
|
||||||
|
if (existingGroup == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (ReuseMode == SecurityGroupReuseMode.Name)
|
||||||
|
{
|
||||||
|
if (!linkedFromFolderAcl && !HasMatchingMarker(existingGroup, secGroup))
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, $"AD group '{secGroup.Name}' is reused by name because NtfsAdGroupReuseMode=Name is active. This bypasses ACL/marker validation for '{secGroup.MarkerPath}'.");
|
||||||
|
|
||||||
|
ApplyExistingGroup(secGroup, existingGroup);
|
||||||
|
return existingGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkedFromFolderAcl)
|
||||||
|
{
|
||||||
|
if (MarkerBackfill)
|
||||||
|
TryWriteMarker(existingGroup, secGroup, true);
|
||||||
|
|
||||||
|
ApplyExistingGroup(secGroup, existingGroup);
|
||||||
|
return existingGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasMatchingMarker(existingGroup, secGroup))
|
||||||
|
{
|
||||||
|
ApplyExistingGroup(secGroup, existingGroup);
|
||||||
|
return existingGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = $"Existing AD group '{secGroup.Name}' was found by name for '{folderPath ?? secGroup.MarkerPath}', but it is not linked on the folder ACL and has no matching LIAM marker. Reuse is blocked in NtfsAdGroupReuseMode=Safe. Set NtfsAdGroupReuseMode=Name to allow legacy name-based reuse.";
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, message);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
public DirectoryEntry EnsureADGroup(string ouPath, IAM_SecurityGroup secGroup, List<UserPrincipal> users, string folderPath = null)
|
public DirectoryEntry EnsureADGroup(string ouPath, IAM_SecurityGroup secGroup, List<UserPrincipal> users, string folderPath = null)
|
||||||
{
|
{
|
||||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||||
@@ -668,8 +846,12 @@ namespace C4IT_IAM_Engine
|
|||||||
{
|
{
|
||||||
secGroup.CreatedNewEntry = false;
|
secGroup.CreatedNewEntry = false;
|
||||||
DirectoryEntry existingGroup = null;
|
DirectoryEntry existingGroup = null;
|
||||||
|
var linkedFromFolderAcl = false;
|
||||||
if (!ForceStrictAdGroupNames)
|
if (!ForceStrictAdGroupNames)
|
||||||
|
{
|
||||||
existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);
|
existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);
|
||||||
|
linkedFromFolderAcl = existingGroup != null;
|
||||||
|
}
|
||||||
|
|
||||||
if (existingGroup == null)
|
if (existingGroup == null)
|
||||||
existingGroup = FindGroupEntry(secGroup.Name);
|
existingGroup = FindGroupEntry(secGroup.Name);
|
||||||
@@ -680,8 +862,8 @@ namespace C4IT_IAM_Engine
|
|||||||
if (existingGroup == null)
|
if (existingGroup == null)
|
||||||
return CreateADGroup(ouPath, secGroup, users);
|
return CreateADGroup(ouPath, secGroup, users);
|
||||||
|
|
||||||
|
existingGroup = ValidateExistingGroupReuse(existingGroup, secGroup, folderPath, linkedFromFolderAcl);
|
||||||
AddMissingMembers(existingGroup, secGroup, users);
|
AddMissingMembers(existingGroup, secGroup, users);
|
||||||
ApplyExistingGroup(secGroup, existingGroup);
|
|
||||||
return existingGroup;
|
return existingGroup;
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
@@ -702,8 +884,12 @@ namespace C4IT_IAM_Engine
|
|||||||
{
|
{
|
||||||
secGroup.CreatedNewEntry = false;
|
secGroup.CreatedNewEntry = false;
|
||||||
DirectoryEntry existingGroup = null;
|
DirectoryEntry existingGroup = null;
|
||||||
|
var linkedFromFolderAcl = false;
|
||||||
if (!ForceStrictAdGroupNames)
|
if (!ForceStrictAdGroupNames)
|
||||||
|
{
|
||||||
existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);
|
existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);
|
||||||
|
linkedFromFolderAcl = existingGroup != null;
|
||||||
|
}
|
||||||
|
|
||||||
if (existingGroup == null)
|
if (existingGroup == null)
|
||||||
existingGroup = FindGroupEntry(secGroup.Name);
|
existingGroup = FindGroupEntry(secGroup.Name);
|
||||||
@@ -714,8 +900,7 @@ namespace C4IT_IAM_Engine
|
|||||||
if (existingGroup == null)
|
if (existingGroup == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
ApplyExistingGroup(secGroup, existingGroup);
|
return ValidateExistingGroupReuse(existingGroup, secGroup, folderPath, linkedFromFolderAcl);
|
||||||
return existingGroup;
|
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
@@ -738,8 +923,8 @@ namespace C4IT_IAM_Engine
|
|||||||
secGroup.Name = groupName;
|
secGroup.Name = groupName;
|
||||||
secGroup.technicalName = "CN=" + groupName + "," + ouPath;
|
secGroup.technicalName = "CN=" + groupName + "," + ouPath;
|
||||||
|
|
||||||
if (!GroupAllreadyExisting(groupName))
|
if (GroupAllreadyExisting(groupName))
|
||||||
{
|
throw new InvalidOperationException($"AD group '{groupName}' already exists. Create mode does not reuse existing groups.");
|
||||||
|
|
||||||
DirectoryEntry entry = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + 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}");
|
||||||
@@ -775,19 +960,9 @@ namespace C4IT_IAM_Engine
|
|||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}");
|
||||||
secGroup.UID = objectid;
|
secGroup.UID = objectid;
|
||||||
secGroup.CreatedNewEntry = true;
|
secGroup.CreatedNewEntry = true;
|
||||||
|
TryWriteMarker(ent, secGroup, false);
|
||||||
return ent;
|
return ent;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
DirectoryEntry e = FindGroupEntry(secGroup.Name);
|
|
||||||
if (e == null)
|
|
||||||
return null;
|
|
||||||
AddMissingMembers(e, secGroup, users);
|
|
||||||
ApplyExistingGroup(secGroup, e);
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
cLogManager.DefaultLogger.LogException(E);
|
cLogManager.DefaultLogger.LogException(E);
|
||||||
@@ -865,11 +1040,17 @@ namespace C4IT_IAM_Engine
|
|||||||
public int targetTyp;
|
public int targetTyp;
|
||||||
public GroupScope Scope;
|
public GroupScope Scope;
|
||||||
public FileSystemRights rights;
|
public FileSystemRights rights;
|
||||||
|
public string MarkerPath;
|
||||||
public IAM_SecurityGroup()
|
public IAM_SecurityGroup()
|
||||||
{
|
{
|
||||||
memberGroups = new List<IAM_SecurityGroup>();
|
memberGroups = new List<IAM_SecurityGroup>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public enum SecurityGroupReuseMode
|
||||||
|
{
|
||||||
|
Safe,
|
||||||
|
Name
|
||||||
|
}
|
||||||
public enum SecurityGroupType
|
public enum SecurityGroupType
|
||||||
{
|
{
|
||||||
[XmlEnum(Name = "0")]
|
[XmlEnum(Name = "0")]
|
||||||
|
|||||||
@@ -215,6 +215,8 @@ Im Diagnose-JSON erscheinen diese Werte unter `AdditionalConfiguration`. Paramet
|
|||||||
| `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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
||||||
|
| `NtfsAdGroupReuseMode` | `Safe`, `Name` | Steuert die Wiederverwendung vorhandener AD-Gruppen im automatischen Ensure. Standard ist `Safe`: vorhandene Gruppen werden nur wiederverwendet, wenn sie bereits passend auf der Ordner-ACL liegen oder einen passenden LIAM-Marker im AD-Attribut `info` besitzen. `Name` erlaubt die Legacy-Wiederverwendung nur anhand des Namens und wird als unsicherer Modus geloggt. |
|
||||||
|
| `NtfsAdGroupMarkerBackfill` | `true`, `1`, `yes` | Ergaenzt bei sicher wiederverwendeten, ACL-verknuepften Gruppen einen LIAM-Marker im AD-Attribut `info`, sofern das Attribut les- und schreibbar ist. |
|
||||||
| `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. |
|
| `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:
|
Beispiele:
|
||||||
|
|||||||
Reference in New Issue
Block a user