Separate BizTalk collection from Checkmk agent

This commit is contained in:
2026-07-30 15:50:42 +02:00
parent e3d7f6a780
commit d15bb6539b
22 changed files with 2051 additions and 988 deletions
+480 -335
View File
@@ -1,110 +1,180 @@
# Dokumentation: BizTalk Checkmk Pulse # Technische Dokumentation: BizTalk Checkmk Pulse
## Zielbild ## 1. Ziel und Randbedingungen
Ziel ist ein wartbares Monitoring fuer BizTalk Server 2020 in den Umgebungen `ACC`, `DEV`, `TST` und `PRD`. Jede Umgebung besitzt einen BizTalk Server 2020 und einen SQL Server. SQL Server wird mit dem Checkmk-eigenen MSSQL-Plugin ueberwacht; fuer BizTalk liefert dieses Projekt die fehlende fachliche und technische Laufzeitsicht. Die Anwendung ueberwacht BizTalk Server 2020 auf Windows Server 2019 mit
Checkmk 2.4. Pro Umgebung (`ACC`, `DEV`, `TST`, `PRD`) wird lokal auf dem
BizTalk-Server gesammelt. Das Checkmk-MSSQL-Plugin bleibt fuer die allgemeine
SQL-Ueberwachung zustaendig; BizTalk Checkmk Pulse liefert die
BizTalk-spezifische Laufzeit- und Berechtigungssicht.
Das Monitoring soll: Verbindliche Randbedingungen:
- auf jeder BizTalk-Maschine lokal laufen - Der Checkmk Windows Agent bleibt `LocalSystem`.
- ohne PowerShell-Abhaengigkeit funktionieren - `LocalSystem` erhaelt keine BizTalk-/SQL-Gruppenmitgliedschaft.
- keine BizTalk-DLLs im Build erzwingen - Ein separates Dienstkonto oder bevorzugt gMSA sammelt minuetlich.
- Checkmk-2.4-kompatible Services und Metriken erzeugen - Der Agentpfad fuehrt niemals WMI- oder SQL-Abfragen aus.
- service-discovery-freundlich und dashboard-tauglich sein - Fehler muessen als gueltige Checkmk-`UNKNOWN`-Services sichtbar werden.
- bei Fehlern gueltige `UNKNOWN`-Services statt kaputter Agent-Ausgaben liefern - Schreiben und Lesen duerfen nie einen halben Snapshot exponieren.
- Laufzeitcode benoetigt keine PowerShell und keine BizTalk-DLL-Referenz.
- Installation, ACL und Scheduled Task muessen reproduzierbar sein.
## Technische Bewertung ## 2. Ausloeser der Architekturanderung
### Option A: Checkmk Local Check mit C#/.NET Framework Der ACC-Agent-Dump vom 29.07.2026 bewies:
Bewertung: empfohlen und umgesetzt.
Vorteile:
- Checkmk 2.4 unterstuetzt Local Checks direkt.
- Windows Server mit BizTalk 2020 bringt .NET Framework in der Regel passend mit.
- `System.Management` kann BizTalk-WMI lesen.
- Keine PowerShell Execution Policy, keine Script-Signing-Frage.
- Kein serverseitiges Checkmk-Python-Plugin notwendig.
- Rollout ist eine einfache Dateiablage unter `%ProgramData%\checkmk\agent\local`.
Nachteile:
- Schwellwerte sind in der `.exe.config`, nicht als Checkmk-Regelsatz in WATO.
- Eigene Graphing-Definitionen sind nicht enthalten; Checkmk zeigt Local-Check-Metriken trotzdem als Performance-Daten und Graphen.
### Option B: Agent Plugin plus serverseitiges Checkmk-Plugin
Bewertung: technisch elegant, aber fuer den ersten produktiven Schritt schwerer.
Vorteile:
- Checkmk-Regeln, Discovery und Metrikdefinitionen koennen sauber zentral modelliert werden.
- Bessere langfristige Erweiterbarkeit als MKP.
Nachteile:
- Checkmk-Check-API-Versionen muessen enger gepflegt werden.
- Server-seitige Installation in jeder Site erforderlich.
- Mehr Aufwand fuer Managed-Services-Betrieb und Updates.
Empfehlung: als Version 2 dieses Projekts denkbar, wenn die Local-Check-Variante stabil in PRD laeuft und zentrale Regelsaetze wirklich benoetigt werden.
### Option C: PowerShell Local Check
Bewertung: nicht empfohlen fuer diese Umgebung.
Vorteile:
- Schnell zu schreiben.
- WMI/CIM-Zugriff ist komfortabel.
Nachteile:
- PowerShell ist in vielen Serverumgebungen eingeschraenkt oder signaturpflichtig.
- Ausfuehrungsverhalten im Checkmk-Agent-Kontext ist haeufiger fehleranfaellig.
### Option D: BizTalk ExplorerOM/OperationsOM
Bewertung: fachlich stark, deployseitig unnoetig schwer.
Vorteile:
- Hoehere BizTalk-Abstraktion als rohe WMI-Klassen.
Nachteile:
- BizTalk-DLL-Versionen muessen beim Build und teilweise zur Laufzeit passen.
- Build-Agenten brauchen BizTalk-Komponenten oder SDK-Dateien.
- Fuer die benoetigten Zustandsdaten reicht WMI aus.
## Architektur
```text ```text
Checkmk Windows Agent Lokaler Prozess: NT AUTHORITY\SYSTEM
| Netzwerkprincipal: BEW\AV23AGPWBIO1$
| startet lokale Checks aus %ProgramData%\checkmk\agent\local Providerfehler: COMException 0x80131904
v Innerer Fehler: Login failed for user 'BEW\AV23AGPWBIO1$'
biztalk_checkmk_pulse.cmd
|
| startet
v
BizTalkCheckmkPulse.exe
|
| liest lokal und prueft im gleichen Sicherheitskontext
+-- WMI root\MicrosoftBizTalkServer
+-- Windows Application Event Log
+-- SQL-Verbindung zu BizTalkMgmtDb/BizTalkMsgBoxDb
|
v
Checkmk Local Check Zeilen nach STDOUT
``` ```
Der `.cmd`-Wrapper liefert auch dann fuer alle sechs stabilen Services gueltige `UNKNOWN`-Zeilen mit Massnahme, wenn die EXE fehlt oder bereits der Prozessstart mit einem Exitcode fehlschlaegt. Die EXE selbst faengt Laufzeitfehler ab und schreibt ebenfalls fuer alle stabilen Services `UNKNOWN`, damit unvollstaendige Deployments oder WMI-Probleme in Checkmk sichtbar bleiben. Lokales `root\MicrosoftBizTalkServer` war erreichbar. Die
datenbankgestuetzten BizTalk-WMI-Klassen scheiterten erst beim Zugriff des
Providers auf den getrennten SQL Server. DCOM-, Firewall- oder pauschale
WMI-ACL-Aenderungen beheben diesen Befund nicht.
## Datenquellen Eine Berechtigung des Maschinenkontos haette die Netzwerkrolle allen
`LocalSystem`-Diensten des BizTalk-Servers vermittelt. Die jetzt implementierte
Producer/Consumer-Trennung beschraenkt sie stattdessen auf ein dediziertes
Konto.
### BizTalk WMI Namespace ## 3. Architektur
```text
Privilegierte Zone
┌──────────────────────────────────────────────────────────────┐
│ Task Scheduler: "BizTalk Checkmk Pulse Provider" │
│ Konto: DOMAIN\svc_biztalk_cmk$ (gMSA empfohlen) │
│ Intervall: 1 Minute, IgnoreNew, Laufzeitlimit 5 Minuten │
│ │
│ BizTalkCheckmkPulse.exe --collect │
│ ├─ WMI root\MicrosoftBizTalkServer │
│ ├─ integrierter SQL-Test an MgmtDb/MsgBoxDb │
│ ├─ lokales Windows Application Event Log │
│ ├─ Checkmk-Formatierung │
│ └─ atomare Snapshot-Publikation + Provider-Log │
└──────────────────────────────┬───────────────────────────────┘
│ Datei/ACL
v
%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot
v
┌──────────────────────────────────────────────────────────────┐
│ Unprivilegierter Transportpfad │
│ Checkmk-Agent (LocalSystem) │
│ └─ local\biztalk_checkmk_pulse.cmd │
│ └─ BizTalkCheckmkPulse.exe --consume │
│ ├─ Format/Alter/Maschine/SHA-256 validieren │
│ └─ Payload unveraendert nach STDOUT │
└──────────────────────────────────────────────────────────────┘
```
Eine gemeinsame EXE implementiert beide Modi. Das reduziert
Versionsabweichungen. Die zentrale Config unter `%ProgramFiles%` gilt fuer
Provider und Consumer.
### 3.1 Provider-Modus
Aufruf:
```cmd
"%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --collect
```
Ablauf:
1. Config laden und absolute Runtimepfade validieren.
2. Tageslogs gemaess `LogRetentionDays` bereinigen.
3. exklusives Handle auf `<SnapshotPath>.provider.lock` halten.
4. WMI-, SQL- und Event-Log-Probes ausfuehren.
5. sechs stabile und optionale dynamische Checkmk-Zeilen formatieren.
6. Snapshot in einer eindeutigen Temporaerdatei desselben Verzeichnisses
schreiben.
7. `Flush(true)` ausfuehren und Temporaerdatei atomar publizieren.
8. Laufzeit, Zeilen- und Diagnosezahl loggen.
Exitcodes:
| Code | Bedeutung |
| --- | --- |
| `0` | Sammlung und Snapshot-Publikation erfolgreich. |
| `1` | unerwarteter Providerfehler; aktueller `UNKNOWN`-Snapshot wird versucht. |
| `2` | Parallelstart oder Snapshot-I/O-Fehler. |
Fachliche WMI-/SQL-Fehler werden strukturiert in den Snapshot geschrieben und
muessen nicht zwingend einen Prozessfehler verursachen. So bleibt die genaue
Fehlerkategorie in Checkmk sichtbar.
### 3.2 Consumer-Modus
Aufruf:
```cmd
"%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
```
Ohne Modusargument ist `--consume` der sichere Default. Der Checkmk-Wrapper
setzt es trotzdem explizit.
Der Consumer:
- oeffnet nur den Snapshot,
- begrenzt die Dateigroesse vor dem Einlesen,
- validiert striktes UTF-8,
- validiert Formatversion und Header,
- fordert die lokale Maschinenkennung,
- berechnet SHA-256 ueber den exakten Payload,
- prueft Zeilenanzahl und Checkmk-Zeilenform,
- verwirft Zeitstempel mehr als fuenf Minuten in der Zukunft,
- verwirft Snapshots nach `SnapshotMaxAgeSeconds`,
- gibt bei Erfolg ausschliesslich den validierten Payload aus.
Er gibt immer Exitcode `0` zurueck, damit ein Fehler die komplette
Checkmk-Agentsektion nicht zerstoert. Jede Ablehnung erzeugt sechs
`UNKNOWN`-Zeilen und einen Eintrag im Consumer-Log.
## 4. Snapshot-Vertrag
Version 1:
```text
BIZTALK_CHECKMK_PULSE_SNAPSHOT_V1
generatedUtc=2026-07-30T12:34:56.1234567Z
machineBase64=QVYyM0FHUFdCSU8x
identityBase64=QkVXXHN2Y19iaXp0YWxrX2NtayQ=
payloadLines=6
payloadSha256=<64 hex characters>
0 "BizTalk Platform" - ...
...
```
Maschine und Identitaet sind Base64-kodiert, um Zeilenumbrueche und
Trennzeichen im Header auszuschliessen. Base64 ist hier keine Verschluesselung.
Die SHA-256-Pruefung schuetzt vor unbemerkter Teil-/Fehlschreibung, nicht vor
einem lokalen Administrator.
### 4.1 Atomare Publikation
Die Temporaerdatei liegt zwingend im Snapshotverzeichnis. Dadurch findet der
Ersatz auf demselben Volume statt. Existiert bereits ein Snapshot, wird
`File.Replace` verwendet; beim ersten Lauf `File.Move`. Bis zum erfolgreichen
Ersatz bleibt der letzte vollstaendige Snapshot erhalten. Seine Alterspruefung
verhindert unbegrenztes Weiterreichen alter Daten.
### 4.2 Keine direkte Checkmk-Spooldatei
Checkmk kann zwar Dateien aus seinem Spoolverzeichnis direkt anhaengen, prueft
deren Inhalt aber laut eigener Dokumentation nicht. Ein fehlerhafter Spool kann
nachfolgende Agentsektionen beeinflussen. Deshalb liegt der Provider-Snapshot
in einem eigenen Verzeichnis und wird erst durch den validierenden Consumer in
die Local-Check-Ausgabe uebernommen.
## 5. Datenquellen
### 5.1 BizTalk WMI
Namespace: Namespace:
@@ -112,286 +182,361 @@ Namespace:
root\MicrosoftBizTalkServer root\MicrosoftBizTalkServer
``` ```
Genutzte Klassen: Pflichtklassen:
| Klasse | Zweck | | Klasse | Zweck |
| --- | --- | | --- | --- |
| `MSBTS_GroupSetting` | BizTalk-Gruppe, Management-DB und Master-MessageBox (`SubscriptionDB*`). | | `MSBTS_GroupSetting` | Gruppe, Read-Only-/Operator-Gruppe, Management- und Master-MessageBox-Ziel. |
| `MSBTS_HostInstance` | Host-Instance-Zustand; Ergebnis wird clientseitig auf den ueberwachten Server begrenzt. | | `MSBTS_HostInstance` | lokale Host-Instance-Zustaende. |
| `MSBTS_ServiceInstance` | Suspended service instances. | | `MSBTS_ServiceInstance` | suspendierte Instanzen. |
| `MSBTS_ReceiveLocation` | Receive-Location-Zustand. | | `MSBTS_ReceiveLocation` | Receive-Location-Zustaende. |
| `MSBTS_SendPort` | Send-Port-Zustand. | | `MSBTS_SendPort` | Send-Port-Zustaende. |
| `MSBTS_Orchestration` | Orchestration-Zustand. | | `MSBTS_Orchestration` | Orchestration-Zustaende. |
| `MSBTS_ReceivePort` | Optionales Best-Effort-Application-Mapping, nur wenn per-Application-Services aktiviert sind. |
Die Plattformabfrage verwendet absichtlich keine vermeintliche Klasse `MSBTS_MessageBoxSetting`: Sie ist nicht Bestandteil des dokumentierten BizTalk-WMI-Schemas. Auch die vorhandene Klasse `MSBTS_MsgBoxSetting` ist fuer die Zielermittlung nicht erforderlich. `MSBTS_GroupSetting` liefert mit `SubscriptionDBServerName` und `SubscriptionDBName` bereits das Ziel der Master-MessageBox. Dadurch entfallen eine Providerabfrage und eine unnoetige Berechtigungs-/Schemaschnittstelle. `MSBTS_GroupSetting` wird mit den dokumentierten Properties
`MgmtDbServerName`, `MgmtDbName`, `SubscriptionDBServerName`,
`SubscriptionDBName`, `BizTalkReadOnlyUserGroup` und
`BizTalkOperatorGroup` gelesen. Eine nicht dokumentierte
`MSBTS_MessageBoxSetting`-Klasse wird nicht abgefragt.
### Windows Application Event Log Hostnamen werden clientseitig verglichen. Sonderzeichen und FQDN-/Kurzname
gelangen nicht in dynamisch erzeugte WQL-Filter.
Der Check liest standardmaessig das lokale Application Log fuer die letzten 60 Minuten und filtert auf Quellen wie: ### 5.2 SQL-Zugriffsprobe
- `BizTalk Server` Der Provider oeffnet fuer Management- und Master-MessageBox-Datenbank eine
- `XLANG/s` kurze `System.Data.SqlClient`-Verbindung mit integrierter
- `ENTSSO` Windows-Authentifizierung, fuehrt `SELECT 1` aus und schliesst sie. Keine
- `BizTalk Server Application` BizTalk-Tabelle wird direkt gelesen oder veraendert.
- `BizTalk Server EDI`
Die Liste ist ueber `EventLogSources` konfigurierbar. Die Ausgabe `execution_identity=` zeigt deshalb das Provider-Konto, nicht mehr
`NT AUTHORITY\SYSTEM`. Der Check beweist, dass genau das Scheduled-Task-Konto
die Ziele erreichen kann.
### SQL-Zugriffsprobe ### 5.3 Event Log
Nach erfolgreicher oder teilweise erfolgreicher Plattformabfrage uebernimmt der SQL-Probe die per `MSBTS_GroupSetting` ermittelten Management- und Master-MessageBox-Ziele. Fuer jedes eindeutige Ziel wird mit `System.Data.SqlClient` eine Verbindung mit integrierter Windows-Authentifizierung geoeffnet und `SELECT 1` ausgefuehrt. Die Verbindung wird unmittelbar danach geschlossen; es werden keine BizTalk-Tabellen gelesen oder veraendert. Der Provider liest das lokale Application Log im konfigurierten Zeitfenster
und filtert Quellen wie `BizTalk Server`, `XLANG/s`, `ENTSSO`,
`BizTalk Server Application` und `BizTalk Server EDI`.
Der Test laeuft unter derselben Identitaet wie der Checkmk Local Check. Damit wird sichtbar, ob `LocalSystem` beziehungsweise das Maschinenkonto des BizTalk-Servers das SQL-Ziel tatsaechlich erreichen und die Datenbank oeffnen kann. Die Probe ist mit `ProbeSqlConnectivity=false` deaktivierbar und verwendet `SqlConnectionTimeoutSeconds` mit dem Default 5 Sekunden je Ziel als Timeout. ## 6. Berechtigungsmodell
Im produktiven Betrieb sollte der komplette Local Check asynchron mit 300 Sekunden Cache ausgefuehrt werden. So fuehren fehlende SQL-Rechte nicht bei jedem Checkmk-Abruf zu weiteren fehlgeschlagenen Login-Ereignissen. Der Trade-off ist eine Zustandsverzoegerung von maximal fuenf Minuten. ### 6.1 Bevorzugte BizTalk-Rolle
## Berechtigungsmodell BizTalk Server 2020 fuehrte die `BizTalk Server Read Only Users`-Gruppe ein.
Sie darf Artefakte, Servicezustand, Message Flow und Trackinginformationen
lesen, aber keine administrativen Operationen ausfuehren. SQL-seitig wird sie
ueber `BTS_READONLY_USERS` vermittelt.
### LocalSystem und lokaler WMI-Zugriff Soll:
Der Checkmk Windows Agent und der Agent Controller laufen standardmaessig als `LocalSystem` (`NT AUTHORITY\SYSTEM`). Der Wrapper und die EXE erben diesen Kontext. Das Plugin verbindet sich lokal mit `\\<eigener-server>\root\MicrosoftBizTalkServer`, setzt keine eigenen Anmeldedaten, nutzt kein Remote-WMI und fuehrt keine veraendernden WMI-Methoden aus. Das lokale Windows Application Event Log wird ebenfalls nur gelesen.
Die lokalen Rechte von `LocalSystem` reichen fuer diese Zugriffe normalerweise aus. Im regulaeren lokalen Betrieb werden deshalb keine zusaetzlichen DCOM-, Firewall- oder pauschalen WMI-Namespace-Freigaben benoetigt.
### Netzwerkidentitaet zum SQL Server
BizTalk-WMI-Klassen koennen ihre Daten aus der BizTalk Management- oder MessageBox-Datenbank beziehen. Liegt SQL Server auf einer anderen Maschine, authentifiziert sich `LocalSystem` dort mit dem Active-Directory-Computerkonto des BizTalk-Servers:
```text ```text
DOMAIN\BIZTALKSERVER$ DOMAIN\svc_biztalk_cmk$
-> konfigurierte BizTalk Server Read Only Users-Domaingruppe
-> vorhandener Windows-Gruppenlogin am SQL Server
-> BTS_READONLY_USERS in den von BizTalk konfigurierten Datenbanken
``` ```
Dieses Konto besitzt nicht automatisch BizTalk- oder SQL-Berechtigungen. Daraus kann die Situation entstehen, dass die Verbindung zum lokalen WMI-Namespace erfolgreich ist, einzelne SQL-gestuetzte WMI-Klassen aber `Access denied`, `UnauthorizedAccessException` oder `UNKNOWN` liefern. Die genaue Gruppe ist aus
`MSBTS_GroupSetting.BizTalkReadOnlyUserGroup` beziehungsweise der
BizTalk-Administration zu lesen. Nicht vom Standardnamen ausgehen.
Der ACC-Test vom 29.07.2026 bestaetigt genau diesen Pfad: Auf `AV23AGPWBIO1` ist WMI erreichbar, aber der Provider meldet `COMException 0x80131904` mit `Login failed for user 'BEW\AV23AGPWBIO1$'`. Die `UNKNOWN`-Services fuer Plattform, Host Instances, Runtime Artifacts und Suspensions sowie die fehlende SQL-Zielermittlung sind Folgefehler. DCOM-, Firewall- oder WMI-ACL-Erweiterungen beheben diesen konkreten SQL-Loginfehler nicht. Der Event-Log-Check arbeitet bereits und seine Fehler/Warnungen sind separat zu bewerten. Die Operator-Gruppe (`BTS_OPERATORS`) wird erst geprueft, wenn eine konkret
benannte erforderliche WMI-Klasse trotz nachgewiesener Read-Only-Konfiguration
und neuem Anmeldetoken abgewiesen wird. Die Erweiterung muss mit Security,
BizTalk- und SQL-Administration begruendet werden.
Die Codekorrektur ersetzt die Berechtigungsfreigabe nicht. Sie klassifiziert den Fehler korrekt, zeigt das abgewiesene Konto und fuehrt die SQL-Folgediagnose als Berechtigungsfehler. Sie veraendert weder AD noch SQL Server. Daher bleibt die neue Version ohne Gruppenfreigabe `UNKNOWN`; umgekehrt kann auch die alte Version nach korrekter Freigabe grundsaetzlich auf die Daten zugreifen. Unzulaessig:
### Pruefung und Freigabe - Checkmk-Dienstkonto aendern,
- Maschinenkonto pauschal berechtigen,
- individueller SQL-Login fuer das Provider-Konto,
- direkte ad-hoc DB-Rollen,
- `db_owner` oder `sysadmin`,
- WMI-/DCOM-Vollzugriff als Reaktion auf einen SQL-Loginfehler.
Der direkte Programmstart in einer administrativen Shell laeuft unter dem angemeldeten Benutzer und ist deshalb kein ausreichender Berechtigungstest. Verbindlich ist die Ausfuehrung durch den Checkmk Agent Controller als `LocalSystem`: ### 6.2 Dateisystem-ACL
Der Installer entfernt die Vererbung an den Runtimeverzeichnissen und setzt
explizite Eintraege mit sprachneutralen Well-known-SIDs:
| Ziel | Collector | SYSTEM | lokale Administratoren |
| --- | --- | --- | --- |
| Runtime-Root | RX | RX | F |
| `data` | Modify | RX | F |
| `logs` | Modify | Modify | F |
`LocalSystem` kann deshalb den Snapshot lesen, aber im Datenverzeichnis nicht
schreiben. Schreibrecht im Logverzeichnis wird benoetigt, um abgelehnte
Snapshots aus dem Consumer-Kontext zu protokollieren.
## 7. Scheduled Task
Name:
```text
BizTalk Checkmk Pulse Provider
```
Sollkonfiguration:
| Einstellung | Wert |
| --- | --- |
| Trigger | einmalig, Wiederholung jede Minute ohne Enddatum |
| Programm | `%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe` |
| Argument | `--collect` |
| Arbeitsverzeichnis | `%ProgramFiles%\BizTalkCheckmkPulse` |
| Benutzer | dediziertes Dienstkonto oder gMSA |
| Mehrfachinstanzen | `IgnoreNew` |
| Laufzeitlimit | 5 Minuten |
| StartWhenAvailable | aktiv |
| Restart | zweimal im Minutenabstand |
Der Task wird mit gespeichertem Dienstkontokennwort beziehungsweise gMSA
ausgefuehrt, also unabhaengig von einer interaktiven Anmeldung.
## 8. Installation
### 8.1 Build
```cmd
scripts\test-release.cmd
scripts\package-release.cmd
```
### 8.2 gMSA
Voraussetzungen:
1. gMSA in AD erstellen und Abrufrecht auf den BizTalk-Server begrenzen.
2. Konto lokal installieren und mit `Test-ADServiceAccount` pruefen.
3. gMSA in die exakt konfigurierte BizTalk-Read-Only-Gruppe aufnehmen.
4. AD-Replikation abwarten.
Installation:
```powershell ```powershell
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump | .\Install-BizTalkCheckmkPulse.ps1 `
Select-String -Pattern "BizTalk|Access denied|Unauthorized|UNKNOWN" -Context 0,1 -CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
``` ```
Bei Zugriffsfehlern gilt folgendes Least-Privilege-Vorgehen: ### 8.3 regulaeres Dienstkonto
1. Die exakt konfigurierte Operator-Gruppe in der BizTalk Administration Console unter `BizTalk Group` > `Properties` > `General` feststellen. ```powershell
2. Das Computerobjekt des BizTalk-Servers durch einen AD-Administrator in genau diese Gruppe aufnehmen. Fuer ACC ist dies `AV23AGPWBIO1` beziehungsweise `BEW\AV23AGPWBIO1$`. .\Install-BizTalkCheckmkPulse.ps1 `
3. Keine direkten SQL-Logins, BizTalk-Datenbankrollen oder `sysadmin`-Rechte fuer das Maschinenkonto anlegen. -CollectorAccount 'BEW\svc_biztalk_cmk' `
4. AD-Replikation abwarten und den Server im Wartungsfenster neu starten; alternativ Maschinen-Tickets mit `klist purge -li 0x3e7` und den Checkmk-Dienst erneuern. -EnvironmentName ACC
5. Den Agent-Dump wiederholen und `operator_group`, zwei SQL-Ziele sowie das Verschwinden der Berechtigungs-`UNKNOWN`s kontrollieren. ```
6. Nur fuer weiterhin abgelehnte, konkret identifizierte WMI-Klassen mit BizTalk- und SQL-Administration pruefen, ob Administratorrechte erforderlich sind.
Die Operator-Rolle ist fuer grundlegendes Monitoring und Zustandsabfragen vorgesehen. Direkte manuelle Aenderungen an den Rollen der BizTalk-SQL-Datenbanken sind zu vermeiden; die durch BizTalk konfigurierte Windows-Gruppe ist die vorgesehene Berechtigungsgrenze. Der Installer muss als lokaler Administrator laufen. Er vergibt keine
AD-/BizTalk-/SQL-Rechte; diese bleiben getrennte administrative Freigaben.
Im Normalfall ist diese Windows-Gruppe bereits als SQL-Gruppenlogin und als Datenbankbenutzer eingerichtet. Ihre Benutzerzuordnung vermittelt `BTS_OPERATORS` unter anderem in `BizTalkMgmtDb` und `BizTalkMsgBoxDb`. `BEW\AV23AGPWBIO1$` erhaelt den Zugriff durch die AD-Gruppenmitgliedschaft und braucht keinen eigenen SQL-Login. ### 8.4 Checkmk
Bleibt der Fehler trotz bestaetigter Gruppenmitgliedschaft, AD-Replikation und erneuertem Maschinen-Token bestehen, pruefen BizTalk- und SQL-Administration: Der installierte Wrapper liegt unter:
1. Ist die exakt konfigurierte Domain-Gruppe in `sys.server_principals` als Windows-Gruppe vorhanden? ```text
2. Existiert ihr Datenbankbenutzer in `BizTalkMgmtDb` und `BizTalkMsgBoxDb`? %ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
3. Ist dieser Benutzer in beiden Datenbanken Mitglied von `BTS_OPERATORS`? ```
4. Entspricht diese Abbildung weiterhin der BizTalk-Konfiguration?
Fehlende Zuordnungen werden fuer die konfigurierte Gruppe konsistent repariert. Ein individueller Login, manuelle Sonderrollen oder `sysadmin` fuer das Maschinenkonto waeren keine geeignete Abkuerzung. Der Agent darf den Consumer synchron ausfuehren. Er liest nur maximal 1 MiB
lokale Daten; ein Checkmk-Async-Cache ist daher nicht notwendig. Falls alte
`check_mk.user.yml`-Regeln fuer den bisherigen 300-Sekunden-Cache existieren,
sollten sie entfernt werden, damit Checkmk den minuetlich aktualisierten
Snapshot nicht zusaetzlich verzoegert.
| Agent-Dump-Ergebnis | Massnahme | Nach dem Agent-Dump:
| --- | --- |
| Plausible Werte fuer alle BizTalk-Services | Keine Berechtigungsaenderung. |
| WMI-Namespace nicht erreichbar | BizTalk-WMI-Provider, WMI-Dienst, Namespace und ACL gezielt pruefen. |
| Nur SQL-gestuetzte Klassen scheitern | Computerkonto in die BizTalk-Operator-Gruppe aufnehmen. |
| Nur Event-Log-Service ist `UNKNOWN` | Lokalen Application-Log-Zugriff pruefen. |
| Fehler bleibt mit Operator-Rolle bestehen | Betroffene Klasse und konkrete BizTalk-/SQL-Rollenanforderung untersuchen. |
### Automatische Diagnose im Programm 1. Service Discovery fuer den BizTalk-Host ausfuehren.
2. sechs stabile Services aufnehmen.
3. Changes aktivieren.
4. Views/Benachrichtigungen nach Umgebung konfigurieren.
WMI-Verbindungsaufbau und jede erforderliche WMI-Klasse werden separat bewertet. Erwartbare Exceptions werden in folgende Kategorien eingeordnet: ## 9. Logging
| Diagnose | Bedeutung | Ausgegebene Massnahme | Pfad:
```text
%ProgramData%\BizTalkCheckmkPulse\logs\
biztalk-checkmk-pulse-YYYYMMDD.log
```
Jede Zeile enthaelt UTC-Zeit, Level, Komponente, PID, Windows-Identitaet und
einzeilige Nachricht. Provider-Erfolge werden pro Lauf geloggt; der Consumer
loggt nur abgelehnte Snapshots. Exceptions werden mit Typ, Nachricht,
HRESULT-/Providerdetails und Stacktrace einzeilig gespeichert.
Logging ist best effort: Ein blockiertes Log darf Checkmk-Ausgabe oder
Snapshot-Publikation nicht zerstoeren. Der Provider entfernt beim Start Dateien
aelter als `LogRetentionDays`.
## 10. Diagnose und Runbook
### 10.1 Grundpruefung
```powershell
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
Get-Item "$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
Get-Content "$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" -Tail 100
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```
### 10.2 Kein Snapshot
Pruefen:
1. Task existiert und ist aktiviert.
2. `LastTaskResult` und Provider-Log.
3. Dienstkonto kann sich als Batch anmelden.
4. gMSA ist lokal installiert und abrufbar.
5. Provider besitzt Modify auf `data` und `logs`.
6. EXE/Config sind ausfuehrbar.
### 10.3 Staler Snapshot
Der Consumer nennt Alter und Grenzwert. Ursachen:
- Task deaktiviert oder Konto/Kennwort ungueltig,
- vorheriger Lauf haengt,
- WMI-/SQL-Timeouts,
- Task Scheduler lief waehrend Wartung nicht,
- Snapshot-Publikation wird durch AV/EDR oder ACL blockiert.
Nicht einfach `SnapshotMaxAgeSeconds` stark erhoehen. Zuerst den minuetlichen
Lauf wiederherstellen. Bei geplanten Wartungen darf Checkmk bewusst `UNKNOWN`
anzeigen.
### 10.4 SHA-256-/Formatfehler
Snapshot nicht reparieren oder direkt an Checkmk kopieren. Task neu starten,
Datentraeger und AV/EDR pruefen. Wiederholt sich der Fehler, Temporaerdateien,
NTFS-Ereignisse und Schreibrechte untersuchen.
### 10.5 BizTalk-/SQL-Permission
1. Log zeigt `execution_identity` des Providers.
2. `BizTalkReadOnlyUserGroup` gegen BizTalk-Konfiguration abgleichen.
3. AD-Gruppenmitgliedschaft des Provider-Kontos pruefen.
4. neuen Logontoken durch neuen Tasklauf sicherstellen.
5. SQL-seitig Gruppenlogin und `BTS_READONLY_USERS` read-only pruefen.
6. keine Einzelrechte erteilen.
7. Nur wenn eine spezifische Klasse weiter abgewiesen wird, Read-Only-Umfang
mit Microsoft-Dokumentation und BizTalk-Betrieb abgleichen; Operator-Rolle
ist eine bewusste Eskalation.
### 10.6 WMI-Schema
`InvalidClass` oder `InvalidQuery` wird als `Wmi/Schema` klassifiziert.
Berechtigungserhoehung kann eine nicht vorhandene Klasse nicht erzeugen.
Installierte BizTalk-Version, WQL und Properties pruefen.
## 11. Konfiguration
Alle AppSettings befinden sich in:
```text
%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
```
Runtime:
| Key | Default | Bereich |
| --- | --- | --- | | --- | --- | --- |
| `Wmi/Permission` | Namespace, BizTalk-WMI-Klasse oder deren eingebetteter SQL-Zugriff verweigert den Zugriff. | Namespace-ACL nur bei Verbindungsfehlern; bei `Login failed for user` das genannte Maschinenkonto der BizTalk-Operator-Gruppe zuordnen. | | `SnapshotPath` | `%ProgramData%\BizTalkCheckmkPulse\data\...` | absolut |
| `Wmi/Connectivity` | WMI-/RPC-Ziel nicht erreichbar. | WMI-Dienst, Provider und bei Remote-WMI zusaetzlich DNS/RPC/Firewall pruefen. | | `SnapshotMaxAgeSeconds` | `180` | 60 bis 86400 |
| `Wmi/Timeout` | WMI-Abfrage ueberschreitet `QueryTimeoutSeconds`. | WMI-, BizTalk- und SQL-Auslastung untersuchen, bevor der Timeout erhoeht wird. | | `SnapshotMaxBytes` | `1048576` | 4096 bis 16777216 |
| `Wmi/Configuration` | BizTalk-WMI-Namespace fehlt oder Plattformdaten sind unvollstaendig. | Provider, Namespace und BizTalk-Konfiguration pruefen. | | `LogDirectory` | `%ProgramData%\BizTalkCheckmkPulse\logs` | absolut |
| `Wmi/Schema` | `InvalidClass` oder `InvalidQuery`; Klasse beziehungsweise WQL passt nicht zum installierten Provider. | Klasse/Properties gegen das BizTalk-WMI-Schema pruefen; keine Rechteerhoehung vornehmen. | | `LogRetentionDays` | `30` | 1 bis 365 |
| `Sql/Permission` | Login oder Datenbankzugriff wird abgelehnt. | Maschinenkonto der BizTalk-Operator-Gruppe zuordnen, Kerberos erneuern, keine direkten DB-Rollen vergeben. |
| `Sql/Connectivity` | SQL-Server oder Instanz nicht erreichbar. | Servername, DNS, SQL-Dienst, TCP-Protokoll, Port und Firewall pruefen. |
| `Sql/Timeout` | SQL-Verbindung oder Testabfrage laeuft in den Timeout. | Netzwerk und SQL-Auslastung pruefen; Timeout nur begruendet anheben. |
| `Sql/Configuration` | WMI-Ziele unvollstaendig oder TLS-, Zertifikats-, SPN-/SSPI-Konfiguration fehlerhaft. | Plattformabfragen beziehungsweise Zertifikatskette, Verschluesselung, SPN und Kerberos gezielt pruefen. |
Jede Diagnose enthaelt Bereich/Kategorie, betroffene Komponente, eine kurze Ursache, `Massnahme:` und `Technik:` mit Exception-Typ, HRESULT oder SQL-Fehlernummer. Bei WMI-Abfragefehlern werden zusaetzlich WQL und Laufzeit bis zum Fehler ausgegeben. Erforderliche Datenquellen besitzen eigene Verfuegbarkeitsflags. Schlaegt beispielsweise `MSBTS_ServiceInstance` fehl, wird `BizTalk Suspended Instances` zwingend `UNKNOWN`; eine leere Ergebnisliste darf nicht als 'keine Suspensions' fehlinterpretiert werden. BizTalk-Probes:
Die Operator-Mitgliedschaft des Computerkontos steht allen auf diesem Server als `LocalSystem` laufenden Diensten fuer Netzwerkzugriffe zur Verfuegung. Falls diese Sicherheitsauswirkung nicht akzeptabel ist, kann ein separater Collector unter einem dedizierten gMSA- oder Dienstkonto mit Operator-Rechten Checkmk-Spooldaten erzeugen. Diese Variante ist noch nicht Bestandteil der aktuellen Implementierung. Der komplette Checkmk-Agent sollte nicht allein fuer dieses Plugin auf eine andere Identitaet umgestellt werden, weil dies alle Agent-Sektionen und Local Checks betrifft. | Key | Default |
| --- | --- |
| `Server` | `.` |
| `QueryTimeoutSeconds` | `25` |
| `ProbeSqlConnectivity` | `true` |
| `SqlConnectionTimeoutSeconds` | `5` |
| `ProbeEventLog` | `true` |
| `EventLogLookbackMinutes` | `60` |
Quellen: Alarmierung:
| Key | Default |
| --- | --- |
| `WarnResumableThreshold` | `1` |
| `CritNonResumableThreshold` | `1` |
| `AlertOnArtifactRuntimeIssues` | `false` |
| `EmitPerApplicationSuspensionServices` | `false` |
| `EventLogWarnThreshold` | `1` |
| `EventLogCritThreshold` | `10` |
## 12. Tests und Abnahmekriterien
Automatisiert:
- Release-Build .NET Framework 4.7.2,
- exakt sechs Self-Test-Services,
- unbekannte Quellen werden `UNKNOWN`,
- dynamische Anwendungsservices nur bei bekannter Anwendung,
- WMI-Queryvertrag nutzt dokumentierte Properties,
- eingebetteter SQL-Loginfehler wird `Permission`,
- abgewiesener Principal wird extrahiert,
- Snapshot-Roundtrip und Ersatz,
- SHA-256-Manipulation wird verworfen,
- Stale-Snapshot wird verworfen,
- Consumer-Fallback enthaelt sechs `UNKNOWN`-Services.
Windows-/ACC-Abnahme:
1. `LastTaskResult=0`.
2. Snapshot wird mindestens dreimal minuetlich aktualisiert.
3. Provider-Log nennt das dedizierte Konto.
4. `BizTalk Platform` zeigt `read_only_group=`.
5. `BizTalk SQL Access`: `targets=2`, `available=2`.
6. keine Permission-`UNKNOWN`s.
7. ACL-Test: `LocalSystem` kann Snapshot lesen, nicht schreiben.
8. Task deaktivieren: nach 180 Sekunden sechs stale-`UNKNOWN`s.
9. Task wieder aktivieren: naechster Snapshot stellt Echtzustand her.
10. Agent-Dump und Checkmk Service Discovery erfolgreich.
Erst nach ACC-Abnahme erfolgt der gestufte Rollout nach DEV/TST/PRD.
## 13. Deinstallation und Rollback
```powershell
.\Uninstall-BizTalkCheckmkPulse.ps1
```
`-KeepRuntimeData` erhaelt Logs und Snapshot. Das Skript entfernt Task,
Wrapper und Programmdateien, aber keine AD-Gruppenmitgliedschaft. Die
Berechtigung wird separat nach Change-Verfahren entzogen.
Rollback auf die alte direkte `LocalSystem`-Architektur ist nicht empfohlen.
Falls kurzfristig notwendig, muss Security die Maschinenkonto-Berechtigung
explizit freigeben; der Checkmk-Agentdienst selbst darf nicht auf das
Collector-Konto umgestellt werden.
## 14. Transport per certutil
Die Source-Uebergabe besteht aus ZIP plus Base64-TXT:
```cmd
certutil -decode <datei>.zip.b64.txt <datei>.zip
certutil -hashfile <datei>.zip SHA256
tar -xf <datei>.zip
```
ZIP und Base64 werden erst nach dem Git-Commit aus genau diesem Commit erzeugt.
## 15. Referenzen
- https://learn.microsoft.com/en-us/biztalk/core/managing-biztalk-server-security
- https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-biztalkreadonlyusergroup-property-wmi
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi
- https://docs.checkmk.com/latest/en/agent_windows.html - https://docs.checkmk.com/latest/en/agent_windows.html
- https://docs.checkmk.com/latest/en/localchecks.html - https://docs.checkmk.com/latest/en/localchecks.html
- https://learn.microsoft.com/en-us/biztalk/core/minimum-security-user-rights - https://docs.checkmk.com/latest/en/spool_directory.html
- https://learn.microsoft.com/en-us/biztalk/core/access-control-and-data-security
- https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
- https://learn.microsoft.com/en-us/biztalk/core/access-control-for-administrative-roles
- https://learn.microsoft.com/en-us/biztalk/core/how-to-modify-group-properties
- https://learn.microsoft.com/en-us/entra/architecture/service-accounts-computer
- https://learn.microsoft.com/en-us/powershell/module/activedirectory/add-adgroupmember
- https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/klist
- https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces
## Resilienz
Das Plugin ist bewusst defensiv gebaut:
- Der dependency-freie Regressionstest laeuft mit dem gleichen .NET-Framework-/MSBuild-Baseline wie die Anwendung.
- WMI-Queries haben ein konfigurierbares Timeout.
- WMI-Namespace und Pflichtklassen werden getrennt auf Berechtigung, Konfiguration, Erreichbarkeit und Timeout geprueft.
- Management- und Master-MessageBox-Datenbank werden aus einer dokumentierten WMI-Klasse ermittelt und mit der echten Agent-Identitaet getestet.
- `InvalidClass`/`InvalidQuery` werden als Schemafehler statt als Berechtigungsproblem ausgewiesen.
- Host Instances werden ohne namensabhaengigen WQL-Filter clientseitig auf den ueberwachten Server begrenzt.
- Bereits fuer Runtime-Zustaende gelesene Artefakte werden fuer das Best-Effort-Application-Mapping wiederverwendet; doppelte WMI-Abfragen entfallen.
- SQL-Fehlernummern werden in Berechtigung, Erreichbarkeit, Timeout oder Providerfehler klassifiziert.
- Fehlgeschlagene Pflichtabfragen erzeugen `UNKNOWN` statt irrefuehrender Nullwerte.
- Optionale WMI-Klassen erzeugen Diagnosehinweise statt Totalabbruch.
- Fehlende Properties werden als leer/unknown behandelt.
- Local-Check-Ausgaben verwenden gueltige Checkmk-Zeilen mit genau vier Feldern.
- Fatal Errors erzeugen `UNKNOWN` fuer alle stabilen Services.
- Event-Log-Auswertung ist isoliert; ein Fehler dort bricht WMI-Monitoring nicht ab.
- Service-Namen sind stabil, damit Service Discovery nicht bei jedem Lauf neue Services erzeugt.
## Statusmodell
### BizTalk Platform
- `OK`: BizTalk-WMI erreichbar.
- `UNKNOWN`: WMI nicht erreichbar oder kompletter Programmfehler.
Dieser Service ist der Integrationsindikator. Wenn er `UNKNOWN` ist, sind Berechtigungen, BizTalk-Installation oder WMI-Repository zu pruefen.
### BizTalk SQL Access
- `OK`: Datenbankziele vollstaendig ermittelt und integrierte Anmeldung an allen Zielen erfolgreich.
- `UNKNOWN`: Zielermittlung unvollstaendig, Anmeldung verweigert, SQL nicht erreichbar, Timeout oder Providerfehler.
- Bei `ProbeSqlConnectivity=false`: `OK` mit sichtbarem Hinweis, dass die Probe deaktiviert ist.
Metriken: `biztalk_sql_targets_total`, `biztalk_sql_targets_available` und `biztalk_sql_targets_failed`. Die Ausgabe nennt ausserdem `execution_identity`, `network_identity` und den Zustand jedes getesteten Datenbankziels.
### BizTalk Suspended Instances
- `OK`: keine suspendierten Instanzen.
- `WARN`: mindestens `WarnResumableThreshold` resumable suspended instances.
- `CRIT`: mindestens `CritNonResumableThreshold` non-resumable suspended instances.
- `UNKNOWN`: Datenquelle nicht lesbar.
### BizTalk Host Instances
- `OK`: alle Host-Instanzen sind started.
- `WARN`: mindestens eine Host-Instanz ist pending.
- `CRIT`: mindestens eine Host-Instanz ist stopped oder unknown.
- `UNKNOWN`: keine Host-Instanzen gefunden oder Datenquelle nicht lesbar.
### BizTalk Runtime Artifacts
- `OK`: Artefakte lesbar, keine unbekannten Statuswerte.
- `WARN`: nur wenn `AlertOnArtifactRuntimeIssues=true` und deaktivierte/inaktive Artefakte vorhanden sind.
- `CRIT`: unbekannte Send-Port- oder Orchestration-Statuswerte.
- `UNKNOWN`: mindestens eine erforderliche WMI-Artefaktklasse nicht lesbar.
Deaktivierte Receive Locations und gestoppte Ports koennen in BizTalk fachlich korrekt sein. Deshalb ist die Alarmierung hier standardmaessig informativ.
### BizTalk Event Log
- `OK`: keine relevanten Fehler/Warnungen ueber Schwellwert.
- `WARN`: Fehler oder Warnungen ab `EventLogWarnThreshold`.
- `CRIT`: Fehler ab `EventLogCritThreshold`.
- `UNKNOWN`: Event Log nicht lesbar.
## Dashboard-Empfehlung
Pro Umgebung sollte der BizTalk-Host in einem eigenen Host-Ordner oder Host-Tag fuer `ACC`, `DEV`, `TST`, `PRD` liegen. Im Dashboard eignen sich:
- Service State Widgets fuer die sechs stabilen BizTalk-Services
- Graphen fuer `biztalk_sql_targets_failed`, `biztalk_suspended_total`, `biztalk_host_instances_stopped`, `biztalk_eventlog_errors`
- Hostgruppe/Ordner pro Umgebung
- Optional eine View gefiltert auf `Service starts with BizTalk`
Empfohlene Reihenfolge im Dashboard:
1. `BizTalk Platform`
2. `BizTalk SQL Access`
3. `BizTalk Suspended Instances`
4. `BizTalk Host Instances`
5. `BizTalk Event Log`
6. `BizTalk Runtime Artifacts`
7. SQL-Server-Services aus dem Checkmk-MSSQL-Plugin
## Rollout-Vorgehen
1. Build-Paket erzeugen.
2. In `DEV` auf dem BizTalk-Server installieren.
3. `--self-test` und normalen Lauf ausfuehren.
4. Agent-Dump pruefen.
5. Checkmk Discovery durchfuehren.
6. Eine Woche Messwerte und false positives beobachten.
7. Nach `TST` und `ACC` uebernehmen.
8. In `PRD` mit `AlertOnArtifactRuntimeIssues=false` starten.
9. Nach Betriebsfreigabe Schwellwerte feinjustieren.
## Troubleshooting
### Service bleibt UNKNOWN
Pruefen:
```cmd
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
```
Wenn WMI nicht erreichbar ist:
```cmd
wmic /namespace:\\root\MicrosoftBizTalkServer path MSBTS_HostInstance get HostName,RunningServer,ServiceState
```
Der Checkmk Windows Agent laeuft normalerweise als LocalSystem. Daher immer auch den Agent-Dump verwenden:
```cmd
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```
Die Diagnose im Service-Text nach `Massnahme:` abarbeiten. Wichtige Kategorien:
- `Wmi/Permission`: lokale Namespace-ACL oder BizTalk-Operator-Zuordnung pruefen.
- `Wmi/Schema`: Klasse/WQL gegen das installierte BizTalk-Schema pruefen; `InvalidClass` ist nicht durch zusaetzliche Rechte loesbar.
- `Sql/Permission`: Maschinenkonto `DOMAIN\BIZTALKSERVER$` und BizTalk-Operator-Gruppe pruefen.
- `Sql/Connectivity`: SQL-Server-/Instanzname, DNS, SQL-Dienst, TCP und Firewall pruefen.
- `Sql/Timeout`: SQL- und Netzwerkauslastung pruefen; Timeout nicht als erste Massnahme erhoehen.
Der direkte Aufruf des Wrappers kann wegen des angemeldeten Administratorkontos ein anderes Ergebnis liefern als der Agent-Dump. Fuer die Freigabe ist immer der Agent-Dump massgeblich.
### Keine Services in Discovery
Pruefen:
- Liegt `biztalk_checkmk_pulse.cmd` direkt unter `%ProgramData%\checkmk\agent\local`?
- Gibt der Wrapper direkt eine Zeile im Format `0 "Service" metric=value Details` aus?
- Wurde der Checkmk-Agent nach Policy-/Bakery-Aenderungen neu ausgerollt?
### Runtime Artifacts zeigt deaktivierte Artefakte
Das ist standardmaessig `OK`, damit gewollt deaktivierte BizTalk-Artefakte nicht alarmieren. Fuer strengere PRD-Standards:
```xml
<add key="AlertOnArtifactRuntimeIssues" value="true" />
```
## Weiterentwicklung
Sinnvolle naechste Ausbaustufen:
- MKP mit Agent-Bakery-Regel fuer zentrale Konfiguration.
- Optionales serverseitiges Check-Plugin nach Checkmk Check API V2.
- Custom Dashboard/View als Checkmk GUI Extension.
- Ergaenzung um MessageBox-Spool/Tracking-Daten, falls operativ benoetigt.
+302 -268
View File
@@ -1,24 +1,80 @@
# BizTalk Checkmk Pulse # BizTalk Checkmk Pulse
`BizTalk Checkmk Pulse` ist ein lokaler Checkmk-Check fuer Microsoft BizTalk Server 2020 auf Windows. Er liest BizTalk-Betriebsdaten ueber WMI aus `root\MicrosoftBizTalkServer`, erzeugt Checkmk-Local-Check-Ausgaben und kann dadurch ohne serverseitigen Python-Check in Checkmk 2.4 integriert werden. `BizTalk Checkmk Pulse` ist ein robuster Checkmk-2.4-Local-Check fuer Microsoft
BizTalk Server 2020 auf Windows Server 2019. Die Anwendung trennt den
berechtigten BizTalk-Datenzugriff vollstaendig vom Checkmk-Agenten:
Der Ansatz ist fuer Umgebungen wie `ACC`, `DEV`, `TST` und `PRD` gedacht, in denen pro Umgebung ein BizTalk Server 2020 und ein SQL Server betrieben werden. SQL Server bleibt beim mitgelieferten Checkmk-MSSQL-Plugin; dieses Projekt ergaenzt die BizTalk-spezifische Sicht. ```text
Scheduled Task (dediziertes Dienstkonto oder gMSA)
|
| jede Minute: BizTalkCheckmkPulse.exe --collect
v
lokales BizTalk-WMI + BizTalk-SQL + Application Event Log
|
| atomarer, versionierter, SHA-256-geschuetzter Snapshot
v
%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot
^
| nur lesen: BizTalkCheckmkPulse.exe --consume
|
Checkmk Windows Agent (LocalSystem)
|
v
sechs stabile Checkmk Local Checks
```
Die Plattform- und SQL-Zielermittlung verwendet ausschliesslich die dokumentierte WMI-Klasse `MSBTS_GroupSetting`. Management-Datenbank und Master-MessageBox werden ueber `MgmtDbServerName`/`MgmtDbName` sowie `SubscriptionDBServerName`/`SubscriptionDBName` gelesen. Eine Klasse `MSBTS_MessageBoxSetting` gehoert nicht zum BizTalk-WMI-Schema und wird bewusst nicht abgefragt. Damit bekommt `LocalSystem` keine BizTalk- oder SQL-Berechtigung. Nur das
dedizierte Provider-Konto wird in die konfigurierte BizTalk Server Read Only
Users-Gruppe aufgenommen. Der Checkmk-Consumer liest keine BizTalk-Datenbank,
verwendet kein WMI und nimmt keine Identitaetswechsel vor.
## Warum Local Check statt serverseitigem Check-Plugin? ## Warum die Architektur geaendert wurde
Checkmk 2.4 kann Windows-Agent-Plugins und Local Checks direkt ausfuehren. Fuer diese Umgebung ist ein C#/.NET-Framework-Programm mit `.cmd`-Wrapper die robusteste Variante: Der ACC-Test vom 29.07.2026 zeigte:
- keine PowerShell-Ausfuehrung erforderlich ```text
- keine BizTalk-ExplorerOM/OperationsOM-DLL als Build-Abhaengigkeit Login failed for user 'BEW\AV23AGPWBIO1$'
- keine Python-Check-API-Abhaengigkeit auf der Checkmk-Site ```
- einfache Verteilung auf die BizTalk-Maschinen per Dateiablage, Softwareverteilung oder Agent Bakery
- automatische Service Discovery in Checkmk
## Erzeugte Checkmk-Services Der Checkmk-Agent lief korrekt als `NT AUTHORITY\SYSTEM` und erreichte lokales
BizTalk-WMI. Datenbankgestuetzte WMI-Abfragen wurden jedoch am SQL Server als
Maschinenkonto `BEW\AV23AGPWBIO1$` abgewiesen. Eine Mitgliedschaft des
Computerkontos wuerde die BizTalk-Netzwerkberechtigung allen als `LocalSystem`
laufenden Diensten des Servers geben.
Standardmaessig entstehen diese stabilen Services: Die neue Trennung reduziert diesen Berechtigungsumfang. Fuer BizTalk Server
2020 ist die konfigurierte `BizTalk Server Read Only Users`-Gruppe mit der
SQL-Datenbankrolle `BTS_READONLY_USERS` die bevorzugte Grenze. Die
`BizTalk Server Operators`-Gruppe ist nur ein dokumentierter Fallback, wenn
eine konkret benoetigte WMI-Klasse trotz bestaetigter Read-Only-Zuordnung
abgewiesen wird. Direkte SQL-Logins, manuelle Datenbankrollen und `sysadmin`
sind keine Loesung.
## Robustheit
Der Datenaustausch ist bewusst defensiv:
- Der Provider schreibt zuerst eine eindeutige Temporaerdatei im Zielordner,
leert Betriebssystempuffer und ersetzt danach den Snapshot atomar.
- Der Snapshot enthaelt Formatversion, UTC-Zeit, Quellmaschine,
Provider-Identitaet, Zeilenanzahl und SHA-256 des Payloads.
- Der Consumer akzeptiert nur denselben Rechner, gueltiges UTF-8, intakte
Checkmk-Zeilen, korrekte SHA-256-Pruefsumme und ein maximales Alter von
standardmaessig 180 Sekunden.
- Fehlende, veraltete, abgeschnittene, manipulierte oder unlesbare Dateien
ergeben sechs gueltige `UNKNOWN`-Services statt einer kaputten Agent-Ausgabe.
- Ein exklusives Lock und die Task-Einstellung `IgnoreNew` verhindern
ueberlappende Providerlaeufe.
- Ein unerwarteter Providerfehler erzeugt nach Moeglichkeit einen aktuellen
`UNKNOWN`-Snapshot und einen ungleich null lautenden Task-Exitcode.
- Provider und Consumer protokollieren in taegliche Dateien; die Aufbewahrung
ist standardmaessig 30 Tage.
- Snapshotgroesse, WMI-/SQL-Timeouts, Log-Retention und Stale-Grenze sind
begrenzt und konfigurierbar.
## Erzeugte Services
Standardmaessig entstehen:
- `BizTalk Platform` - `BizTalk Platform`
- `BizTalk SQL Access` - `BizTalk SQL Access`
@@ -27,323 +83,301 @@ Standardmaessig entstehen diese stabilen Services:
- `BizTalk Runtime Artifacts` - `BizTalk Runtime Artifacts`
- `BizTalk Event Log` - `BizTalk Event Log`
Wenn `EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` gesetzt wird, wird der Name vor den Suffix gesetzt, z.B. `BizTalk PRD Suspended Instances`. Das ist praktisch, wenn die Umgebung bereits im Hostnamen oder Ordner abgebildet ist, aber nicht zwingend noetig. Mit `EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` wird die Umgebung in den
Servicenamen aufgenommen, zum Beispiel `BizTalk ACC Platform`.
Details zu Statuslogik und Metriken stehen in [docs/CheckmkServices.md](docs/CheckmkServices.md). Statuslogik und Metriken: [docs/CheckmkServices.md](docs/CheckmkServices.md)
Beispielausgaben stehen in [docs/ExampleOutput.md](docs/ExampleOutput.md).
Die konkrete ACC-Freigabeanleitung steht als Textdatei in [docs/ACC-WMI-SQL-Berechtigung.txt](docs/ACC-WMI-SQL-Berechtigung.txt).
## Build Beispielausgaben: [docs/ExampleOutput.md](docs/ExampleOutput.md)
Voraussetzungen auf einem Windows-Build-Host: ## Voraussetzungen
Build-Host:
- Visual Studio 2019/2022 Build Tools oder Visual Studio - Visual Studio 2019/2022 Build Tools oder Visual Studio
- MSBuild im `PATH` - MSBuild im `PATH`
- .NET Framework 4.7.2 Developer Pack - .NET Framework 4.7.2 Developer Pack
Build: BizTalk-Server:
- Windows Server 2019
- BizTalk Server 2020 und lokaler Namespace
`root\MicrosoftBizTalkServer`
- .NET Framework 4.7.2
- Checkmk Windows Agent
- administrativer Zugriff fuer die einmalige Installation
- dediziertes AD-Dienstkonto oder bevorzugt gMSA fuer den Provider
Das Provider-Konto benoetigt:
- lokales Recht zur Ausfuehrung als Scheduled Task
- lokalen Lese-/Ausfuehrungszugriff auf die installierte EXE
- Schreibzugriff nur auf Snapshot- und Logverzeichnis
- Mitgliedschaft in der exakt konfigurierten BizTalk Server Read Only
Users-Gruppe
Es soll weder lokaler Administrator noch SQL-`sysadmin` sein. Fuer ein gMSA
muss der BizTalk-Server das verwaltete Kennwort abrufen duerfen und das Konto
lokal installiert sein.
## Build und Tests
```cmd ```cmd
scripts\build-release.cmd scripts\build-release.cmd
```
Build plus dependency-free regression tests:
```cmd
scripts\test-release.cmd scripts\test-release.cmd
```
Deployment-Paket erstellen:
```cmd
scripts\package-release.cmd scripts\package-release.cmd
``` ```
Ergebnis: Das Paket wird unter `artifacts\BizTalkCheckmkPulse-deploy` erzeugt:
```text ```text
artifacts\BizTalkCheckmkPulse-deploy\ BizTalkCheckmkPulse-deploy\
Install-BizTalkCheckmkPulse.ps1
Uninstall-BizTalkCheckmkPulse.ps1
biztalk_checkmk_pulse.cmd biztalk_checkmk_pulse.cmd
BizTalkCheckmkPulse\ application\
BizTalkCheckmkPulse.exe BizTalkCheckmkPulse.exe
BizTalkCheckmkPulse.exe.config BizTalkCheckmkPulse.exe.config
``` ```
Validierung nach dem Build: Format-Self-Test ohne WMI, SQL oder Event Log:
```cmd ```cmd
artifacts\BizTalkCheckmkPulse-deploy\biztalk_checkmk_pulse.cmd --self-test artifacts\BizTalkCheckmkPulse-deploy\application\BizTalkCheckmkPulse.exe --self-test
``` ```
Der Self-Test fuehrt keine WMI-, SQL- oder Event-Log-Abfrage aus und muss genau sechs `OK`-Zeilen liefern. Der Test-Runner prueft zusaetzlich UNKNOWN-Fallbacks, dynamische Application-Services, FQDN-/Kurznamensvergleich ohne WQL-Namensfilter und den dokumentierten `MSBTS_GroupSetting`-Queryvertrag. Die zusaetzliche lokale Verifikation mit Mono/MSBuild 16 prueft Build und Ausgabeformat, ersetzt aber nicht den Windows-/BizTalk-Test. Erwartet werden exakt sechs `OK`-Zeilen. Die Regressionstests pruefen
zusaetzlich Snapshot-Roundtrip, atomaren Ersatz, SHA-256-Manipulation,
Stale-Erkennung, stabile Fallbacks und die bestehenden BizTalk-WMI-Diagnosen.
Ein Mono-Build ist eine hilfreiche Quellcodepruefung, ersetzt aber nicht die
Windows-/BizTalk-Laufzeitvalidierung.
## Installation auf dem BizTalk-Server ## Berechtigung vorbereiten
Kopiere den Inhalt von `artifacts\BizTalkCheckmkPulse-deploy` nach: Die exakte Read-Only-Gruppe wird in der BizTalk Administration Console unter
den Eigenschaften der BizTalk-Gruppe abgelesen. Ein bereits berechtigtes Konto
kann sie alternativ ermitteln:
```powershell
Get-CimInstance `
-Namespace root/MicrosoftBizTalkServer `
-ClassName MSBTS_GroupSetting |
Select-Object Name, BizTalkReadOnlyUserGroup, BizTalkOperatorGroup,
MgmtDbServerName, MgmtDbName
```
Ein AD-Administrator nimmt das neue Provider-Konto in
`BizTalkReadOnlyUserGroup` auf. Bei einem gMSA endet der Kontoname mit `$`.
Nach AD-Replikation muss ein regulaeres Dienstkonto einen neuen Anmeldetoken
erhalten; bei gMSA wird der Task nach der Gruppenfreigabe neu gestartet.
Die BizTalk-Konfiguration muss die Domain-Gruppe bereits als Windows-Login und
in `BizTalkMgmtDb`, `BizTalkMsgBoxDb`, `BizTalkDTADb`,
`BizTalkRuleEngineDb` sowie gegebenenfalls `BAMPrimaryImport` mit
`BTS_READONLY_USERS` abbilden. Eine fehlende Abbildung wird durch BizTalk- und
SQL-Administration fuer die Gruppe repariert, nicht als Einzelberechtigung
fuer das Provider-Konto.
## Installation mit gMSA
Deployment-Paket auf den BizTalk-Server kopieren. In administrativer Windows
PowerShell:
```powershell
Set-Location C:\Temp\BizTalkCheckmkPulse-deploy
# Optional, falls das gMSA noch nicht lokal installiert wurde:
Install-ADServiceAccount -Identity svc_biztalk_cmk
Test-ADServiceAccount -Identity svc_biztalk_cmk
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
```
## Installation mit regulaerem Dienstkonto
```powershell
Set-Location C:\Temp\BizTalkCheckmkPulse-deploy
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk' `
-EnvironmentName ACC
```
Der Installer fragt das Kennwort ueber `Get-Credential` ab und speichert es
durch die Windows-Aufgabenplanung. Das Kennwort steht weder in der
Konfigurationsdatei noch in den Logs.
Der Installer:
1. kopiert EXE und Config nach
`%ProgramFiles%\BizTalkCheckmkPulse`,
2. erstellt `%ProgramData%\BizTalkCheckmkPulse\data` und `logs`,
3. setzt explizite ACLs fuer Administratoren, Provider und `LocalSystem`,
4. installiert nur den kleinen `.cmd`-Consumer unter
`%ProgramData%\checkmk\agent\local`,
5. registriert `BizTalk Checkmk Pulse Provider` minuetlich mit
`IgnoreNew`, fuenf Minuten Laufzeitlimit und zwei Wiederholungen,
6. fuehrt den Self-Test aus und startet den Provider einmalig.
PowerShell wird nur fuer Installation und Betriebsdiagnose verwendet. Der
minuetliche Provider und der Checkmk-Consumer sind .NET-/CMD-Laufzeitcode und
haengen nicht von der PowerShell Execution Policy ab.
## Verifikation auf dem Server
Task und letzter Lauf:
```powershell
Get-ScheduledTask -TaskName 'BizTalk Checkmk Pulse Provider' |
Select-Object TaskName, State
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider' |
Select-Object LastRunTime, LastTaskResult, NextRunTime
```
Provider-Log:
```powershell
Get-ChildItem "$env:ProgramData\BizTalkCheckmkPulse\logs" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 3 Name, Length, LastWriteTime
Get-Content `
"$env:ProgramData\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log" `
-Tail 100
```
Snapshot und Consumer:
```powershell
Get-Item `
"$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot" |
Select-Object FullName, Length, LastWriteTimeUtc
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
```
Verbindlicher Test im echten `LocalSystem`-Kontext:
```powershell
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
Select-String -Pattern "BizTalk|UNKNOWN|Snapshot|Permission" -Context 0,1
```
Danach in Checkmk eine Service Discovery ausfuehren, die sechs Services
aufnehmen und Changes aktivieren. Ein zusaetzlicher Checkmk-Async-Cache ist
nicht erforderlich: Der Consumer liest nur eine kleine lokale Datei und der
Provider besitzt bereits seinen eigenen Minutentakt.
## Dateisystem und Sicherheitsgrenzen
```text ```text
%ProgramData%\checkmk\agent\local %ProgramFiles%\BizTalkCheckmkPulse\
BizTalkCheckmkPulse.exe
BizTalkCheckmkPulse.exe.config
%ProgramData%\BizTalkCheckmkPulse\
data\
biztalk-checkmk-pulse.snapshot
biztalk-checkmk-pulse.snapshot.provider.lock
logs\
biztalk-checkmk-pulse-YYYYMMDD.log
%ProgramData%\checkmk\agent\local\
biztalk_checkmk_pulse.cmd
``` ```
Zielstruktur: ACL-Soll:
```text | Pfad | Provider | LocalSystem | Administratoren |
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd | --- | --- | --- | --- |
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe | Programm | Lesen/Ausfuehren | Lesen/Ausfuehren | Vollzugriff |
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config | `data` | Aendern | Lesen/Ausfuehren | Vollzugriff |
``` | `logs` | Aendern | Aendern | Vollzugriff |
Manueller Test auf dem BizTalk-Server: Der Snapshot enthaelt Monitoringzustand und kompakte Fehlerdetails, aber keine
Passwoerter oder Nachrichteninhalte. Der Consumer validiert die Datei trotzdem
```cmd vollstaendig, bevor er sie an Checkmk weitergibt.
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd" --self-test
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
```
Agent-Ausgabe wie Checkmk sie sieht:
```cmd
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```
Danach in Checkmk fuer den BizTalk-Host eine Service Discovery ausfuehren, die neuen Services aufnehmen und Changes aktivieren.
## Konfiguration ## Konfiguration
Die Konfiguration liegt neben der EXE: Datei:
```text ```text
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config %ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
``` ```
Wichtige Werte: Wichtige Werte:
| Key | Default | Bedeutung | | Key | Default | Bedeutung |
| --- | --- | --- | | --- | --- | --- |
| `Server` | `.` | Lokaler BizTalk-Server. Remote-WMI ist moeglich, aber nicht empfohlen. | | `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST`, `PRD`. |
| `ServicePrefix` | `BizTalk` | Prefix fuer alle Checkmk-Services. | | `SnapshotPath` | `%ProgramData%\BizTalkCheckmkPulse\data\...` | Gemeinsame Provider-/Consumer-Datei. |
| `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST` oder `PRD`. | | `SnapshotMaxAgeSeconds` | `180` | Ab diesem Alter liefert der Consumer `UNKNOWN`. |
| `QueryTimeoutSeconds` | `25` | WMI-Timeout pro Query. | | `SnapshotMaxBytes` | `1048576` | Harte Eingabegroesse fuer den Consumer. |
| `ProbeSqlConnectivity` | `true` | Prueft die integrierte Windows-Anmeldung an der per WMI ermittelten Management- und Master-MessageBox-Datenbank. | | `LogDirectory` | `%ProgramData%\BizTalkCheckmkPulse\logs` | Tageslogs. |
| `SqlConnectionTimeoutSeconds` | `5` | Timeout je SQL-Ziel fuer Verbindungsaufbau und harmlose Testabfrage. | | `LogRetentionDays` | `30` | Provider bereinigt aeltere Logs. |
| `WarnResumableThreshold` | `1` | WARN ab n resumable suspended instances. | | `QueryTimeoutSeconds` | `25` | WMI-Timeout je Query. |
| `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable suspended instances. | | `SqlConnectionTimeoutSeconds` | `5` | SQL-Timeout je Ziel. |
| `AlertOnArtifactRuntimeIssues` | `false` | Wenn `true`, werden deaktivierte Receive Locations und inaktive Ports/Orchestrations als WARN gewertet. | | `WarnResumableThreshold` | `1` | WARN ab n resumable Suspensions. |
| `EmitPerApplicationSuspensionServices` | `false` | Erzeugt zusaetzliche Services pro Anwendung mit Suspensions. | | `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable Suspensions. |
| `ProbeEventLog` | `true` | Liest das Windows Application Log nach BizTalk-bezogenen Sources. | | `AlertOnArtifactRuntimeIssues` | `false` | WARN fuer bewusst inaktive Artefakte aktivieren. |
| `EventLogLookbackMinutes` | `60` | Zeitraum fuer Event-Log-Auswertung. | | `EmitPerApplicationSuspensionServices` | `false` | Zusaetzliche Anwendungsservices. |
| `EventLogWarnThreshold` | `1` | WARN ab n Errors oder Warnings. | | `EventLogLookbackMinutes` | `60` | Event-Log-Zeitfenster des Providers. |
| `EventLogCritThreshold` | `10` | CRIT ab n Errors. |
Empfehlung: Nach einer Config-Aenderung den Scheduled Task manuell starten. Der Consumer
liest den naechsten atomar publizierten Snapshot.
- In `DEV`, `TST`, `ACC`: `AlertOnArtifactRuntimeIssues=false`, damit bewusst gestoppte Artefakte nicht rauschen. ## Fehlerbilder
- In `PRD`: erst zwei Wochen beobachten; danach nur aktivieren, wenn die Runtime-Artefakte wirklich als Betriebsstandard immer aktiv sein muessen.
- `EmitPerApplicationSuspensionServices=true` nur verwenden, wenn Anwendungsteams eigene Services benoetigen. Sonst bleibt die Discovery schlanker.
## Integration in Checkmk Managed Services Edition 2.4 | Beobachtung | Ursache / Massnahme |
| --- | --- |
| Alle sechs Services melden fehlenden Snapshot | Task, Provider-Log, Task-Konto und ACL pruefen. |
| Snapshot ist `stale` | `LastTaskResult`, Laufzeit, WMI-/SQL-Timeout und Log pruefen. |
| SHA-256 oder Format ungueltig | Datei nicht manuell bearbeiten; Datentraeger/AV und Schreibpfad pruefen, Task neu starten. |
| Provider meldet `Login failed` | Provider-Konto und exakt konfigurierte Read-Only-Gruppe sowie `BTS_READONLY_USERS` pruefen. |
| `Wmi/Schema` | Klasse/Properties gegen BizTalk-2020-Schema pruefen; keine Rechte ausweiten. |
| Nur Event Log `UNKNOWN` | lokalen Application-Log-Zugriff des Provider-Kontos pruefen. |
| Task-Result `2` | Parallelstart oder Snapshot-I/O; Log und Lock/ACL pruefen. |
Aufgaben der Checkmk-Kollegen: Ausfuehrliche Betriebs- und Architekturdetails stehen in
[Dokumentation.md](Dokumentation.md).
1. BizTalk-Hosts in Checkmk anlegen oder bestehende Hosts pruefen. ## Deinstallation
2. Sicherstellen, dass der Checkmk Windows Agent installiert, registriert und erreichbar ist.
3. Plugin-Dateien auf die BizTalk-Server verteilen, manuell oder per Agent Bakery.
4. Optional `EnvironmentName` je Umgebung setzen, z.B. `ACC`, `DEV`, `TST` oder `PRD`.
5. Agent-Ausgabe mit `cmk-agent-ctl.exe dump` pruefen.
6. Service Discovery fuer jeden BizTalk-Host ausfuehren.
7. Gefundene `BizTalk ...` Services aufnehmen und Changes aktivieren.
8. Views, Dashboards, Servicegruppen und Benachrichtigungen fuer die BizTalk-Services konfigurieren.
Manuelle Integration:
1. Dateien auf dem BizTalk-Server nach `%ProgramData%\checkmk\agent\local` kopieren.
2. Optional `BizTalkCheckmkPulse.exe.config` je Umgebung anpassen.
3. Agent-Dump pruefen.
4. Service Discovery auf dem BizTalk-Host ausfuehren.
5. Services in ein BizTalk-Dashboard aufnehmen.
Integration ueber Agent Bakery:
1. Deployment-Dateien in der Checkmk-Site als Custom-Agent-Dateien bereitstellen.
2. Windows-Agent-Regel fuer die BizTalk-Hosts erstellen.
3. Agent backen und auf ACC/DEV/TST/PRD-BizTalk-Hosts ausrollen.
4. Discovery und Dashboard-Aufnahme durchfuehren.
Die Checkmk-Dokumentation nennt fuer Windows Local Checks `%ProgramData%\checkmk\agent\local` und fuer Windows Agent Plugins `%ProgramData%\checkmk\agent\plugins`. Dieses Projekt nutzt bewusst `local`, weil der Zustand direkt vom Host berechnet und sofort als Checkmk-Service geliefert wird.
## Berechtigungen fuer WMI und BizTalk-Datenbanken
### Ausfuehrungskontext
Der Checkmk Windows Agent und der Agent Controller laufen standardmaessig als `LocalSystem` (`NT AUTHORITY\SYSTEM`). Der `.cmd`-Wrapper und `BizTalkCheckmkPulse.exe` erben diesen Sicherheitskontext.
Das Programm:
- verbindet sich lokal mit `\\<eigener-server>\root\MicrosoftBizTalkServer`
- setzt keine separaten WMI-Anmeldedaten
- verwendet kein Remote-WMI
- ruft keine veraendernden BizTalk-WMI-Methoden auf
- liest zusaetzlich nur das lokale Windows Application Event Log
Fuer den lokalen WMI-Verbindungsaufbau und das lokale Application Event Log reichen die Rechte von `LocalSystem` normalerweise aus. Es muessen deshalb im Regelfall keine DCOM-, Firewall- oder WMI-Namespace-Freigaben eingerichtet werden.
### Zugriff auf einen separaten SQL Server
Einige BizTalk-WMI-Klassen beziehen ihre Daten aus der BizTalk Management- oder MessageBox-Datenbank. Wenn der SQL Server auf einer anderen Maschine laeuft, greift ein unter `LocalSystem` ausgefuehrter Prozess im Netzwerk mit dem Computerkonto des BizTalk-Servers zu:
```text
DOMAIN\BIZTALKSERVER$
```
Die weitreichenden lokalen Rechte von `LocalSystem` ergeben nicht automatisch Berechtigungen auf dem entfernten SQL Server. Deshalb kann die Verbindung zum lokalen WMI-Namespace funktionieren, waehrend einzelne SQL-gestuetzte BizTalk-WMI-Abfragen mit `Access denied`, `UnauthorizedAccessException` oder `UNKNOWN` fehlschlagen.
### Einordnung des ACC-Befunds vom 29.07.2026
Der Agent-Dump von `AV23AGPWBIO1` zeigt den entscheidenden inneren Providerfehler:
```text
COMException HRESULT=0x80131904
Internal error from OLEDB provider: 'Login failed for user 'BEW\AV23AGPWBIO1$'.'
```
Damit ist der lokale Namespace `root\MicrosoftBizTalkServer` bereits erreichbar. Der BizTalk-WMI-Provider kann aber seine SQL-gestuetzten Abfragen nicht ausfuehren, weil SQL Server das Computerkonto `BEW\AV23AGPWBIO1$` ablehnt. Zusaetzliche DCOM-, Firewall- oder WMI-Namespace-Rechte sind fuer diesen konkreten Fehler nicht die richtige Massnahme. Die `UNKNOWN`-Zustaende bei Platform, Host Instances, Runtime Artifacts und Suspended Instances sowie `targets=0` bei SQL Access sind Folgewirkungen derselben fehlenden Berechtigung.
`BizTalk Event Log` funktioniert unabhaengig von diesem SQL-Zugriff. Die dort sichtbaren zwei Fehler und sechs Warnungen sind echte Ereignisse im betrachteten Zeitfenster und nach Behebung der Berechtigung separat zu untersuchen.
### Codekorrektur und Berechtigungsfreigabe sind zwei getrennte Schritte
Die ueberarbeitete Version vergibt keine Windows-, Active-Directory- oder SQL-Rechte. Sie erkennt den eingebetteten SQL-Loginfehler nur korrekt, nennt das abgewiesene Konto und verhindert eine irrefuehrende Provider-/Konfigurationsdiagnose. Ohne Berechtigungsfreigabe bleibt der Check deshalb auch mit der neuen EXE `UNKNOWN`.
Der Zugriff funktioniert erst, wenn `BEW\AV23AGPWBIO1$` Mitglied der exakt konfigurierten BizTalk-Operator-Gruppe ist und das erneuerte Maschinen-Token diese Mitgliedschaft enthaelt. Normalerweise ist diese Windows-Gruppe bereits durch die BizTalk-Konfiguration als SQL-Login beziehungsweise Datenbankbenutzer mit `BTS_OPERATORS` in den BizTalk-Datenbanken eingerichtet. Das Maschinenkonto erbt diese SQL-Rechte ueber seine Gruppenmitgliedschaft; ein eigener SQL-Login fuer `BEW\AV23AGPWBIO1$` ist dann weder erforderlich noch gewuenscht.
### Test im echten Checkmk-Kontext
Ein manueller Aufruf von `BizTalkCheckmkPulse.exe` oder des Wrappers verwendet das Konto der angemeldeten Person. Ein erfolgreicher manueller Test beweist daher nicht, dass die Ausfuehrung durch Checkmk als `LocalSystem` ebenfalls funktioniert.
Der verbindliche Test erfolgt ueber den Agent Controller:
```powershell ```powershell
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump | .\Uninstall-BizTalkCheckmkPulse.ps1
Select-String -Pattern "BizTalk|Login failed|Access denied|Unauthorized|UNKNOWN" -Context 0,1
``` ```
Erwartet werden die sechs stabilen `BizTalk ...` Services mit plausiblen Daten. Insbesondere `BizTalk Platform` und `BizTalk SQL Access` duerfen nicht wegen eines WMI-, SQL- oder Berechtigungsfehlers `UNKNOWN` sein. Das Skript fragt vor dem Entfernen. Mit `-KeepRuntimeData` bleiben Snapshot und
Logs erhalten:
Das Programm prueft Berechtigungen aktiv: ```powershell
.\Uninstall-BizTalkCheckmkPulse.ps1 -KeepRuntimeData
- Der WMI-Namespace-Verbindungsaufbau und jede erforderliche WMI-Klasse werden getrennt bewertet.
- Fehler werden als `Permission`, `Connectivity`, `Timeout`, `Configuration`, `Schema` oder `Provider` klassifiziert.
- `BizTalk SQL Access` oeffnet mit integrierter Windows-Authentifizierung eine Verbindung zur ermittelten Management- und Master-MessageBox-Datenbank und fuehrt `SELECT 1` aus.
- Die Service-Ausgabe nennt Ausfuehrungsidentitaet, erwartete Netzwerkidentitaet, betroffene Komponente, technische Ursache und konkrete Massnahme.
- Ein im BizTalk-WMI-Provider eingebettetes `Login failed for user` wird als `Wmi/Permission` klassifiziert; das tatsaechlich abgewiesene Konto wird in die Diagnose und in `BizTalk SQL Access` uebernommen.
- Nach erfolgreicher Plattformabfrage zeigt `BizTalk Platform` mit `operator_group=` die von BizTalk konfigurierte Operator-Gruppe.
- Fehlgeschlagene Pflichtabfragen werden nie als leerer, erfolgreicher Datenbestand gewertet. Der betroffene Service wird `UNKNOWN`.
`Sql/Configuration` weist je nach Detailtext entweder auf eine unvollstaendige WMI-Zielermittlung oder auf TLS-, Zertifikats-, SPN-/SSPI-Probleme hin. Die Diagnose empfiehlt bewusst nicht, SQL-Verschluesselung pauschal abzuschalten.
`Wmi/Schema` kennzeichnet `InvalidClass` oder `InvalidQuery`. Das ist kein Berechtigungsfehler: Klassen- und Property-Namen muessen gegen das installierte BizTalk-WMI-Schema geprueft werden; eine Rechteerhoehung kann eine nicht vorhandene Klasse nicht erzeugen. Die technischen Details enthalten die ausgefuehrte WQL-Abfrage und deren Laufzeit bis zum Fehler.
Der SQL-Zugriffstest ersetzt nicht das Checkmk-MSSQL-Plugin auf dem SQL-Server. Er prueft ausschliesslich, ob genau die Identitaet des BizTalk Local Checks die fuer seine Diagnose benoetigten BizTalk-Datenbankziele erreichen kann.
Da bei fehlenden Rechten jeder Lauf einen abgewiesenen SQL-Login erzeugen kann, wird fuer den produktiven Betrieb eine asynchrone Ausfuehrung mit 300 Sekunden Cache empfohlen. Das begrenzt SQL-Logeintraege und Last, verzoegert einen Zustandswechsel aber um maximal fuenf Minuten. Beispiel in `%ProgramData%\checkmk\agent\check_mk.user.yml`:
```yaml
local:
enabled: yes
execution:
- pattern: $CUSTOM_LOCAL_PATH$\biztalk_checkmk_pulse.cmd
async: yes
run: yes
cache_age: 300
``` ```
### Vorgehen bei Berechtigungsfehlern Die AD-Gruppenmitgliedschaft des Provider-Kontos wird bewusst nicht automatisch
geaendert und muss separat durch AD-/BizTalk-Administration entfernt werden.
1. Den Agent-Dienst und sein Startkonto kontrollieren: ## Certutil-Transport
```powershell Zu jeder Uebergabe wird ein Source-ZIP und eine certutil-kompatible
Get-CimInstance Win32_Service | Base64-Textdatei erzeugt. Auf Windows:
Where-Object { $_.Name -match 'check|cmk' -or $_.DisplayName -match 'checkmk' } |
Select-Object Name, DisplayName, State, StartName
```
2. Die exakt konfigurierte Operator-Gruppe in der BizTalk Administration Console unter `BizTalk Group` > `Properties` > `General` > `BizTalk Operators Group` ablesen. Nicht blind vom Standardnamen ausgehen. Alternativ kann ein bereits berechtigtes Konto abfragen: ```cmd
certutil -decode biztalk-checkmk-pulse-source-<datum>-<commit>.zip.b64.txt biztalk-checkmk-pulse-source.zip
certutil -hashfile biztalk-checkmk-pulse-source.zip SHA256
tar -xf biztalk-checkmk-pulse-source.zip
```
```powershell Die konkrete Datei und SHA-256-Summe werden bei der Uebergabe genannt.
Get-CimInstance -Namespace root/MicrosoftBizTalkServer -ClassName MSBTS_GroupSetting |
Select-Object Name, BizTalkOperatorGroup, MgmtDbServerName, MgmtDbName
```
3. Ein AD-Administrator nimmt das Computerobjekt in genau diese Gruppe auf. Fuer ACC ist das Computerobjekt `AV23AGPWBIO1`, dessen Netzwerkprincipal `BEW\AV23AGPWBIO1$` ist:
```powershell
Import-Module ActiveDirectory
$computer = Get-ADComputer -Identity 'AV23AGPWBIO1'
Add-ADGroupMember -Identity '<EXAKTE_BIZTALK_OPERATOR_GRUPPE>' -Members $computer -WhatIf
```
Nach Kontrolle der aufgeloesten Ziele wird derselbe Befehl ohne `-WhatIf` ausgefuehrt. Anschliessend die Mitgliedschaft mit `Get-ADPrincipalGroupMembership -Identity 'AV23AGPWBIO1'` pruefen.
4. AD-Replikation abwarten. Die sicherste Aktivierung ist ein Neustart des BizTalk-Servers im Wartungsfenster. Ohne Neustart kann ein Administrator die Maschinen-Tickets mit `klist purge -li 0x3e7` verwerfen und danach den zuvor ermittelten Checkmk-Agent-Dienst neu starten.
5. Den Test im echten Agent-Kontext wiederholen:
```powershell
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
Select-String -Pattern "BizTalk|Login failed|Wmi/Permission|Sql/Permission|UNKNOWN" -Context 0,1
```
6. Erwartet werden Platform und SQL Access mit vollstaendiger Zielermittlung, zwei erreichbaren Datenbankzielen und keine berechtigungsbedingten `UNKNOWN`-Services. Erst wenn eine konkret benannte Klasse danach weiter scheitert, wird deren Rollenanforderung mit BizTalk- und SQL-Administration untersucht.
Die Operator-Rolle ist fuer grundlegende Administration und Monitoring vorgesehen und kann Zustands- und Message-Flow-Informationen lesen, ohne BizTalk-Konfiguration oder Nachrichteninhalte einzusehen. Das Plugin fuehrt ausschliesslich Leseabfragen aus. Direkte manuelle Aenderungen an BizTalk-SQL-Datenbankrollen sollten nicht vorgenommen werden; die von BizTalk konfigurierte Windows-Gruppe ist die vorgesehene Berechtigungsgrenze.
Bleibt `Login failed` nach bestaetigter Gruppenmitgliedschaft, AD-Replikation und Token-Erneuerung bestehen, pruefen BizTalk- und SQL-Administration die Gruppenabbildung auf dem SQL Server. Die konfigurierte Domain-Gruppe muss als Windows-Gruppenlogin aufloesbar sein und ihre Datenbankbenutzer muessen den von BizTalk vorgesehenen Rollen angehoeren, insbesondere `BTS_OPERATORS` in `BizTalkMgmtDb` und `BizTalkMsgBoxDb`. Eine fehlende oder abweichende Abbildung wird mit der BizTalk-Konfiguration abgeglichen und fuer die Gruppe repariert, nicht durch einen ad-hoc Einzel-Login fuer das Maschinenkonto umgangen.
Entscheidungsmatrix:
| Ergebnis im Agent-Dump | Massnahme |
| --- | --- |
| Alle BizTalk-Services liefern plausible Daten | Keine Berechtigungsaenderung erforderlich. |
| Verbindung zu `root\MicrosoftBizTalkServer` scheitert | Namespace, BizTalk-WMI-Provider, WMI-Dienst und Namespace-ACL gezielt pruefen. Keine pauschalen WMI-Rechte vergeben. |
| Service meldet `Wmi/Schema` beziehungsweise `InvalidClass`/`InvalidQuery` | WMI-Klasse und Properties gegen das BizTalk-Schema pruefen. Keine Berechtigungen erweitern. |
| `BizTalk SQL Access` meldet `Sql/Permission` | Computerkonto `DOMAIN\BIZTALKSERVER$` in die konfigurierte BizTalk-Operator-Gruppe aufnehmen, Kerberos erneuern und erneut testen. Keine direkten BizTalk-DB-Rollen vergeben. |
| `BizTalk SQL Access` meldet `Sql/Connectivity` oder `Sql/Timeout` | Server-/Instanzname, DNS, SQL-Dienst, TCP-Protokoll, Port und Firewall aus Sicht des BizTalk-Servers pruefen. |
| SQL-gestuetzte BizTalk-Klassen liefern `0x80131904`, `Login failed for user` oder `UNKNOWN` | Das in der Meldung genannte Computerkonto in die exakt konfigurierte BizTalk-Operator-Gruppe aufnehmen. |
| `BizTalk Event Log` ist `UNKNOWN` | Zugriff auf das lokale Application Log pruefen; `LocalSystem` kann es normalerweise lesen. |
| Fehler bleibt trotz Operator-Rolle bestehen | Betroffene WMI-Klasse und BizTalk-/SQL-Rollenzuordnung mit den Fachadministratoren untersuchen. |
### Sicherheitsabwaegung und Alternative
Wird das Computerkonto in `BizTalk Server Operators` aufgenommen, erhalten alle Dienste, die auf diesem BizTalk-Server als `LocalSystem` laufen, diese Netzwerkberechtigung. Das ist vor dem Rollout mit der Security- und BizTalk-Administration abzustimmen.
Falls diese Freigabe nicht zulaessig ist, ist die sauberere Alternative ein separater Collector unter einem dedizierten gMSA- oder Dienstkonto mit Operator-Rechten. Dieser Collector kann seine Checkmk-Ausgabe in eine Spool-Datei schreiben. Der Checkmk-Agent liest dann nur die bereits erzeugten Daten ein. Den gesamten Checkmk-Agent-Dienst sollte man nicht allein fuer dieses Plugin von `LocalSystem` auf ein anderes Konto umstellen, weil dadurch alle Agent-Sektionen und Local Checks betroffen sind.
Empfohlene Alarmierung:
- `BizTalk Platform`: `UNKNOWN` immer untersuchen, da dann WMI, Rechte oder Deployment betroffen sind.
- `BizTalk SQL Access`: `UNKNOWN` als Integrations-, Berechtigungs- oder Verbindungsproblem behandeln und die eingebettete Massnahme abarbeiten.
- `BizTalk Suspended Instances`: in `PRD` direkt alarmieren; in Nicht-PRD nach Betriebsbedarf.
- `BizTalk Host Instances`: `CRIT` alarmieren, weil gestoppte Host Instances Laufzeitverarbeitung verhindern koennen.
- `BizTalk Runtime Artifacts`: zunaechst beobachten; strengere Alarmierung erst aktivieren, wenn deaktivierte Artefakte nicht fachlich gewollt sind.
- `BizTalk Event Log`: Schwellwerte nach Beobachtungsphase anpassen.
## Quellen ## Quellen
- Checkmk Local Checks: https://docs.checkmk.com/latest/en/localchecks.html - Microsoft: BizTalk `MSBTS_GroupSetting.BizTalkReadOnlyUserGroup`
- Checkmk Windows Agent und Plugin-Pfade: https://docs.checkmk.com/latest/en/agent_windows.html - Microsoft: Windows Groups and User Accounts in BizTalk Server
- Checkmk Agent-Based Plugin-Entwicklung: https://docs.checkmk.com/latest/en/devel_check_plugins.html - Microsoft: Managing BizTalk Server Security
- Checkmk Bakery API: https://docs.checkmk.com/latest/en/bakery_api.html - Checkmk: Windows Agent und Local Checks
- Checkmk MKP-Pakete: https://docs.checkmk.com/latest/en/mkps.html
- Microsoft BizTalk WMI `MSBTS_ServiceInstance`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-wmi Die genauen Links stehen in [Dokumentation.md](Dokumentation.md).
- Microsoft BizTalk WMI `ServiceStatus`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-servicestatus-property-wmi
- Microsoft BizTalk WMI `MSBTS_GroupSetting`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi
- Microsoft BizTalk Windows-Gruppen und SQL-Rollen: https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
- Microsoft BizTalk administrative Rollen: https://learn.microsoft.com/en-us/biztalk/core/access-control-for-administrative-roles
- Microsoft BizTalk Gruppen-Eigenschaften: https://learn.microsoft.com/en-us/biztalk/core/how-to-modify-group-properties
- Microsoft BizTalk WMI Core Server Classes: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/core-server-classes
- Microsoft BizTalk Mindestberechtigungen: https://learn.microsoft.com/en-us/biztalk/core/minimum-security-user-rights
- Microsoft BizTalk Access Control: https://learn.microsoft.com/en-us/biztalk/core/access-control-and-data-security
- Microsoft LocalSystem und Computerkonten: https://learn.microsoft.com/en-us/entra/architecture/service-accounts-computer
- Microsoft `Add-ADGroupMember`: https://learn.microsoft.com/en-us/powershell/module/activedirectory/add-adgroupmember
- Microsoft `klist`: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/klist
- Microsoft WMI Namespace Security: https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces
+171
View File
@@ -0,0 +1,171 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$CollectorAccount,
[switch]$Gmsa,
[ValidateSet("", "ACC", "DEV", "TST", "PRD")]
[string]$EnvironmentName = "",
[string]$InstallDirectory = "$env:ProgramFiles\BizTalkCheckmkPulse",
[string]$CheckmkLocalDirectory = "$env:ProgramData\checkmk\agent\local"
)
$ErrorActionPreference = "Stop"
Set-StrictMode -Version 2.0
$taskName = "BizTalk Checkmk Pulse Provider"
$runtimeRoot = Join-Path $env:ProgramData "BizTalkCheckmkPulse"
$dataDirectory = Join-Path $runtimeRoot "data"
$logDirectory = Join-Path $runtimeRoot "logs"
$sourceApplication = Join-Path $PSScriptRoot "application"
$sourceWrapper = Join-Path $PSScriptRoot "biztalk_checkmk_pulse.cmd"
$targetExe = Join-Path $InstallDirectory "BizTalkCheckmkPulse.exe"
$targetConfig = "$targetExe.config"
$targetWrapper = Join-Path $CheckmkLocalDirectory "biztalk_checkmk_pulse.cmd"
function Assert-Administrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "Die Installation muss in einer administrativen Windows PowerShell ausgefuehrt werden."
}
}
function Invoke-Icacls {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string[]]$Arguments
)
& icacls.exe $Path @Arguments | Out-Host
if ($LASTEXITCODE -ne 0) {
throw "icacls ist fuer '$Path' mit Exitcode $LASTEXITCODE fehlgeschlagen."
}
}
Assert-Administrator
if (-not (Test-Path -LiteralPath $sourceApplication -PathType Container)) {
throw "Quellverzeichnis fehlt: $sourceApplication"
}
if (-not (Test-Path -LiteralPath $sourceWrapper -PathType Leaf)) {
throw "Checkmk-Wrapper fehlt: $sourceWrapper"
}
if ($Gmsa -and -not $CollectorAccount.EndsWith('$')) {
throw "Ein gMSA-Kontoname muss mit '$' enden, z.B. DOMAIN\svc_biztalk_cmk$."
}
Write-Host "Installiere Programmdateien nach $InstallDirectory ..."
New-Item -ItemType Directory -Path $InstallDirectory -Force | Out-Null
Copy-Item -Path (Join-Path $sourceApplication "*") -Destination $InstallDirectory -Force
[xml]$configuration = Get-Content -LiteralPath $targetConfig
$environmentSetting = $configuration.configuration.appSettings.add |
Where-Object { $_.key -eq "EnvironmentName" } |
Select-Object -First 1
if ($null -eq $environmentSetting) {
throw "EnvironmentName fehlt in $targetConfig."
}
$environmentSetting.value = $EnvironmentName
$configuration.Save($targetConfig)
Write-Host "Erzeuge Runtime-Verzeichnisse und Least-Privilege-ACLs ..."
New-Item -ItemType Directory -Path $runtimeRoot, $dataDirectory, $logDirectory -Force | Out-Null
# Runtime-Root: Provider lesen, SYSTEM lesen, lokale Administratoren verwalten.
Invoke-Icacls -Path $runtimeRoot -Arguments @(
"/inheritance:r",
"/grant:r",
"*S-1-5-18:(OI)(CI)(RX)",
"*S-1-5-32-544:(OI)(CI)(F)",
"${CollectorAccount}:(OI)(CI)(RX)"
)
# Daten: ausschliesslich Provider schreibt, LocalSystem liest den Snapshot.
Invoke-Icacls -Path $dataDirectory -Arguments @(
"/inheritance:r",
"/grant:r",
"*S-1-5-18:(OI)(CI)(RX)",
"*S-1-5-32-544:(OI)(CI)(F)",
"${CollectorAccount}:(OI)(CI)(M)"
)
# Logs: Provider und LocalSystem-Consumer duerfen getrennte Diagnosezeilen anhaengen.
Invoke-Icacls -Path $logDirectory -Arguments @(
"/inheritance:r",
"/grant:r",
"*S-1-5-18:(OI)(CI)(M)",
"*S-1-5-32-544:(OI)(CI)(F)",
"${CollectorAccount}:(OI)(CI)(M)"
)
Write-Host "Installiere Checkmk Local Check nach $CheckmkLocalDirectory ..."
New-Item -ItemType Directory -Path $CheckmkLocalDirectory -Force | Out-Null
Copy-Item -LiteralPath $sourceWrapper -Destination $targetWrapper -Force
Write-Host "Registriere Scheduled Task '$taskName' ..."
$action = New-ScheduledTaskAction `
-Execute $targetExe `
-Argument "--collect" `
-WorkingDirectory $InstallDirectory
$trigger = New-ScheduledTaskTrigger `
-Once `
-At (Get-Date).AddMinutes(1) `
-RepetitionInterval (New-TimeSpan -Minutes 1)
$settings = New-ScheduledTaskSettingsSet `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
-StartWhenAvailable `
-RestartCount 2 `
-RestartInterval (New-TimeSpan -Minutes 1)
if ($Gmsa) {
$principal = New-ScheduledTaskPrincipal `
-UserId $CollectorAccount `
-LogonType Password `
-RunLevel Highest
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
}
else {
$credential = Get-Credential `
-UserName $CollectorAccount `
-Message "Kennwort fuer das dedizierte BizTalk-Monitoringkonto eingeben"
if ($credential.UserName -ne $CollectorAccount) {
throw "Das eingegebene Konto stimmt nicht mit CollectorAccount ueberein."
}
$principal = New-ScheduledTaskPrincipal `
-UserId $CollectorAccount `
-LogonType Password `
-RunLevel Highest
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
Register-ScheduledTask `
-TaskName $taskName `
-InputObject $task `
-User $CollectorAccount `
-Password $credential.GetNetworkCredential().Password `
-Force | Out-Null
}
Write-Host "Fuehre formatseitigen Self-Test aus ..."
$selfTest = & $targetExe --self-test
if ($LASTEXITCODE -ne 0 -or @($selfTest).Count -ne 6) {
throw "Self-Test fehlgeschlagen. Erwartet wurden sechs Checkmk-Zeilen."
}
Write-Host "Starte den Provider einmalig ..."
Start-ScheduledTask -TaskName $taskName
Write-Host ""
Write-Host "Installation abgeschlossen."
Write-Host "Naechste Pruefungen:"
Write-Host " Get-ScheduledTaskInfo -TaskName '$taskName'"
Write-Host " Get-Content '$logDirectory\biztalk-checkmk-pulse-*.log' -Tail 50"
Write-Host " & '$targetExe' --consume"
Write-Host " & 'C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe' dump"
@@ -0,0 +1,30 @@
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
param(
[string]$InstallDirectory = "$env:ProgramFiles\BizTalkCheckmkPulse",
[string]$CheckmkLocalDirectory = "$env:ProgramData\checkmk\agent\local",
[switch]$KeepRuntimeData
)
$ErrorActionPreference = "Stop"
$taskName = "BizTalk Checkmk Pulse Provider"
$runtimeRoot = Join-Path $env:ProgramData "BizTalkCheckmkPulse"
$wrapper = Join-Path $CheckmkLocalDirectory "biztalk_checkmk_pulse.cmd"
if ($PSCmdlet.ShouldProcess($taskName, "Scheduled Task entfernen")) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
}
if (Test-Path -LiteralPath $wrapper) {
if ($PSCmdlet.ShouldProcess($wrapper, "Checkmk Local Check entfernen")) {
Remove-Item -LiteralPath $wrapper -Force
}
}
if (Test-Path -LiteralPath $InstallDirectory) {
if ($PSCmdlet.ShouldProcess($InstallDirectory, "Programmdateien entfernen")) {
Remove-Item -LiteralPath $InstallDirectory -Recurse -Force
}
}
if (-not $KeepRuntimeData -and (Test-Path -LiteralPath $runtimeRoot)) {
if ($PSCmdlet.ShouldProcess($runtimeRoot, "Snapshot und Logs entfernen")) {
Remove-Item -LiteralPath $runtimeRoot -Recurse -Force
}
}
+4 -4
View File
@@ -1,11 +1,11 @@
@echo off @echo off
setlocal setlocal
set "BASE=%~dp0BizTalkCheckmkPulse" set "BASE=%ProgramFiles%\BizTalkCheckmkPulse"
set "EXE=%BASE%\BizTalkCheckmkPulse.exe" set "EXE=%BASE%\BizTalkCheckmkPulse.exe"
if not exist "%EXE%" ( if not exist "%EXE%" (
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen und danach cmk-agent-ctl.exe dump erneut ausfuehren. echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Install-BizTalkCheckmkPulse.ps1 erneut als Administrator ausfuehren und danach cmk-agent-ctl.exe dump erneut ausfuehren.
echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen. echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen. echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen. echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
@@ -14,10 +14,10 @@ if not exist "%EXE%" (
exit /b 0 exit /b 0
) )
"%EXE%" %* "%EXE%" --consume %*
set "EXITCODE=%ERRORLEVEL%" set "EXITCODE=%ERRORLEVEL%"
if not "%EXITCODE%"=="0" ( if not "%EXITCODE%"=="0" (
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe konnte nicht erfolgreich gestartet werden, Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, EXE/Config-Dateien und Windows Application Log pruefen. echo 3 "BizTalk Platform" - Snapshot-Consumer konnte nicht erfolgreich gestartet werden, Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, Installation und Provider-Log pruefen.
echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, EXE/Config-Dateien und Windows Application Log pruefen. echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, EXE/Config-Dateien und Windows Application Log pruefen.
echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen. echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen. echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
+76 -113
View File
@@ -1,152 +1,115 @@
BizTalk Checkmk Pulse - ACC Berechtigungsfreigabe BizTalk Checkmk Pulse - ACC Berechtigung und Architektur
Stand: 29.07.2026 Stand: 30.07.2026
BEFUND BEFUND
====== ======
Host: AV23AGPWBIO1 Host: AV23AGPWBIO1
Lokale Identitaet: NT AUTHORITY\SYSTEM (LocalSystem) Checkmk-Identitaet: NT AUTHORITY\SYSTEM
Netzwerkidentitaet: BEW\AV23AGPWBIO1$ Alte Netzwerkidentitaet: BEW\AV23AGPWBIO1$
Fehler: COMException HRESULT=0x80131904 Fehler: COMException HRESULT=0x80131904
Innerer Fehler: Login failed for user 'BEW\AV23AGPWBIO1$' Innerer Fehler: Login failed for user 'BEW\AV23AGPWBIO1$'
Der lokale BizTalk-WMI-Namespace ist erreichbar. Der BizTalk-WMI-Provider wird Der lokale BizTalk-WMI-Namespace war erreichbar. Datenbankgestuetzte
aber bei seinem SQL-Zugriff als Maschinenkonto abgewiesen. Deshalb sind BizTalk-WMI-Abfragen scheiterten am SQL-Login des Maschinenkontos.
Platform, Host Instances, Runtime Artifacts und Suspended Instances UNKNOWN.
BizTalk SQL Access kann ohne MSBTS_GroupSetting keine Ziele ermitteln.
Keine DCOM-, Firewall- oder WMI-Namespace-Rechte erweitern. Keinen direkten NEUE VERBINDLICHE ARCHITEKTUR
SQL-Login, keine manuellen BizTalk-Datenbankrollen und kein sysadmin fuer das =============================
Maschinenkonto anlegen.
WICHTIG ZUR NEUEN VERSION Das Maschinenkonto erhaelt keine BizTalk-Berechtigung.
=========================
Die neue EXE repariert keine Berechtigungen. Sie erkennt den eingebetteten 1. Ein dediziertes Dienstkonto oder bevorzugt gMSA laeuft minuetlich als
SQL-Loginfehler korrekt, zeigt BEW\AV23AGPWBIO1$ und klassifiziert die Scheduled Task "BizTalk Checkmk Pulse Provider".
Folgediagnose als Berechtigungsproblem. Ohne die nachfolgende Freigabe bleiben 2. Dieses Konto fuehrt BizTalkCheckmkPulse.exe --collect aus.
die betroffenen Checks auch mit der neuen Version UNKNOWN. 3. Es schreibt atomar einen SHA-256-geschuetzten Snapshot nach:
Die alte Version kann nach korrekter Freigabe grundsaetzlich funktionieren. %ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot
Empfohlen ist die neue Version zusammen mit der Gruppenfreigabe, weil sie
Fehlerursache, Konto und Massnahme eindeutig anzeigt.
FREIGABE 4. Der Checkmk-Agent bleibt LocalSystem und startet nur den Consumer.
======== 5. Der Consumer liest und validiert den Snapshot. Er fuehrt kein WMI und
keinen SQL-Zugriff aus.
1. Exakte Operator-Gruppe ermitteln BERECHTIGUNG DES PROVIDER-KONTOS
================================
In der BizTalk Administration Console: Bevorzugte Rolle fuer BizTalk Server 2020:
BizTalk Group > Properties > General > BizTalk Operators Group BizTalk Server Read Only Users
SQL-Datenbankrolle: BTS_READONLY_USERS
Nicht ungeprueft den Standardnamen verwenden. Mit einem bereits berechtigten Die exakt konfigurierte Gruppe mit einem bereits berechtigten Konto ermitteln:
Konto ist alternativ folgende Abfrage moeglich:
Get-CimInstance -Namespace root/MicrosoftBizTalkServer ` Get-CimInstance -Namespace root/MicrosoftBizTalkServer `
-ClassName MSBTS_GroupSetting | -ClassName MSBTS_GroupSetting |
Select-Object Name, BizTalkOperatorGroup, MgmtDbServerName, MgmtDbName Select-Object Name, BizTalkReadOnlyUserGroup, BizTalkOperatorGroup,
MgmtDbServerName, MgmtDbName
2. Computerobjekt durch einen AD-Administrator aufnehmen Das Provider-Konto durch AD-Administration in BizTalkReadOnlyUserGroup
aufnehmen. Nicht vom Standardnamen ausgehen.
Import-Module ActiveDirectory Die Operator-Gruppe ist nur eine begruendungspflichtige Eskalation, falls eine
$computer = Get-ADComputer -Identity 'AV23AGPWBIO1' konkret benoetigte WMI-Klasse trotz bestaetigter Read-Only-Zuordnung und neuem
Add-ADGroupMember ` Anmeldetoken verweigert wird.
-Identity '<EXAKTE_BIZTALK_OPERATOR_GRUPPE>' `
-Members $computer `
-WhatIf
Ziele kontrollieren und danach denselben Add-ADGroupMember-Befehl ohne NICHT DURCHFUEHREN
-WhatIf ausfuehren. ==================
Mitgliedschaft pruefen: - keinen SQL-Einzellogin fuer Provider- oder Maschinenkonto
- keine ad-hoc BizTalk-Datenbankrollen
- kein db_owner oder sysadmin
- keine pauschalen DCOM-/WMI-ACL-Erweiterungen
- Checkmk-Agentdienst nicht auf das Provider-Konto umstellen
- Maschinenkonto BEW\AV23AGPWBIO1$ nicht fuer diese Loesung berechtigen
Get-ADPrincipalGroupMembership -Identity 'AV23AGPWBIO1' | INSTALLATION
Select-Object Name, SamAccountName ============
3. Neue Gruppenzugehoerigkeit aktivieren gMSA:
AD-Replikation abwarten. Bevorzugt den BizTalk-Server in einem Wartungsfenster .\Install-BizTalkCheckmkPulse.ps1 `
neu starten. Alternativ in einer administrativen Shell: -CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
klist purge -li 0x3e7 Regulaeres Dienstkonto:
Danach den zuvor eindeutig ermittelten Checkmk-Agent-Dienst neu starten. Falls .\Install-BizTalkCheckmkPulse.ps1 `
die neue Mitgliedschaft nicht wirksam wird, den Server neu starten. -CollectorAccount 'BEW\svc_biztalk_cmk' `
-EnvironmentName ACC
4. Im echten Checkmk-Kontext verifizieren VERIFIKATION
============
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
Get-Content `
"$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" `
-Tail 100
Get-Item `
"$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump | & "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
Select-String ` Select-String -Pattern "BizTalk|UNKNOWN|Permission|snapshot" -Context 0,1
-Pattern "BizTalk|Login failed|Wmi/Permission|Sql/Permission|UNKNOWN" `
-Context 0,1
Erwartetes Ergebnis: ERWARTET
========
- BizTalk Platform ist OK und zeigt operator_group=<exakte Gruppe>. - LastTaskResult = 0
- BizTalk SQL Access zeigt targets=2, available=2, - Snapshot wird minuetlich aktualisiert
discovery_complete=True. - Provider-Log nennt das dedizierte Konto
- Host Instances, Runtime Artifacts und Suspended Instances sind nicht mehr - BizTalk Platform zeigt read_only_group=<exakte Gruppe>
wegen WMI-/SQL-Berechtigungen UNKNOWN. - BizTalk SQL Access zeigt targets=2 und available=2
- Kein Login failed fuer BEW\AV23AGPWBIO1$. - keine berechtigungsbedingten UNKNOWN-Services
- LocalSystem besitzt auf data nur Lesezugriff
Der separate Event-Log-Service zeigte im Screenshot zwei Fehler und sechs
Warnungen. Diese Ereignisse bleiben nach der Berechtigungsfreigabe fachlich zu
pruefen.
WENN ES DANACH WEITERHIN SCHEITERT
==================================
Normalerweise wurde die konfigurierte BizTalk-Operator-Gruppe bereits durch die
BizTalk-Konfiguration am SQL Server eingerichtet. Das Maschinenkonto erbt ueber
seine AD-Gruppenmitgliedschaft die Rolle BTS_OPERATORS und benoetigt keinen
eigenen SQL-Login.
Bleibt "Login failed" trotz bestaetigter Mitgliedschaft, AD-Replikation und
Token-Erneuerung bestehen, prueft die SQL-Administration read-only:
SELECT name, type_desc
FROM sys.server_principals
WHERE name = N'BEW\<EXAKTE_BIZTALK_OPERATOR_GRUPPE>';
Danach jeweils in BizTalkMgmtDb und BizTalkMsgBoxDb:
SELECT
member_principal.name AS member_name,
role_principal.name AS role_name
FROM sys.database_role_members AS drm
JOIN sys.database_principals AS role_principal
ON role_principal.principal_id = drm.role_principal_id
JOIN sys.database_principals AS member_principal
ON member_principal.principal_id = drm.member_principal_id
WHERE member_principal.name =
N'BEW\<EXAKTE_BIZTALK_OPERATOR_GRUPPE>'
AND role_principal.name = N'BTS_OPERATORS';
Fehlt die Abbildung, gleichen BizTalk- und SQL-Administration sie mit der
BizTalk-Konfiguration ab und reparieren sie fuer die konfigurierte Gruppe.
Keinen Einzel-Login oder Sonderrollen fuer BEW\AV23AGPWBIO1$ als Umgehung
anlegen.
SICHERHEIT
==========
Die Gruppenmitgliedschaft des Computerobjekts steht allen Diensten zur
Verfuegung, die auf AV23AGPWBIO1 als LocalSystem laufen und auf
Netzwerkressourcen zugreifen. Mit Security und BizTalk-Betrieb abstimmen.
Ist dies nicht akzeptabel, ist ein separater Collector unter einem dedizierten
gMSA-/Dienstkonto mit BizTalk-Operator-Rechten die sauberere Architektur. Nicht
allein fuer dieses Plugin die Identitaet des gesamten Checkmk-Agent-Dienstes
aendern.
QUELLEN QUELLEN
======= =======
https://learn.microsoft.com/en-us/windows/win32/services/localsystem-account https://learn.microsoft.com/en-us/biztalk/core/managing-biztalk-server-security
https://learn.microsoft.com/en-us/entra/architecture/service-accounts-computer
https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
https://learn.microsoft.com/en-us/biztalk/core/access-control-for-administrative-roles https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-biztalkreadonlyusergroup-property-wmi
https://learn.microsoft.com/en-us/biztalk/core/how-to-modify-group-properties https://docs.checkmk.com/latest/en/agent_windows.html
https://learn.microsoft.com/en-us/powershell/module/activedirectory/add-adgroupmember https://docs.checkmk.com/latest/en/localchecks.html
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/klist
+9 -3
View File
@@ -8,12 +8,17 @@ Alle Services werden als Checkmk Local Checks ausgegeben. Das Format ist:
Die Ausgabe ist so gebaut, dass sie auch mit Checkmk 2.4.0p4 kompatibel bleibt: zwischen den vier Feldern steht genau ein normales Leerzeichen. Die Ausgabe ist so gebaut, dass sie auch mit Checkmk 2.4.0p4 kompatibel bleibt: zwischen den vier Feldern steht genau ein normales Leerzeichen.
Die fachlichen Daten werden minuetlich vom privilegierten Scheduled-Task-
Provider erzeugt. Der Checkmk-Agent liest als `LocalSystem` nur den
integritaets- und altersgeprueften Snapshot. Ist dieser Transport nicht
verfuegbar, liefern alle sechs stabilen Services `UNKNOWN`.
## BizTalk Platform ## BizTalk Platform
Zweck: Zweck:
- Prueft, ob der BizTalk-WMI-Namespace erreichbar ist. - Prueft, ob der BizTalk-WMI-Namespace erreichbar ist.
- Zeigt Server, Gruppe, konfigurierte BizTalk-Operator-Gruppe, Management-DB und Master-MessageBox-DB aus `MSBTS_GroupSetting`. - Zeigt Server, Gruppe, konfigurierte BizTalk-Read-Only-/Operator-Gruppe, Management-DB und Master-MessageBox-DB aus `MSBTS_GroupSetting`.
Status: Status:
@@ -31,11 +36,12 @@ Metriken:
Zweck: Zweck:
- Ermittelt Management- und Master-MessageBox-Datenbank ueber `MSBTS_GroupSetting`. - Ermittelt Management- und Master-MessageBox-Datenbank ueber `MSBTS_GroupSetting`.
- Oeffnet mit der Windows-Identitaet des Checkmk-Agentprozesses eine integrierte SQL-Verbindung. - Oeffnet mit der Windows-Identitaet des privilegierten Provider-Tasks eine integrierte SQL-Verbindung.
- Fuehrt die harmlose Abfrage `SELECT 1` aus und schliesst die Verbindung sofort wieder. - Fuehrt die harmlose Abfrage `SELECT 1` aus und schliesst die Verbindung sofort wieder.
- Zeigt lokale Ausfuehrungsidentitaet und erwartete Netzwerkidentitaet an. - Zeigt lokale Ausfuehrungsidentitaet und erwartete Netzwerkidentitaet an.
Der Service ersetzt nicht das Checkmk-MSSQL-Plugin. Er prueft den fuer das BizTalk-Monitoring relevanten Zugriff des Local Checks. Der Service ersetzt nicht das Checkmk-MSSQL-Plugin. Er prueft den fuer das
BizTalk-Monitoring relevanten Zugriff des dedizierten Provider-Kontos.
Status: Status:
+8 -8
View File
@@ -14,8 +14,8 @@
## Gesunder BizTalk-Server ## Gesunder BizTalk-Server
```text ```text
0 "BizTalk Platform" - BizTalk WMI and platform data reachable, server=BIZTALK01, group=BizTalkMgmtDb, operator_group=CONTOSO\BizTalk Server Operators, mgmt_db=SQL01\BizTalkMgmtDb, msgbox_db=SQL01\BizTalkMsgBoxDb 0 "BizTalk Platform" - BizTalk WMI and platform data reachable, server=BIZTALK01, group=BizTalkMgmtDb, read_only_group=CONTOSO\BizTalk Server Read Only Users, operator_group=CONTOSO\BizTalk Server Operators, mgmt_db=SQL01\BizTalkMgmtDb, msgbox_db=SQL01\BizTalkMsgBoxDb
0 "BizTalk SQL Access" biztalk_sql_targets_total=2;;;0|biztalk_sql_targets_available=2;;;0|biztalk_sql_targets_failed=0;;1;0 SQL access targets=2, available=2, discovery_complete=True, execution_identity=NT AUTHORITY\SYSTEM, network_identity=<DOMAIN>\BIZTALK01$; databases=Management=SQL01\BizTalkMgmtDb:OK, MasterMessageBox=SQL01\BizTalkMsgBoxDb:OK 0 "BizTalk SQL Access" biztalk_sql_targets_total=2;;;0|biztalk_sql_targets_available=2;;;0|biztalk_sql_targets_failed=0;;1;0 SQL access targets=2, available=2, discovery_complete=True, execution_identity=CONTOSO\svc_biztalk_cmk$, network_identity=CONTOSO\svc_biztalk_cmk$; databases=Management=SQL01\BizTalkMgmtDb:OK, MasterMessageBox=SQL01\BizTalkMsgBoxDb:OK
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|biztalk_suspended_resumable=0;1;;0|biztalk_suspended_nonresumable=0;;1;0 No suspended BizTalk service instances found. 0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|biztalk_suspended_resumable=0;1;;0|biztalk_suspended_nonresumable=0;;1;0 No suspended BizTalk service instances found.
0 "BizTalk Host Instances" biztalk_host_instances_total=4;;;0|biztalk_host_instances_started=4;;;0|biztalk_host_instances_stopped=0;;1;0|biztalk_host_instances_pending=0;1;;0|biztalk_host_instances_unknown=0;;1;0 Host instances total=4, started=4, stopped=0, pending=0, unknown=0 0 "BizTalk Host Instances" biztalk_host_instances_total=4;;;0|biztalk_host_instances_started=4;;;0|biztalk_host_instances_stopped=0;;1;0|biztalk_host_instances_pending=0;1;;0|biztalk_host_instances_unknown=0;;1;0 Host instances total=4, started=4, stopped=0, pending=0, unknown=0
0 "BizTalk Runtime Artifacts" biztalk_applications=8;;;0|biztalk_receive_locations=24;;;0|biztalk_receive_locations_disabled=0;1;;0|biztalk_send_ports=31;;;0|biztalk_send_ports_started=31;;;0|biztalk_send_ports_inactive=0;1;;0|biztalk_send_ports_unknown=0;;1;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=12;;;0|biztalk_orchestrations_inactive=0;1;;0|biztalk_orchestrations_unknown=0;;1;0 Applications=8; receive_locations total=24, disabled=0; send_ports total=31, started=31, inactive=0, unknown=0; orchestrations total=12, started=12, inactive=0, unknown=0 0 "BizTalk Runtime Artifacts" biztalk_applications=8;;;0|biztalk_receive_locations=24;;;0|biztalk_receive_locations_disabled=0;1;;0|biztalk_send_ports=31;;;0|biztalk_send_ports_started=31;;;0|biztalk_send_ports_inactive=0;1;;0|biztalk_send_ports_unknown=0;;1;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=12;;;0|biztalk_orchestrations_inactive=0;1;;0|biztalk_orchestrations_unknown=0;;1;0 Applications=8; receive_locations total=24, disabled=0; send_ports total=31, started=31, inactive=0, unknown=0; orchestrations total=12, started=12, inactive=0, unknown=0
@@ -31,20 +31,20 @@
## SQL-Berechtigungsfehler ## SQL-Berechtigungsfehler
```text ```text
3 "BizTalk SQL Access" biztalk_sql_targets_total=2;;;0|biztalk_sql_targets_available=0;;;0|biztalk_sql_targets_failed=2;;1;0 SQL access targets=2, available=0, discovery_complete=True, execution_identity=NT AUTHORITY\SYSTEM, network_identity=<DOMAIN>\BIZTALK01$; databases=Management=SQL01\BizTalkMgmtDb:FAILED, MasterMessageBox=SQL01\BizTalkMsgBoxDb:FAILED; diagnostics: Sql/Permission [Management SQL01\BizTalkMgmtDb]: SQL Server hat die integrierte Anmeldung abgelehnt. Erwartete Netzwerkidentitaet: <DOMAIN>\BIZTALK01$. Massnahme: <DOMAIN>\BIZTALK01$ in die konfigurierte BizTalk-Operator-Gruppe aufnehmen; keine direkten BizTalk-DB-Rollen vergeben. Danach Kerberos-Tickets erneuern und den Agent-Dump wiederholen. Technik: SqlException Number=18456: Login failed for user 3 "BizTalk SQL Access" biztalk_sql_targets_total=2;;;0|biztalk_sql_targets_available=0;;;0|biztalk_sql_targets_failed=2;;1;0 SQL access targets=2, available=0, discovery_complete=True, execution_identity=CONTOSO\svc_biztalk_cmk$, network_identity=CONTOSO\svc_biztalk_cmk$; databases=Management=SQL01\BizTalkMgmtDb:FAILED, MasterMessageBox=SQL01\BizTalkMsgBoxDb:FAILED; diagnostics: Sql/Permission [Management SQL01\BizTalkMgmtDb]: SQL Server hat die integrierte Anmeldung abgelehnt. Massnahme: Provider-Konto und konfigurierte BizTalk-Read-Only-Gruppe mit BTS_READONLY_USERS pruefen; keine direkten BizTalk-DB-Rollen vergeben. Technik: SqlException Number=18456: Login failed for user
``` ```
## WMI-Berechtigungsfehler ## WMI-Berechtigungsfehler
```text ```text
3 "BizTalk Suspended Instances" - Suspendierte Instanzen konnten nicht verlaesslich gelesen werden. Ein leerer Datenbestand wird nicht als OK gewertet.; diagnostics: Wmi/Permission [MSBTS_ServiceInstance]: Zugriff auf MSBTS_ServiceInstance wurde im LocalSystem-Kontext verweigert. Massnahme: Agent-Dump pruefen und Computerkonto <DOMAIN>\BIZTALK01$ zunaechst der konfigurierten BizTalk-Operator-Gruppe zuordnen. Technik: UnauthorizedAccessException HRESULT=0x80070005: Access is denied 3 "BizTalk Suspended Instances" - Suspendierte Instanzen konnten nicht verlaesslich gelesen werden. Ein leerer Datenbestand wird nicht als OK gewertet.; diagnostics: Wmi/Permission [MSBTS_ServiceInstance]: Zugriff des Provider-Kontos auf MSBTS_ServiceInstance wurde verweigert. Massnahme: Provider-Identitaet und konfigurierte BizTalk-Read-Only-Gruppe pruefen. Technik: UnauthorizedAccessException HRESULT=0x80070005: Access is denied
``` ```
## SQL-Loginfehler innerhalb des BizTalk-WMI-Providers ## Fehlender oder veralteter Provider-Snapshot
```text ```text
3 "BizTalk Platform" - BizTalk-Plattformdaten konnten nicht vollstaendig gelesen werden.; diagnostics: Wmi/Permission [MSBTS_GroupSetting]: BizTalk-WMI-Provider konnte den SQL-Zugriff fuer BEW\AV23AGPWBIO1$ nicht anmelden. Massnahme: Computerkonto BEW\AV23AGPWBIO1$ der in der BizTalk-Gruppe konfigurierten BizTalk-Operator-Gruppe zuordnen; keine direkten SQL-Logins oder Datenbankrollen vergeben. Technik: COMException HRESULT=0x80131904: Internal error from OLEDB provider: 'Login failed for user 'BEW\AV23AGPWBIO1$'.' 3 "BizTalk Platform" - Privilegierter BizTalk-Datensnapshot nicht verfuegbar: Snapshot is stale: age=241s, maximum=180s. Massnahme: Scheduled Task 'BizTalk Checkmk Pulse Provider', Provider-Log, Snapshot-Alter und ACL pruefen.
3 "BizTalk SQL Access" biztalk_sql_targets_total=0;;;0|biztalk_sql_targets_available=0;;;0|biztalk_sql_targets_failed=0;;1;0 SQL access targets=0, available=0, discovery_complete=False, execution_identity=NT AUTHORITY\SYSTEM, network_identity=BEW\AV23AGPWBIO1$; diagnostics: Sql/Permission [BizTalk database discovery]: SQL-Zielermittlung ist unvollstaendig, weil der BizTalk-WMI-Provider beim SQL-Zugriff abgewiesen wurde. 3 "BizTalk SQL Access" - Privilegierter BizTalk-Datensnapshot nicht verfuegbar: Snapshot is stale: age=241s, maximum=180s. Massnahme: Scheduled Task 'BizTalk Checkmk Pulse Provider', Provider-Log, Snapshot-Alter und ACL pruefen.
``` ```
## WMI-Schemafehler ## WMI-Schemafehler
@@ -56,5 +56,5 @@
## Fehlendes Deployment ## Fehlendes Deployment
```text ```text
3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter C:\ProgramData\checkmk\agent\local\BizTalkCheckmkPulse. Massnahme: Deployment-Paket und Dateirechte pruefen und danach cmk-agent-ctl.exe dump erneut ausfuehren. 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter C:\Program Files\BizTalkCheckmkPulse. Massnahme: Install-BizTalkCheckmkPulse.ps1 erneut als Administrator ausfuehren und danach cmk-agent-ctl.exe dump erneut ausfuehren.
``` ```
+112 -217
View File
@@ -1,247 +1,142 @@
# Integration in Checkmk # Checkmk-Integration und Betriebsuebergabe
## Zielstruktur auf dem BizTalk-Server ## Verantwortungsgrenzen
| Team | Aufgabe |
| --- | --- |
| AD/Security | dediziertes Dienstkonto oder gMSA; Aufnahme in exakt konfigurierte BizTalk-Read-Only-Gruppe |
| BizTalk/SQL | Gruppenabbildung und `BTS_READONLY_USERS` bestaetigen; keine Einzelrechte |
| Windows | Paket installieren, ACL und Scheduled Task pruefen |
| Checkmk | Wrapper verteilen beziehungsweise Installation koordinieren, Discovery und Alarmierung |
Der Checkmk-Agent bleibt `LocalSystem`. Er greift weder auf BizTalk-WMI noch
auf SQL zu. Der minuetliche Scheduled Task sammelt unter dem privilegierten
Provider-Konto und publiziert einen validierbaren Snapshot.
## Installation
gMSA:
```powershell
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
```
Regulaeres Dienstkonto:
```powershell
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk' `
-EnvironmentName ACC
```
Der Installer legt den Local Check hier ab:
```text ```text
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd %ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
``` ```
## Manuelle Installation Die EXE liegt zentral hier:
1. Release-Paket auf den BizTalk-Server kopieren.
2. Inhalt nach `%ProgramData%\checkmk\agent\local` kopieren.
3. Optional `EnvironmentName` in der `.exe.config` setzen.
4. Test ausfuehren:
```cmd
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd" --self-test
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
```
5. Agent-Ausgabe pruefen:
```cmd
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```
6. In Checkmk:
- Host oeffnen
- Service Discovery ausfuehren
- gefundene `BizTalk ...` Services aufnehmen
- Changes aktivieren
## Aufgaben in Checkmk
Die Checkmk-Kollegen muessen kein serverseitiges Python-Check-Plugin installieren. Das Plugin liefert bereits fertige Local-Check-Services ueber den Windows Agent. In Checkmk selbst sind diese Schritte erforderlich:
1. BizTalk-Hosts fuer `ACC`, `DEV`, `TST` und `PRD` anlegen oder vorhandene Hosts pruefen.
2. Windows-Agent-Status pruefen: Host muss Agent-Daten liefern.
3. Nach Installation des Local Checks den Agent-Dump pruefen.
4. Service Discovery fuer jeden BizTalk-Host ausfuehren.
5. Gefundene `BizTalk ...` Services aufnehmen.
6. Changes aktivieren.
7. Views oder Dashboards mit Filter `Service starts with: BizTalk` anlegen.
8. Benachrichtigungen und Eskalationen je Umgebung definieren.
Empfohlene Service-Behandlung:
| Service | Empfehlung |
| --- | --- |
| `BizTalk Platform` | `UNKNOWN` immer als Integrationsproblem behandeln. |
| `BizTalk SQL Access` | `UNKNOWN` anhand der Kategorie `Permission`, `Connectivity`, `Timeout`, `Configuration` oder `Provider` bearbeiten. |
| `BizTalk Suspended Instances` | In `PRD` alarmieren; in `ACC`/`TST`/`DEV` nach Teamvereinbarung. |
| `BizTalk Host Instances` | `CRIT` alarmieren. |
| `BizTalk Runtime Artifacts` | Erst beobachten; strenge Alarmierung nur bei klar definiertem Runtime-Sollzustand. |
| `BizTalk Event Log` | Schwellwerte nach Beobachtungsphase feinjustieren. |
## Agent Bakery
Checkmk Managed Services Edition 2.4 enthaelt die kommerziellen Mechanismen fuer Agent Bakery. Fuer einen sauberen Rollout:
1. Deployment-Dateien als Custom Files oder ueber ein spaeteres MKP bereitstellen.
2. Regel nur auf BizTalk-Hosts anwenden, z.B. Host-Tag `application:biztalk`.
3. Gebackenen Windows-Agenten fuer die BizTalk-Hosts installieren.
4. Service Discovery ausfuehren.
Hinweis: Dieses Repository enthaelt bewusst noch kein Bakery-Python-Plugin. Die manuelle bzw. dateibasierte Verteilung ist die risikoarme erste Version. Eine Bakery-Erweiterung ist eine sinnvolle Version-2-Ausbaustufe.
## Berechtigungen fuer WMI und BizTalk-Datenbanken
### Ausfuehrung als LocalSystem
Der Checkmk Windows Agent und der Agent Controller laufen standardmaessig unter `LocalSystem` (`NT AUTHORITY\SYSTEM`). Dadurch werden auch `biztalk_checkmk_pulse.cmd` und `BizTalkCheckmkPulse.exe` in diesem Kontext gestartet.
Das Plugin verbindet sich mit dem lokalen Namespace `\\<eigener-server>\root\MicrosoftBizTalkServer`, setzt keine separaten Anmeldedaten und verwendet kein Remote-WMI. Es fuehrt ausschliesslich WMI-Leseabfragen aus und liest das lokale Windows Application Event Log. Fuer diese lokalen Zugriffe sind normalerweise keine zusaetzlichen DCOM-, Firewall- oder WMI-Namespace-Freigaben erforderlich.
### Besonderheit bei getrenntem SQL Server
Mehrere BizTalk-WMI-Klassen lesen Daten aus der BizTalk Management- oder MessageBox-Datenbank. Bei einem getrennten SQL Server verwendet `LocalSystem` fuer diesen Netzwerkzugriff das Active-Directory-Computerkonto des BizTalk-Servers:
```text ```text
DOMAIN\BIZTALKSERVER$ %ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe
``` ```
Lokale Administratorrechte von `LocalSystem` gelten nicht automatisch auf dem entfernten SQL Server. Daher koennen SQL-gestuetzte WMI-Abfragen fehlschlagen, obwohl der lokale WMI-Namespace grundsaetzlich erreichbar ist. ## Checkmk-Agent-Konfiguration
### Konkreter ACC-Befund Der Wrapper darf synchron laufen, da er nur einen kleinen lokalen Snapshot
liest. Eine alte Async-Regel mit `cache_age: 300` aus der direkten
WMI-Architektur soll entfernt werden; andernfalls addiert sie eine unnoetige
Verzoegerung zum minuetlichen Provider.
Auf `AV23AGPWBIO1` meldet der Provider `COMException HRESULT=0x80131904` und als inneren SQL-Fehler `Login failed for user 'BEW\AV23AGPWBIO1$'`. Das beweist zugleich: Aktive Agentkonfiguration pruefen:
- der Check laeuft als `NT AUTHORITY\SYSTEM` ```powershell
- der lokale BizTalk-WMI-Namespace ist erreichbar & "C:\Program Files (x86)\checkmk\service\check_mk_agent.exe" showconfig local
- der Netzwerkzugriff erfolgt als Maschinenkonto `BEW\AV23AGPWBIO1$` ```
- dieses Konto besitzt noch nicht die durch BizTalk vermittelte SQL-Berechtigung
Die vielen `UNKNOWN`-Services sind Folgewirkungen desselben Fehlers. WMI-Namespace-ACL, DCOM und Firewall muessen fuer diesen Befund nicht erweitert werden. Die zwei Fehler und sechs Warnungen in `BizTalk Event Log` sind davon unabhaengig und separat auszuwerten. Verbindlicher Agent-Dump:
### Was die neue Version aendert - und was nicht
Die neue Version verbessert Diagnose und Betriebsfuehrung:
- `0x80131904` mit `Login failed for user` wird als `Wmi/Permission` erkannt.
- Das wirklich abgewiesene Konto wird aus der Providerfehlermeldung uebernommen.
- Die unvollstaendige SQL-Zielermittlung wird als `Sql/Permission` statt als allgemeine Konfigurationstoerung ausgegeben.
- Nach erfolgreichem Zugriff wird die konfigurierte Gruppe als `operator_group=` sichtbar.
Die EXE kann und darf jedoch keine AD-Gruppenmitgliedschaft, SQL-Logins oder Datenbankrollen anlegen. Die alte Version kann nach korrekter Berechtigungsfreigabe grundsaetzlich arbeiten; die neue Version ohne Freigabe bleibt weiterhin `UNKNOWN`. Der Sollzustand ist daher: neue Version plus korrekte Gruppenberechtigung.
### Verbindlicher Funktionstest
Der direkte Start des Wrappers in einer administrativen Eingabeaufforderung ist nur ein Vorabtest, weil er mit dem angemeldeten Benutzer laeuft. Fuer die Berechtigungspruefung ist die Ausgabe unter dem echten Checkmk-Kontext massgeblich:
```powershell ```powershell
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump | & "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
Select-String -Pattern "BizTalk|Login failed|Access denied|Unauthorized|UNKNOWN" -Context 0,1 Select-String -Pattern "BizTalk|UNKNOWN|snapshot|Permission" -Context 0,1
``` ```
Die Ausgabe muss die sechs stabilen `BizTalk ...` Services mit plausiblen Daten enthalten. `Access denied`, `UnauthorizedAccessException`, `Sql/Permission` und ein berechtigungsbedingtes `UNKNOWN` weisen auf eine fehlende Rollenzuordnung hin. ## Service Discovery
Die EXE klassifiziert WMI- und SQL-Probleme und schreibt zu jeder Diagnose `Massnahme:` und `Technik:`. WMI-Schemafehler (`InvalidClass`/`InvalidQuery`) werden separat als `Wmi/Schema` ausgewiesen und duerfen nicht durch Rechteerweiterungen behandelt werden. Eine fehlgeschlagene erforderliche WMI-Abfrage erzeugt beim betroffenen Service immer `UNKNOWN`; fehlende Daten werden nicht als Nullbestand und damit nicht als `OK` ausgegeben. 1. Provider dreimal erfolgreich laufen lassen.
2. `LastTaskResult=0`, frischen Snapshot und Log pruefen.
3. Agent-Dump im `LocalSystem`-Kontext pruefen.
4. Service Discovery fuer den BizTalk-Host ausfuehren.
5. sechs stabile Services aufnehmen.
6. Changes aktivieren.
7. Views, Servicegruppen und Benachrichtigungen einrichten.
Fuer den produktiven Betrieb wird empfohlen, den Local Check alle 300 Sekunden asynchron auszufuehren. Dadurch erzeugen fehlende SQL-Rechte nicht bei jedem Agent-Abruf neue fehlgeschlagene Logins. In `%ProgramData%\checkmk\agent\check_mk.user.yml`: Host-Tags:
```yaml ```text
local: env:ACC|DEV|TST|PRD
enabled: yes app:biztalk
execution:
- pattern: $CUSTOM_LOCAL_PATH$\biztalk_checkmk_pulse.cmd
async: yes
run: yes
cache_age: 300
``` ```
Alternativ koennen die Checkmk-Kollegen in der Agent Bakery die Regeln `Set execution mode for plug-ins and local checks` und `Set cache age for plug-ins and local checks` verwenden. Der Cache reduziert Last und SQL-Fehlerlogs; ein Zustandswechsel wird dadurch um maximal die konfigurierte Cache-Zeit verzoegert. Servicefilter:
### Least-Privilege-Vorgehen
1. Dienstkonto mit `Get-CimInstance Win32_Service` kontrollieren; der ACC-Dump bestaetigt bereits `NT AUTHORITY\SYSTEM`.
2. In der BizTalk Administration Console unter `BizTalk Group` > `Properties` > `General` den Wert `BizTalk Operators Group` ablesen. Alternativ kann ein berechtigtes Konto `MSBTS_GroupSetting.BizTalkOperatorGroup` abfragen.
3. Ein AD-Administrator fuegt das Computerobjekt `AV23AGPWBIO1` genau dieser Gruppe hinzu:
```powershell
Import-Module ActiveDirectory
$computer = Get-ADComputer -Identity 'AV23AGPWBIO1'
Add-ADGroupMember -Identity '<EXAKTE_BIZTALK_OPERATOR_GRUPPE>' -Members $computer -WhatIf
```
Erst nach Kontrolle ohne `-WhatIf` ausfuehren. Keine direkten SQL-Logins, Datenbankrollen oder `sysadmin`-Rechte fuer `BEW\AV23AGPWBIO1$` anlegen.
4. AD-Replikation abwarten und den Server im Wartungsfenster neu starten. Alternativ Maschinen-Tickets in einer administrativen Shell mit `klist purge -li 0x3e7` verwerfen und den Checkmk-Agent-Dienst neu starten.
5. Agent-Dump erneut ausfuehren. Erwartet werden `operator_group=<...>`, `targets=2`, `available=2`, `discovery_complete=True` und keine berechtigungsbedingten `UNKNOWN`-Services.
6. Nur bei weiterhin abgelehnten, konkret identifizierten WMI-Klassen mit BizTalk- und SQL-Administration klaeren, ob erweiterte Rechte erforderlich sind.
Die BizTalk-Operator-Rolle ist fuer grundlegende Administration und Monitoring vorgesehen. Sie kann Zustands- und Message-Flow-Informationen lesen, darf aber keine Konfiguration oder Nachrichteninhalte einsehen. Das entspricht dem lesenden Funktionsumfang dieses Plugins. Keine direkten SQL-Rollen in den BizTalk-Datenbanken hinzufuegen; die von BizTalk konfigurierte Windows-Gruppe soll die Berechtigungen vermitteln.
### Wenn die Gruppenmitgliedschaft allein nicht genuegt
Normalerweise hat die BizTalk-Konfiguration die konfigurierte Domain-Gruppe bereits auf dem SQL Server abgebildet und ihren Datenbankbenutzern die Rolle `BTS_OPERATORS` zugeordnet. Das Maschinenkonto benoetigt dann keinen eigenen SQL-Login, sondern erbt den Zugriff ueber die AD-Gruppe.
Bleibt der Loginfehler nach AD-Replikation und Token-Erneuerung bestehen, prueft die SQL-Administration zunaechst read-only den Gruppenlogin:
```sql
SELECT name, type_desc
FROM sys.server_principals
WHERE name = N'BEW\<EXAKTE_BIZTALK_OPERATOR_GRUPPE>';
```
Anschliessend wird in `BizTalkMgmtDb` und `BizTalkMsgBoxDb` die Rollenzuordnung geprueft:
```sql
SELECT
member_principal.name AS member_name,
role_principal.name AS role_name
FROM sys.database_role_members AS drm
JOIN sys.database_principals AS role_principal
ON role_principal.principal_id = drm.role_principal_id
JOIN sys.database_principals AS member_principal
ON member_principal.principal_id = drm.member_principal_id
WHERE member_principal.name = N'BEW\<EXAKTE_BIZTALK_OPERATOR_GRUPPE>'
AND role_principal.name = N'BTS_OPERATORS';
```
Liefert die Abfrage keinen passenden Eintrag, wird die fehlende Gruppenabbildung mit der BizTalk-Konfiguration verglichen und durch BizTalk-/SQL-Administration fuer die konfigurierte Gruppe repariert. Kein Einzel-Login fuer `BEW\AV23AGPWBIO1$`, keine ad-hoc Rollenvergabe und kein `sysadmin` als Umgehung.
Entscheidungsmatrix:
| Beobachtung | Bewertung und Massnahme |
| --- | --- |
| Alle sechs stabilen Services liefern plausible Werte | Keine Aenderung erforderlich. |
| `root\MicrosoftBizTalkServer` ist nicht erreichbar | BizTalk-WMI-Provider, WMI-Dienst, Namespace und dessen ACL gezielt pruefen. |
| `Wmi/Schema` beziehungsweise `InvalidClass`/`InvalidQuery` | Klassen- und Property-Namen gegen das installierte BizTalk-WMI-Schema pruefen; keine Berechtigungen erweitern. |
| `BizTalk SQL Access` meldet `Sql/Permission` | Maschinenkonto in die konfigurierte BizTalk-Operator-Gruppe aufnehmen, Kerberos erneuern und Agent-Dump wiederholen. |
| `BizTalk SQL Access` meldet `Sql/Connectivity` | SQL-Server-/Instanzname, DNS, Dienst, TCP-Port und Firewall pruefen. |
| `BizTalk SQL Access` meldet `Sql/Timeout` | SQL-/Netzwerkauslastung untersuchen; Timeout nur nach Ursachenanalyse anpassen. |
| SQL-gestuetzte Klassen melden `0x80131904` oder `Login failed for user` | Das exakt genannte Computerkonto in die konfigurierte BizTalk-Operator-Gruppe aufnehmen. |
| Nur `BizTalk Event Log` ist `UNKNOWN` | Lokalen Zugriff auf das Application Event Log pruefen. |
| Fehler bleibt nach Operator-Zuweisung bestehen | Exakte WMI-Klasse anhand der Service-Ausgabe bestimmen und deren BizTalk-/SQL-Rollenanforderung pruefen. |
### Sicherheitsauswirkung und gMSA-Alternative
Die Mitgliedschaft des Computerkontos in der Operator-Gruppe gilt fuer alle Dienste, die auf dem BizTalk-Server als `LocalSystem` laufen und mit dem Computerkonto auf Netzwerkressourcen zugreifen. Diese Auswirkung muss mit Security und BizTalk-Betrieb abgestimmt werden.
Falls das Computerkonto keine BizTalk-Rechte erhalten darf, kann ein separater Collector unter einem dedizierten gMSA- oder Dienstkonto mit Operator-Rechten die Checkmk-Ausgabe erzeugen und als Spool-Datei bereitstellen. Das waere eine eigene Betriebsvariante und ist in der aktuellen Local-Check-Version noch nicht implementiert. Den gesamten Checkmk-Agent-Dienst nur fuer dieses Plugin auf ein anderes Konto umzustellen ist nicht empfohlen, da die Identitaetsaenderung alle Agent-Sektionen und Local Checks betrifft.
Quellen:
- Checkmk Windows Agent: https://docs.checkmk.com/latest/en/agent_windows.html
- Checkmk Local Checks: https://docs.checkmk.com/latest/en/localchecks.html
- BizTalk Minimum Security User Rights: https://learn.microsoft.com/en-us/biztalk/core/minimum-security-user-rights
- BizTalk Access Control and Data Security: https://learn.microsoft.com/en-us/biztalk/core/access-control-and-data-security
- BizTalk `MSBTS_GroupSetting`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi
- BizTalk Windows-Gruppen und SQL-Rollen: https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
- BizTalk administrative Rollen: https://learn.microsoft.com/en-us/biztalk/core/access-control-for-administrative-roles
- BizTalk Gruppen-Eigenschaften: https://learn.microsoft.com/en-us/biztalk/core/how-to-modify-group-properties
- BizTalk WMI Core Server Classes: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/core-server-classes
- Microsoft LocalSystem und Computerkonten: https://learn.microsoft.com/en-us/entra/architecture/service-accounts-computer
- Microsoft `Add-ADGroupMember`: https://learn.microsoft.com/en-us/powershell/module/activedirectory/add-adgroupmember
- Microsoft `klist`: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/klist
- Microsoft WMI Namespace Security: https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces
## Empfohlene Host-Struktur
Host-Tags oder Ordner:
- `env:ACC`
- `env:DEV`
- `env:TST`
- `env:PRD`
- `app:biztalk`
Service-Filter fuer Views:
```text ```text
Service starts with: BizTalk Service starts with: BizTalk
``` ```
Dashboard-Kacheln: ## Bakery-/Softwareverteilung
- Host/Service state fuer BizTalk-Host Die komplette Installation umfasst mehr als eine Dateiablage:
- Service state fuer `BizTalk Suspended Instances`
- Service state fuer `BizTalk SQL Access` - Programmdateien unter `%ProgramFiles%`,
- Graph `biztalk_suspended_total` - Runtimeverzeichnisse und ACLs,
- Graph `biztalk_host_instances_stopped` - Scheduled Task mit Providerkonto,
- Graph `biztalk_eventlog_errors` - Local-Check-Wrapper.
- MSSQL-Services des SQL-Servers daneben
Der Wrapper allein kann per Agent Bakery verteilt werden, ersetzt aber nicht
die lokale Providerinstallation und die Kontofreigabe. Fuer den ersten Rollout
ist das signierte/abgenommene Deployment-Paket mit administrativer
Installationsautomation die klarere Variante.
## Healthchecks
```powershell
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
Get-Item "$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
Get-Content "$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" -Tail 50
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
```
Soll:
- Task laeuft jede Minute und endet mit `0`.
- Snapshot ist kleiner als `SnapshotMaxBytes` und juenger als 180 Sekunden.
- Log nennt das dedizierte Providerkonto.
- Platform zeigt `read_only_group=...`.
- SQL Access zeigt `targets=2`, `available=2`.
- keine berechtigungsbedingten `UNKNOWN`-Services.
## Alarmierung der Transportkette
Ein Ausfall des Providers wird ueber alle sechs Services als `UNKNOWN`
sichtbar. Die Summary nennt fehlenden, unlesbaren, ungueltigen oder stale
Snapshot. Als Betriebsregel sollte `UNKNOWN` dieser Services genauso
eskaliert werden wie ein technischer Monitoringausfall.
Optional kann Windows Task Scheduler zusaetzlich durch vorhandene Checkmk
Task-/Event-Log-Regeln ueberwacht werden. Das ist eine Ergaenzung, kein Ersatz
fuer die eingebaute Stale-Pruefung.
## Rollout
Empfohlene Reihenfolge:
1. ACC: Berechtigung, Task, Snapshot, Stale-Test und Discovery abnehmen.
2. DEV/TST: gleiche Automatisierung und umgebungsspezifische Config.
3. PRD: Change, Wartungsfenster, Healthcheck und fachliche Plausibilitaet.
Ausfuehrliche Architektur, ACL, Fehlerbilder und Abnahmekriterien:
[Dokumentation.md](../Dokumentation.md).
+3 -2
View File
@@ -6,15 +6,16 @@ if errorlevel 1 exit /b 1
set "ROOT=%~dp0.." set "ROOT=%~dp0.."
set "OUT=%ROOT%\artifacts\BizTalkCheckmkPulse-deploy" set "OUT=%ROOT%\artifacts\BizTalkCheckmkPulse-deploy"
set "APP=%OUT%\BizTalkCheckmkPulse" set "APP=%OUT%\application"
if exist "%OUT%" rmdir /s /q "%OUT%" if exist "%OUT%" rmdir /s /q "%OUT%"
mkdir "%APP%" mkdir "%APP%"
copy "%ROOT%\deployment\checkmk\biztalk_checkmk_pulse.cmd" "%OUT%\" >nul copy "%ROOT%\deployment\checkmk\biztalk_checkmk_pulse.cmd" "%OUT%\" >nul
copy "%ROOT%\deployment\Install-BizTalkCheckmkPulse.ps1" "%OUT%\" >nul
copy "%ROOT%\deployment\Uninstall-BizTalkCheckmkPulse.ps1" "%OUT%\" >nul
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe" "%APP%\" >nul copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe" "%APP%\" >nul
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe.config" "%APP%\" >nul copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe.config" "%APP%\" >nul
echo Deployment package written to %OUT% echo Deployment package written to %OUT%
exit /b 0 exit /b 0
+9 -1
View File
@@ -6,8 +6,16 @@
<add key="ServicePrefix" value="BizTalk" /> <add key="ServicePrefix" value="BizTalk" />
<add key="EnvironmentName" value="" /> <add key="EnvironmentName" value="" />
<!-- Provider/Consumer-Datei. Der Scheduled Task schreibt, LocalSystem liest. -->
<add key="SnapshotPath" value="%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot" />
<!-- Bei minuetlicher Provider-Ausfuehrung nach drei Minuten als UNKNOWN bewerten. -->
<add key="SnapshotMaxAgeSeconds" value="180" />
<add key="SnapshotMaxBytes" value="1048576" />
<add key="LogDirectory" value="%ProgramData%\BizTalkCheckmkPulse\logs" />
<add key="LogRetentionDays" value="30" />
<add key="QueryTimeoutSeconds" value="25" /> <add key="QueryTimeoutSeconds" value="25" />
<!-- Testet integrierte Windows-Anmeldung an den per WMI ermittelten BizTalk-Datenbanken. --> <!-- Testet die Anmeldung des privilegierten Provider-Kontos an den ermittelten BizTalk-Datenbanken. -->
<add key="ProbeSqlConnectivity" value="true" /> <add key="ProbeSqlConnectivity" value="true" />
<add key="SqlConnectionTimeoutSeconds" value="5" /> <add key="SqlConnectionTimeoutSeconds" value="5" />
<add key="WarnResumableThreshold" value="1" /> <add key="WarnResumableThreshold" value="1" />
@@ -43,10 +43,12 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="CheckmkLocalFormatter.cs" /> <Compile Include="CheckmkLocalFormatter.cs" />
<Compile Include="EventLogProbe.cs" /> <Compile Include="EventLogProbe.cs" />
<Compile Include="FileLogger.cs" />
<Compile Include="MonitoringOptions.cs" /> <Compile Include="MonitoringOptions.cs" />
<Compile Include="Models.cs" /> <Compile Include="Models.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="SqlConnectivityProbe.cs" /> <Compile Include="SqlConnectivityProbe.cs" />
<Compile Include="SnapshotStore.cs" />
<Compile Include="WmiBizTalkProbe.cs" /> <Compile Include="WmiBizTalkProbe.cs" />
<Compile Include="WmiHelpers.cs" /> <Compile Include="WmiHelpers.cs" />
</ItemGroup> </ItemGroup>
@@ -83,6 +83,22 @@ namespace BizTalkCheckmkPulse
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", action); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", action);
} }
/// <summary>
/// Liefert stabile UNKNOWN-Services, wenn der Consumer keinen gueltigen Provider-Snapshot lesen kann.
/// </summary>
public IEnumerable<string> FormatSnapshotFailure(string reason)
{
var detail = "Privilegierter BizTalk-Datensnapshot nicht verfuegbar: "
+ SanitizeDetail(reason)
+ " Massnahme: Scheduled Task 'BizTalk Checkmk Pulse Provider', Provider-Log, Snapshot-Alter und ACL pruefen.";
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Platform"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("SQL Access"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Runtime Artifacts"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", detail);
}
/// <summary> /// <summary>
/// Formatiert Erreichbarkeit und Basisdaten des BizTalk-WMI-Providers. /// Formatiert Erreichbarkeit und Basisdaten des BizTalk-WMI-Providers.
/// </summary> /// </summary>
@@ -96,6 +112,7 @@ namespace BizTalkCheckmkPulse
detail.Append(available ? "BizTalk WMI and platform data reachable" : "BizTalk WMI or required platform data not readable"); detail.Append(available ? "BizTalk WMI and platform data reachable" : "BizTalk WMI or required platform data not readable");
detail.Append(", server=").Append(EmptyAsUnknown(result.Platform.ServerName)); detail.Append(", server=").Append(EmptyAsUnknown(result.Platform.ServerName));
AppendOptional(detail, "group", result.Platform.GroupName); AppendOptional(detail, "group", result.Platform.GroupName);
AppendOptional(detail, "read_only_group", result.Platform.ReadOnlyUserGroup);
AppendOptional(detail, "operator_group", result.Platform.OperatorGroup); AppendOptional(detail, "operator_group", result.Platform.OperatorGroup);
AppendOptional(detail, "mgmt_db", JoinDb(result.Platform.ManagementDbServer, result.Platform.ManagementDbName)); AppendOptional(detail, "mgmt_db", JoinDb(result.Platform.ManagementDbServer, result.Platform.ManagementDbName));
AppendOptional(detail, "msgbox_db", JoinDb(result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName)); AppendOptional(detail, "msgbox_db", JoinDb(result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName));
@@ -307,7 +324,7 @@ namespace BizTalkCheckmkPulse
CheckState.Unknown, CheckState.Unknown,
_options.ServiceName("Event Log"), _options.ServiceName("Event Log"),
"-", "-",
"Application Event Log konnte nicht gelesen werden: " + result.EventLog.Failure + " Massnahme: lokalen Event-Log-Zugriff des Checkmk-Agentkontos und den Windows Event Log Dienst pruefen."); "Application Event Log konnte nicht gelesen werden: " + result.EventLog.Failure + " Massnahme: lokalen Event-Log-Zugriff des Provider-Kontos und den Windows Event Log Dienst pruefen.");
} }
var state = _options.EventLogCritThreshold > 0 && result.EventLog.Errors >= _options.EventLogCritThreshold var state = _options.EventLogCritThreshold > 0 && result.EventLog.Errors >= _options.EventLogCritThreshold
+150
View File
@@ -0,0 +1,150 @@
using System;
using System.Globalization;
using System.IO;
using System.Security.Principal;
using System.Text;
using System.Threading;
namespace BizTalkCheckmkPulse
{
/// <summary>
/// Kleine, ausfallsichere Tagesdatei-Protokollierung ohne externe Abhaengigkeiten.
/// Logging-Fehler duerfen weder Provider noch Checkmk-Ausgabe blockieren.
/// </summary>
internal sealed class FileLogger
{
private readonly string _directory;
private readonly int _retentionDays;
private readonly string _component;
public FileLogger(string directory, int retentionDays, string component)
{
_directory = directory;
_retentionDays = retentionDays;
_component = string.IsNullOrWhiteSpace(component) ? "application" : component;
}
public void Info(string message)
{
Write("INFO", message, null);
}
public void Warning(string message)
{
Write("WARN", message, null);
}
public void Error(string message, Exception exception)
{
Write("ERROR", message, exception);
}
public void Prune()
{
try
{
if (!Directory.Exists(_directory))
{
return;
}
var cutoff = DateTime.UtcNow.Date.AddDays(-_retentionDays);
foreach (var file in Directory.GetFiles(_directory, "biztalk-checkmk-pulse-*.log"))
{
try
{
if (File.GetLastWriteTimeUtc(file) < cutoff)
{
File.Delete(file);
}
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
}
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
}
private void Write(string level, string message, Exception exception)
{
try
{
Directory.CreateDirectory(_directory);
var path = Path.Combine(
_directory,
"biztalk-checkmk-pulse-" + DateTime.UtcNow.ToString("yyyyMMdd", CultureInfo.InvariantCulture) + ".log");
var line = new StringBuilder()
.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture))
.Append(" level=").Append(level)
.Append(" component=").Append(_component)
.Append(" pid=").Append(System.Diagnostics.Process.GetCurrentProcess().Id)
.Append(" identity=").Append(CurrentIdentity())
.Append(" message=").Append(SingleLine(message));
if (exception != null)
{
line.Append(" exception=").Append(SingleLine(exception.ToString()));
}
line.Append(Environment.NewLine);
var bytes = new UTF8Encoding(false).GetBytes(line.ToString());
for (var attempt = 0; attempt < 3; attempt++)
{
try
{
using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read))
{
stream.Write(bytes, 0, bytes.Length);
stream.Flush();
}
return;
}
catch (IOException)
{
if (attempt == 2)
{
return;
}
Thread.Sleep(25 * (attempt + 1));
}
}
}
catch (Exception)
{
// Logging bleibt bewusst best effort.
}
}
private static string CurrentIdentity()
{
try
{
var identity = WindowsIdentity.GetCurrent();
return identity == null ? "(unknown)" : SingleLine(identity.Name);
}
catch (Exception)
{
return "(unknown)";
}
}
private static string SingleLine(string value)
{
return (value ?? string.Empty)
.Replace("\r", "\\r")
.Replace("\n", "\\n")
.Replace("\t", " ");
}
}
}
+1
View File
@@ -125,6 +125,7 @@ namespace BizTalkCheckmkPulse
public string ServerName { get; set; } public string ServerName { get; set; }
public string GroupName { get; set; } public string GroupName { get; set; }
public string OperatorGroup { get; set; } public string OperatorGroup { get; set; }
public string ReadOnlyUserGroup { get; set; }
public string ManagementDbServer { get; set; } public string ManagementDbServer { get; set; }
public string ManagementDbName { get; set; } public string ManagementDbName { get; set; }
public string MessageBoxDbServer { get; set; } public string MessageBoxDbServer { get; set; }
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Configuration; using System.Configuration;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
namespace BizTalkCheckmkPulse namespace BizTalkCheckmkPulse
@@ -27,6 +28,12 @@ namespace BizTalkCheckmkPulse
public int EventLogWarnThreshold { get; set; } public int EventLogWarnThreshold { get; set; }
public int EventLogCritThreshold { get; set; } public int EventLogCritThreshold { get; set; }
public IReadOnlyList<string> EventLogSources { get; set; } public IReadOnlyList<string> EventLogSources { get; set; }
public string SnapshotPath { get; set; }
public int SnapshotMaxAgeSeconds { get; set; }
public int SnapshotMaxBytes { get; set; }
public string LogDirectory { get; set; }
public int LogRetentionDays { get; set; }
public bool Collect { get; set; }
public bool SelfTest { get; set; } public bool SelfTest { get; set; }
/// <summary> /// <summary>
@@ -50,6 +57,12 @@ namespace BizTalkCheckmkPulse
EventLogWarnThreshold = 1; EventLogWarnThreshold = 1;
EventLogCritThreshold = 10; EventLogCritThreshold = 10;
EventLogSources = new[] { "BizTalk Server", "XLANG/s", "ENTSSO", "BizTalk Server Application", "BizTalk Server EDI" }; EventLogSources = new[] { "BizTalk Server", "XLANG/s", "ENTSSO", "BizTalk Server Application", "BizTalk Server EDI" };
var commonData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
SnapshotPath = Path.Combine(commonData, "BizTalkCheckmkPulse", "data", "biztalk-checkmk-pulse.snapshot");
SnapshotMaxAgeSeconds = 180;
SnapshotMaxBytes = 1048576;
LogDirectory = Path.Combine(commonData, "BizTalkCheckmkPulse", "logs");
LogRetentionDays = 30;
} }
/// <summary> /// <summary>
@@ -91,8 +104,17 @@ namespace BizTalkCheckmkPulse
options.EventLogWarnThreshold = ReadInt(settings, "EventLogWarnThreshold", options.EventLogWarnThreshold, 0, 1000000); options.EventLogWarnThreshold = ReadInt(settings, "EventLogWarnThreshold", options.EventLogWarnThreshold, 0, 1000000);
options.EventLogCritThreshold = ReadInt(settings, "EventLogCritThreshold", options.EventLogCritThreshold, 0, 1000000); options.EventLogCritThreshold = ReadInt(settings, "EventLogCritThreshold", options.EventLogCritThreshold, 0, 1000000);
options.EventLogSources = ReadSources(ReadString(settings, "EventLogSources", string.Join("|", options.EventLogSources))); options.EventLogSources = ReadSources(ReadString(settings, "EventLogSources", string.Join("|", options.EventLogSources)));
options.SnapshotPath = Environment.ExpandEnvironmentVariables(ReadString(settings, "SnapshotPath", options.SnapshotPath));
options.SnapshotMaxAgeSeconds = ReadInt(settings, "SnapshotMaxAgeSeconds", options.SnapshotMaxAgeSeconds, 60, 86400);
options.SnapshotMaxBytes = ReadInt(settings, "SnapshotMaxBytes", options.SnapshotMaxBytes, 4096, 16777216);
options.LogDirectory = Environment.ExpandEnvironmentVariables(ReadString(settings, "LogDirectory", options.LogDirectory));
options.LogRetentionDays = ReadInt(settings, "LogRetentionDays", options.LogRetentionDays, 1, 365);
ApplyArguments(options, args ?? new string[0]); ApplyArguments(options, args ?? new string[0]);
if (!options.SelfTest)
{
Validate(options);
}
return options; return options;
} }
@@ -110,6 +132,14 @@ namespace BizTalkCheckmkPulse
{ {
options.SelfTest = true; options.SelfTest = true;
} }
else if (EqualsAny(arg, "--collect", "/collect"))
{
options.Collect = true;
}
else if (EqualsAny(arg, "--consume", "/consume"))
{
options.Collect = false;
}
else if (EqualsAny(arg, "--server", "/server") && i + 1 < args.Length) else if (EqualsAny(arg, "--server", "/server") && i + 1 < args.Length)
{ {
options.Server = args[++i]; options.Server = args[++i];
@@ -133,6 +163,22 @@ namespace BizTalkCheckmkPulse
} }
} }
/// <summary>
/// Verhindert unsichere oder mehrdeutige Laufzeitpfade.
/// </summary>
private static void Validate(MonitoringOptions options)
{
if (string.IsNullOrWhiteSpace(options.SnapshotPath) || !Path.IsPathRooted(options.SnapshotPath))
{
throw new ConfigurationErrorsException("SnapshotPath must be an absolute path.");
}
if (string.IsNullOrWhiteSpace(options.LogDirectory) || !Path.IsPathRooted(options.LogDirectory))
{
throw new ConfigurationErrorsException("LogDirectory must be an absolute path.");
}
}
/// <summary> /// <summary>
/// Liest einen getrimmten Konfigurationswert. /// Liest einen getrimmten Konfigurationswert.
/// </summary> /// </summary>
+141 -25
View File
@@ -1,19 +1,22 @@
using System; using System;
using System.Collections.Generic; using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Text; using System.Text;
namespace BizTalkCheckmkPulse namespace BizTalkCheckmkPulse
{ {
/// <summary> /// <summary>
/// Einstiegspunkt des Checkmk Local Checks. /// Einstiegspunkt fuer privilegierten Provider und unprivilegierten Checkmk-Consumer.
/// </summary> /// </summary>
internal static class Program internal static class Program
{ {
/// <summary> /// <summary>
/// Laedt die Konfiguration, fuehrt die Probes aus und schreibt Checkmk-Zeilen nach STDOUT. /// Laedt die Konfiguration und fuehrt je nach Modus Provider oder Snapshot-Consumer aus.
/// </summary> /// </summary>
/// <param name="args">Kommandozeilenargumente wie <c>--self-test</c> oder <c>--environment</c>.</param> /// <param name="args">Kommandozeilenargumente wie <c>--self-test</c> oder <c>--environment</c>.</param>
/// <returns>Immer 0, damit Diagnosefehler als UNKNOWN-Service statt als kaputte Agent-Sektion erscheinen.</returns> /// <returns>Consumer immer 0; Provider 0 bei Erfolg und ungleich 0 bei einem Laufzeitfehler.</returns>
private static int Main(string[] args) private static int Main(string[] args)
{ {
MonitoringOptions options = null; MonitoringOptions options = null;
@@ -34,38 +37,151 @@ namespace BizTalkCheckmkPulse
return 0; return 0;
} }
var result = new ProbeResult(); return options.Collect
var wmiProbe = new WmiBizTalkProbe(options); ? RunProvider(options, formatter)
wmiProbe.Query(result); : RunConsumer(options, formatter);
// Der SQL-Test verwendet bewusst dieselbe Windows-Identitaet wie der Checkmk-Agent.
var sqlProbe = new SqlConnectivityProbe(options);
sqlProbe.Query(result);
if (options.ProbeEventLog)
{
var eventLogProbe = new EventLogProbe(options);
eventLogProbe.Query(result);
}
foreach (var line in formatter.Format(result))
{
Console.WriteLine(line);
}
return 0;
} }
catch (Exception ex) catch (Exception ex)
{ {
var fallbackOptions = options ?? new MonitoringOptions(); var fallbackOptions = options ?? new MonitoringOptions();
var formatter = new CheckmkLocalFormatter(fallbackOptions); var formatter = new CheckmkLocalFormatter(fallbackOptions);
foreach (var line in formatter.FormatFatal("BizTalk Checkmk Pulse failed: " + ex.GetType().Name + ": " + ex.Message)) foreach (var line in formatter.FormatSnapshotFailure(
"Programmstart fehlgeschlagen: " + ex.GetType().Name + ": " + ex.Message))
{
Console.WriteLine(line);
}
return options != null && options.Collect ? 1 : 0;
}
}
private static int RunProvider(MonitoringOptions options, CheckmkLocalFormatter formatter)
{
var logger = new FileLogger(options.LogDirectory, options.LogRetentionDays, "provider");
logger.Prune();
var stopwatch = Stopwatch.StartNew();
var lockPath = options.SnapshotPath + ".provider.lock";
try
{
EnsureProviderIdentity();
Directory.CreateDirectory(Path.GetDirectoryName(options.SnapshotPath));
using (new FileStream(lockPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
logger.Info("Collection started. snapshot=" + options.SnapshotPath);
var result = Collect(options);
var lines = formatter.Format(result).ToArray();
new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
.Write(lines, DateTime.UtcNow, CurrentIdentity());
stopwatch.Stop();
logger.Info(
"Collection completed. lines="
+ lines.Length
+ " diagnostics="
+ result.Diagnostics.Count
+ " elapsed_ms="
+ stopwatch.ElapsedMilliseconds);
return 0;
}
}
catch (IOException ex)
{
logger.Warning("Collection skipped or snapshot I/O failed: " + ex.Message);
return 2;
}
catch (Exception ex)
{
stopwatch.Stop();
logger.Error("Collection failed after " + stopwatch.ElapsedMilliseconds + " ms.", ex);
TryWriteFatalSnapshot(options, formatter, ex, logger);
return 1;
}
}
private static void EnsureProviderIdentity()
{
using (var identity = WindowsIdentity.GetCurrent())
{
if (identity != null
&& identity.User != null
&& identity.User.IsWellKnown(WellKnownSidType.LocalSystemSid))
{
throw new InvalidOperationException(
"Provider mode must not run as LocalSystem. Configure the dedicated Scheduled Task account.");
}
}
}
private static int RunConsumer(MonitoringOptions options, CheckmkLocalFormatter formatter)
{
var snapshot = new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
.Read(DateTime.UtcNow, TimeSpan.FromSeconds(options.SnapshotMaxAgeSeconds));
if (snapshot.IsSuccess)
{
foreach (var line in snapshot.Lines)
{ {
Console.WriteLine(line); Console.WriteLine(line);
} }
return 0; return 0;
} }
new FileLogger(options.LogDirectory, options.LogRetentionDays, "consumer")
.Warning("Snapshot rejected. reason=" + snapshot.Error + " path=" + options.SnapshotPath);
foreach (var line in formatter.FormatSnapshotFailure(snapshot.Error))
{
Console.WriteLine(line);
}
return 0;
}
private static ProbeResult Collect(MonitoringOptions options)
{
var result = new ProbeResult();
new WmiBizTalkProbe(options).Query(result);
new SqlConnectivityProbe(options).Query(result);
if (options.ProbeEventLog)
{
new EventLogProbe(options).Query(result);
}
return result;
}
private static void TryWriteFatalSnapshot(
MonitoringOptions options,
CheckmkLocalFormatter formatter,
Exception exception,
FileLogger logger)
{
try
{
var lines = formatter
.FormatFatal("Privilegierter Datenprovider fehlgeschlagen: " + exception.GetType().Name + ": " + exception.Message)
.ToArray();
new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
.Write(lines, DateTime.UtcNow, CurrentIdentity());
logger.Warning("A current UNKNOWN snapshot was written after the provider failure.");
}
catch (Exception snapshotException)
{
logger.Error("Fatal UNKNOWN snapshot could not be written.", snapshotException);
}
}
private static string CurrentIdentity()
{
try
{
var identity = WindowsIdentity.GetCurrent();
return identity == null ? "(unknown)" : identity.Name;
}
catch (Exception)
{
return "(unknown)";
}
} }
} }
} }
@@ -1,3 +1,10 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Reflection;
[assembly: InternalsVisibleTo("BizTalkCheckmkPulse.Tests")] [assembly: InternalsVisibleTo("BizTalkCheckmkPulse.Tests")]
[assembly: AssemblyTitle("BizTalk Checkmk Pulse")]
[assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")]
[assembly: AssemblyCompany("BEW")]
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
[assembly: AssemblyVersion("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]
+353
View File
@@ -0,0 +1,353 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Diagnostics;
namespace BizTalkCheckmkPulse
{
/// <summary>
/// Schreibt und liest einen atomaren, integritaetsgeschuetzten Checkmk-Snapshot.
/// </summary>
internal sealed class SnapshotStore
{
internal const string Magic = "BIZTALK_CHECKMK_PULSE_SNAPSHOT_V1";
private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(false, true);
private readonly string _path;
private readonly int _maxBytes;
public SnapshotStore(string path, int maxBytes)
{
_path = path;
_maxBytes = maxBytes;
}
public void Write(IReadOnlyCollection<string> lines, DateTime generatedUtc, string identity)
{
ValidatePayload(lines);
var payload = string.Join("\n", lines) + "\n";
var payloadBytes = StrictUtf8.GetBytes(payload);
var content = BuildHeader(generatedUtc, identity, lines.Count, Hash(payloadBytes)) + payload;
var contentBytes = StrictUtf8.GetBytes(content);
if (contentBytes.Length > _maxBytes)
{
throw new InvalidDataException("Snapshot exceeds configured SnapshotMaxBytes.");
}
var directory = Path.GetDirectoryName(_path);
if (string.IsNullOrWhiteSpace(directory))
{
throw new InvalidOperationException("Snapshot path has no parent directory.");
}
Directory.CreateDirectory(directory);
var temporaryPath = Path.Combine(
directory,
Path.GetFileName(_path) + "." + Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture) + "." + Guid.NewGuid().ToString("N") + ".tmp");
try
{
using (var stream = new FileStream(
temporaryPath,
FileMode.CreateNew,
FileAccess.Write,
FileShare.None,
4096,
FileOptions.WriteThrough))
{
stream.Write(contentBytes, 0, contentBytes.Length);
stream.Flush(true);
}
if (File.Exists(_path))
{
File.Replace(temporaryPath, _path, null, true);
}
else
{
File.Move(temporaryPath, _path);
}
}
finally
{
try
{
if (File.Exists(temporaryPath))
{
File.Delete(temporaryPath);
}
}
catch (IOException)
{
}
}
}
public SnapshotReadResult Read(DateTime utcNow, TimeSpan maximumAge)
{
try
{
var bytes = ReadBytesWithRetry();
var text = StrictUtf8.GetString(bytes).Replace("\r\n", "\n");
var separator = text.IndexOf("\n\n", StringComparison.Ordinal);
if (separator < 0)
{
return SnapshotReadResult.Failed("Snapshot header separator is missing.");
}
var headerLines = text.Substring(0, separator).Split('\n');
if (headerLines.Length != 6 || !string.Equals(headerLines[0], Magic, StringComparison.Ordinal))
{
return SnapshotReadResult.Failed("Snapshot format or version is invalid.");
}
DateTime generatedUtc;
int expectedLineCount;
if (!TryReadDate(headerLines[1], "generatedUtc=", out generatedUtc)
|| !TryReadInt(headerLines[4], "payloadLines=", out expectedLineCount))
{
return SnapshotReadResult.Failed("Snapshot metadata is invalid.");
}
var expectedMachine = DecodeHeader(headerLines[2], "machineBase64=");
DecodeHeader(headerLines[3], "identityBase64=");
var expectedHash = ReadHeaderValue(headerLines[5], "payloadSha256=");
if (!string.Equals(expectedMachine, Environment.MachineName, StringComparison.OrdinalIgnoreCase))
{
return SnapshotReadResult.Failed("Snapshot was created for a different machine.");
}
var payload = text.Substring(separator + 2);
if (!payload.EndsWith("\n", StringComparison.Ordinal)
|| payload.IndexOf("\n\n", StringComparison.Ordinal) >= 0)
{
return SnapshotReadResult.Failed("Snapshot payload framing is invalid.");
}
var payloadBytes = StrictUtf8.GetBytes(payload);
if (!FixedTimeEquals(expectedHash, Hash(payloadBytes)))
{
return SnapshotReadResult.Failed("Snapshot SHA-256 validation failed.");
}
var lines = payload
.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
.ToArray();
if (lines.Length != expectedLineCount)
{
return SnapshotReadResult.Failed("Snapshot payload line count is invalid.");
}
ValidatePayload(lines);
var age = utcNow - generatedUtc;
if (age < TimeSpan.FromMinutes(-5))
{
return SnapshotReadResult.Failed("Snapshot timestamp is too far in the future.");
}
if (age > maximumAge)
{
return SnapshotReadResult.Failed(
"Snapshot is stale: age="
+ Math.Floor(age.TotalSeconds).ToString(CultureInfo.InvariantCulture)
+ "s, maximum="
+ Math.Floor(maximumAge.TotalSeconds).ToString(CultureInfo.InvariantCulture)
+ "s.");
}
return SnapshotReadResult.Success(lines, generatedUtc);
}
catch (FileNotFoundException)
{
return SnapshotReadResult.Failed("Snapshot file does not exist.");
}
catch (DirectoryNotFoundException)
{
return SnapshotReadResult.Failed("Snapshot directory does not exist.");
}
catch (UnauthorizedAccessException ex)
{
return SnapshotReadResult.Failed("Snapshot cannot be read: " + ex.Message);
}
catch (IOException ex)
{
return SnapshotReadResult.Failed("Snapshot I/O failed: " + ex.Message);
}
catch (Exception ex)
{
return SnapshotReadResult.Failed("Snapshot validation failed: " + ex.GetType().Name + ": " + ex.Message);
}
}
private byte[] ReadBytesWithRetry()
{
for (var attempt = 0; ; attempt++)
{
try
{
using (var stream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
{
if (stream.Length <= 0 || stream.Length > _maxBytes)
{
throw new InvalidDataException("Snapshot size is outside the allowed range.");
}
var bytes = new byte[(int)stream.Length];
var offset = 0;
while (offset < bytes.Length)
{
var read = stream.Read(bytes, offset, bytes.Length - offset);
if (read == 0)
{
throw new EndOfStreamException("Unexpected end of snapshot.");
}
offset += read;
}
return bytes;
}
}
catch (IOException)
{
if (attempt >= 2)
{
throw;
}
Thread.Sleep(25 * (attempt + 1));
}
}
}
private static string BuildHeader(DateTime generatedUtc, string identity, int lineCount, string hash)
{
return Magic + "\n"
+ "generatedUtc=" + generatedUtc.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture) + "\n"
+ "machineBase64=" + Convert.ToBase64String(StrictUtf8.GetBytes(Environment.MachineName)) + "\n"
+ "identityBase64=" + Convert.ToBase64String(StrictUtf8.GetBytes(identity ?? string.Empty)) + "\n"
+ "payloadLines=" + lineCount.ToString(CultureInfo.InvariantCulture) + "\n"
+ "payloadSha256=" + hash + "\n\n";
}
private static void ValidatePayload(IEnumerable<string> lines)
{
if (lines == null)
{
throw new ArgumentNullException("lines");
}
var count = 0;
foreach (var line in lines)
{
count++;
if (string.IsNullOrWhiteSpace(line)
|| line.IndexOf('\r') >= 0
|| line.IndexOf('\n') >= 0
|| line.Length < 5
|| line[1] != ' '
|| line[2] != '"'
|| line[0] < '0'
|| line[0] > '3')
{
throw new InvalidDataException("Snapshot contains an invalid Checkmk local-check line.");
}
}
if (count < 6)
{
throw new InvalidDataException("Snapshot must contain all six stable services.");
}
}
private static string Hash(byte[] bytes)
{
using (var sha = SHA256.Create())
{
return string.Concat(sha.ComputeHash(bytes).Select(x => x.ToString("x2", CultureInfo.InvariantCulture)));
}
}
private static bool FixedTimeEquals(string left, string right)
{
if (left == null || right == null || left.Length != right.Length)
{
return false;
}
var difference = 0;
for (var i = 0; i < left.Length; i++)
{
difference |= left[i] ^ right[i];
}
return difference == 0;
}
private static string ReadHeaderValue(string line, string prefix)
{
if (line == null || !line.StartsWith(prefix, StringComparison.Ordinal))
{
throw new InvalidDataException("Missing snapshot header " + prefix);
}
return line.Substring(prefix.Length);
}
private static string DecodeHeader(string line, string prefix)
{
return StrictUtf8.GetString(Convert.FromBase64String(ReadHeaderValue(line, prefix)));
}
private static bool TryReadDate(string line, string prefix, out DateTime result)
{
return DateTime.TryParseExact(
ReadHeaderValue(line, prefix),
"o",
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
out result);
}
private static bool TryReadInt(string line, string prefix, out int result)
{
return int.TryParse(ReadHeaderValue(line, prefix), NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
}
}
internal sealed class SnapshotReadResult
{
private SnapshotReadResult()
{
Lines = new string[0];
}
public bool IsSuccess { get; private set; }
public string Error { get; private set; }
public IReadOnlyList<string> Lines { get; private set; }
public DateTime GeneratedUtc { get; private set; }
public static SnapshotReadResult Success(IReadOnlyList<string> lines, DateTime generatedUtc)
{
return new SnapshotReadResult
{
IsSuccess = true,
Lines = lines,
GeneratedUtc = generatedUtc
};
}
public static SnapshotReadResult Failed(string error)
{
return new SnapshotReadResult
{
IsSuccess = false,
Error = error
};
}
}
}
@@ -56,8 +56,8 @@ namespace BizTalkCheckmkPulse
? "SQL-Zielermittlung ist unvollstaendig, weil der BizTalk-WMI-Provider beim SQL-Zugriff abgewiesen wurde." ? "SQL-Zielermittlung ist unvollstaendig, weil der BizTalk-WMI-Provider beim SQL-Zugriff abgewiesen wurde."
: "SQL-Zielermittlung ist unvollstaendig, weil nicht alle erforderlichen BizTalk-Plattformklassen gelesen wurden.", : "SQL-Zielermittlung ist unvollstaendig, weil nicht alle erforderlichen BizTalk-Plattformklassen gelesen wurden.",
Action = wmiPermissionFailure Action = wmiPermissionFailure
? "Zuerst die Wmi/Permission-Diagnose und die Mitgliedschaft des Computerkontos in der konfigurierten BizTalk-Operator-Gruppe beheben." ? "Zuerst die Wmi/Permission-Diagnose sowie Provider-Konto, konfigurierte BizTalk-Read-Only-Gruppe und BTS_READONLY_USERS pruefen."
: "Zuerst den Service 'BizTalk Platform' und MSBTS_GroupSetting pruefen. Danach den Agent-Dump erneut ausfuehren." : "Zuerst den Service 'BizTalk Platform' und MSBTS_GroupSetting im Provider-Log pruefen. Danach den Scheduled Task erneut starten."
}); });
} }
@@ -112,7 +112,7 @@ namespace BizTalkCheckmkPulse
Component = "Windows identity", Component = "Windows identity",
Required = false, Required = false,
Summary = "Die Windows-Ausfuehrungsidentitaet konnte nicht bestimmt werden.", Summary = "Die Windows-Ausfuehrungsidentitaet konnte nicht bestimmt werden.",
Action = "Agent-Dienstkonto mit 'sc.exe qc CheckMKService' beziehungsweise in services.msc pruefen.", Action = "Konto des Scheduled Tasks 'BizTalk Checkmk Pulse Provider' pruefen.",
TechnicalDetails = ex.GetType().Name + ": " + ex.Message TechnicalDetails = ex.GetType().Name + ": " + ex.Message
}); });
} }
@@ -268,7 +268,7 @@ namespace BizTalkCheckmkPulse
case DiagnosticCategory.Timeout: case DiagnosticCategory.Timeout:
return "SQL-Verbindungsaufbau oder Testabfrage hat das konfigurierte Zeitlimit ueberschritten."; return "SQL-Verbindungsaufbau oder Testabfrage hat das konfigurierte Zeitlimit ueberschritten.";
case DiagnosticCategory.Connectivity: case DiagnosticCategory.Connectivity:
return "SQL Server oder die konfigurierte SQL-Instanz ist aus dem Checkmk-Agent-Kontext nicht erreichbar."; return "SQL Server oder die konfigurierte SQL-Instanz ist aus dem Provider-Kontext nicht erreichbar.";
case DiagnosticCategory.Configuration: case DiagnosticCategory.Configuration:
return "SQL-Verbindung scheitert an TLS-, Zertifikats-, SPN- oder SSPI-Konfiguration."; return "SQL-Verbindung scheitert an TLS-, Zertifikats-, SPN- oder SSPI-Konfiguration.";
default: default:
@@ -288,7 +288,7 @@ namespace BizTalkCheckmkPulse
switch (category) switch (category)
{ {
case DiagnosticCategory.Permission: case DiagnosticCategory.Permission:
return networkIdentity + " in die konfigurierte BizTalk-Operator-Gruppe aufnehmen; keine direkten BizTalk-DB-Rollen vergeben. Danach Kerberos-Tickets erneuern und den Agent-Dump wiederholen."; return "Provider-Konto " + networkIdentity + " und dessen Mitgliedschaft in der konfigurierten BizTalk-Read-Only-Gruppe sowie BTS_READONLY_USERS pruefen; keine direkten BizTalk-DB-Rollen vergeben. Danach den Scheduled Task mit neuem Anmeldetoken starten.";
case DiagnosticCategory.Timeout: case DiagnosticCategory.Timeout:
return "Netzwerkpfad, DNS, SQL-Port, Firewall und Auslastung fuer " + target.Server + " pruefen; Timeout nur nach Ursachenanalyse erhoehen."; return "Netzwerkpfad, DNS, SQL-Port, Firewall und Auslastung fuer " + target.Server + " pruefen; Timeout nur nach Ursachenanalyse erhoehen.";
case DiagnosticCategory.Connectivity: case DiagnosticCategory.Connectivity:
+7 -6
View File
@@ -25,7 +25,7 @@ namespace BizTalkCheckmkPulse
private const int OrchestrationStarted = 4; private const int OrchestrationStarted = 4;
private const int EAccessDenied = unchecked((int)0x80070005); private const int EAccessDenied = unchecked((int)0x80070005);
internal const string GroupSettingQuery = internal const string GroupSettingQuery =
"SELECT Name, BizTalkOperatorGroup, MgmtDbServerName, MgmtDbName, SubscriptionDBServerName, SubscriptionDBName FROM MSBTS_GroupSetting"; "SELECT Name, BizTalkOperatorGroup, BizTalkReadOnlyUserGroup, MgmtDbServerName, MgmtDbName, SubscriptionDBServerName, SubscriptionDBName FROM MSBTS_GroupSetting";
private readonly MonitoringOptions _options; private readonly MonitoringOptions _options;
/// <summary> /// <summary>
@@ -106,6 +106,7 @@ namespace BizTalkCheckmkPulse
groupFound = true; groupFound = true;
result.Platform.GroupName = FirstNonEmpty(WmiHelpers.GetString(item, "Name"), WmiHelpers.GetString(item, "MgmtDbName")); result.Platform.GroupName = FirstNonEmpty(WmiHelpers.GetString(item, "Name"), WmiHelpers.GetString(item, "MgmtDbName"));
result.Platform.OperatorGroup = WmiHelpers.GetString(item, "BizTalkOperatorGroup"); result.Platform.OperatorGroup = WmiHelpers.GetString(item, "BizTalkOperatorGroup");
result.Platform.ReadOnlyUserGroup = WmiHelpers.GetString(item, "BizTalkReadOnlyUserGroup");
result.Platform.ManagementDbServer = WmiHelpers.GetString(item, "MgmtDbServerName"); result.Platform.ManagementDbServer = WmiHelpers.GetString(item, "MgmtDbServerName");
result.Platform.ManagementDbName = WmiHelpers.GetString(item, "MgmtDbName"); result.Platform.ManagementDbName = WmiHelpers.GetString(item, "MgmtDbName");
result.Platform.MessageBoxDbServer = WmiHelpers.GetString(item, "SubscriptionDBServerName"); result.Platform.MessageBoxDbServer = WmiHelpers.GetString(item, "SubscriptionDBServerName");
@@ -672,7 +673,7 @@ namespace BizTalkCheckmkPulse
return "BizTalk-WMI-Provider konnte den SQL-Zugriff fuer " + sqlLoginPrincipal + " nicht anmelden."; return "BizTalk-WMI-Provider konnte den SQL-Zugriff fuer " + sqlLoginPrincipal + " nicht anmelden.";
} }
return "Zugriff auf " + component + " wurde im LocalSystem-Kontext verweigert."; return "Zugriff des privilegierten Provider-Kontos auf " + component + " wurde verweigert.";
case DiagnosticCategory.Configuration: case DiagnosticCategory.Configuration:
return connectionFailure return connectionFailure
? "BizTalk-WMI-Namespace ist nicht vorhanden oder nicht korrekt registriert." ? "BizTalk-WMI-Namespace ist nicht vorhanden oder nicht korrekt registriert."
@@ -704,13 +705,13 @@ namespace BizTalkCheckmkPulse
case DiagnosticCategory.Permission: case DiagnosticCategory.Permission:
if (!string.IsNullOrWhiteSpace(sqlLoginPrincipal)) if (!string.IsNullOrWhiteSpace(sqlLoginPrincipal))
{ {
return "Computerkonto " + sqlLoginPrincipal return "Provider-Konto " + sqlLoginPrincipal
+ " der in der BizTalk-Gruppe konfigurierten BizTalk-Operator-Gruppe zuordnen; keine direkten SQL-Logins oder Datenbankrollen vergeben."; + " und dessen Mitgliedschaft in der konfigurierten BizTalk-Read-Only-Gruppe sowie BTS_READONLY_USERS pruefen; keine direkten SQL-Logins oder Datenbankrollen vergeben.";
} }
return connectionFailure return connectionFailure
? "Namespace-ACL fuer root\\MicrosoftBizTalkServer gezielt pruefen. LocalSystem benoetigt lokalen Lesezugriff; keine pauschalen WMI-Rechte vergeben." ? "Lokalen Namespace-Zugriff des Scheduled-Task-Kontos auf root\\MicrosoftBizTalkServer gezielt pruefen; keine pauschalen WMI-Rechte vergeben."
: "Agent-Dump pruefen und Computerkonto <DOMAIN>\\" + Environment.MachineName + "$ zunaechst der konfigurierten BizTalk-Operator-Gruppe zuordnen."; : "Provider-Identitaet und Mitgliedschaft in der konfigurierten BizTalk-Read-Only-Gruppe pruefen; Operator-Rechte nur nach klassenspezifischer Analyse erwaegen.";
case DiagnosticCategory.Configuration: case DiagnosticCategory.Configuration:
return "BizTalk-WMI-Provider/Namespace auf dem BizTalk-Server pruefen und gegebenenfalls mit dem BizTalk-Setup reparieren."; return "BizTalk-WMI-Provider/Namespace auf dem BizTalk-Server pruefen und gegebenenfalls mit dem BizTalk-Setup reparieren.";
case DiagnosticCategory.Schema: case DiagnosticCategory.Schema:
+117
View File
@@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace BizTalkCheckmkPulse.Tests namespace BizTalkCheckmkPulse.Tests
{ {
@@ -21,6 +23,12 @@ namespace BizTalkCheckmkPulse.Tests
Run("ProviderSqlLoginFailureIsPermission", ProviderSqlLoginFailureIsPermission); Run("ProviderSqlLoginFailureIsPermission", ProviderSqlLoginFailureIsPermission);
Run("RejectedSqlPrincipalIsExtracted", RejectedSqlPrincipalIsExtracted); Run("RejectedSqlPrincipalIsExtracted", RejectedSqlPrincipalIsExtracted);
Run("PlatformPermissionPropagatesToSqlDiscovery", PlatformPermissionPropagatesToSqlDiscovery); Run("PlatformPermissionPropagatesToSqlDiscovery", PlatformPermissionPropagatesToSqlDiscovery);
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
Run("SnapshotRejectsDifferentMachine", SnapshotRejectsDifferentMachine);
Run("SnapshotReportsMissingFile", SnapshotReportsMissingFile);
Run("SnapshotFailureEmitsStableUnknownServices", SnapshotFailureEmitsStableUnknownServices);
if (Failures.Count == 0) if (Failures.Count == 0)
{ {
@@ -81,6 +89,7 @@ namespace BizTalkCheckmkPulse.Tests
var query = WmiBizTalkProbe.GroupSettingQuery; var query = WmiBizTalkProbe.GroupSettingQuery;
Assert(query.Contains("FROM MSBTS_GroupSetting"), "MSBTS_GroupSetting query missing"); Assert(query.Contains("FROM MSBTS_GroupSetting"), "MSBTS_GroupSetting query missing");
Assert(query.Contains("BizTalkOperatorGroup"), "configured operator group property missing"); Assert(query.Contains("BizTalkOperatorGroup"), "configured operator group property missing");
Assert(query.Contains("BizTalkReadOnlyUserGroup"), "configured read-only group property missing");
Assert(query.Contains("SubscriptionDBServerName"), "master MessageBox server property missing"); Assert(query.Contains("SubscriptionDBServerName"), "master MessageBox server property missing");
Assert(query.Contains("SubscriptionDBName"), "master MessageBox database property missing"); Assert(query.Contains("SubscriptionDBName"), "master MessageBox database property missing");
Assert(query.IndexOf("MessageBoxSetting", StringComparison.OrdinalIgnoreCase) < 0, "unsupported MessageBoxSetting class present"); Assert(query.IndexOf("MessageBoxSetting", StringComparison.OrdinalIgnoreCase) < 0, "unsupported MessageBoxSetting class present");
@@ -128,6 +137,114 @@ namespace BizTalkCheckmkPulse.Tests
"SQL discovery classification"); "SQL discovery classification");
} }
private static void SnapshotRoundTripPreservesLines()
{
WithTemporarySnapshot((path, store) =>
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
var generated = DateTime.UtcNow;
store.Write(lines, generated, "DOMAIN\\collector$");
var result = store.Read(generated.AddSeconds(10), TimeSpan.FromMinutes(3));
Assert(result.IsSuccess, "snapshot should be readable: " + result.Error);
AssertEqual(lines.Length, result.Lines.Count, "snapshot line count");
Assert(lines.SequenceEqual(result.Lines), "snapshot payload changed");
var replacement = lines.Select(x => x.Replace("Self test OK", "Replacement OK")).ToArray();
store.Write(replacement, generated.AddSeconds(30), "DOMAIN\\collector$");
var replaced = store.Read(generated.AddSeconds(40), TimeSpan.FromMinutes(3));
Assert(replaced.IsSuccess, "replaced snapshot should be readable");
Assert(replacement.SequenceEqual(replaced.Lines), "atomic replacement payload changed");
});
}
private static void SnapshotRejectsTampering()
{
WithTemporarySnapshot((path, store) =>
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
store.Write(lines, DateTime.UtcNow, "DOMAIN\\collector$");
var content = File.ReadAllText(path, Encoding.UTF8);
File.WriteAllText(path, content.Replace("Self test OK", "Tampered output"), new UTF8Encoding(false));
var result = store.Read(DateTime.UtcNow, TimeSpan.FromMinutes(3));
Assert(!result.IsSuccess, "tampered snapshot must be rejected");
Assert(result.Error.IndexOf("SHA-256", StringComparison.OrdinalIgnoreCase) >= 0, "tamper reason should mention SHA-256");
});
}
private static void SnapshotRejectsStaleData()
{
WithTemporarySnapshot((path, store) =>
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
var generated = DateTime.UtcNow.AddMinutes(-10);
store.Write(lines, generated, "DOMAIN\\collector$");
var result = store.Read(DateTime.UtcNow, TimeSpan.FromMinutes(3));
Assert(!result.IsSuccess, "stale snapshot must be rejected");
Assert(result.Error.IndexOf("stale", StringComparison.OrdinalIgnoreCase) >= 0, "stale reason missing");
});
}
private static void SnapshotRejectsDifferentMachine()
{
WithTemporarySnapshot((path, store) =>
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
store.Write(lines, DateTime.UtcNow, "DOMAIN\\collector$");
var content = File.ReadAllText(path, Encoding.UTF8);
var currentMachine = Convert.ToBase64String(Encoding.UTF8.GetBytes(Environment.MachineName));
var otherMachine = Convert.ToBase64String(Encoding.UTF8.GetBytes("OTHER-SERVER"));
File.WriteAllText(
path,
content.Replace("machineBase64=" + currentMachine, "machineBase64=" + otherMachine),
new UTF8Encoding(false));
var result = store.Read(DateTime.UtcNow, TimeSpan.FromMinutes(3));
Assert(!result.IsSuccess, "snapshot for another machine must be rejected");
Assert(result.Error.IndexOf("different machine", StringComparison.OrdinalIgnoreCase) >= 0, "machine mismatch reason missing");
});
}
private static void SnapshotReportsMissingFile()
{
WithTemporarySnapshot((path, store) =>
{
var result = store.Read(DateTime.UtcNow, TimeSpan.FromMinutes(3));
Assert(!result.IsSuccess, "missing snapshot must be rejected");
Assert(result.Error.IndexOf("does not exist", StringComparison.OrdinalIgnoreCase) >= 0, "missing file reason missing");
});
}
private static void SnapshotFailureEmitsStableUnknownServices()
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions())
.FormatSnapshotFailure("Snapshot file does not exist.")
.ToArray();
AssertEqual(6, lines.Length, "snapshot failure stable service count");
Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "snapshot failure must be UNKNOWN");
Assert(lines.All(x => x.Contains("Scheduled Task")), "snapshot failure must contain provider action");
}
private static void WithTemporarySnapshot(Action<string, SnapshotStore> test)
{
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.Tests." + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
var path = Path.Combine(directory, "snapshot.txt");
try
{
test(path, new SnapshotStore(path, 1048576));
}
finally
{
if (Directory.Exists(directory))
{
Directory.Delete(directory, true);
}
}
}
private static ProbeResult CreateSuspensionResult(string applicationName) private static ProbeResult CreateSuspensionResult(string applicationName)
{ {
var result = new ProbeResult(); var result = new ProbeResult();