Skip to content

Commit f971409

Browse files
Gate NVD collection imports through Rust
1 parent aa4bb9e commit f971409

5 files changed

Lines changed: 65 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ python3 manage.py import_cve_context_csv <tenant-slug> /pfad/zu/cve_context.csv
185185

186186
Ab V23.5 ist der Vulnerability-Import standardmäßig auf **Rust-only-Normalisierung** gestellt (`VULN_INTEL_RUST_ONLY=True`).
187187
Wenn `RUST_BACKEND_URL` fehlt, brechen CVE-Upserts absichtlich mit Fehler ab, um Mischbetrieb zu vermeiden.
188+
NVD-Collection-Imports laufen im Rust-only-Modus nicht mehr über den Python-Service; die Django-Commands `import_nvd_cves` und `sync_nvd_recent` sind Kompatibilitäts-Wrapper um die Rust-CLI.
188189
Außerdem ist der Cutover-Default jetzt `RUST_STRICT_MODE=True`, damit Risk-/Guidance-/Report-Pfade ohne Rust-Backend nicht still auf Legacy-Fallbacks zurückfallen.
189190

190191
Direkte NVD-Collection-Imports koennen optional mit `--cve-tag`, `--cpe-name`, `--last-mod-start-date` und `--last-mod-end-date` gefiltert werden.

apps/vulnerability_intelligence/services.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ def upsert_cve(cve_id: str, *, assume_normalized: bool = False):
173173

174174
@staticmethod
175175
def import_cve_collection(*, cve_tag='', cpe_name='', has_kev=False, last_mod_start='', last_mod_end='', results_per_page=2000, max_pages=1, request_delay_seconds=0.6):
176+
if bool(getattr(settings, 'VULN_INTEL_RUST_ONLY', True)):
177+
raise RuntimeError(
178+
'Python-NVD-Collection-Import ist im Rust-only-Modus deaktiviert. '
179+
'Verwenden Sie die Management-Commands import_nvd_cves oder sync_nvd_recent; '
180+
'sie orchestrieren den Rust-Primärpfad.'
181+
)
176182
start_index = 0
177183
imported = 0
178184
seen = 0

apps/vulnerability_intelligence/tests.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323

2424
class NVDServiceTests(TestCase):
25+
@override_settings(VULN_INTEL_RUST_ONLY=False)
2526
def test_import_cve_collection_upserts_records(self):
2627
payload = {
2728
'totalResults': 1,
@@ -52,6 +53,10 @@ def test_import_cve_collection_upserts_records(self):
5253
self.assertEqual(result['imported_records'], 1)
5354
self.assertEqual(CVERecord.objects.filter(cve_id='CVE-2026-0001').count(), 1)
5455

56+
def test_import_cve_collection_rejects_python_path_when_rust_only_enabled(self):
57+
with self.assertRaisesRegex(RuntimeError, 'Python-NVD-Collection-Import'):
58+
NVDService.import_cve_collection(results_per_page=1, max_pages=1)
59+
5560
def test_request_payload_retries_on_http_429(self):
5661
query = {'cveId': 'CVE-2026-0002'}
5762
response_mock = Mock()
@@ -313,6 +318,56 @@ def test_import_via_rust_delegates_to_rust_cli(self):
313318
mocked.assert_called_once()
314319

315320

321+
class RustPrimaryCollectionCommandTests(TestCase):
322+
def test_import_nvd_cves_delegates_collection_import_to_rust_cli(self):
323+
with patch(
324+
'apps.vulnerability_intelligence.management.commands.import_nvd_cves.run_rust_canary',
325+
return_value='{"seen_records":1,"imported_records":1}',
326+
) as mocked:
327+
call_command(
328+
'import_nvd_cves',
329+
'--results-per-page=10',
330+
'--max-pages=2',
331+
'--has-kev',
332+
'--skip-healthcheck',
333+
)
334+
335+
mocked.assert_called_once_with(
336+
subcommand='import-collection',
337+
args=[
338+
'--results-per-page', '10',
339+
'--max-pages', '2',
340+
'--has-kev',
341+
'--skip-healthcheck',
342+
],
343+
)
344+
345+
def test_sync_nvd_recent_delegates_recent_sync_to_rust_cli(self):
346+
with patch(
347+
'apps.vulnerability_intelligence.management.commands.sync_nvd_recent.run_rust_canary',
348+
return_value='{"seen_records":1,"imported_records":1}',
349+
) as mocked:
350+
call_command(
351+
'sync_nvd_recent',
352+
'--hours=6',
353+
'--results-per-page=25',
354+
'--max-pages=3',
355+
'--cve-tag=disputed',
356+
'--skip-healthcheck',
357+
)
358+
359+
mocked.assert_called_once_with(
360+
subcommand='sync-recent',
361+
args=[
362+
'--hours', '6',
363+
'--results-per-page', '25',
364+
'--max-pages', '3',
365+
'--cve-tag', 'disputed',
366+
'--skip-healthcheck',
367+
],
368+
)
369+
370+
316371
class CanaryParityReportTests(TestCase):
317372
@override_settings(RUST_BACKEND_URL='http://rust-backend:9000')
318373
def test_report_command_delegates_to_rust_cli(self):

docs/RUST_ABLOESE_CHECKLISTE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ Komplette Ablösung der CVE-Normalisierungspfade in der Vulnerability-Intelligen
1313
- [x] **README auf Rust-only Betrieb aktualisiert**.
1414
- [x] **Regression-Tests ergänzt/angepasst** (Rust-only Verhalten und Legacy-Command).
1515
- [x] **Versionierter Rust-Normalisierungsvertrag eingeführt** (`/api/v1/nvd/normalize` mit `api_version` und stabilen Fehlercodes wie `invalid_cve_id`).
16+
- [x] **Python-Collection-Import im Rust-only-Modus gesperrt**; `import_nvd_cves` und `sync_nvd_recent` orchestrieren den Rust-Primärpfad über die Rust-CLI.
1617

1718
## Verbleibende Ablösearbeiten
1819
- [ ] CVE-Normalisierung vollständig als produktiven Rust-Primärpfad konsolidieren (inkl. Monitoring, Betriebsdoku und finaler Entfernung des Python-Kompatibilitätsendpunkts).
19-
- [ ] NVD-Collection-Import logikseitig als Rust-Primärpfad bereitstellen und Django auf orchestrierende Rolle begrenzen.
20+
- [ ] NVD-Collection-Import in Rust um Persistenz-/DB-Schreibpfad erweitern, damit Django nicht mehr für CVE-Upserts benötigt wird.
2021
- [ ] Parity-Reports als optionales Audit-Feature markieren (nicht als Betriebsnotwendigkeit).
2122
- [ ] Betriebsdoku für Stage/Prod um verpflichtende Rust-Health-SLOs und Alerting ergänzen.
2223
- [ ] CI-Policy: Fail, wenn `VULN_INTEL_RUST_ONLY=True` und rust-backend Integrationstests nicht grün.

docs/RUST_CUTOVER_PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Kontrollierter Wechsel von Python-Pfaden auf Rust-Pfade ohne Big-Bang-Risiko.
2424

2525
## Stufe 3 – Teil-Cutover
2626

27-
- `import_nvd_cves_canary --apply-source=rust --strict` fuer Stage erzwingen
27+
- Rust-CLI/Wrapper (`import_nvd_cves`, `sync_nvd_recent`, `import_nvd_cves_canary`) fuer Stage mit verpflichtendem Rust-Healthcheck erzwingen
2828
- In Production schrittweise per Job-Kohorten aktivieren
2929
- Mismatch-Grenzwerte definieren (z. B. <0.5%)
3030

0 commit comments

Comments
 (0)