From 4fa5f67c32cb23f4e6d2bbc7846696f89d8c6117 Mon Sep 17 00:00:00 2001 From: Admin MPCZ Date: Sat, 11 Apr 2026 21:56:59 +0200 Subject: [PATCH] Qualys agents: bouton Rafraichir + cron 6h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - refresh_all_agents(): bulk sync tous les assets depuis API Qualys QPS - Bouton "Rafraîchir depuis Qualys" sur la page agents - Cron toutes les 6h: refresh_agents.py (prod + demo) - Message de confirmation après refresh --- app/routers/qualys.py | 19 ++++++++ app/scripts/refresh_agents.py | 16 ++++++ app/services/qualys_service.py | 84 ++++++++++++++++++++++++++++++++ app/templates/qualys_agents.html | 21 +++++++- 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 app/scripts/refresh_agents.py diff --git a/app/routers/qualys.py b/app/routers/qualys.py index 3898d4d..27d2e66 100644 --- a/app/routers/qualys.py +++ b/app/routers/qualys.py @@ -512,10 +512,29 @@ async def qualys_agents_page(request: Request, db=Depends(get_db)): ctx.update({ "app_name": APP_NAME, "keys": keys, "summary": summary, "no_agent_servers": no_agent, "inactive_agents": inactive, + "msg": request.query_params.get("msg", ""), }) return templates.TemplateResponse("qualys_agents.html", ctx) +@router.post("/qualys/agents/refresh") +async def qualys_agents_refresh(request: Request, db=Depends(get_db)): + user = get_current_user(request) + if not user: + return RedirectResponse(url="/login") + perms = get_user_perms(db, user) + if not can_edit(perms, "qualys"): + return RedirectResponse(url="/qualys/agents") + from ..services.qualys_service import refresh_all_agents + try: + stats = refresh_all_agents(db) + msg = f"refresh_ok_{stats.get('created',0)}_{stats.get('updated',0)}" + except Exception as e: + import traceback; traceback.print_exc() + msg = "refresh_error" + return RedirectResponse(url=f"/qualys/agents?msg={msg}", status_code=303) + + @router.get("/qualys/agents/export-no-agent") async def export_no_agent_csv(request: Request, db=Depends(get_db)): user = get_current_user(request) diff --git a/app/scripts/refresh_agents.py b/app/scripts/refresh_agents.py new file mode 100644 index 0000000..c9b4bca --- /dev/null +++ b/app/scripts/refresh_agents.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""Cron script: refresh Qualys agents data every 6h""" +import sys +sys.path.insert(0, "/opt/patchcenter") + +from app.database import SessionLocal, SessionLocalDemo +from app.services.qualys_service import refresh_all_agents + +for factory, name in [(SessionLocal, "prod"), (SessionLocalDemo, "demo")]: + try: + db = factory() + result = refresh_all_agents(db) + print(f"[{name}] {result.get('msg', result)}") + db.close() + except Exception as e: + print(f"[{name}] ERROR: {e}") diff --git a/app/services/qualys_service.py b/app/services/qualys_service.py index f772158..d39e6b7 100644 --- a/app/services/qualys_service.py +++ b/app/services/qualys_service.py @@ -532,3 +532,87 @@ def invalidate_search_cache(): def get_cache_stats(): """Stats du cache""" return _cache.stats() + + +def refresh_all_agents(db): + """Rafraichit tous les agents depuis l'API Qualys QPS (bulk)""" + qualys_url, qualys_user, qualys_pass, qualys_proxy = _get_qualys_creds(db) + if not qualys_user: + return {"ok": False, "msg": "Credentials Qualys non configurés"} + proxies = {"https": qualys_proxy, "http": qualys_proxy} if qualys_proxy else None + + stats = {"created": 0, "updated": 0, "errors": 0} + + try: + r = requests.post( + f"{qualys_url}/qps/rest/2.0/search/am/hostasset", + json={"ServiceRequest": {"preferences": {"limitResults": 1000}}}, + auth=(qualys_user, qualys_pass), + verify=False, timeout=120, proxies=proxies, + headers={"X-Requested-With": "PatchCenter", "Content-Type": "application/json"}) + except Exception as e: + return {"ok": False, "msg": str(e)} + + if r.status_code != 200 or "SUCCESS" not in r.text: + return {"ok": False, "msg": f"API HTTP {r.status_code}"} + + for block in r.text.split("")[1:]: + block = block.split("")[0] + try: + asset_id = (parse_xml(block, "id") or [""])[0] + name = (parse_xml(block, "name") or [""])[0] + hostname = name.split(".")[0].lower() if name else "" + address = (parse_xml(block, "address") or [""])[0] + fqdn = (parse_xml(block, "fqdn") or [""])[0] + os_val = (parse_xml(block, "os") or [""])[0] + + agent_status = "" + agent_version = "" + last_checkin = None + if "" in block: + ab = block[block.find(""):block.find("")] + agent_status = (parse_xml(ab, "status") or [""])[0] + agent_version = (parse_xml(ab, "agentVersion") or [""])[0] + lc = (parse_xml(ab, "lastCheckedIn") or [""])[0] + last_checkin = lc if lc else None + + os_family = None + if any(k in os_val.lower() for k in ("linux", "red hat", "centos", "debian")): + os_family = "linux" + elif "windows" in os_val.lower(): + os_family = "windows" + + # Match server + srv = db.execute(text("SELECT id FROM servers WHERE LOWER(hostname)=LOWER(:h)"), + {"h": hostname}).fetchone() + server_id = srv.id if srv else None + + existing = db.execute(text("SELECT id FROM qualys_assets WHERE qualys_asset_id=:qid"), + {"qid": int(asset_id)}).fetchone() + + if existing: + db.execute(text("""UPDATE qualys_assets SET + name=:name, hostname=:hn, fqdn=:fqdn, ip_address=:ip, os=:os, os_family=:osf, + agent_status=:ast, agent_version=:av, last_checkin=:lc, server_id=:sid, updated_at=now() + WHERE qualys_asset_id=:qid"""), + {"qid": int(asset_id), "name": name, "hn": hostname, "fqdn": fqdn or None, + "ip": address or None, "os": os_val, "osf": os_family, + "ast": agent_status, "av": agent_version, "lc": last_checkin, "sid": server_id}) + stats["updated"] += 1 + else: + db.execute(text("""INSERT INTO qualys_assets + (qualys_asset_id, name, hostname, fqdn, ip_address, os, os_family, + agent_status, agent_version, last_checkin, server_id) + VALUES (:qid, :name, :hn, :fqdn, :ip, :os, :osf, :ast, :av, :lc, :sid)"""), + {"qid": int(asset_id), "name": name, "hn": hostname, "fqdn": fqdn or None, + "ip": address or None, "os": os_val, "osf": os_family, + "ast": agent_status, "av": agent_version, "lc": last_checkin, "sid": server_id}) + stats["created"] += 1 + + except Exception: + stats["errors"] += 1 + + db.commit() + stats["ok"] = True + stats["msg"] = f"{stats['created']} créés, {stats['updated']} mis à jour" + return stats diff --git a/app/templates/qualys_agents.html b/app/templates/qualys_agents.html index ab33da4..4b6f4e2 100644 --- a/app/templates/qualys_agents.html +++ b/app/templates/qualys_agents.html @@ -6,9 +6,28 @@

Agents Qualys

Activation keys et versions des agents déployés

- Recherche +
+
+ +
+ Déployer + Recherche +
+{% if 'refresh_ok' in msg %} +
+ Données rafraîchies depuis Qualys. +
+{% elif msg == 'refresh_error' %} +
+ Erreur lors du rafraîchissement. +
+{% endif %} +
{{ summary.total_assets or 0 }}
Total assets