From ee160fc06cb448daab25519e2036b2249340da71 Mon Sep 17 00:00:00 2001 From: Chevallier Date: Mon, 1 Jun 2026 09:51:24 +0200 Subject: [PATCH] ajout doc + correction --- CREATE_FIREWALL_TOOL.md | 119 +++++++++++ CREATE_SWITCH_PARSING.md | 2 +- CREATE_SWITCH_TOOL.md | 119 +++++++++++ Computacenter_logo.png | Bin 0 -> 88314 bytes Parseurs_config_Firewall/README.md | 13 +- Parseurs_config_Firewall/src/main.py | 18 ++ .../src/scripts/export_interfaces.py | 201 ++++++++++++++++++ .../src/scripts/export_matrice_flux.py | 34 ++- .../src/scripts/export_modele.py | 49 +++-- .../src/scripts/json_Stormshield.py | 22 +- .../src/scripts/objets/data.py | 1 + .../scripts/style_excel/style_addresses.py | 44 ++++ .../src/scripts/style_excel/style_services.py | 51 +++++ README.md | 1 + gui_firewall.py | 43 ++-- help_Firewall.md | 1 + main.py | 26 ++- 17 files changed, 684 insertions(+), 60 deletions(-) create mode 100644 CREATE_FIREWALL_TOOL.md create mode 100644 CREATE_SWITCH_TOOL.md create mode 100644 Computacenter_logo.png create mode 100644 Parseurs_config_Firewall/src/scripts/export_interfaces.py create mode 100644 Parseurs_config_Firewall/src/scripts/style_excel/style_addresses.py create mode 100644 Parseurs_config_Firewall/src/scripts/style_excel/style_services.py diff --git a/CREATE_FIREWALL_TOOL.md b/CREATE_FIREWALL_TOOL.md new file mode 100644 index 0000000..ec3fece --- /dev/null +++ b/CREATE_FIREWALL_TOOL.md @@ -0,0 +1,119 @@ +# Guide de Développement : Créer son propre outil Firewall + +Ce guide explique comment étendre l'outil d'analyse réseau en ajoutant un nouveau outil pour les firewalls. + +> **Note :** Tout au long de ce guide, remplacez **`OUTIL`** (en majuscule ou minuscule) par le nom de l'outil que vous souhaitez développer'. + +--- + +## Étape 1 : Création du script de l'outil + +Dans le dossier `Parseurs_config_Firewall/src/scripts/` (ou `script/` selon votre arborescence), créez un nouveau fichier Python nommé **`export_OUTIL.py`**. + +*Il est fortement recommandé de s'inspirer des parseurs existants comme `export_matrice_routage.py`, `export_matrice_flux.py` ou `export_interfaces.py`.* + +Voici le squelette de code à utiliser : + +```python +def export_to_excel(json_file_path, output_file_excel): + """ + Export firewall data from JSON to Excel + Args: + json_file_path: Path to the JSON file to process + output_file_excel: Path to the output Excel file + """ + + # Votre code +``` + +## Étape 2 : Intégration dans le moteur principal (`main.py`) + +Pour que le script global prenne en charge le nouveau modèle, ouvrez le fichier `Parseurs_config_Firewall/src/main.py` : + +1. Importez votre nouvelle fonction en haut du fichier : +```python +from scripts.export_OUTIL import export_to_excel as export_OUTIL_to_excel +``` + +2. Ajoutez la condition correspondante dans le bloc de sélection de l'outil : +```python +if "-X" in sys.argv: + print(f"\nGénération de l'export les OUTIL...") + if "-o" in sys.argv: + o_index = sys.argv.index("-o") + if o_index + 1 < len(sys.argv): + output_file_excel = os.path.join(f"{output_path}OUTIL_{firewall_type}_{sys.argv[o_index + 1]}.xlsx") + else: + print("Erreur: nom de fichier de sortie manquant après '-o'.") + sys.exit(1) + else: + timestamp = time.strftime("%Y%m%d") + output_file_excel = f"{output_path}OUTIL_{firewall_type}_{timestamp}.xlsx" + output_file_excel = verify_if_file_exists(output_file_excel) + excel_file = export_OUTIL_to_excel(output_file_json, output_file_excel) + print(f"✓ Processus terminé. Fichiers générés:\n - JSON: {output_file_json}\n - Excel: {excel_file}") +``` +> **Note :** Remplacez **`X`** de la première ligne par la lettre souhaitez (en dehors de celles déjà existante [o,f,r,i])'. + +3. Mettez à jour les instructions textuelles d'usage affichées dans la console si le paramètre saisi est incorrect afin d'inclure `"OUTIL"` dans la liste des choix valides. +```python +print(" -X Generate OUTIL report (optional)") +``` +> **Note :** Remplacez **`X`** par la lettre précédemmment choisi'. + + +## Étape 3 : Adaptation de l'Interface Graphique (GUI) + +Afin que le nouveau modèle apparaisse dans l'interface graphique unifiée : + +1. Ouvrez le fichier de gestion de la GUI (`gui_firewall.py` racine ou script GUI dédié). +2. Dans la fonction `open_firewall_gui_multi` : + +- Ajouter la variable : +```python +OUTIL = tk.BooleanVar() +``` + +- Dans la fonction `process()`, ajouter : +```python +if OUTIL.get(): + cmd.append("-X") +``` + +- Ajouter égalemment dans la fonction `open_firewall_gui_multi`, le bouton de sélection de l'outil avec les autres existant : +```python +ttk.Checkbutton( + content, + text="Description courte de l'outil", + variable=OUTIL + ).pack(anchor="w", padx=10, pady=(0, 0)) + OUTIL.set(True) +``` + +3. Dans la fonction `open_firewall_gui` : + +- Répéter les mêmes opérations que pour `open_firewall_gui_multi` +- Dans la fonction `update_output_label(*args)` ajouter et modifier les éléments suivants : +```python +def update_output_label(*args): + ... + if output_var.get(): #ligne déjà existante + f_OUTIL = os.path.join(OUTPUT_DIR, f"OUTIL_{fw}_{output_var.get()}.xlsx") + ... + else : + f_OUTIL = os.path.join(OUTPUT_DIR, f"OUTIL_{fw}_{dt}.xlsx") + ... + + output_label_var.set("Fichier de sortie :\n" + f_json + ("\n" + f_flux if matrice_flux.get() else "") + ("\n" + f_routage if matrice_routage.get() else "") + ("\n" + f_interfaces if interfaces.get() else "") + ("\n" + f_OUTIL if OUTIL.get() else "")) #ligne déjà existante a modifier +``` +- Ajouter vers la fin de la fonction `open_firewall_gui` avec les autres existant : +```python +OUTIL.trace_add("write", update_output_label) +``` + +4. Ouvrez le fichier de gestion principale de la GUI (`main.py` racine ou script GUI dédié). + +- Dans la fonction `open_main_gui()` ajouter dans le Label du Firewall : +```python +"\n + possibilité de générer un rapport des OUTIL", +``` \ No newline at end of file diff --git a/CREATE_SWITCH_PARSING.md b/CREATE_SWITCH_PARSING.md index 910a23d..decd06e 100644 --- a/CREATE_SWITCH_PARSING.md +++ b/CREATE_SWITCH_PARSING.md @@ -404,7 +404,7 @@ Pour que le script global prenne en charge le nouveau modèle, ouvrez le fichier import scripts.json_modele as json_modele ``` -2. Ajoutez la condition correspondante dans le bloc de sélection du type de firewall : +2. Ajoutez la condition correspondante dans le bloc de sélection du type de switch : ```python elif switch_type == "modele": json_modele.generate_json_modele(input_data, output_file1_json, output_file2_json) diff --git a/CREATE_SWITCH_TOOL.md b/CREATE_SWITCH_TOOL.md new file mode 100644 index 0000000..357ebee --- /dev/null +++ b/CREATE_SWITCH_TOOL.md @@ -0,0 +1,119 @@ +# Guide de Développement : Créer son propre outil Switch + +Ce guide explique comment étendre l'outil d'analyse réseau en ajoutant un nouveau outil pour les switches. + +> **Note :** Tout au long de ce guide, remplacez **`OUTIL`** (en majuscule ou minuscule) par le nom de l'outil que vous souhaitez développer'. + +--- + +## Étape 1 : Création du script de l'outil + +Dans le dossier `Parseurs_logs_Switch/src/scripts/` (ou `script/` selon votre arborescence), créez un nouveau fichier Python nommé **`export_OUTIL.py`**. + +*Il est fortement recommandé de s'inspirer des parseurs existants comme `export_rapport_stack.py`, `export_rapport_interfaces.py`, `export_rapport_uplink.py` ou `export_schema_infra.py`.* + +Voici le squelette de code à utiliser : + +```python +def main(output_dir, output_file) + """ + Export switch data from JSON to Excel + Args: + json_file_path: Path to the JSON file to process + output_file: Path to the output Excel file + """ + + # Votre code +``` + +## Étape 2 : Intégration dans le moteur principal (`main.py`) + +Pour que le script global prenne en charge le nouveau modèle, ouvrez le fichier `Parseurs_logs_Switch/src/main.py` : + +1. Importez votre nouvelle fonction en haut du fichier : +```python +from scripts.export_OUTIL import main as rapport_OUTIL +``` + +2. Ajoutez la condition correspondante dans le bloc de sélection de l'outil : +```python +if "-X" in sys.argv: + print(f"\nGénération de OUTIL...") + if "-o" in sys.argv: + o_index = sys.argv.index("-o") + if o_index + 1 < len(sys.argv): + output_file_html = os.path.join(f"{output_path}OUTIL_{switch_type}_{sys.argv[o_index + 1]}.html") + else: + print("Erreur: nom de fichier de sortie manquant après '-o'.") + sys.exit(1) + else: + timestamp = time.strftime("%Y%m%d") + output_file_html = f"{output_path}OUTIL_{switch_type}_{timestamp}.html" + output_file_html = verify_if_file_exists(output_file_html) + html_file = OUTIL(output_path, output_file_html) + print(f"✓ Processus terminé. Fichiers générés:\n - JSON: {output_file1_json} & {output_file2_json}\n - HTML: {html_file}") +``` +> **Note :** Remplacez **`X`** de la première ligne par la lettre souhaitez (en dehors de celles déjà existante [o,d,u,s,i])'. + +3. Mettez à jour les instructions textuelles d'usage affichées dans la console si le paramètre saisi est incorrect afin d'inclure `"OUTIL"` dans la liste des choix valides. +```python +print(" -X Generate OUTIL report (optional)") +``` +> **Note :** Remplacez **`X`** par la lettre précédemmment choisi'. + + +## Étape 3 : Adaptation de l'Interface Graphique (GUI) + +Afin que le nouveau modèle apparaisse dans l'interface graphique unifiée : + +1. Ouvrez le fichier de gestion de la GUI (`gui_switch.py` racine ou script GUI dédié). +2. Dans la fonction `open_switch_gui_multi` : + +- Ajouter la variable : +```python +OUTIL = tk.BooleanVar() +``` + +- Dans la fonction `process()`, ajouter : +```python +if OUTIL.get(): + cmd.append("-X") +``` + +- Ajouter égalemment dans la fonction `open_switch_gui_multi`, le bouton de sélection de l'outil avec les autres existant : +```python +ttk.Checkbutton( + content, + text="Description courte de l'outil", + variable=OUTIL + ).pack(anchor="w", padx=10, pady=(0, 0)) + OUTIL.set(True) +``` + +3. Dans la fonction `open_switch_gui` : + +- Répéter les mêmes opérations que pour `open_switch_gui_multi` +- Dans la fonction `update_output_label(*args)` ajouter et modifier les éléments suivants : +```python +def update_output_label(*args): + ... + if output_var.get(): #ligne déjà existante + f_OUTIL = os.path.join(OUTPUT_DIR, f"OUTIL_{fw}_{output_var.get()}.xlsx") + ... + else : + f_OUTIL = os.path.join(OUTPUT_DIR, f"OUTIL_{fw}_{dt}.xlsx") + ... + + output_label_var.set("Fichier de sortie :\n" + s_json1 + "\n" + s_json2 + ("\n" + s_uplink if rapport_uplink.get() else "") + ("\n" + s_stack if rapport_stack.get() else "") + ("\n" + s_interfaces if rapport_interfaces.get() else "") + ("\n" + s_schema if schema_infra.get() else "") + ("\n" + f_OUTIL if OUTIL.get() else "")) #ligne déjà existante a modifier +``` +- Ajouter vers la fin de la fonction `open_switch_gui` avec les autres existant : +```python +OUTIL.trace_add("write", update_output_label) +``` + +4. Ouvrez le fichier de gestion principale de la GUI (`main.py` racine ou script GUI dédié). + +- Dans la fonction `open_main_gui()` ajouter dans le Label du Switch : +```python +"\n + possibilité de générer un rapport des OUTIL", +``` \ No newline at end of file diff --git a/Computacenter_logo.png b/Computacenter_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5e5091cdd252a8849015c534357e6178fed65144 GIT binary patch literal 88314 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 Il7?^oAMbL$mH+?% literal 0 HcmV?d00001 diff --git a/Parseurs_config_Firewall/README.md b/Parseurs_config_Firewall/README.md index 6f230c1..f7709c5 100644 --- a/Parseurs_config_Firewall/README.md +++ b/Parseurs_config_Firewall/README.md @@ -6,6 +6,7 @@ Cet outil permet de **parser les configurations de différents types de firewall - Il fournit également la possibilité de générer une **matrice de flux au format Excel** pour visualiser les communications et règles de trafic dans l’infrastructure. - Il founit également la possibilité de générer une **matrice de routage au format Excel** pour visualiser les routes statiques dans l’infrastructure. +- Il founit également la possibilité de générer le **détail des interfaces au format Excel** pour visualiser la configuration des interfaces. ## Fonctionnalités principales @@ -46,6 +47,10 @@ Cet outil permet de **parser les configurations de différents types de firewall - Script Python qui utilise le JSON normalisé pour générer automatiquement une matrice Excel détaillant : - les routes statiques +7. **Génération des interfaces** + - Script Python qui utilise le JSON normalisé pour générer automatiquement une matrice Excel détaillant : + - la configuration des interfaces + ## Utilisation ### Pré-requis @@ -59,9 +64,9 @@ pip install -r .\src\requirements.txt #### Commandes principales ```bash -python3 .\src\main.py stormshield .\src\input\backup\ -f -r -python3 .\src\main.py paloalto .\src\input\nomfichier -f -r -python3 .\src\main.py forcepoint .\src\input\nomfichier -f -r +python3 .\src\main.py stormshield .\src\input\backup\ -f -r -i +python3 .\src\main.py paloalto .\src\input\nomfichier -f -r -i +python3 .\src\main.py forcepoint .\src\input\nomfichier -f -r -i ``` #### Options @@ -70,6 +75,8 @@ python3 .\src\main.py forcepoint .\src\input\nomfichier -f -r | -o [nom_fichier] | Spécifie le nom du fichier JSON de sortie (optionnel) | -f | Génère un rapport Excel de type matrice de flux (optionnel) | -r | Génère un rapport Excel de type matrice de routage (optionnel) +| -i | Generate interface report (optional) + --- ## Arborescence du projet diff --git a/Parseurs_config_Firewall/src/main.py b/Parseurs_config_Firewall/src/main.py index cc80ab4..f8a0323 100644 --- a/Parseurs_config_Firewall/src/main.py +++ b/Parseurs_config_Firewall/src/main.py @@ -6,6 +6,7 @@ from scripts.json_Stormshield import generate_json_stormshield from scripts.json_Forcepoint import generate_json_forcepoint from scripts.export_matrice_flux import export_to_excel as export_flux_to_excel from scripts.export_matrice_routage import export_to_excel as export_routing_to_excel +from scripts.export_interfaces import export_to_excel as export_interfaces_to_excel def verify_if_file_exists(name): base, ext = os.path.splitext(name) @@ -24,6 +25,7 @@ def main(): print(" -o Specify output JSON file name (optional)") print(" -f Generate matrix flux report (optional)") print(" -r Generate routing matrix report (optional)") + print(" -i Generate interface report (optional)") sys.exit(1) firewall_type = sys.argv[1].lower() @@ -85,5 +87,21 @@ def main(): excel_file = export_routing_to_excel(output_file_json, output_file_excel) print(f"✓ Processus terminé. Fichiers générés:\n - JSON: {output_file_json}\n - Excel: {excel_file}") + if "-i" in sys.argv: + print(f"\nGénération de l'export les interfaces...") + if "-o" in sys.argv: + o_index = sys.argv.index("-o") + if o_index + 1 < len(sys.argv): + output_file_excel = os.path.join(f"{output_path}interfaces_{firewall_type}_{sys.argv[o_index + 1]}.xlsx") + else: + print("Erreur: nom de fichier de sortie manquant après '-o'.") + sys.exit(1) + else: + timestamp = time.strftime("%Y%m%d") + output_file_excel = f"{output_path}interfaces_{firewall_type}_{timestamp}.xlsx" + output_file_excel = verify_if_file_exists(output_file_excel) + excel_file = export_interfaces_to_excel(output_file_json, output_file_excel) + print(f"✓ Processus terminé. Fichiers générés:\n - JSON: {output_file_json}\n - Excel: {excel_file}") + if __name__ == "__main__": main() \ No newline at end of file diff --git a/Parseurs_config_Firewall/src/scripts/export_interfaces.py b/Parseurs_config_Firewall/src/scripts/export_interfaces.py new file mode 100644 index 0000000..f67a226 --- /dev/null +++ b/Parseurs_config_Firewall/src/scripts/export_interfaces.py @@ -0,0 +1,201 @@ +import json +import pandas as pd +from openpyxl import load_workbook +from openpyxl.styles import PatternFill, Border, Side, Alignment, Font +from openpyxl.utils import get_column_letter + + +center = Alignment(horizontal="center", vertical="center", wrap_text=True) +left = Alignment(horizontal="left", vertical="center", wrap_text=True) + +header_fill = PatternFill(start_color="2F5496", fill_type="solid") +iface_fill = PatternFill(start_color="BDD7EE", fill_type="solid") +sub_fill = PatternFill(start_color="DDEBF7", fill_type="solid") +disabled_fill= PatternFill(start_color="F2F2F2", fill_type="solid") + +thick_border = Border(*(Side(style="thick"),)*4) +thin_border = Border(*(Side(style="thin"),)*4) +medium_side = Side(style="medium") + + +def _thin(top=False, bottom=False, left_s=False, right_s=False): + return Border( + top=Side(style="thin") if top else Side(style=None), + bottom=Side(style="thin") if bottom else Side(style=None), + left=Side(style="thin") if left_s else Side(style=None), + right=Side(style="thin") if right_s else Side(style=None), + ) + + +def _build_vrf_map(data): + """Retourne {interface_name: vrf_name} depuis network-instances.""" + vrf_map = {} + for ni in data.get("openconfig-network-instance:network-instances", {}).get("network-instance", []): + vrf_name = ni.get("name", "") + for iface in ni.get("interfaces", {}).get("interface", []): + iface_id = iface.get("id", "") + if iface_id: + vrf_map[iface_id] = vrf_name + return vrf_map + + +def _parse_interfaces(data): + vrf_map = _build_vrf_map(data) + rows = [] + + for iface in data.get("openconfig-interfaces:interfaces", {}).get("interface", []): + cfg = iface.get("config", {}) + iface_name = cfg.get("name", iface.get("name", "")) + iface_type = cfg.get("type", "") + iface_desc = cfg.get("description", "") + enabled = cfg.get("enabled", True) + + subinterfaces = iface.get("subinterfaces", {}).get("subinterface", []) + + if not subinterfaces: + rows.append({ + "Interface": iface_name, + "Type": iface_type, + "Description": iface_desc, + "Enabled": "Oui" if enabled else "Non", + "VLAN": "", + "IP": "", + "Préfixe": "", + "VRF": vrf_map.get(iface_name, ""), + "_is_sub": False, + }) + else: + for sub in subinterfaces: + sub_cfg = sub.get("config", {}) + vlan = sub.get("index", sub_cfg.get("index", "")) + sub_name = f"{iface_name}.{vlan}" if vlan != 0 else iface_name + sub_enabled = sub_cfg.get("enabled", enabled) + + addresses = ( + sub.get("oc-ip:ipv4", {}) + .get("oc-ip:addresses", {}) + .get("oc-ip:address", []) + ) + + if not addresses: + rows.append({ + "Interface": iface_name, + "Type": iface_type, + "Description": iface_desc, + "Enabled": "Oui" if sub_enabled else "Non", + "VLAN": "" if vlan == 0 else vlan, + "IP": "", + "Préfixe": "", + "VRF": vrf_map.get(sub_name, vrf_map.get(iface_name, "")), + "_is_sub": vlan != 0, + }) + else: + for addr in addresses: + addr_cfg = addr.get("oc-ip:config", {}) + rows.append({ + "Interface": iface_name, + "Type": iface_type, + "Description": iface_desc, + "Enabled": "Oui" if sub_enabled else "Non", + "VLAN": "" if vlan == 0 else vlan, + "IP": addr_cfg.get("oc-ip:ip", ""), + "Préfixe": addr_cfg.get("oc-ip:prefix-length", ""), + "VRF": vrf_map.get(sub_name, vrf_map.get(iface_name, "")), + "_is_sub": vlan != 0, + }) + + return rows + + + +def _style_interfaces(ws): + COLS = { + "Interface": 1, + "Type": 2, + "Description": 3, + "Enabled": 4, + "VLAN": 5, + "IP": 6, + "Préfixe": 7, + "VRF": 8, + } + N = len(COLS) + + for col in range(1, N + 1): + cell = ws.cell(row=1, column=col) + cell.fill = header_fill + cell.font = Font(bold=True, color="FFFFFF", size=10) + cell.alignment = center + cell.border = thin_border + + ws.freeze_panes = "A2" + + max_width = {c: len(str(ws.cell(row=1, column=c).value or "")) for c in range(1, N + 1)} + + prev_iface = None + group_start = 2 + + for row in range(2, ws.max_row + 1): + iface_val = ws.cell(row=row, column=COLS["Interface"]).value + enabled = ws.cell(row=row, column=COLS["Enabled"]).value + is_last = (row == ws.max_row) + next_iface = ws.cell(row=row + 1, column=COLS["Interface"]).value if not is_last else None + + if enabled == "Non": + row_fill = disabled_fill + elif ws.cell(row=row, column=COLS["VLAN"]).value: + row_fill = sub_fill + else: + row_fill = iface_fill + + for col in range(1, N + 1): + cell = ws.cell(row=row, column=col) + if col != COLS["Interface"]: + cell.fill = row_fill + cell.alignment = left if col not in (COLS["Enabled"], COLS["VLAN"], COLS["Préfixe"]) else center + cell.border = thin_border + + val = cell.value + max_width[col] = max(max_width[col], len(str(val or ""))) + + if iface_val != next_iface or is_last: + if row > group_start: + ws.merge_cells( + start_row=group_start, start_column=COLS["Interface"], + end_row=row, end_column=COLS["Interface"] + ) + for r in range(group_start, row + 1): + c = ws.cell(r, COLS["Interface"]) + c.fill = iface_fill + c.font = Font(bold=True, size=10) + c.alignment = center + c.border = thick_border + group_start = row + 1 + + ws.row_dimensions[row].height = 18 + + for col, width in max_width.items(): + ws.column_dimensions[get_column_letter(col)].width = min(width + 4, 45) + + +def export_to_excel(json_file_path: str, output_file_excel: str) -> str: + with open(json_file_path, "r", encoding="utf-8") as f: + data = json.load(f) + + rows = _parse_interfaces(data) + + export_rows = [{k: v for k, v in r.items() if k != "_is_sub"} for r in rows] + + df = pd.DataFrame(export_rows, columns=[ + "Interface", "Type", "Description", "Enabled", "VLAN", "IP", "Préfixe", "VRF" + ]) + + with pd.ExcelWriter(output_file_excel, engine="openpyxl") as writer: + df.to_excel(writer, sheet_name="Interfaces", index=False) + + wb = load_workbook(output_file_excel) + _style_interfaces(wb["Interfaces"]) + wb.save(output_file_excel) + + print(f"✓ Export interfaces OK : {output_file_excel}") + return output_file_excel \ No newline at end of file diff --git a/Parseurs_config_Firewall/src/scripts/export_matrice_flux.py b/Parseurs_config_Firewall/src/scripts/export_matrice_flux.py index 5b1ed23..8097aa5 100644 --- a/Parseurs_config_Firewall/src/scripts/export_matrice_flux.py +++ b/Parseurs_config_Firewall/src/scripts/export_matrice_flux.py @@ -8,6 +8,8 @@ from openpyxl import load_workbook from scripts.style_excel.style_address_groups import style_address_groups from scripts.style_excel.style_service_groups import style_service_groups from scripts.style_excel.style_matrice_flux import style_matrice_flux +from scripts.style_excel.style_addresses import style_addresses +from scripts.style_excel.style_services import style_services def export_to_excel(json_file_path, output_file_excel): """ @@ -192,20 +194,46 @@ def export_to_excel(json_file_path, output_file_excel): for m in cfg.get("members", []) ]) + df_addresses = pd.DataFrame([ + { + "Name": name, + "IP / Masque": cfg.get("ip_netmask", "") if "/" in cfg.get("ip_netmask", "") + else (cfg.get("ip_netmask", "") + "/32" if cfg.get("ip_netmask") else ""), + "Description": cfg.get("description", ""), + "Tags": cfg.get("misc", ""), + } + for name, cfg in sorted(addresses.items()) + ]) + + df_services = pd.DataFrame([ + { + "Name": name, + "Protocol": cfg.get("protocol", ""), + "Port": cfg.get("port", ""), + "Description": cfg.get("description", ""), + } + for name, cfg in sorted(services.items()) + ]) + # === Export Excel === firewall_type = data.get("firewall-device", {}).get("type", "firewall").replace(" ","_").lower() date = time.strftime("%Y%m%d") with pd.ExcelWriter(output_file_excel, engine="openpyxl") as writer: - df_addr.to_excel(writer,sheet_name="Address-Groups",index=False) - df_srv.to_excel(writer,sheet_name="Service-Groups",index=False) - df.to_excel(writer,sheet_name="Matrice Flux",index=False,startrow=1) + df_addresses.to_excel(writer, sheet_name="Addresses", index=False) + df_addr.to_excel(writer, sheet_name="Address-Groups", index=False) + df_services.to_excel(writer, sheet_name="Services", index=False) + df_srv.to_excel(writer, sheet_name="Service-Groups", index=False) + df.to_excel(writer, sheet_name="Matrice Flux", index=False, startrow=1) + wb = load_workbook(output_file_excel) if "Sheet1" in wb.sheetnames: del wb["Sheet1"] + style_addresses(wb["Addresses"]) style_address_groups(wb["Address-Groups"], address_groups) + style_services(wb["Services"]) style_service_groups(wb["Service-Groups"], service_groups) style_matrice_flux(wb["Matrice Flux"]) diff --git a/Parseurs_config_Firewall/src/scripts/export_modele.py b/Parseurs_config_Firewall/src/scripts/export_modele.py index d4d01d7..c4dcf77 100644 --- a/Parseurs_config_Firewall/src/scripts/export_modele.py +++ b/Parseurs_config_Firewall/src/scripts/export_modele.py @@ -38,37 +38,46 @@ class ParserMixin: "name": interface.name, "config": { "name": interface.name, - "type": "iana-if-type:ethernetCsmacd" if interface.interface_type == "ethernet" else "iana-if-type:ieee8023adLag", + "type": interface.interface_type if interface.interface_type else None, "enabled": interface.enabled, "description": interface.comment or "" } } if interface.ip: - intf_config["subinterfaces"] = { - "subinterface": [ - { + subinterfaces = [ + { + "index": interface.misc or 0, + "config": { "index": interface.misc or 0, - "config": { - "index": interface.misc or 0, - "enabled": interface.enabled - }, - "oc-ip:ipv4": { - "oc-ip:addresses": { - "oc-ip:address": [ - { + "enabled": interface.enabled + }, + "oc-ip:ipv4": { + "oc-ip:addresses": { + "oc-ip:address": [ + { + "oc-ip:ip": interface.ip, + "oc-ip:config": { "oc-ip:ip": interface.ip, - "oc-ip:config": { - "oc-ip:ip": interface.ip, - "oc-ip:prefix-length": interface.netmask if interface.netmask else 24 - } + "oc-ip:prefix-length": interface.netmask if interface.netmask else 24 } - ] - } + } + ] } } - ] - } + } + ] + + if interface.interface_type == "vlan": + subinterfaces.append({ + "index": interface.vlan or None, + "config": { + "index": interface.vlan or None, + } + }) + + intf_config["subinterfaces"] = {"subinterface": subinterfaces} + openconfig["openconfig-interfaces:interfaces"]["interface"].append(intf_config) diff --git a/Parseurs_config_Firewall/src/scripts/json_Stormshield.py b/Parseurs_config_Firewall/src/scripts/json_Stormshield.py index ba43f08..b15bc8b 100644 --- a/Parseurs_config_Firewall/src/scripts/json_Stormshield.py +++ b/Parseurs_config_Firewall/src/scripts/json_Stormshield.py @@ -46,13 +46,17 @@ class StormshieldParser(ParserMixin): current_section = line.strip("[]") continue + inline_comment = "" + if "#" in line: + inline_comment = line.split("#", 1)[1].strip() + if current_section in ("Host", "Network", "Fqdn"): match = re.match(r"([^=]+)=([^,#]+)", line) if match: name = match.group(1).strip() ip = match.group(2).strip() self.config["address_objects"].append( - AddressObject(name=name, ip_netmask=ip) + AddressObject(name=name, ip_netmask=ip, description=inline_comment) ) elif current_section == "Service": @@ -62,7 +66,7 @@ class StormshieldParser(ParserMixin): port = match.group(2) proto = match.group(3).lower() self.config["service_objects"].append( - ServiceObject(name=name, protocol=proto, port=port) + ServiceObject(name=name, protocol=proto, port=port, description=inline_comment) ) def _parse_address_groups(self): @@ -156,18 +160,7 @@ class StormshieldParser(ParserMixin): def _add_interface(self, section_name, data): """Crée un objet Interface à partir d’une section complète""" - if section_name.lower().startswith("vlan"): - iface_type = "vlan" - elif section_name.lower().startswith("bridge"): - iface_type = "bridge" - elif section_name.lower().startswith("wifi"): - iface_type = "wifi" - elif section_name.lower().startswith("loopback"): - iface_type = "loopback" - elif section_name.lower().startswith("agg"): - iface_type = "aggregate" - else: - iface_type = "ethernet" + iface_type = section_name.rstrip("0123456789") enabled = data.get("State", "0") == "1" name = data.get("Name", section_name) @@ -187,6 +180,7 @@ class StormshieldParser(ParserMixin): comment=comment, interface_type=iface_type, enabled=enabled, + vlan=data.get("Tag") if data.get("Tag") and iface_type == "vlan" else None ) self.config["interfaces"].append(interface) diff --git a/Parseurs_config_Firewall/src/scripts/objets/data.py b/Parseurs_config_Firewall/src/scripts/objets/data.py index 7035710..6b31ec5 100644 --- a/Parseurs_config_Firewall/src/scripts/objets/data.py +++ b/Parseurs_config_Firewall/src/scripts/objets/data.py @@ -61,6 +61,7 @@ class Interface: comment: str = None interface_type: str = None enabled: bool = True + vlan: str = None @dataclass class SecurityRule: diff --git a/Parseurs_config_Firewall/src/scripts/style_excel/style_addresses.py b/Parseurs_config_Firewall/src/scripts/style_excel/style_addresses.py new file mode 100644 index 0000000..73f0be7 --- /dev/null +++ b/Parseurs_config_Firewall/src/scripts/style_excel/style_addresses.py @@ -0,0 +1,44 @@ +from openpyxl.styles import PatternFill, Border, Side, Alignment, Font +from openpyxl.utils import get_column_letter + +center = Alignment(horizontal="center", vertical="center") +left = Alignment(horizontal="left", vertical="center") + +red_fill = PatternFill(start_color="FF9999", fill_type="solid") +yellow_fill = PatternFill(start_color="FFFF99", fill_type="solid") + +thick_border = Border(*(Side(style="thick"),)*4) +thin_border = Border(*(Side(style="thin"),)*4) + +def style_addresses(ws): + + col_name, col_ip, col_desc, col_tags = 1, 2, 3, 4 + + max_width = {1: 0, 2: 0, 3: 0, 4: 0} + + for row in range(2, ws.max_row + 1): + name = ws.cell(row=row, column=col_name).value + ip = ws.cell(row=row, column=col_ip).value + desc = ws.cell(row=row, column=col_desc).value + tags = ws.cell(row=row, column=col_tags).value + + ws.cell(row=row, column=col_name).fill = red_fill + ws.cell(row=row, column=col_name).font = Font(bold=True) + ws.cell(row=row, column=col_name).alignment = left + ws.cell(row=row, column=col_name).border = thin_border + + for col in (col_ip, col_desc, col_tags): + ws.cell(row=row, column=col).alignment = left + ws.cell(row=row, column=col).border = thick_border + ws.cell(row=row, column=col).fill = yellow_fill + + max_width[1] = max(max_width[1], len(str(name or ""))) + max_width[2] = max(max_width[2], len(str(ip or ""))) + max_width[3] = max(max_width[3], len(str(desc or ""))) + max_width[4] = max(max_width[4], len(str(tags or ""))) + + for col in max_width: + ws.column_dimensions[get_column_letter(col)].width = max_width[col] + 2 + + for row in range(2, ws.max_row + 1): + ws.row_dimensions[row].height = 18 \ No newline at end of file diff --git a/Parseurs_config_Firewall/src/scripts/style_excel/style_services.py b/Parseurs_config_Firewall/src/scripts/style_excel/style_services.py new file mode 100644 index 0000000..182cc0d --- /dev/null +++ b/Parseurs_config_Firewall/src/scripts/style_excel/style_services.py @@ -0,0 +1,51 @@ +from openpyxl.styles import PatternFill, Border, Side, Alignment, Font +from openpyxl.utils import get_column_letter + +center = Alignment(horizontal="center", vertical="center") +left = Alignment(horizontal="left", vertical="center") + +red_fill = PatternFill(start_color="FF9999", fill_type="solid") +yellow_fill = PatternFill(start_color="FFFF99", fill_type="solid") + +thick_border = Border(*(Side(style="thick"),)*4) +thin_border = Border(*(Side(style="thin"),)*4) + +def style_services(ws): + + col_name, col_proto, col_port, col_desc = 1, 2, 3, 4 + + max_width = {1: 0, 2: 0, 3: 0, 4: 0} + + for row in range(2, ws.max_row + 1): + name = ws.cell(row=row, column=col_name).value + proto = ws.cell(row=row, column=col_proto).value + port = ws.cell(row=row, column=col_port).value + desc = ws.cell(row=row, column=col_desc).value + + ws.cell(row=row, column=col_name).fill = red_fill + ws.cell(row=row, column=col_name).font = Font(bold=True) + ws.cell(row=row, column=col_name).alignment = left + ws.cell(row=row, column=col_name).border = thin_border + + ws.cell(row=row, column=col_proto).alignment = center + ws.cell(row=row, column=col_proto).border = thick_border + ws.cell(row=row, column=col_proto).fill = yellow_fill + + ws.cell(row=row, column=col_port).alignment = center + ws.cell(row=row, column=col_port).border = thick_border + ws.cell(row=row, column=col_port).fill = yellow_fill + + ws.cell(row=row, column=col_desc).alignment = left + ws.cell(row=row, column=col_desc).border = thick_border + ws.cell(row=row, column=col_desc).fill = yellow_fill + + max_width[1] = max(max_width[1], len(str(name or ""))) + max_width[2] = max(max_width[2], len(str(proto or ""))) + max_width[3] = max(max_width[3], len(str(port or ""))) + max_width[4] = max(max_width[4], len(str(desc or ""))) + + for col in max_width: + ws.column_dimensions[get_column_letter(col)].width = max_width[col] + 2 + + for row in range(2, ws.max_row + 1): + ws.row_dimensions[row].height = 18 \ No newline at end of file diff --git a/README.md b/README.md index b2e8cf1..1f6ad73 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Données extraites : Fichiers et dossiers de configurations d'origine. Rapports générés : - Matrice de flux (Excel) pour visualiser les communications et les règles de trafic. - Matrice de routage (Excel) pour cartographier les routes statiques. +- Détail des interfaces (Excel) pour visualiser la configuration des interfaces. ``` - Module Switch diff --git a/gui_firewall.py b/gui_firewall.py index 3cb00fc..d55d076 100644 --- a/gui_firewall.py +++ b/gui_firewall.py @@ -75,11 +75,12 @@ class ToolTip: def open_firewall_gui_multi(app, OUTPUT_DIR, HELP_FILE_FW, FIREWALL_DIR, FIREWALL_MAIN): app2 = tk.Toplevel(app) app2.title("Analyse Configuration Firewall - Plusieurs firewall") - app2.geometry("800x450") + app2.geometry("800x500") app2.resizable(True, True) matrice_flux = tk.BooleanVar() matrice_routage = tk.BooleanVar() + interfaces = tk.BooleanVar() content = make_scrollable(app2) @@ -158,6 +159,8 @@ def open_firewall_gui_multi(app, OUTPUT_DIR, HELP_FILE_FW, FIREWALL_DIR, FIREWAL cmd.append("-f") if matrice_routage.get(): cmd.append("-r") + if interfaces.get(): + cmd.append("-i") cmd.append("-o") if item["type"] == "file": @@ -376,9 +379,16 @@ def open_firewall_gui_multi(app, OUTPUT_DIR, HELP_FILE_FW, FIREWALL_DIR, FIREWAL content, text="Générer la matrice de routage en Excel (route statique uniquement)", variable=matrice_routage - ).pack(anchor="w", padx=10, pady=(0, 10)) + ).pack(anchor="w", padx=10, pady=(0, 0)) matrice_routage.set(True) + ttk.Checkbutton( + content, + text="Générer la configuration des interfaces en Excel", + variable=interfaces + ).pack(anchor="w", padx=10, pady=(0, 10)) + interfaces.set(True) + ttk.Label( content, text="Dossier de sortie :", @@ -418,7 +428,7 @@ def open_firewall_gui(root, BASE_DIR): app = tk.Toplevel(root) app.title("Analyse Configuration Firewall") - app.geometry("800x450") + app.geometry("800x500") app.resizable(True, True) firewall_var = tk.StringVar() @@ -427,6 +437,7 @@ def open_firewall_gui(root, BASE_DIR): matrice_flux = tk.BooleanVar() matrice_routage = tk.BooleanVar() + interfaces = tk.BooleanVar() def browse_input(): fw_type = firewall_var.get() @@ -453,22 +464,15 @@ def open_firewall_gui(root, BASE_DIR): f_json = os.path.join(OUTPUT_DIR, f"{fw}_{output_var.get()}.json") f_flux = os.path.join(OUTPUT_DIR, f"matrice_flux_{fw}_{output_var.get()}.xlsx") f_routage = os.path.join(OUTPUT_DIR, f"matrice_routage_{fw}_{output_var.get()}.xlsx") + f_interfaces = os.path.join(OUTPUT_DIR, f"interfaces_{fw}_{output_var.get()}.xlsx") else: dt = datetime.now().strftime("%Y%m%d") f_json = os.path.join(OUTPUT_DIR, f"{fw}_{dt}.json") f_flux = os.path.join(OUTPUT_DIR, f"matrice_flux_{fw}_{dt}.xlsx") f_routage = os.path.join(OUTPUT_DIR, f"matrice_routage_{fw}_{dt}.xlsx") + f_interfaces = os.path.join(OUTPUT_DIR, f"interfaces_{fw}_{dt}.xlsx") - if not matrice_flux.get(): - if not matrice_routage.get(): - output_label_var.set("Fichier de sortie :\n" + f_json) - else: - output_label_var.set("Fichiers de sortie :\n" + f_json + "\n" + f_routage) - else: - if not matrice_routage.get(): - output_label_var.set("Fichiers de sortie :\n" + f_json + "\n" + f_flux) - else: - output_label_var.set("Fichiers de sortie :\n" + f_json + "\n" + f_flux + "\n" + f_routage) + output_label_var.set("Fichier de sortie :\n" + f_json + ("\n" + f_flux if matrice_flux.get() else "") + ("\n" + f_routage if matrice_routage.get() else "") + ("\n" + f_interfaces if interfaces.get() else "")) app.update_idletasks() def open_output_folder(): @@ -518,9 +522,10 @@ def open_firewall_gui(root, BASE_DIR): if matrice_flux.get(): cmd.append("-f") - if matrice_routage.get(): cmd.append("-r") + if interfaces.get(): + cmd.append("-i") print("Commande exécutée :", " ".join(cmd)) print("Dossier courant (cwd) :", FIREWALL_DIR) @@ -636,15 +641,23 @@ def open_firewall_gui(root, BASE_DIR): app, text="Générer la matrice de routage en Excel (route statique uniquement)", variable=matrice_routage - ).pack(anchor="w", padx=10, pady=(0, 10)) + ).pack(anchor="w", padx=10, pady=(0, 0)) matrice_routage.set(True) + ttk.Checkbutton( + app, + text="Générer la configuration des interfaces en Excel", + variable=interfaces + ).pack(anchor="w", padx=10, pady=(0, 10)) + interfaces.set(True) + output_label_var = tk.StringVar() ttk.Label(app, textvariable=output_label_var).pack(anchor="w", padx=10) firewall_var.trace_add("write", update_output_label) output_var.trace_add("write", update_output_label) matrice_flux.trace_add("write", update_output_label) matrice_routage.trace_add("write", update_output_label) + interfaces.trace_add("write", update_output_label) update_output_label() ttk.Button( diff --git a/help_Firewall.md b/help_Firewall.md index d72a67d..2f9a451 100644 --- a/help_Firewall.md +++ b/help_Firewall.md @@ -6,6 +6,7 @@ Cet outil permet de **récupérer les données des configurations de différents - Il fournit également la possibilité de générer une **matrice de flux au format Excel** pour visualiser les communications et règles de trafic dans l’infrastructure. - Il founit également la possibilité de générer une **matrice de routage au format Excel** pour visualiser les routes statiques dans l’infrastructure. +- Il founit également la possibilité de générer le **détail des interfaces au format Excel** pour visualiser la configuration des interfaces. ## Utilisation diff --git a/main.py b/main.py index e5e9a83..640a5d6 100644 --- a/main.py +++ b/main.py @@ -58,9 +58,10 @@ def bootstrap_venv(): def relaunch_in_venv(): print("[INFO] Relance du script dans le venv…") + script_path = os.path.abspath(__file__) subprocess.check_call([ venv_python(), - __file__ + script_path ]) sys.exit(0) @@ -68,14 +69,30 @@ def relaunch_in_venv(): def open_main_gui(): root = tk.Tk() root.title("Analyse Réseau") - root.geometry("650x350") + root.geometry("650x500") root.resizable(True, True) + header_frame = tk.Frame(root) + header_frame.pack(fill="x", pady=10, padx=10) + + logo_path = os.path.join(BASE_DIR, "Computacenter_logo.png") + if os.path.isfile(logo_path): + try: + logo_img = tk.PhotoImage(file=logo_path) + + logo_img = logo_img.subsample(15, 15) + + logo_label = tk.Label(header_frame, image=logo_img) + logo_label.image = logo_img + logo_label.pack(side="right") + except Exception as e: + print(f"[WARN] Impossible de charger le logo : {e}") + ttk.Label( root, text="Analyse Réseau", font=("Arial", 16, "bold") - ).pack(pady=20) + ).pack(pady=(0, 20)) ttk.Label( root, @@ -94,7 +111,8 @@ def open_main_gui(): root, text="(Convertir les données au format normalisé Yang dans un fichier JSON)" \ "\n + possibilité de générer une matrice de flux en Excel" \ - "\n + possibilité de générer une matrice de routage en Excel (route statique uniquement)", + "\n + possibilité de générer une matrice de routage en Excel (route statique uniquement)" \ + "\n + possibilité de générer un rapport des interfaces en Excel", font=("Arial", 9, "italic"), anchor="center", justify="center"