OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

settings.php

9 KB
<?php $this->layout('layouts/admin', [
    'page_title'         => 'CVE-Einstellungen',
    'current_admin_page' => 'cve-settings',
]); ?>
<?php $nvdKeySet = !empty($nvd_api_key_set); ?>

<div class="settings-page">
    <section class="settings-card">
        <div class="settings-card-header">
            <span class="settings-card-icon">&#128737;</span>
            <div>
                <h2><?= $this->te('NVD-API-Zugang') ?></h2>
                <p class="settings-card-desc"><?= $this->te('Schlüssel für den Abruf der Sicherheitsmeldungen aus der National Vulnerability Database.') ?></p>
            </div>
        </div>
        <div class="settings-card-body">
            <p style="line-height:1.55;"><?= $this->t('Die CVE-Daten stammen von der offiziellen <a href="https://nvd.nist.gov/" target="_blank" rel="noopener">National Vulnerability Database (NVD)</a>. Ohne API-Key erlaubt die NVD nur sehr wenige Anfragen (ca. 5 / 30 s) – die Aktualisierung ist dann langsam und kann am Rate-Limit scheitern. Ein <strong>kostenloser</strong> API-Key erhöht das Limit deutlich (ca. 50 / 30 s).') ?></p>
            <p style="line-height:1.55;"><?= $this->te('Key anfordern:') ?>
                <a href="https://nvd.nist.gov/developers/request-an-api-key" target="_blank" rel="noopener"><?= $this->te('nvd.nist.gov/developers/request-an-api-key') ?></a>.</p>

            <form method="post" action="/admin/cve/settings" style="margin-top:1rem;">
                <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
                <div class="form-group">
                    <label for="nvd-key"><?= $this->te('NVD-API-Key') ?></label>
                    <input type="password" id="nvd-key" name="nvd_api_key" class="form-control" autocomplete="off"
                           placeholder="<?= $nvdKeySet ? $this->te('•••••••• (gesetzt)') : $this->te('z. B. 12345678-90ab-cdef-1234-567890abcdef') ?>">
                    <small class="form-hint">
                        <?= $this->te('Der Schlüssel wird verschlüsselt in der Datenbank gespeichert – keine Konfigurationsdatei nötig.') ?>
                        <?= $nvdKeySet ? $this->te('Leer lassen, um den hinterlegten Schlüssel beizubehalten.') : $this->te('Ohne Schlüssel gelten die engeren Abrufgrenzen der NVD.') ?>
                    </small>
                    <?php if ($nvdKeySet): ?>
                    <label class="form-hint" style="display:block;margin-top:.4rem;">
                        <input type="checkbox" name="remove_nvd_api_key" value="1">
                        <?= $this->te('Hinterlegten Schlüssel entfernen und ohne arbeiten') ?>
                    </label>
                    <?php endif; ?>
                </div>

                <hr style="margin:1.5rem 0;border:none;border-top:1px solid var(--gray-200);">

                <h3 style="font-size:1rem;font-weight:700;margin:0 0 .35rem;"><?= $this->te('&#127465;&#127466; BSI CERT-Bund Warnungen') ?></h3>
                <p class="form-hint" style="margin-top:0;line-height:1.55;">
                    <?= $this->t('Zusätzlich zu NVD können die deutschsprachigen Sicherheitswarnungen des <a href="https://wid.cert-bund.de/" target="_blank" rel="noopener">BSI CERT-Bund (WID)</a> eingelesen und – über die referenzierten CVE-IDs mit deiner Watchlist verknüpft – direkt auf der CVE-Seite angezeigt werden. Watchlist-relevante Warnungen werden hervorgehoben; jede CVE mit deutscher Warnung erhält ein 🇩🇪-Badge.') ?>
                </p>
                <p class="form-hint" style="margin-top:.4rem;line-height:1.55;">
                    <?= $this->t('Bevorzugt wird die <strong>strukturierte JSON-Quelle</strong> abgerufen (liefert die referenzierten CVE-IDs und den Schweregrad mit). Ist sie nicht erreichbar, wird automatisch auf den <strong>RSS-Feed</strong> zurückgefallen. Der Server benötigt ausgehenden Zugriff auf <code>wid.cert-bund.de</code>.') ?>
                </p>
                <div class="form-group">
                    <label class="settings-toggle">
                        <input type="checkbox" name="bsi_enabled" value="1" <?= !empty($bsi_enabled) ? 'checked' : '' ?>>
                        <span class="toggle-text">
                            <strong><?= $this->te('BSI CERT-Bund Warnungen aktivieren') ?></strong>
                            <small><?= $this->te('Liest den CERT-Bund-Feed ein und zeigt die Warnungen verknüpft auf der CVE-Seite an.') ?></small>
                        </span>
                    </label>
                </div>
                <div class="form-group">
                    <label for="bsi-api"><?= $this->te('Strukturierte Quelle – CSAF (bevorzugt)') ?></label>
                    <input type="url" id="bsi-api" name="bsi_api_url" class="form-control" autocomplete="off"
                           value="<?= $this->e($bsi_api_url ?? '') ?>" placeholder="<?= $this->e($bsi_default_api ?? '') ?>">
                    <small class="form-hint"><?= $this->t('CSAF-Provider-Metadata des CERT-Bund (liefert über den ROLIE-Feed die einzelnen Advisory-Dokumente inkl. CVE-Bezügen). Leer lassen für den Standard: <code>{standard}</code>.', ['standard' => $this->e($bsi_default_api ?? '')]) ?></small>
                </div>
                <div class="form-group">
                    <label for="bsi-feed"><?= $this->te('RSS-/Atom-Feed (Fallback)') ?></label>
                    <input type="url" id="bsi-feed" name="bsi_feed_url" class="form-control" autocomplete="off"
                           value="<?= $this->e($bsi_feed_url ?? '') ?>" placeholder="<?= $this->e($bsi_default_feed ?? '') ?>">
                    <small class="form-hint"><?= $this->t('Leer lassen für den Standard-Feed: <code>{standard}</code>. Die Aktualisierung läuft zusammen mit dem CVE-Refresh.', ['standard' => $this->e($bsi_default_feed ?? '')]) ?></small>
                </div>

                <div class="form-group" style="margin-top:.5rem;">
                    <button type="button" class="btn btn-default btn-sm" id="bsiTestBtn" onclick="bsiTestSource()">
                        <?= $this->te('&#128269; Quellen testen') ?>
                    </button>
                    <small class="form-hint"><?= $this->te('Prüft beide BSI-Quellen live: HTTP-Status, gefundene Einträge und referenzierte CVEs. Zuerst speichern, falls du die URL geändert hast.') ?></small>
                    <pre id="bsiTestOut" style="display:none;margin-top:.6rem;padding:.75rem;background:var(--gray-900);color:var(--gray-200);border-radius:.4rem;font-size:.74rem;line-height:1.5;max-height:340px;overflow:auto;white-space:pre-wrap;word-break:break-word;"></pre>
                </div>

                <button type="submit" class="btn btn-primary"><?= $this->te('Speichern') ?></button>
                <a href="/cve" class="btn btn-default"><?= $this->te('Zur CVE-Übersicht') ?></a>
            </form>

            <script>
            function bsiTestSource() {
                var btn = document.getElementById('bsiTestBtn');
                var out = document.getElementById('bsiTestOut');
                btn.disabled = true; var orig = btn.innerHTML; btn.textContent = 'Teste…';
                out.style.display = 'block'; out.textContent = 'Rufe BSI-Quellen ab…';
                fetch('/admin/cve/bsi-test', {
                    method: 'POST',
                    headers: {
                        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content,
                        'X-Requested-With': 'XMLHttpRequest'
                    }
                })
                .then(r => r.json())
                .then(d => {
                    var s = d.structured || {}, r = d.rss || {};
                    var lines = [];
                    lines.push('=== Strukturierte JSON-Quelle ===');
                    lines.push('URL:              ' + (d.api_url || ''));
                    lines.push('HTTP:             ' + s.http_code + (s.curl_error ? '  cURL-Fehler: ' + s.curl_error : ''));
                    lines.push('Body-Länge:       ' + s.body_length);
                    lines.push('Entdeckte Dok.:   ' + (s.discovered_docs || 0));
                    lines.push('Beispiel geparst: ' + s.parsed_count + ' Dok.   CVEs (Beispiel): ' + s.cve_total);
                    if (s.parse_error) lines.push('Parse-Fehler:     ' + s.parse_error);
                    lines.push('Hinweis: Der Test lädt nur wenige Beispiel-Dokumente; der Refresh holt alle.');
                    lines.push('Body-Auszug:');
                    lines.push((s.body_excerpt || '').slice(0, 1200));
                    lines.push('');
                    lines.push('=== RSS-Fallback ===');
                    lines.push('URL:           ' + (d.feed_url || ''));
                    lines.push('HTTP:          ' + r.http_code + (r.curl_error ? '  cURL-Fehler: ' + r.curl_error : ''));
                    lines.push('Geparste Items:' + r.parsed_count + '   CVEs gesamt: ' + r.cve_total);
                    if (s.sample) { lines.push(''); lines.push('Beispiel-Item (strukturiert):'); lines.push(JSON.stringify(s.sample, null, 2)); }
                    out.textContent = lines.join('\n');
                })
                .catch(() => { out.textContent = 'Test fehlgeschlagen (Netzwerk-/Serverfehler).'; })
                .finally(() => { btn.disabled = false; btn.innerHTML = orig; });
            }
            </script>
        </div>
    </section>
</div>