# -*- coding: utf-8 -*- """Generate SANEF Qualys V3 - resume execution + points de vigilance.docx""" from docx import Document from docx.shared import Pt, RGBColor, Inches, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH doc = Document() style = doc.styles['Normal'] style.font.name = 'Calibri' style.font.size = Pt(10) # ===== Cover ===== title = doc.add_heading('SANEF — Qualys Taxonomie V3', 0) title.alignment = WD_ALIGN_PARAGRAPH.CENTER sub = doc.add_paragraph() sub.alignment = WD_ALIGN_PARAGRAPH.CENTER r = sub.add_run('Résumé d\'exécution + points de vigilance') r.font.size = Pt(14) r.italic = True meta = doc.add_paragraph() meta.alignment = WD_ALIGN_PARAGRAPH.CENTER r = meta.add_run('Date : 22 avril 2026\nRédacteur : Khalid MOUTAOUAKIL — SECOPS\nVersion : 1.0') r.font.size = Pt(11) doc.add_paragraph() doc.add_paragraph('_' * 80) doc.add_paragraph() # ===== 1. Résumé ===== doc.add_heading('1. Résumé de la session', level=1) doc.add_paragraph( "Cette session a consisté à déployer la taxonomie V3 des tags Qualys SANEF, " "définie dans le document « Processus d'affectation des Tags Qualys — Nomenclature V3 ». " "L'ensemble des règles a été créé directement dans la console Qualys (module Asset Inventory) " "et appliqué sur le parc actif (~3,22k assets sur 6,24k au total)." ) doc.add_heading('1.1 Tags créés', level=2) doc.add_paragraph('5 parents (containers organisationnels statiques) :', style='List Bullet') for p in ['OS', 'EQT', 'ENV', 'POS', 'TAG']: doc.add_paragraph(p, style='List Bullet 2') doc.add_paragraph('23 tags dynamiques (Asset Inventory rule engine) :', style='List Bullet') for line in [ 'OS-LIN-SRV (729), OS-WIN-SRV (505), OS-WIN-WKS (~2056), OS-MAC (7), OS-ESX (2)', 'EQT-VIR (956), EQT-SRV (307), EQT-SWI (72)', 'ENV-PRD (~772), ENV-REC (~302), ENV-PPR (~76), ENV-TST (83), ENV-DEV (~21)', 'POS-FL, POS-INF, POS-PEA, POS-TRA, POS-BI, POS-GES (counts variables, voir console)', 'NOM-LEGACY (219) — KPI dette de conformité V3', 'TAG-EMV (~34) — zone PCI-DSS', 'TAG-OBS (~190+) — OS EOL liste explicite', ]: doc.add_paragraph(line, style='List Bullet 2') doc.add_paragraph('Tags statiques (bulk SQATM) :', style='List Bullet') for line in [ 'TAG-SED (10) — exposition directe Internet', 'TAG-SEI (22) — exposition indirecte derrière frontal', 'TAG-ELS (vide) — extension de licence (exclusion EOL)', 'TAG-DEC, TAG-INT, TAG-SIC, TAG-SIA — statiques manuels', ]: doc.add_paragraph(line, style='List Bullet 2') doc.add_heading('1.2 Découvertes techniques majeures', level=2) findings = [ ("Limites QQL Tag rule engine", "Le moteur Asset Inventory pour la création de règles n'accepte que la syntaxe starts-with " "(asset.name:vp*). Les opérateurs CONTAINS (*X*) et les guillemets (\"X\") échouent. La clé " "tags.name peut être utilisée dans les recherches interactives mais PAS dans la création de " "règles dynamiques (l'API rejette le commit avec « Error while committing transaction »)."), ("FQDN .sanef.groupe non env-spécifique", "Découverte initialement supposée comme indicateur Production, le domaine AD interne " ".sanef.groupe est en réalité utilisé par tous les serveurs SANEF (PRD, REC, PPR, TST, DEV). " "Utiliser comme filtre broad provoque des overlaps massifs entre tags ENV. Solution adoptée : " "scoper FQDN par pattern hostname précis (ex : vmm*.sanef-int.adds pour les Win11 prod uniquement)."), ("Conflits de préfixes 5-char entre POS", "Plusieurs préfixes hostname appartiennent à des domaines différents selon contexte applicatif " "(ex : vppea* = FL pour BOOST + PEA pour SVP). Résolution par énumération 7-char spécifique " "(vppeaab* BOOST = FL, vppeaaa*/ar*/ae* SVP = PEA) et clauses NOT exclusives."), ("Zone DMZ multi-subnet", "La DMZ SANEF est segmentée en 15 sous-réseaux /24 différents. Pas de CIDR unique. " "Impossible de faire une règle dynamique simple par IP. Tagging actuel TAG-SED/SEI en static " "via SQATM bulk."), ("Tag built-in Qualys « Obsolete »", "Qualys maintient automatiquement un tag « Obsolete » basé sur sa détection EOL. ~190 assets " "SANEF sont taggés. Ce tag NE peut PAS être référencé dans une règle de tag custom (limitation " "Qualys). On utilise une liste OS explicite pour TAG-OBS."), ("Postes Superviseur Péage (svp*) — exclusion ENV-TST et EQT-SRV", "Le préfixe sv* visait initialement les serveurs physiques de test (svboomcla2, etc.). " "Découverte : ~100 postes de travail Windows 11 (HP Elite Mini desktops SVP2xxxx + HP EliteBook " "SVP1xxxx) commencent aussi par svp* — ce sont des postes Superviseur Péage, pas des serveurs. " "Sans exclusion explicite, ils étaient ramassés par ENV-TST (~180 au lieu de 83) et " "potentiellement par EQT-SRV. Correction : ajout de « and not asset.name:svp* » sur les deux " "règles. ENV-TST passe à 83 assets, cohérent avec le périmètre attendu."), ] for h, t in findings: p = doc.add_paragraph() p.add_run(h + ' : ').bold = True p.add_run(t) # ===== 2. Points de vigilance ===== doc.add_page_break() doc.add_heading('2. Points de vigilance', level=1) doc.add_paragraph( "Les éléments suivants nécessitent une surveillance ou des actions futures pour maintenir " "la cohérence et l'exhaustivité de la taxonomie V3." ) doc.add_heading("2.1 POS-DMZ — à adapter une fois la DMZ normalisée", level=2) doc.add_paragraph( "Statut actuel : pas de tag POS-DMZ créé. La gestion DMZ est faite via TAG-SED (10 frontaux) " "et TAG-SEI (22 backends), tous statiques." ) p = doc.add_paragraph() p.add_run('Problème : ').bold = True p.add_run( "la DMZ SANEF s'étend sur 15 sous-réseaux /24 différents (192.168.2/24, 192.168.20/24, " "192.168.31/24, 10.41.20/24, 10.42.30/24, 10.205.0/24, etc.). Aucun CIDR unique ne permet " "actuellement une règle dynamique simple." ) p = doc.add_paragraph() p.add_run('Action requise : ').bold = True p.add_run( "demander à l'équipe réseau SANEF de fournir soit (a) le CIDR DMZ consolidé (si réorganisation " "envisagée), soit (b) une nomenclature IP DMZ-spécifique (par exemple un /20 dédié). Une fois " "obtenu, basculer TAG-SED et TAG-SEI en règles dynamiques basées sur " "asset.interface:(address: )." ) p = doc.add_paragraph() p.add_run('Alternative : ').bold = True p.add_run( "si la DMZ reste multi-subnet, garder TAG-SED/TAG-SEI en static. Documenter dans la procédure " "patcheur SECOPS : tout nouveau serveur en zone DMZ doit être taggé manuellement via SQATM " "selon son rôle (frontal = SED, backend = SEI)." ) p = doc.add_paragraph() p.add_run('Plages publiques connues pour TAG-SED dynamique : ').bold = True p.add_run("83.68.96.0/24 (Juniper firewalls), 83.68.99.0/24 (F5 BIG-IP). Ces plages peuvent être " "utilisées pour basculer TAG-SED en dynamique partiel dès aujourd'hui :") doc.add_paragraph( "asset.interface:(address: 83.68.96.0/24) or asset.interface:(address: 83.68.99.0/24)", style='Intense Quote' ) doc.add_heading('2.2 TAG-ELS — saisie manuelle au cas par cas', level=2) doc.add_paragraph( "TAG-ELS (Extended Life License) identifie les serveurs sous contrat de support sécurité " "étendu payé (Microsoft ESU, Red Hat ELS, etc.). Ces serveurs ne doivent PAS être considérés " "comme TAG-OBS car ils reçoivent encore des correctifs de sécurité." ) p = doc.add_paragraph() p.add_run('Statut actuel : ').bold = True p.add_run("TAG-ELS créé (statique, vide). Aucun asset taggué.") p = doc.add_paragraph() p.add_run('Action requise : ').bold = True p.add_run( "lister les serveurs SANEF sous contrats ELS (vérifier avec la DSI / responsables applicatifs / " "renouvellements de licence Microsoft/RedHat). Saisir manuellement via SQATM avec le tag TAG-ELS " "sur chaque asset concerné." ) p = doc.add_paragraph() p.add_run('Workflow opérationnel : ').bold = True p.add_run( "lorsqu'un asset est ajouté à TAG-ELS, retirer manuellement TAG-OBS si présent (les deux tags " "ne doivent pas coexister). Limitation : la chaîne de tag (tag rule excluant un autre tag) " "n'est pas supportée par Qualys, donc le double-tagging doit être nettoyé manuellement." ) p = doc.add_paragraph() p.add_run('Reporting recommandé : ').bold = True p.add_run("créer une « Saved Search » Qualys pour le reporting opérationnel :") doc.add_paragraph('tags.name:"TAG-OBS" and not tags.name:"TAG-ELS"', style='Intense Quote') doc.add_paragraph("Cette query, valide en recherche interactive, donne les « vrais EOL à patcher » " "et est utilisable dans les dashboards.") doc.add_heading("2.3 TAG-OBS — adapter la liste OS quand un nouvel OS devient obsolète", level=2) doc.add_paragraph( "TAG-OBS est défini en Option A (liste OS explicite) car la chaîne sur le tag built-in Qualys " "« Obsolete » n'est pas supportée. La liste actuelle couvre 15 OS / familles." ) p = doc.add_paragraph() p.add_run('Liste OS actuelle (à maintenir) : ').bold = True oses = [ "Windows XP (EOL 2014-04-08)", "Windows Server 2003 (EOL 2015-07-14)", "Windows Server 2008 / 2008 R2 (EOL 2020-01-14)", "Windows Server 2012 / 2012 R2 (EOL 2023-10-10)", "Windows 7 (EOL 2020-01-14)", "Red Hat Enterprise Linux 5 (EOL 2017-03-31)", "Red Hat Enterprise Linux 6 (EOL 2020-11-30)", "CentOS 5 / 6 / 7 (CentOS 7 EOL 2024-06-30)", "Ubuntu 14.04 / 16.04 / 18.04 (EOL 2019/2021/2023)", "Debian 8 / 9 (EOL 2020/2022)", ] for o in oses: doc.add_paragraph(o, style='List Bullet') p = doc.add_paragraph() p.add_run('Surveillance recommandée : ').bold = True p.add_run( "vérifier au moins une fois par an (idéalement en début d'année) si de nouveaux OS sont passés EOL. " "Sources fiables : endoflife.date, sites éditeurs (microsoft.com/lifecycle, redhat.com/support, " "ubuntu.com/about/release-cycle)." ) p = doc.add_paragraph() p.add_run('OS à surveiller dans les 12-24 mois : ').bold = True upcoming = [ "RHEL 7 — EOL 2024-06-30 (déjà à inclure si pas déjà fait)", "Windows Server 2012 R2 ESU — EOL 2026-10-13", "Ubuntu 20.04 — EOL 2025-04 standard, ESM jusqu'à 2030", "Debian 10 — EOL 2024-06-30 standard, LTS jusqu'à 2024-06", "CentOS Stream 8 — EOL 2024-05-31", ] for o in upcoming: doc.add_paragraph(o, style='List Bullet') p = doc.add_paragraph() p.add_run('Procédure de mise à jour TAG-OBS : ').bold = True doc.add_paragraph("Ouvrir Qualys Console > AssetView > Configuration > Tags > TAG-OBS > Edit", style='List Number') doc.add_paragraph('Ajouter la nouvelle clause OR à la query : or operatingSystem:""', style='List Number') doc.add_paragraph("Cliquer Test pour vérifier le count", style='List Number') doc.add_paragraph("Save", style='List Number') doc.add_paragraph("Mettre à jour le fichier de référence SANEF_Qualys_Tags_V3_RuleTypes_v2.xlsx", style='List Number') doc.add_heading("2.4 Mécanisme natif Qualys « Obsolete » (à connaître)", level=2) doc.add_paragraph( "Qualys propose un tag système « Obsolete » auto-appliqué à partir d'une base de connaissance " "interne sur les fins de support OS. Ce tag est maintenu par Qualys (pas par SANEF), s'applique " "automatiquement aux assets quand leur OS bascule EOL." ) p = doc.add_paragraph() p.add_run("Couverture observée dans le parc SANEF : ").bold = True p.add_run("~190 assets (181 dans l'export AssetView et 180 dans Cloud Agent au 22 avril 2026).") p = doc.add_paragraph() p.add_run("Pourquoi ne pas utiliser uniquement ce tag : ").bold = True p.add_run( "(1) il n'est PAS référençable dans une règle de tag dynamique custom (limitation Qualys " "confirmée), donc impossible de créer un TAG-OBS = mirror du tag Qualys ; " "(2) il n'inclut pas toujours toutes les variantes EOL (parfois décalage temporel ou critères " "différents) ; " "(3) il ne permet pas l'exclusion ELS via règle." ) p = doc.add_paragraph() p.add_run("Comment l'utiliser malgré ses limites : ").bold = True p.add_run( "via les Saved Searches dans la console Qualys. Exemple de query à sauvegarder pour le reporting :" ) doc.add_paragraph('tags.name:"Obsolete" and not tags.name:"TAG-ELS"', style='Intense Quote') p = doc.add_paragraph() p.add_run("Comparaison TAG-OBS (Option A) vs Obsolete built-in : ").bold = True p.add_run( "TAG-OBS = liste explicite contrôlée par SECOPS, plus large potentiellement, mais nécessite " "maintenance. Obsolete built-in = auto-update Qualys, sans maintenance, mais sans contrôle SANEF " "et non chaînable. La meilleure approche est de conserver les deux et de les utiliser en croisant " "leur intersection." ) # ===== 3. Suite à faire ===== doc.add_page_break() doc.add_heading('3. Suite à faire (TODO)', level=1) todos = [ ("Bulk SQATM TAG-SED, TAG-SEI", "Appliqué aujourd'hui via SQATM (10 SED, 22 SEI). Vérifier à la prochaine revue qu'aucun nouveau " "asset DMZ n'a été oublié."), ("Récupérer CIDR DMZ auprès équipe réseau", "Pour basculer TAG-SED/TAG-SEI en dynamique IP-based. Voir section 2.1."), ("Saisir TAG-ELS sur les serveurs concernés", "Inventaire des contrats ELS à faire avec la DSI. Voir section 2.2."), ("Nettoyer tags legacy concurrents dans Qualys", "Plusieurs tags concurrents détectés dans les exports : TAG-SRVEXPOSEINTERNET, DMZ, SED, " "ZONE-DMZ. Décision à prendre : garder en parallèle, supprimer ou consolider sur la nomenclature V3."), ("Documenter procédure SECOPS pour nouveaux serveurs", "Procédure à intégrer dans le wiki DokuWiki SANEF (page infra:sanef_utiles:sanef_utiles:qualys_tagv3) : " "tout nouveau serveur SANEF doit être taggé selon V3 dans les 48h après scan Qualys initial."), ("Dashboard Qualys « Conformité V3 »", "Construire un dashboard avec : count NOM-LEGACY (KPI dette renommage), count TAG-OBS (hors ELS), " "% serveurs avec OS-* + ENV-* + POS-* + EQT-* tags présents (couverture V3)."), ("Surveillance annuelle TAG-OBS", "Revérifier la liste OS EOL en janvier 2027 et ajouter les nouveaux OS passés EOL. Voir 2.3."), ] for title_, desc in todos: p = doc.add_paragraph(style='List Bullet') p.add_run(title_).bold = True p.add_run(' : ' + desc) doc.add_paragraph() doc.add_paragraph('_' * 80) p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER r = p.add_run("SANEF DSI / Sécurité Opérationnelle — Plan d'action Qualys V3") r.italic = True r.font.size = Pt(9) out = r"C:\Claude\sanef\QL\docs\SANEF_Qualys_V3_Resume_Vigilance.docx" doc.save(out) print("Saved:", out) import os print("Size:", os.path.getsize(out), "bytes")