OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

IpamScanAgentScriptTest.php

6 KB
<?php

declare(strict_types=1);

use OpenNIT\Controllers\Admin\IpamScanAgentController;

/**
 * Deckt die beiden Scan-Agenten ab, die das IPAM über die API befüllen.
 *
 * Die Skripte laufen unbeaufsichtigt über viele Subnetze. Festgehalten wird
 * deshalb, was einen solchen Lauf beherrschbar hält: die parallele
 * Detailerhebung, die Zeitgrenzen jedes blockierenden Schritts, die
 * Plausibilitätsbremse gegen stellvertretend antwortende Netze und die Sperre
 * gegen sich überholende Läufe.
 */
return static function (): void {

    $script = static function (string $methode): string {
        $klasse = new ReflectionClass(IpamScanAgentController::class);
        $objekt = $klasse->newInstanceWithoutConstructor();
        $m = new ReflectionMethod(IpamScanAgentController::class, $methode);
        $m->setAccessible(true);
        return (string)$m->invoke($objekt, 'https://doku.example.test');
    };

    $bash = static fn(): string => $script('agentScript');
    $ps   = static fn(): string => $script('agentScriptWindows');

    test('Der Bash-Agent erhebt die Details parallel', function () use ($bash) {
        $text = $bash();
        assertTrue(str_contains($text, 'detail_one() {'), 'Die Detailerhebung je Host fehlt als eigene Funktion');
        assertTrue(str_contains($text, '{ detail_one "$ip" > "$ddir/$ip.json"; } &'),
            'Die Detailerhebung läuft nicht im Hintergrund');
        assertTrue(str_contains($text, 'DETAIL_PARALLEL="${OPENNIT_DETAIL_PARALLEL:-16}"'),
            'Die Obergrenze der gleichzeitigen Detailerhebungen fehlt');
        // Jeder Host öffnet während des Portscans so viele Verbindungen, wie
        // SCAN_PORTS Ports führt – die Detailphase läuft daher schmaler.
        assertTrue(str_contains($text, 'PARALLEL="${OPENNIT_PARALLEL:-48}"'), 'Die Obergrenze der Pings fehlt');
    });

    test('Jeder blockierende Aufloesungsschritt hat eine Zeitgrenze', function () use ($bash) {
        $text = $bash();
        assertTrue(str_contains($text, 'lim() {'), 'Der Helfer für Zeitgrenzen fehlt');
        // getent kennt selbst keine Grenze und wartet sonst die ganze
        // resolv.conf ab.
        assertTrue(str_contains($text, 'lim "$RDNS_TIMEOUT" getent hosts'), 'getent läuft ohne Zeitgrenze');
        assertTrue(str_contains($text, 'lim "$RDNS_TIMEOUT" nmblookup'), 'nmblookup läuft ohne Zeitgrenze');
        assertTrue(str_contains($text, 'lim "$RDNS_TIMEOUT" avahi-resolve-address'), 'avahi läuft ohne Zeitgrenze');
        assertTrue(str_contains($text, 'RDNS="${OPENNIT_RDNS:-1}"'), 'Die Rückwärtsauflösung lässt sich nicht abschalten');
    });

    test('Ein vollstaendig antwortendes Subnetz wird nicht uebertragen', function () use ($bash, $ps) {
        $text = $bash();
        assertTrue(str_contains($text, 'MAX_ONLINE_PCT="${OPENNIT_MAX_ONLINE_PCT:-90}"'), 'Der Schwellwert fehlt');
        assertTrue(str_contains($text, 'ALLOW_FULL_SUBNET="${OPENNIT_ALLOW_FULL_SUBNET:-0}"'),
            'Die bewusste Ausnahme fehlt');
        assertTrue(str_contains($text, 'Proxy-ARP-Verdacht'), 'Der Hinweis auf die Ursache fehlt');
        assertTrue(str_contains($ps(), 'Proxy-ARP-Verdacht'), 'Der Windows-Agent kennt die Bremse nicht');
    });

    test('Ein abgebrochener Lauf markiert nichts als offline', function () use ($bash, $ps) {
        // Nach einem Abbruch fehlen erreichbare Hosts in der Meldung; sie
        // dürfen deshalb nicht als verschwunden gewertet werden.
        $text = $bash();
        assertTrue(str_contains($text, 'SUBNET_MAX_SECONDS="${OPENNIT_SUBNET_MAX_SECONDS:-900}"'),
            'Das Zeitbudget je Subnetz fehlt');
        assertTrue(str_contains($text, 'keine Offline-Markierung'), 'Die Offline-Markierung wird nicht ausgesetzt');
        assertTrue(str_contains($ps(), '$off = $false'), 'Der Windows-Agent setzt die Markierung nicht aus');
    });

    test('Sich ueberholende Laeufe werden abgewiesen', function () use ($bash, $ps) {
        $text = $bash();
        assertTrue(str_contains($text, 'flock -n 9'), 'Die Sperrdatei fehlt');
        assertTrue(str_contains($text, 'Ein Lauf ist bereits aktiv'), 'Der Hinweis auf den laufenden Scan fehlt');
        // Ein übersprungener Lauf ist kein Fehler – sonst meldet ihn Cron.
        assertTrue(str_contains($text, "exit 0"), 'Der abgewiesene Lauf endet nicht fehlerfrei');
        assertTrue(str_contains($ps(), "New-Object System.Threading.Mutex"), 'Dem Windows-Agent fehlt die Sperre');
    });

    test('Beide Agenten fassen den Lauf am Ende zusammen', function () use ($bash, $ps) {
        assertTrue(str_contains($bash(), 'log "Zusammenfassung'), 'Dem Bash-Agent fehlt die Zusammenfassung');
        assertTrue(str_contains($ps(), 'Zusammenfassung'), 'Dem Windows-Agent fehlt die Zusammenfassung');
        assertTrue(str_contains($bash(), 'TOTAL_MAX_SECONDS'), 'Das Zeitbudget je Lauf fehlt');
    });

    test('Der Windows-Agent loest die Namen gebuendelt auf', function () use ($ps) {
        $text = $ps();
        assertTrue(str_contains($text, 'function Get-HostNames'), 'Die gebündelte Auflösung fehlt');
        assertTrue(str_contains($text, 'GetHostEntryAsync'), 'Die Auflösung blockiert weiterhin je Host');
        assertTrue(str_contains($text, '$DnsTimeoutMs'), 'Die Zeitgrenze der Auflösung fehlt');
        assertFalse(str_contains($text, 'Get-HostFor'), 'Die blockierende Einzelauflösung ist noch vorhanden');
    });

    test('Das Bash-Script ist syntaktisch gueltig', function () use ($bash) {
        if (trim((string)@shell_exec('command -v bash 2>/dev/null')) === '') {
            return; // ohne bash nicht prüfbar
        }
        $datei = tempnam(sys_get_temp_dir(), 'ipam-agent-');
        file_put_contents($datei, $bash());
        $ausgabe = [];
        $code = 0;
        exec('bash -n ' . escapeshellarg($datei) . ' 2>&1', $ausgabe, $code);
        unlink($datei);
        assertSame(0, $code, 'bash -n meldet: ' . implode("\n", $ausgabe));
    });
};