Initial commit: BizTalk SAP environment inventory
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using BizTalkSapEnvironmentInventory.Collectors;
|
||||
using BizTalkSapEnvironmentInventory.Configuration;
|
||||
using BizTalkSapEnvironmentInventory.Models;
|
||||
using BizTalkSapEnvironmentInventory.Reporting;
|
||||
|
||||
namespace BizTalkSapEnvironmentInventory.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Abhängigkeitsfreier Testläufer für Secret-Schutz, SAP-Bindingparser und DOCX-Paket.
|
||||
/// </summary>
|
||||
internal static class SelfTestRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Führt alle sicherheits- und ausgaberelevanten Tests aus.
|
||||
/// </summary>
|
||||
public static void Run()
|
||||
{
|
||||
TestSanitizer();
|
||||
TestBindingParser();
|
||||
TestDocxPackage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schreibt einen dauerhaften Musterbericht für eine externe Word-/LibreOffice-Prüfung.
|
||||
/// </summary>
|
||||
public static void WriteSample(string path)
|
||||
{
|
||||
var document = new InventoryDocument
|
||||
{
|
||||
EnvironmentName = "ACC-MUSTER",
|
||||
ComputerName = "BIZTALK-ACC-01",
|
||||
ToolVersion = "1.0.0.0",
|
||||
StartedUtc = DateTime.UtcNow.AddSeconds(-3),
|
||||
CompletedUtc = DateTime.UtcNow
|
||||
};
|
||||
document.System.Properties.Add(
|
||||
new NameValueRecord("BizTalk Produktversion", "3.13.x (Muster)"));
|
||||
document.System.Properties.Add(
|
||||
new NameValueRecord("BizTalk Management SQL Server", "SQL-ACC-01"));
|
||||
document.Applications.Add(new ApplicationRecord
|
||||
{
|
||||
Name = "MasterData_SAP_WebGIS",
|
||||
Status = "Gestartet"
|
||||
});
|
||||
var endpoint = new SapEndpointRecord
|
||||
{
|
||||
ApplicationName = "MasterData_SAP_WebGIS",
|
||||
Direction = "Senden",
|
||||
Name = "Send_SAP_WebGIS",
|
||||
ParentPortName = "Send_SAP_WebGIS",
|
||||
AdapterName = "WCF-SAP",
|
||||
HostName = "Snd_SAP_ERP_C1_64_B",
|
||||
Status = "Gestartet",
|
||||
Address = "sap://Client=100;lang=DE@A/sap-acc.example.local/00",
|
||||
SecurityMode = "SAP SNC",
|
||||
CredentialReference = "Kennwort wird nicht dokumentiert.",
|
||||
Source = "Muster"
|
||||
};
|
||||
endpoint.Properties.Add(new NameValueRecord("Client", "100", "SAP-URI"));
|
||||
endpoint.Properties.Add(new NameValueRecord("GatewayHost", "sap-gw-acc", "SAP-URI"));
|
||||
endpoint.Properties.Add(new NameValueRecord("UseSnc", "true", "Binding"));
|
||||
endpoint.Properties.Add(new NameValueRecord("SncLibrary", @"C:\SAP\sapcrypto.dll", "Binding"));
|
||||
endpoint.Operations.Add("http://Microsoft.LobServices.Sap/2007/03/Idoc/3/ORDERS05");
|
||||
document.SapEndpoints.Add(endpoint);
|
||||
document.SectionStatuses.Add(new SectionStatus
|
||||
{
|
||||
Name = "Mustererfassung",
|
||||
Status = "Erfolgreich",
|
||||
Message = "Musterbericht",
|
||||
Required = true,
|
||||
DurationMilliseconds = 42
|
||||
});
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = "Offen",
|
||||
Area = "SAP WE20",
|
||||
Message = "Partnerprofil muss aus SAP ergänzt werden.",
|
||||
RecommendedAction = "WE20-Export beifügen.",
|
||||
Owner = "SAP Basis"
|
||||
});
|
||||
new DocxReportWriter().Write(document, Path.GetFullPath(path));
|
||||
}
|
||||
|
||||
private static void TestSanitizer()
|
||||
{
|
||||
var source = XElement.Parse(
|
||||
"<configuration password=\"clear\"><add token=\"abc\" />"
|
||||
+ "<EncryptedData><CipherValue>blob</CipherValue></EncryptedData></configuration>");
|
||||
var result = SensitiveDataSanitizer.SanitizeXml(source).ToString();
|
||||
Assert(!result.Contains("clear"), "XML-Sanitizer ließ Kennwort stehen.");
|
||||
Assert(!result.Contains("abc"), "XML-Sanitizer ließ Token stehen.");
|
||||
Assert(!result.Contains("blob"), "XML-Sanitizer ließ Ciphertext stehen.");
|
||||
|
||||
var uri = SensitiveDataSanitizer.SanitizeText(
|
||||
"sap://Client=100;Password=secret@A/sap.example/00?token=abc");
|
||||
Assert(!uri.Contains("secret"), "URI-Sanitizer ließ Kennwort stehen.");
|
||||
Assert(!uri.Contains("abc"), "URI-Sanitizer ließ Token stehen.");
|
||||
|
||||
var embeddedXml = SensitiveDataSanitizer.SanitizeText(
|
||||
"<binding password=\"xmlsecret\" token='xmltoken' />");
|
||||
Assert(!embeddedXml.Contains("xmlsecret"), "Text-Sanitizer ließ XML-Kennwort stehen.");
|
||||
Assert(!embeddedXml.Contains("xmltoken"), "Text-Sanitizer ließ XML-Token stehen.");
|
||||
}
|
||||
|
||||
private static void TestBindingParser()
|
||||
{
|
||||
var directory = CreateTemporaryDirectory();
|
||||
var logPath = Path.Combine(directory, "test.log");
|
||||
try
|
||||
{
|
||||
var document = new InventoryDocument
|
||||
{
|
||||
EnvironmentName = "TEST",
|
||||
ComputerName = "BIZTALK-TEST",
|
||||
StartedUtc = DateTime.UtcNow
|
||||
};
|
||||
var options = CommandLineOptions.Parse(new[]
|
||||
{
|
||||
"--environment", "TEST",
|
||||
"--output", directory
|
||||
});
|
||||
using (var logger = new ConsoleFileLogger(logPath))
|
||||
{
|
||||
var collector = new BindingExportCollector(options, document, logger, 30);
|
||||
collector.ParseBindingDocument(CreateSampleBinding(), "Self-Test");
|
||||
}
|
||||
|
||||
Assert(document.SapEndpoints.Count == 2, "Nicht alle SAP-Endpunkte wurden erkannt.");
|
||||
var endpoint = document.SapEndpoints.Single(item => item.Direction == "Senden");
|
||||
Assert(endpoint.GetProperty("Client") == "100", "SAP Client wurde nicht aus URI gelesen.");
|
||||
Assert(
|
||||
endpoint.GetProperty("ApplicationServerHost") == "sap.example.local",
|
||||
"SAP Application Server wurde nicht aus URI gelesen.");
|
||||
Assert(endpoint.GetProperty("UseSnc") == "true", "UseSnc wurde nicht gelesen.");
|
||||
Assert(
|
||||
endpoint.Properties.All(item => !item.Value.Contains("supersecret")),
|
||||
"Binding-Parser ließ ein Kennwort stehen.");
|
||||
Assert(endpoint.SecurityMode == "SAP SNC", "SNC-Modus wurde nicht erkannt.");
|
||||
Assert(endpoint.ApplicationName == "SAP_Order", "Anwendungszuordnung fehlt.");
|
||||
|
||||
var receive = document.SapEndpoints.Single(item => item.Direction == "Empfangen");
|
||||
Assert(receive.AdapterName == "WCF-Custom", "WCF-Custom SAP-Endpunkt fehlt.");
|
||||
Assert(
|
||||
receive.GetProperty("ListenerProgramId") == "BIZTALK_TEST",
|
||||
"Listener Program ID wurde nicht aus URI gelesen.");
|
||||
Assert(
|
||||
receive.GetProperty("DestinationName") == "RFC_TEST",
|
||||
"Destination Name wurde nicht aus URI gelesen.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeleteDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TestDocxPackage()
|
||||
{
|
||||
var directory = CreateTemporaryDirectory();
|
||||
var path = Path.Combine(directory, "self-test.docx");
|
||||
try
|
||||
{
|
||||
var document = new InventoryDocument
|
||||
{
|
||||
EnvironmentName = "<ACC&TEST>",
|
||||
ComputerName = "BIZTALK&01",
|
||||
ToolVersion = "1.0.0.0",
|
||||
StartedUtc = DateTime.UtcNow.AddSeconds(-1),
|
||||
CompletedUtc = DateTime.UtcNow
|
||||
};
|
||||
document.Applications.Add(new ApplicationRecord
|
||||
{
|
||||
Name = "SAP_Order",
|
||||
Status = "Gestartet"
|
||||
});
|
||||
document.SapEndpoints.Add(new SapEndpointRecord
|
||||
{
|
||||
ApplicationName = "SAP_Order",
|
||||
Direction = "Senden",
|
||||
Name = "SAP<Send>",
|
||||
AdapterName = "WCF-SAP",
|
||||
Address = "sap://Client=100@A/sap.example.local/00",
|
||||
SecurityMode = "SAP SNC",
|
||||
CredentialReference = "Kein Kennwort",
|
||||
Source = "Self-Test"
|
||||
});
|
||||
document.SapEndpoints[0].Properties.Add(
|
||||
new NameValueRecord("Password", "supersecret", "Self-Test"));
|
||||
|
||||
new DocxReportWriter().Write(document, path);
|
||||
using (var archive = ZipFile.OpenRead(path))
|
||||
{
|
||||
foreach (var required in new[]
|
||||
{
|
||||
"[Content_Types].xml",
|
||||
"_rels/.rels",
|
||||
"word/document.xml",
|
||||
"word/styles.xml",
|
||||
"word/_rels/document.xml.rels",
|
||||
"docProps/core.xml",
|
||||
"docProps/app.xml"
|
||||
})
|
||||
{
|
||||
Assert(archive.GetEntry(required) != null, "DOCX-Part fehlt: " + required);
|
||||
}
|
||||
|
||||
foreach (var entry in archive.Entries.Where(item =>
|
||||
item.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)
|
||||
|| item.FullName.EndsWith(".rels", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
using (var stream = entry.Open())
|
||||
{
|
||||
XDocument.Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
AssertPackageNamespaces(archive);
|
||||
|
||||
using (var stream = archive.GetEntry("word/document.xml").Open())
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
var xml = reader.ReadToEnd();
|
||||
Assert(!xml.Contains("supersecret"), "DOCX enthält Testkennwort.");
|
||||
Assert(!xml.Contains("<ACC&TEST>"), "Text wurde nicht XML-sicher geschrieben.");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeleteDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertPackageNamespaces(ZipArchive archive)
|
||||
{
|
||||
var contentTypes = LoadEntryXml(archive, "[Content_Types].xml");
|
||||
var contentTypeNamespace = (XNamespace)
|
||||
"http://schemas.openxmlformats.org/package/2006/content-types";
|
||||
Assert(
|
||||
contentTypes.Root.Elements(contentTypeNamespace + "Override").Any(),
|
||||
"Content-Type-Overrides liegen nicht im OPC-Namespace.");
|
||||
|
||||
var relationships = LoadEntryXml(archive, "_rels/.rels");
|
||||
var relationshipNamespace = (XNamespace)
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships";
|
||||
Assert(
|
||||
relationships.Root.Elements(relationshipNamespace + "Relationship").Count() == 3,
|
||||
"Paketbeziehungen liegen nicht vollständig im OPC-Namespace.");
|
||||
}
|
||||
|
||||
private static XDocument LoadEntryXml(ZipArchive archive, string entryName)
|
||||
{
|
||||
using (var stream = archive.GetEntry(entryName).Open())
|
||||
{
|
||||
return XDocument.Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private static XDocument CreateSampleBinding()
|
||||
{
|
||||
return XDocument.Parse(
|
||||
@"<BindingInfo>
|
||||
<ModuleRefCollection>
|
||||
<ModuleRef Name=""[Application:SAP_Order]"" />
|
||||
</ModuleRefCollection>
|
||||
<SendPortCollection>
|
||||
<SendPort Name=""Send_SAP_Order"">
|
||||
<ApplicationName>SAP_Order</ApplicationName>
|
||||
<PrimaryTransport>
|
||||
<Address>sap://Client=100;lang=DE@A/sap.example.local/00?GwHost=gateway.example.local&GwServ=sapgw00</Address>
|
||||
<TransportType Name=""WCF-SAP"" />
|
||||
<SendHandler Name=""Snd_SAP_ERP_C1_64_B"" />
|
||||
<TransportTypeData><CustomProps>
|
||||
<BindingType vt=""8"">sapBinding</BindingType>
|
||||
<BindingConfiguration vt=""8"">&lt;binding name=""sapBinding"" useSnc=""true"" sncLibrary=""C:\SAP\sapcrypto.dll"" sncPartnerName=""p:SAP/ERP"" /&gt;</BindingConfiguration>
|
||||
<Password vt=""8"">supersecret</Password>
|
||||
<Action vt=""8"">http://Microsoft.LobServices.Sap/2007/03/Idoc/3/ORDERS05</Action>
|
||||
</CustomProps></TransportTypeData>
|
||||
</PrimaryTransport>
|
||||
</SendPort>
|
||||
</SendPortCollection>
|
||||
<ReceivePortCollection>
|
||||
<ReceivePort Name=""Receive_SAP_Order"">
|
||||
<ApplicationName>SAP_Order</ApplicationName>
|
||||
<ReceiveLocations>
|
||||
<ReceiveLocation Name=""ReceiveLocation_SAP_Order"" Enable=""true"">
|
||||
<Address>sap://Client=100;lang=DE@D/RFC_TEST?ListenerGwHost=gateway.example.local&ListenerGwServ=sapgw00&ListenerProgramId=BIZTALK_TEST</Address>
|
||||
<ReceiveLocationTransportType Name=""WCF-Custom"" />
|
||||
<ReceiveHandler Name=""Rcv_SAP_ERP_C1_64_B"" />
|
||||
<ReceiveLocationTransportTypeData>
|
||||
<CustomProps>
|
||||
<BindingType vt=""8"">sapBinding</BindingType>
|
||||
<BindingConfiguration vt=""8""><binding name=""sapBinding"" useSnc=""true"" sncLibrary=""C:\SAP\sapcrypto.dll"" sncPartnerName=""p:SAP/ERP"" /></BindingConfiguration>
|
||||
</CustomProps>
|
||||
</ReceiveLocationTransportTypeData>
|
||||
</ReceiveLocation>
|
||||
</ReceiveLocations>
|
||||
</ReceivePort>
|
||||
</ReceivePortCollection>
|
||||
</BindingInfo>");
|
||||
}
|
||||
|
||||
private static string CreateTemporaryDirectory()
|
||||
{
|
||||
var path = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"BizTalkSapInventoryTests-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void DeleteDirectory(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void Assert(bool condition, string message)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user