feat: ensure missing NTFS permission groups

This commit is contained in:
Meik
2026-03-10 08:51:59 +01:00
parent 58d7529329
commit 812deeaa74
6 changed files with 1049 additions and 375 deletions

View File

@@ -225,6 +225,134 @@ namespace C4IT_IAM_SET
}
}
private ResultToken checkRequiredVariablesForEnsure()
{
ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
resultToken.resultErrorId = 0;
if (String.IsNullOrEmpty(ConfigID))
{
resultToken.resultErrorId = 30001;
resultToken.resultMessage = "Kein ConfigID gewählt ";
return resultToken;
}
if (string.IsNullOrEmpty(username) | String.IsNullOrEmpty(new NetworkCredential("", password).Password) | String.IsNullOrEmpty(domainName))
{
resultToken.resultErrorId = 30002;
resultToken.resultMessage = "Fehlende Anmeldeinformationen";
return resultToken;
}
if (String.IsNullOrEmpty(groupPrefix))
{
resultToken.resultErrorId = 30004;
resultToken.resultMessage = "Kein Gruppen Präfix angegeben";
return resultToken;
}
if (String.IsNullOrEmpty(newFolderPath))
{
resultToken.resultErrorId = 30005;
resultToken.resultMessage = "Kein Pfad für Verzeichnis angegeben";
return resultToken;
}
if (String.IsNullOrEmpty(baseFolder))
{
resultToken.resultErrorId = 30007;
resultToken.resultMessage = "Kein Basisverzeichnis angegeben";
return resultToken;
}
return resultToken;
}
private void InitializeFolderContext()
{
newDataArea = new DataArea();
var folder = new IAM_Folder
{
configurationID = ConfigID,
technicalName = newFolderPath,
targetType = (int)IAM_TargetType.FileSystem,
Parent = newFolderParent,
ParentUID = string.IsNullOrWhiteSpace(newFolderParent) ? string.Empty : DataArea.GetUniqueDataAreaID(newFolderParent),
baseFolder = baseFolder
};
newDataArea.IAM_Folders.Add(folder);
newSecurityGroups = new SecurityGroups
{
username = username,
domainName = domainName,
password = password
};
}
public ResultToken ensureDataAreaPermissions(bool ensureTraverseGroups = false)
{
LogMethodBegin(MethodBase.GetCurrentMethod());
try
{
var resultToken = checkRequiredVariablesForEnsure();
if (resultToken.resultErrorId != 0)
return resultToken;
if (Connection != null)
Connection.Dispose();
using (Connection = new cNetworkConnection(baseFolder, username, new NetworkCredential("", password).Password))
{
if (!Directory.Exists(newFolderPath))
{
resultToken.resultErrorId = 30203;
resultToken.resultMessage = "Verzeichnis existiert nicht";
return resultToken;
}
var parentDirectory = Directory.GetParent(newFolderPath);
if (string.IsNullOrWhiteSpace(newFolderParent))
newFolderParent = parentDirectory?.FullName;
InitializeFolderContext();
ensureADGroups(resultToken);
resultToken = ensureFolderPermissions(resultToken);
if (resultToken.resultErrorId != 0)
return resultToken;
if (ensureTraverseGroups)
{
var traverseResult = SetTraversePermissions();
if (traverseResult != null)
{
resultToken.createdGroups.AddRange(traverseResult.createdGroups);
resultToken.reusedGroups.AddRange(traverseResult.reusedGroups);
resultToken.addedAclEntries.AddRange(traverseResult.addedAclEntries);
resultToken.skippedAclEntries.AddRange(traverseResult.skippedAclEntries);
resultToken.ensuredTraverseGroups.AddRange(traverseResult.ensuredTraverseGroups);
resultToken.warnings.AddRange(traverseResult.warnings);
if (traverseResult.resultErrorId != 0)
{
resultToken.resultErrorId = traverseResult.resultErrorId;
resultToken.resultMessage = traverseResult.resultMessage;
return resultToken;
}
}
}
resultToken.resultMessage = "Gruppen und ACLs erfolgreich sichergestellt";
return resultToken;
}
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
throw;
}
finally
{
LogMethodEnd(MethodBase.GetCurrentMethod());
}
}
private ResultToken SetTraversePermissions()
{
LogMethodBegin(MethodBase.GetCurrentMethod());
@@ -349,30 +477,33 @@ namespace C4IT_IAM_SET
continue;
}
GroupPrincipal parentTraverseGroup = null;
string relativePathRaw = DataArea.GetRelativePath(parent.FullName, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
DefaultLogger.LogEntry(LogLevels.Debug, $"relativePath vor Normalisierung: {relativePathRaw}");
var relativePathSegments = relativePathRaw.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
var sanitizedSegments = relativePathSegments.Select(Helper.SanitizePathSegment).ToArray();
var relativePath = sanitizedSegments.Length > 0 ? string.Join("_", sanitizedSegments) : string.Empty;
DefaultLogger.LogEntry(LogLevels.Debug, $"relativePath nach Normalisierung: {relativePath}");
var folderName = sanitizedSegments.Length > 0
? sanitizedSegments[sanitizedSegments.Length - 1]
: Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)));
string traverseRegex = null;
try
{
traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, relativePath, sanitizedSegments, folderName);
DefaultLogger.LogEntry(LogLevels.Debug, $"traverseRegex: {traverseRegex}");
}
catch (Exception ex)
{
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler bei der Erstellung von traverseRegex: {ex.Message}");
continue;
}
GroupPrincipal parentTraverseGroup = null;
var parentTraverseAclExists = false;
string relativePathRaw = DataArea.GetRelativePath(parent.FullName, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
DefaultLogger.LogEntry(LogLevels.Debug, $"relativePath vor Normalisierung: {relativePathRaw}");
var relativePathSegments = relativePathRaw.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
var sanitizedSegments = relativePathSegments.Select(Helper.SanitizePathSegment).ToArray();
var relativePath = sanitizedSegments.Length > 0 ? string.Join("_", sanitizedSegments) : string.Empty;
DefaultLogger.LogEntry(LogLevels.Debug, $"relativePath nach Normalisierung: {relativePath}");
var folderName = sanitizedSegments.Length > 0
? sanitizedSegments[sanitizedSegments.Length - 1]
: Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)));
var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, relativePath, sanitizedSegments, folderName);
var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, relativePath, sanitizedSegments, folderName);
string traverseRegex = null;
try
{
traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, relativePath, sanitizedSegments, folderName);
DefaultLogger.LogEntry(LogLevels.Debug, $"traverseRegex: {traverseRegex}");
}
catch (Exception ex)
{
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler bei der Erstellung von traverseRegex: {ex.Message}");
continue;
}
foreach (FileSystemAccessRule acl in ACLs)
{
@@ -397,12 +528,29 @@ namespace C4IT_IAM_SET
if (princ != null && Regex.IsMatch(princ.Name, traverseRegex, RegexOptions.IgnoreCase))
{
parentTraverseGroup = princ;
parentTraverseAclExists = true;
DefaultLogger.LogEntry(LogLevels.Debug, $"parentTraverseGroup gesetzt: {parentTraverseGroup.Name}");
}
if (parentTraverseGroup != null)
break;
}
if (parentTraverseGroup == null && !string.IsNullOrEmpty(traverseNameTemplate))
{
for (var loop = 0; loop < 20; loop++)
{
var candidateName = traverseNameTemplate.ReplaceLoopTag(loop);
parentTraverseGroup = GroupPrincipal.FindByIdentity(domainContext, candidateName);
if (parentTraverseGroup == null)
continue;
resultToken.reusedGroups.Add(candidateName);
resultToken.ensuredTraverseGroups.Add(candidateName);
DefaultLogger.LogEntry(LogLevels.Debug, $"Vorhandene Traverse-Gruppe wiederverwendet: {candidateName}");
break;
}
}
if (parentTraverseGroup == null && !traverseGroupTemplate.NamingTemplate.Equals(string.Empty))
{
DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle neue TraverseGroup.");
@@ -412,24 +560,22 @@ namespace C4IT_IAM_SET
continue;
}
IAM_SecurityGroup newTraverseGroup = null;
var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, relativePath, sanitizedSegments, folderName);
var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, relativePath, sanitizedSegments, folderName);
var loop = 0;
do
{
try
{
newTraverseGroup = new IAM_SecurityGroup()
{
Name = traverseNameTemplate.ReplaceLoopTag(loop),
description = traverseDescriptionTemplate.ReplaceLoopTag(loop),
technicalName = "CN=" + traverseNameTemplate.ReplaceLoopTag(loop) + "," + groupOUPath,
Scope = traverseGroupTemplate.Scope
};
DefaultLogger.LogEntry(LogLevels.Debug, $"Erstellte TraverseGroup: {newTraverseGroup.Name} (Loop: {loop})");
loop++;
}
IAM_SecurityGroup newTraverseGroup = null;
var loop = 0;
do
{
try
{
newTraverseGroup = new IAM_SecurityGroup()
{
Name = traverseNameTemplate.ReplaceLoopTag(loop),
description = traverseDescriptionTemplate.ReplaceLoopTag(loop),
technicalName = "CN=" + traverseNameTemplate.ReplaceLoopTag(loop) + "," + groupOUPath,
Scope = traverseGroupTemplate.Scope
};
DefaultLogger.LogEntry(LogLevels.Debug, $"Erstellte TraverseGroup: {newTraverseGroup.Name} (Loop: {loop})");
loop++;
}
catch (Exception ex)
{
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Erstellen von newTraverseGroup: {ex.Message}");
@@ -452,6 +598,8 @@ namespace C4IT_IAM_SET
{
newSecurityGroups.CreateADGroup(groupOUPath, newTraverseGroup, null);
DefaultLogger.LogEntry(LogLevels.Debug, $"AD-Gruppe erstellt: {newTraverseGroup.Name}");
resultToken.createdGroups.Add(newTraverseGroup.Name);
resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name);
}
catch (Exception ex)
{
@@ -474,6 +622,8 @@ namespace C4IT_IAM_SET
AccessControlType.Allow));
DefaultLogger.LogEntry(LogLevels.Debug, $"Setze Traverse-ACL auf: {parent.FullName} für {parentTraverseGroup.DistinguishedName}");
parent.SetAccessControl(accesscontrol);
resultToken.addedAclEntries.Add(parentTraverseGroup.Name);
parentTraverseAclExists = true;
}
catch (Exception ex)
{
@@ -488,6 +638,37 @@ namespace C4IT_IAM_SET
}
}
if (parentTraverseGroup != null && !parentTraverseAclExists)
{
try
{
var accessControl = parent.GetAccessControl();
var rules = accessControl.GetAccessRules(true, true, typeof(SecurityIdentifier)).Cast<FileSystemAccessRule>();
var hasAcl = rules.Any(rule =>
rule.AccessControlType == AccessControlType.Allow
&& rule.IdentityReference.Value == parentTraverseGroup.Sid.Value
&& (rule.FileSystemRights & FileSystemRights.Read) == FileSystemRights.Read);
if (hasAcl)
{
resultToken.skippedAclEntries.Add(parentTraverseGroup.Name);
}
else
{
accessControl.AddAccessRule(new FileSystemAccessRule(parentTraverseGroup.Sid,
FileSystemRights.Read, InheritanceFlags.None, PropagationFlags.None,
AccessControlType.Allow));
parent.SetAccessControl(accessControl);
resultToken.addedAclEntries.Add(parentTraverseGroup.Name);
}
}
catch (Exception ex)
{
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Sicherstellen der Traverse-ACL: {ex.Message}");
continue;
}
}
if (parentTraverseGroup != null)
{
if (i == lvl)
@@ -566,6 +747,9 @@ namespace C4IT_IAM_SET
DefaultLogger.LogEntry(LogLevels.Debug, "parentTraverseGroup ist null.");
}
if (parentTraverseGroup != null && !resultToken.ensuredTraverseGroups.Contains(parentTraverseGroup.Name))
resultToken.ensuredTraverseGroups.Add(parentTraverseGroup.Name);
// Aktualisiere parent und lvl für die nächste Iteration
parent = parent.Parent;
if (parent != null)
@@ -620,6 +804,136 @@ namespace C4IT_IAM_SET
LogMethodEnd(MethodBase.GetCurrentMethod());
}
}
private static bool HasMatchingAccessRule(DirectoryInfo directory, SecurityIdentifier sid, FileSystemRights rights)
{
var rules = directory.GetAccessControl(AccessControlSections.Access)
.GetAccessRules(true, true, typeof(SecurityIdentifier))
.Cast<FileSystemAccessRule>();
foreach (var rule in rules)
{
if (rule.AccessControlType != AccessControlType.Allow)
continue;
if (!(rule.IdentityReference is SecurityIdentifier ruleSid))
continue;
if (!string.Equals(ruleSid.Value, sid.Value, StringComparison.OrdinalIgnoreCase))
continue;
if ((rule.FileSystemRights & rights) == rights)
return true;
}
return false;
}
private ResultToken ensureFolderPermissions(ResultToken resultToken)
{
LogMethodBegin(MethodBase.GetCurrentMethod());
try
{
var directory = new DirectoryInfo(newDataArea.IAM_Folders[0].technicalName);
foreach (var currentSecGroup in newSecurityGroups.IAM_SecurityGroups)
{
if (string.IsNullOrWhiteSpace(currentSecGroup?.UID))
{
resultToken.warnings.Add($"Keine SID für Gruppe '{currentSecGroup?.Name}' verfügbar.");
continue;
}
if (!(groupPermissionStrategy == PermissionGroupStrategy.AGDLP && currentSecGroup.Scope == GroupScope.Local
|| groupPermissionStrategy == PermissionGroupStrategy.AGP && currentSecGroup.Scope == GroupScope.Global))
{
continue;
}
var sid = new SecurityIdentifier(currentSecGroup.UID);
if (HasMatchingAccessRule(directory, sid, currentSecGroup.rights))
{
resultToken.skippedAclEntries.Add(currentSecGroup.Name);
continue;
}
DataArea.AddDirectorySecurity(newDataArea.IAM_Folders[0].baseFolder, newDataArea.IAM_Folders[0].technicalName, sid, currentSecGroup.rights, AccessControlType.Allow);
resultToken.addedAclEntries.Add(currentSecGroup.Name);
}
return resultToken;
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
throw;
}
finally
{
LogMethodEnd(MethodBase.GetCurrentMethod());
}
}
private void ensureADGroups(ResultToken resultToken)
{
LogMethodBegin(MethodBase.GetCurrentMethod());
try
{
newSecurityGroups.IAM_SecurityGroups.Clear();
newSecurityGroups.GenerateNewSecurityGroups(baseFolder,
newDataArea.IAM_Folders[0].technicalName,
groupPrefix,
groupOUPath,
groupPermissionStrategy,
groupTraverseTag,
groupReadTag,
groupWriteTag,
groupOwnerTag,
groupDLTag,
groupGTag,
groupCustomTags,
templates,
ReadACLPermission,
WriteACLPermission,
OwnerACLPermission,
0);
List<UserPrincipal> owners = getUserPrincipalBySid(ownerUserSids);
List<UserPrincipal> writers = getUserPrincipalBySid(writerUserSids);
List<UserPrincipal> readers = getUserPrincipalBySid(readerUserSids);
for (int i = 0; newSecurityGroups.IAM_SecurityGroups.Count > i; i++)
{
List<UserPrincipal> users;
if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupOwnerTag.ToUpper()))
users = owners;
else if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupWriteTag.ToUpper()))
users = writers;
else if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupReadTag.ToUpper()))
users = readers;
else
users = null;
var groupAlreadyExists = newSecurityGroups.GroupAllreadyExisting(newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper());
newSecurityGroups.EnsureADGroup(groupOUPath, newSecurityGroups.IAM_SecurityGroups[i], users);
if (groupAlreadyExists)
resultToken.reusedGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name);
else
resultToken.createdGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name);
}
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
throw;
}
finally
{
LogMethodEnd(MethodBase.GetCurrentMethod());
}
}
private ResultToken createFolder()
{
LogMethodBegin(MethodBase.GetCurrentMethod());