Resetting Wazuh or Nessus in Settings left the dashboard banner red with
"last run failed", and the scheduled sync kept running and failing.
The reset stores encrypt_value(""), which is non-empty ciphertext, and
configured_sources() read the raw column, so every reset source still
counted as configured. It now reads the decrypted value. That one function
drives the Sync Health state, the scheduler's skip for unconfigured
scanners and the failure-mail skip, so all three follow the reset again.
The existing tests covered the reset and the health state separately; the
new one resets through update_setting and then asks sync_health.
125 lines
5.8 KiB
Python
125 lines
5.8 KiB
Python
"""Eine konfigurierte Integration muss wieder auf "not configured" zurueck.
|
|
|
|
Field report 2026-09-10: keiner der konfigurierten Syncjobs liess sich leeren.
|
|
Zwei Gruende, beide unabhaengig:
|
|
|
|
1. Der Admin loescht alle Felder im Dialog und speichert. Der Merge in
|
|
`update_setting` holt fuer jedes leer eingereichte Secret-Feld den
|
|
gespeicherten Wert zurueck — gebaut, damit man Host und IDs aendern kann,
|
|
ohne das Passwort neu zu tippen. Beim Zuruecksetzen arbeitet genau das
|
|
dagegen: das Secret ueberlebt. Und selbst mit leeren Feldern bleibt ein
|
|
nicht-leerer JSON-String stehen, den die Oberflaeche als "configured"
|
|
liest. Ein PUT mit leerem Wert ist kein gueltiges JSON, laeuft also am
|
|
Merge vorbei und loescht die Zeile fachlich.
|
|
|
|
2. Sync Health blieb auch danach rot. Der Zustand `unconfigured` galt nur,
|
|
solange die Quelle noch nie gelaufen war — nach dem Reset lagen die alten
|
|
sync_runs-Zeilen noch da, und die Quelle fiel auf `stale` statt grau zu
|
|
werden. Die Historie bleibt (das Ledger loescht niemand), nur der Zustand
|
|
folgt jetzt der Konfiguration.
|
|
|
|
Run: venv/bin/python -m pytest tests/test_integration_reset.py
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
from types import SimpleNamespace
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
os.environ.setdefault("JWT_SECRET_KEY", "test-key-test-key-test-key-test-key")
|
|
from cryptography.fernet import Fernet # noqa: E402
|
|
os.environ.setdefault("AUTH_PROVIDER_CRYPTO_KEY", Fernet.generate_key().decode())
|
|
|
|
from sqlalchemy import create_engine # noqa: E402
|
|
from sqlalchemy.orm import sessionmaker # noqa: E402
|
|
|
|
import app.models # noqa: F401,E402 (registers every mapper)
|
|
from app.models.base import Base # noqa: E402
|
|
from app.models.sync_run import SyncRun # noqa: E402
|
|
from app.auth.setting_crypto import read_setting_value # noqa: E402
|
|
from app.routers.settings import SettingUpdate, get_setting, update_setting # noqa: E402
|
|
|
|
ADMIN = SimpleNamespace(id=None) # update_setting needs a user; id None = no actor lookup
|
|
from app.services import sync_run_service as svc # noqa: E402
|
|
from app.services.intune_service import load_intune_config # noqa: E402
|
|
|
|
|
|
def _db():
|
|
engine = create_engine("sqlite://")
|
|
Base.metadata.create_all(engine)
|
|
return sessionmaker(bind=engine)()
|
|
|
|
|
|
FULL = {"tenant_id": "t-1", "client_id": "c-1", "client_secret": "s3cret", "verify_ssl": True}
|
|
|
|
|
|
def test_reset_loescht_config_samt_secret():
|
|
db = _db()
|
|
update_setting("intune_config", SettingUpdate(value=json.dumps(FULL)), db=db, current_user=ADMIN)
|
|
assert load_intune_config(db) is not None
|
|
|
|
update_setting("intune_config", SettingUpdate(value=""), db=db, current_user=ADMIN)
|
|
|
|
# Nichts bleibt liegen: weder im Klartext hinter der Verschluesselung ...
|
|
assert not (read_setting_value(db, "intune_config") or "")
|
|
# ... noch in der Antwort, aus der die Oberflaeche "configured" ableitet.
|
|
assert get_setting("intune_config", db=db, current_user=None)["value"] is None
|
|
# ... und der Scheduler ueberspringt den Job wieder.
|
|
assert load_intune_config(db) is None
|
|
|
|
|
|
def test_alle_felder_leeren_ist_kein_reset():
|
|
"""Der Merge ist Absicht — der Reset darf sich nicht darauf verlassen."""
|
|
db = _db()
|
|
update_setting("intune_config", SettingUpdate(value=json.dumps(FULL)), db=db, current_user=ADMIN)
|
|
emptied = {**FULL, "tenant_id": "", "client_id": "", "client_secret": ""}
|
|
update_setting("intune_config", SettingUpdate(value=json.dumps(emptied)), db=db, current_user=ADMIN)
|
|
|
|
stored = json.loads(read_setting_value(db, "intune_config"))
|
|
assert stored["client_secret"] == "s3cret" # Secret ueberlebt
|
|
assert get_setting("intune_config", db=db, current_user=None)["value"] # Badge bliebe gruen
|
|
|
|
|
|
def test_sync_health_wird_grau_obwohl_laeufe_existieren():
|
|
now = datetime.now()
|
|
old = SyncRun(source="wazuh", trigger="scheduled", status="completed", stats="{}",
|
|
started_at=now - timedelta(days=30), finished_at=now - timedelta(days=30))
|
|
by = {h["source"]: h for h in svc.summarize_health(
|
|
latest={"wazuh": old}, last_success={"wazuh": old}, configured=set(), now=now)}
|
|
assert by["wazuh"]["state"] == "unconfigured"
|
|
assert by["wazuh"]["configured"] is False
|
|
|
|
|
|
def test_konfigurierte_quelle_bleibt_bewertet():
|
|
"""Gegenprobe: solange die Config steht, entscheidet weiter der Lauf."""
|
|
now = datetime.now()
|
|
old = SyncRun(source="wazuh", trigger="scheduled", status="completed", stats="{}",
|
|
started_at=now - timedelta(days=30), finished_at=now - timedelta(days=30))
|
|
by = {h["source"]: h for h in svc.summarize_health(
|
|
latest={"wazuh": old}, last_success={"wazuh": old}, configured={"wazuh"}, now=now)}
|
|
assert by["wazuh"]["state"] == "stale"
|
|
|
|
|
|
def test_reset_ueber_die_api_macht_sync_health_grau():
|
|
"""Field report 2026-10-26: Wazuh und Nessus zurueckgesetzt, Banner blieb rot.
|
|
|
|
Der Reset speichert `encrypt_value("")` — Chiffretext, nicht leer. Die
|
|
beiden Tests oben pruefen Reset und Health getrennt; erst zusammen
|
|
sieht man, dass `configured_sources` die rohe Spalte las.
|
|
"""
|
|
db = _db()
|
|
now = datetime.now()
|
|
for key in ("wazuh_config", "nessus_config"):
|
|
update_setting(key, SettingUpdate(value=json.dumps({"api_url": "https://w", "access_key": "k"})),
|
|
db=db, current_user=ADMIN)
|
|
update_setting(key, SettingUpdate(value=""), db=db, current_user=ADMIN)
|
|
for source in ("wazuh", "nessus"):
|
|
db.add(SyncRun(source=source, trigger="scheduled", status="failed", stats="{}",
|
|
error_message="configuration missing", started_at=now, finished_at=now))
|
|
db.commit()
|
|
|
|
by = {h["source"]: h for h in svc.sync_health(db)}
|
|
assert by["wazuh"]["state"] == "unconfigured"
|
|
assert by["nessus"]["state"] == "unconfigured"
|