QL/gen_resume.py

303 lines
15 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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: <CIDR>)."
)
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:"<nouvel OS>"', 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")