Add NTFS ensure level filters

This commit is contained in:
Meik
2026-07-09 22:43:41 +02:00
parent e23397c2ac
commit 3ca1a41816
2 changed files with 198 additions and 3 deletions

View File

@@ -53,6 +53,20 @@ namespace C4IT.LIAM
public int Level { get; set; } = -1;
}
private sealed class cNtfsLevelRange
{
public bool IsConfigured { get; set; }
public bool IsValid { get; set; } = true;
public int MinLevel { get; set; } = int.MinValue;
public int MaxLevel { get; set; } = int.MaxValue;
public string ErrorMessage { get; set; } = string.Empty;
public bool Contains(int level)
{
return !IsConfigured || level >= MinLevel && level <= MaxLevel;
}
}
private sealed class cNtfsDataAreaDiagnostics
{
private const int MaxSamples = 10;
@@ -123,6 +137,10 @@ namespace C4IT.LIAM
private const string AdditionalConfigurationExcludePathsKey = "NtfsExcludePaths";
private const string AdditionalConfigurationIncludePathsKey = "NtfsIncludePaths";
private const string AdditionalConfigurationTraverseBoundaryPathKey = "NtfsTraverseBoundaryPath";
private const string AdditionalConfigurationPermissionGroupsMinLevelKey = "NtfsPermissionGroupsMinLevel";
private const string AdditionalConfigurationPermissionGroupsMaxLevelKey = "NtfsPermissionGroupsMaxLevel";
private const string AdditionalConfigurationTraverseGroupsMinLevelKey = "NtfsTraverseGroupsMinLevel";
private const string AdditionalConfigurationTraverseGroupsMaxLevelKey = "NtfsTraverseGroupsMaxLevel";
private const string AdditionalConfigurationGroupNameSanitizeReplacementKey = "NtfsGroupNameSanitizeReplacement";
private const string AdditionalConfigurationPreserveAdGroupNameCaseKey = "PreserveNtfsAdGroupNameCase";
private const string AdditionalConfigurationAdDomainControllersKey = "NtfsAdDomainControllers";
@@ -1155,6 +1173,16 @@ namespace C4IT.LIAM
});
}
string levelSkipReason;
if (!IsPermissionLevelManagedPath(classification, out levelSkipReason))
{
return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
{
resultErrorId = 30008,
resultMessage = levelSkipReason
});
}
var parentPath = Directory.GetParent(folderPath)?.FullName;
var engine = CreateFilesystemEngine(
folderPath,
@@ -1208,7 +1236,19 @@ namespace C4IT.LIAM
});
}
if (!IsTraversePermissionManagedPath(folderPath))
var traverseLevelRange = GetTraverseLevelRange();
if (!traverseLevelRange.IsValid)
{
var message = $"NTFS traverse ensure skipped for '{folderPath}' because {traverseLevelRange.ErrorMessage}";
LogEntry(message, LogLevels.Warning);
return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
{
resultErrorId = 30008,
resultMessage = message
});
}
if (!IsTraversePermissionCandidatePath(folderPath))
{
return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
{
@@ -1355,6 +1395,16 @@ namespace C4IT.LIAM
}
private bool IsPermissionManagedPath(string path, params eNtfsPathKind[] supportedKinds)
{
if (!IsPermissionManagedPathCandidate(path, supportedKinds))
return false;
var classification = ClassifyPath(path);
string levelSkipReason;
return IsPermissionLevelManagedPath(classification, out levelSkipReason);
}
private bool IsPermissionManagedPathCandidate(string path, params eNtfsPathKind[] supportedKinds)
{
var classification = ClassifyPath(path);
if (!IsSupportedPermissionManagedPathKind(classification, supportedKinds))
@@ -1365,13 +1415,25 @@ namespace C4IT.LIAM
if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
return false;
return IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule);
if (!IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule))
return false;
return true;
}
private bool IsTraversePermissionManagedPath(string path)
{
if (!IsTraversePermissionCandidatePath(path))
return false;
string levelSkipReason;
return IsTraverseLevelManagedPath(ClassifyPath(path), out levelSkipReason);
}
private bool IsTraversePermissionCandidatePath(string path)
{
if (string.IsNullOrWhiteSpace(GetAdditionalConfigurationValue(AdditionalConfigurationTraverseBoundaryPathKey)))
return IsPermissionManagedFolderPath(path);
return IsPermissionManagedPathCandidate(path, eNtfsPathKind.Folder);
var classification = ClassifyPath(path);
if (classification == null || classification.Kind == eNtfsPathKind.ServerRoot || classification.Kind == eNtfsPathKind.Unknown)
@@ -1385,6 +1447,112 @@ namespace C4IT.LIAM
return Directory.Exists(path);
}
private bool IsPermissionLevelManagedPath(cNtfsPathClassification classification, out string skipReason)
{
return IsLevelManagedPath(
classification,
GetPermissionLevelRange(),
"permission group ensure",
out skipReason);
}
private bool IsTraverseLevelManagedPath(cNtfsPathClassification classification, out string skipReason)
{
return IsLevelManagedPath(
classification,
GetTraverseLevelRange(),
"traverse group ensure",
out skipReason);
}
private bool IsLevelManagedPath(cNtfsPathClassification classification, cNtfsLevelRange range, string operationName, out string skipReason)
{
skipReason = string.Empty;
if (classification == null)
{
skipReason = $"NTFS {operationName} skipped because the path could not be classified.";
return false;
}
if (range == null || !range.IsConfigured)
return true;
if (!range.IsValid)
{
skipReason = $"NTFS {operationName} skipped for '{classification.NormalizedPath}' because {range.ErrorMessage}";
LogEntry(skipReason, LogLevels.Warning);
return false;
}
if (range.Contains(classification.Level))
return true;
skipReason = $"NTFS {operationName} skipped for '{classification.NormalizedPath}' because level {classification.Level} is outside configured range {range.MinLevel}..{range.MaxLevel}.";
LogEntry(skipReason, LogLevels.Debug);
return false;
}
private cNtfsLevelRange GetPermissionLevelRange()
{
return GetConfiguredLevelRange(
AdditionalConfigurationPermissionGroupsMinLevelKey,
AdditionalConfigurationPermissionGroupsMaxLevelKey,
"permission group level range");
}
private cNtfsLevelRange GetTraverseLevelRange()
{
return GetConfiguredLevelRange(
AdditionalConfigurationTraverseGroupsMinLevelKey,
AdditionalConfigurationTraverseGroupsMaxLevelKey,
"traverse group level range");
}
private cNtfsLevelRange GetConfiguredLevelRange(string minKey, string maxKey, string label)
{
var range = new cNtfsLevelRange();
var minValue = GetAdditionalConfigurationValue(minKey);
var maxValue = GetAdditionalConfigurationValue(maxKey);
var hasMin = !string.IsNullOrWhiteSpace(minValue);
var hasMax = !string.IsNullOrWhiteSpace(maxValue);
range.IsConfigured = hasMin || hasMax;
if (!range.IsConfigured)
return range;
int parsedValue;
if (hasMin)
{
if (!int.TryParse(minValue, out parsedValue))
{
range.IsValid = false;
range.ErrorMessage = $"AdditionalConfiguration '{minKey}' for {label} is not a valid integer: '{minValue}'.";
return range;
}
range.MinLevel = parsedValue;
}
if (hasMax)
{
if (!int.TryParse(maxValue, out parsedValue))
{
range.IsValid = false;
range.ErrorMessage = $"AdditionalConfiguration '{maxKey}' for {label} is not a valid integer: '{maxValue}'.";
return range;
}
range.MaxLevel = parsedValue;
}
if (range.MinLevel > range.MaxLevel)
{
range.IsValid = false;
range.ErrorMessage = $"AdditionalConfiguration {label} is invalid because min level {range.MinLevel} is greater than max level {range.MaxLevel}.";
}
return range;
}
private static bool IsSupportedPermissionManagedPathKind(cNtfsPathClassification classification, params eNtfsPathKind[] supportedKinds)
{
if (classification == null || supportedKinds == null || supportedKinds.Length == 0)

View File

@@ -210,6 +210,8 @@ Im Diagnose-JSON erscheinen diese Werte unter `AdditionalConfiguration`. Paramet
| `NtfsIncludePaths` | Pfadliste, getrennt mit `;`, &#124; 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 `;`, &#124; 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. Der Boundary-Pfad selbst darf ebenfalls als Traverse-Ziel verarbeitet werden. |
| `NtfsPermissionGroupsMinLevel` / `NtfsPermissionGroupsMaxLevel` | Ganzzahl, z. B. `0`, `1`, `2` | Optionaler Levelbereich fuer Owner/Write/Read-Ensure. Ebene `0` ist der `RootPath`; Unterordner sind `1`, `2`, `3`, ... Wenn beide Parameter leer sind, bleibt das bisherige Verhalten unveraendert. |
| `NtfsTraverseGroupsMinLevel` / `NtfsTraverseGroupsMaxLevel` | Ganzzahl, z. B. `-1`, `0`, `2` | Optionaler Levelbereich fuer Traverse-Ensure. Ebene `0` ist der `RootPath`; Parent-Pfade oberhalb des `RootPath` sind `-1`, `-2`, ... Wenn beide Parameter leer sind, bleibt das bisherige Verhalten unveraendert. |
| `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. |
@@ -224,6 +226,10 @@ EnsureNtfsTraverseGroups=1
NtfsIncludePaths=Finance\*;HR\Reports
NtfsExcludePaths=*\_archive\*
NtfsTraverseBoundaryPath=\\fileserver\file_shares
NtfsPermissionGroupsMinLevel=1
NtfsPermissionGroupsMaxLevel=2
NtfsTraverseGroupsMinLevel=-1
NtfsTraverseGroupsMaxLevel=2
NtfsGroupNameSanitizeReplacement=.
PreserveNtfsAdGroupNameCase=1
NtfsAdDomainControllers=dc01.contoso.local,dc02.contoso.local
@@ -261,6 +267,27 @@ EnsureNtfsTraverseGroups=1
In diesem Beispiel koennen Traverse-Gruppen fuer `...\Projekte` selbst und fuer die Ordner darunter sichergestellt werden. Der Boundary-Pfad selbst ist gueltig; er muss nicht zusaetzlich als `NtfsIncludePaths`-Match enthalten sein, solange es nur um Traverse geht.
#### Levelbereiche fuer automatisches Ensure
Die optionalen Levelbereiche schraenken ein, auf welchen Ebenen Gruppen und ACLs automatisch sichergestellt werden. Wenn keiner der Level-Parameter gesetzt ist, bleibt das bisherige Verhalten unveraendert.
Ebene `0` ist immer der konfigurierte `RootPath`. Unterordner unterhalb des `RootPath` haben positive Level (`1`, `2`, `3`, ...). Parent-Pfade oberhalb des `RootPath` haben negative Level (`-1`, `-2`, ...). Negative Level sind vor allem fuer Traverse relevant, wenn `NtfsTraverseBoundaryPath` oberhalb des `RootPath` liegt.
Beispiel:
```text
RootPath=\\server\share\Bereich
NtfsTraverseBoundaryPath=\\server\share
NtfsPermissionGroupsMinLevel=1
NtfsPermissionGroupsMaxLevel=2
NtfsTraverseGroupsMinLevel=-1
NtfsTraverseGroupsMaxLevel=2
```
Damit werden Owner/Write/Read-Gruppen nur fuer `\\server\share\Bereich\...` auf Ebene `1` und `2` erzeugt. Traverse-Gruppen duerfen zusaetzlich bis zum Share auf Ebene `-1` erzeugt werden. `NtfsTraverseBoundaryPath` bleibt die harte Grenze fuer Traverse; die Min-/Max-Level legen nur fest, welche Ebenen innerhalb dieses Korridors verarbeitet werden.
Wenn nur ein Min- oder Max-Wert gesetzt ist, ist die andere Seite offen. Ungueltige Werte oder ein Min-Wert groesser als der Max-Wert fuehren dazu, dass die betroffene Ensure-Phase mit einer Warning uebersprungen wird.
#### WhatIf / Preview
Wenn `WhatIf=1` gesetzt ist, fuehrt LIAM die automatische NTFS-Verarbeitung als Preview aus. Fuer Permission-Ensure und Traverse-Ensure werden geplante Gruppen, wiederverwendete Gruppen, geplante ACLs, bestehende ACLs, Traverse-Gruppen und Warnings im Ergebnis und im Debug-Log ausgegeben. Es werden keine AD-Gruppen, Gruppenmitgliedschaften oder ACLs dauerhaft geaendert.