ajout doc + correction

This commit is contained in:
Chevallier
2026-06-01 09:51:24 +02:00
parent 2a357d4d90
commit ee160fc06c
17 changed files with 684 additions and 60 deletions

View File

@@ -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 linfrastructure.
- Il founit également la possibilité de générer une **matrice de routage au format Excel** pour visualiser les routes statiques dans linfrastructure.
- 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

View File

@@ -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 <output_file> 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()

View File

@@ -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

View File

@@ -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"])

View File

@@ -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)

View File

@@ -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 dune 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)

View File

@@ -61,6 +61,7 @@ class Interface:
comment: str = None
interface_type: str = None
enabled: bool = True
vlan: str = None
@dataclass
class SecurityRule:

View File

@@ -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

View File

@@ -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