126 lines
4.3 KiB
C#
126 lines
4.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using System.Xml.Linq;
|
|
|
|
namespace BizTalkSapEnvironmentInventory.Infrastructure
|
|
{
|
|
/// <summary>
|
|
/// Zentraler Schutz gegen die Aufnahme von Kennwörtern, Tokens und Schlüsseldaten in Log oder DOCX.
|
|
/// </summary>
|
|
internal static class SensitiveDataSanitizer
|
|
{
|
|
private static readonly string[] SensitiveFragments =
|
|
{
|
|
"password", "passwd", "passphrase", "pwd", "secret", "token",
|
|
"privatekey", "clientsecret", "accesskey", "apikey", "connectionstring"
|
|
};
|
|
|
|
private static readonly Regex UriSecretPattern = new Regex(
|
|
@"(?i)(password|passwd|passphrase|pwd|secret|token|clientsecret)\s*=\s*(?:""[^""]*""|'[^']*'|[^;&\s""']+)",
|
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
|
|
|
private static readonly Regex UserInfoPattern = new Regex(
|
|
@"(?i)([a-z][a-z0-9+.-]*://)([^/@:\s]+):([^/@\s]+)@",
|
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
|
|
|
/// <summary>
|
|
/// Erkennt sensitive Feldnamen unabhängig von Großschreibung und Trennzeichen.
|
|
/// </summary>
|
|
public static bool IsSensitiveName(string name)
|
|
{
|
|
var normalized = (name ?? string.Empty).Replace("_", string.Empty)
|
|
.Replace("-", string.Empty);
|
|
return SensitiveFragments.Any(fragment =>
|
|
normalized.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redigiert einen Wert anhand seines semantischen Feldnamens.
|
|
/// </summary>
|
|
public static string RedactValue(string name, string value)
|
|
{
|
|
if (IsSensitiveName(name))
|
|
{
|
|
return string.IsNullOrWhiteSpace(value)
|
|
? "Nicht gesetzt"
|
|
: "Konfiguriert (Wert nicht dokumentiert)";
|
|
}
|
|
|
|
return SanitizeText(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Entfernt Secrets aus URI-/Key-Value-Texten und eingebetteten Benutzerinformationen.
|
|
/// </summary>
|
|
public static string SanitizeText(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var sanitized = UriSecretPattern.Replace(
|
|
value,
|
|
match => match.Groups[1].Value + "=***REDACTED***");
|
|
sanitized = UserInfoPattern.Replace(
|
|
sanitized,
|
|
match => match.Groups[1].Value + match.Groups[2].Value + ":***REDACTED***@");
|
|
return sanitized;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Erstellt eine secret-bereinigte Kopie eines XML-Teilbaums.
|
|
/// </summary>
|
|
public static XElement SanitizeXml(XElement source)
|
|
{
|
|
if (source == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (IsSensitiveName(source.Name.LocalName)
|
|
|| IsCipherElement(source.Name.LocalName))
|
|
{
|
|
return new XElement(source.Name, "***REDACTED***");
|
|
}
|
|
|
|
var result = new XElement(source.Name);
|
|
foreach (var attribute in source.Attributes())
|
|
{
|
|
result.Add(new XAttribute(
|
|
attribute.Name,
|
|
IsSensitiveName(attribute.Name.LocalName)
|
|
? "***REDACTED***"
|
|
: SanitizeText(attribute.Value)));
|
|
}
|
|
|
|
foreach (var node in source.Nodes())
|
|
{
|
|
var element = node as XElement;
|
|
if (element != null)
|
|
{
|
|
result.Add(SanitizeXml(element));
|
|
continue;
|
|
}
|
|
|
|
var text = node as XText;
|
|
if (text != null)
|
|
{
|
|
result.Add(new XText(SanitizeText(text.Value)));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static bool IsCipherElement(string name)
|
|
{
|
|
return string.Equals(name, "EncryptedData", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(name, "CipherData", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(name, "CipherValue", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|
|
}
|