diff --git a/app/services/qualys_service.py b/app/services/qualys_service.py index a6f281f..c0f3a8b 100644 --- a/app/services/qualys_service.py +++ b/app/services/qualys_service.py @@ -32,8 +32,25 @@ def parse_xml(txt, tag): return re.findall(f"<{tag}>([^<]*){tag}>", txt) +# Mapping : noms "user-friendly" → noms exacts attendus par l'API Qualys 2.0 +# cf https://docs.qualys.com/en/am/api — search/am/hostasset criteria fields +QUALYS_FIELD_MAP = { + "hostname": "name", + "host": "name", + "fqdn": "dnsHostName", + "dns": "dnsHostName", + "ip": "address", + "ip_address": "address", + "id": "id", + "asset_id": "id", + "netbios": "netbiosName", +} + + def search_assets_api(db, query, field="name", operator="CONTAINS", force_refresh=False): - """Recherche des assets via l'API Qualys — cache 10 min""" + """Recherche des assets via l'API Qualys — cache 10 min.""" + # Traduit le field front vers le field Qualys (sinon "INVALID_FIELD") + field = QUALYS_FIELD_MAP.get((field or "").lower(), field) cache_key = f"qualys:search:{field}:{query}" if not force_refresh: @@ -60,7 +77,7 @@ def search_assets_api(db, query, field="name", operator="CONTAINS", force_refres "" ) r = requests.post( - f"{qualys_url}/qps/rest/3.0/search/am/hostasset", + f"{qualys_url}/qps/rest/2.0/search/am/hostasset", data=xml_body, auth=(qualys_user, qualys_pass), verify=False, timeout=60, proxies=proxies, @@ -98,7 +115,7 @@ def get_all_tags_api(db): try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/search/am/tag", + f"{qualys_url}/qps/rest/2.0/search/am/tag", json={"ServiceRequest": {"preferences": {"limitResults": 1000}}}, auth=(qualys_user, qualys_pass), verify=False, timeout=60, proxies=proxies, @@ -169,7 +186,7 @@ def create_tag_api(db, tag_name): proxies = {"https": qualys_proxy, "http": qualys_proxy} if qualys_proxy else None try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/create/am/tag", + f"{qualys_url}/qps/rest/2.0/create/am/tag", json={"ServiceRequest": {"data": {"Tag": {"name": tag_name}}}}, auth=(qualys_user, qualys_pass), verify=False, timeout=30, proxies=proxies, headers={"Content-Type": "application/json"}) @@ -195,7 +212,7 @@ def delete_tag_api(db, qualys_tag_id): proxies = {"https": qualys_proxy, "http": qualys_proxy} if qualys_proxy else None try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/delete/am/tag/{qualys_tag_id}", + f"{qualys_url}/qps/rest/2.0/delete/am/tag/{qualys_tag_id}", auth=(qualys_user, qualys_pass), verify=False, timeout=30, proxies=proxies, headers={"Content-Type": "application/json"}) if r.status_code == 200 and "SUCCESS" in r.text: @@ -216,7 +233,7 @@ def add_tag_to_asset_api(db, asset_id, tag_id): proxies = {"https": qualys_proxy, "http": qualys_proxy} if qualys_proxy else None try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/update/am/hostasset/{asset_id}", + f"{qualys_url}/qps/rest/2.0/update/am/hostasset/{asset_id}", json={"ServiceRequest": {"data": {"HostAsset": {"tags": {"add": {"TagSimple": {"id": tag_id}}}}}}}, auth=(qualys_user, qualys_pass), verify=False, timeout=30, proxies=proxies, headers={"Content-Type": "application/json"}) @@ -240,7 +257,7 @@ def remove_tag_from_asset_api(db, asset_id, tag_id): proxies = {"https": qualys_proxy, "http": qualys_proxy} if qualys_proxy else None try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/update/am/hostasset/{asset_id}", + f"{qualys_url}/qps/rest/2.0/update/am/hostasset/{asset_id}", json={"ServiceRequest": {"data": {"HostAsset": {"tags": {"remove": {"TagSimple": {"id": tag_id}}}}}}}, auth=(qualys_user, qualys_pass), verify=False, timeout=30, proxies=proxies, headers={"Content-Type": "application/json"}) @@ -300,7 +317,7 @@ def sync_server_qualys(db, server_id): # Recuperer l'asset complet avec tags try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/search/am/hostasset", + f"{qualys_url}/qps/rest/2.0/search/am/hostasset", json={"ServiceRequest": { "filters": {"Criteria": [ {"field": "id", "operator": "EQUALS", "value": str(qid)} @@ -388,7 +405,7 @@ def _find_asset_by_hostname(qualys_url, qualys_user, qualys_pass, hostname, prox """Cherche un asset Qualys par hostname""" try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/search/am/hostasset", + f"{qualys_url}/qps/rest/2.0/search/am/hostasset", json={"ServiceRequest": { "preferences": {"limitResults": 5}, "filters": {"Criteria": [ @@ -688,7 +705,7 @@ def _refresh_all_agents_impl(db, mode="diff"): for attempt in range(3): try: r = sess.post( - f"{qualys_url}/qps/rest/3.0/search/am/hostasset", + f"{qualys_url}/qps/rest/2.0/search/am/hostasset", json=payload, timeout=600) break except Exception: @@ -913,7 +930,7 @@ def _fetch_asset_ids_by_tag(db, tag_name): proxies = {"https": qualys_proxy, "http": qualys_proxy} if qualys_proxy else None try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/search/am/hostasset", + f"{qualys_url}/qps/rest/2.0/search/am/hostasset", json={"ServiceRequest": { "preferences": {"limitResults": 1000}, "filters": {"Criteria": [ @@ -1156,7 +1173,7 @@ def fetch_all_qualys_assets(db, with_progress=False): "filters": {"Criteria": criteria}}} try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/search/am/hostasset", + f"{qualys_url}/qps/rest/2.0/search/am/hostasset", json=body, auth=(qualys_user, qualys_pass), verify=False, timeout=180, proxies=proxies, headers={"Content-Type": "application/json"} @@ -1264,7 +1281,7 @@ def delete_qualys_asset(db, asset_id): proxies = {"https": qualys_proxy, "http": qualys_proxy} if qualys_proxy else None try: r = requests.post( - f"{qualys_url}/qps/rest/3.0/delete/am/hostasset/{int(asset_id)}", + f"{qualys_url}/qps/rest/2.0/delete/am/hostasset/{int(asset_id)}", auth=(qualys_user, qualys_pass), verify=False, timeout=60, proxies=proxies, headers={"Content-Type": "application/json"} ) diff --git a/app/services/qualys_tags_service.py b/app/services/qualys_tags_service.py index cb33bb2..5b55b3d 100644 --- a/app/services/qualys_tags_service.py +++ b/app/services/qualys_tags_service.py @@ -70,7 +70,7 @@ def list_qualys_tags(db): """Liste tous les tags Qualys (nom, id, type DYN/STAT, ruleText).""" # search all tags payload = {"ServiceRequest": {"preferences": {"limitResults": 1000}}} - r = _qualys_post(db, "/qps/rest/3.0/search/am/tag", payload) + r = _qualys_post(db, "/qps/rest/2.0/search/am/tag", payload) if not r.get("ok"): return {"ok": False, "msg": r.get("msg") or f"HTTP {r.get('status')}", "tags": []} tags = [] @@ -175,7 +175,7 @@ def create_static_tag(db, name, color="", description=""): } } } - r = _qualys_post(db, "/qps/rest/3.0/create/am/tag", payload) + r = _qualys_post(db, "/qps/rest/2.0/create/am/tag", payload) if r.get("ok"): return {"ok": True, "msg": f"Tag '{name}' cree"} return {"ok": False, "msg": r.get("msg") or f"HTTP {r.get('status')}: {r.get('text', '')[:200]}"} @@ -183,7 +183,7 @@ def create_static_tag(db, name, color="", description=""): def delete_tag(db, tag_id): """Supprime un tag Qualys par id.""" - r = _qualys_post(db, f"/qps/rest/3.0/delete/am/tag/{tag_id}", {}) + r = _qualys_post(db, f"/qps/rest/2.0/delete/am/tag/{tag_id}", {}) return {"ok": r.get("ok"), "msg": r.get("msg") or f"HTTP {r.get('status')}"} diff --git a/app/templates/qualys_duplicates.html b/app/templates/qualys_duplicates.html index 8880db7..afede88 100644 --- a/app/templates/qualys_duplicates.html +++ b/app/templates/qualys_duplicates.html @@ -45,7 +45,7 @@
Le plus recent (en haut de chaque groupe) est probablement l'asset actif. Les autres sont des zombies (anciennes installations, ré-IPs, doublons de scan).
- Bouton Supprimer = appel API Qualys POST /qps/rest/3.0/delete/am/hostasset/{id}.
+ Bouton Supprimer = appel API Qualys POST /qps/rest/2.0/delete/am/hostasset/{id}.
{% if not can_delete %}
Tu n'as pas la permission edit pour supprimer.{% endif %}