fw_rules_builder/fw_report.py

331 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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.

"""
fw_report.py — генератор Excel-отчёта по правилам firewall.
Читает данные из fw_settings.py и создаёт файл fw_report.xlsx со страницами:
- Правила (Rules)
- Объекты (Objects)
- Группы объектов (Groups)
- Сервисы (Services)
- Группы сервисов (Service Groups)
"""
import sys
import os
# Добавляем текущую директорию в путь, чтобы импортировать fw_settings
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from fw_settings import servers, nets, groups, services, service_groups, rules
from openpyxl import Workbook
from openpyxl.styles import (
Font, PatternFill, Alignment, Border, Side, GradientFill
)
from openpyxl.utils import get_column_letter
# ─── Цветовая палитра ────────────────────────────────────────────────────────
CLR_HEADER_BG = "1F4E79" # тёмно-синий — заголовки таблиц
CLR_HEADER_FG = "FFFFFF" # белый текст
CLR_SPAN_BG = "D6E4F0" # голубой — строки-разделители (span)
CLR_SPAN_FG = "1F4E79" # тёмно-синий текст
CLR_ALLOW_BG = "E2EFDA" # светло-зелёный — allow
CLR_DENY_BG = "FCE4D6" # светло-красный — deny
CLR_ALT_BG = "F2F2F2" # светло-серый — чётные строки
CLR_WHITE = "FFFFFF"
THIN = Side(style="thin", color="BFBFBF")
BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
def make_fill(hex_color: str) -> PatternFill:
return PatternFill("solid", fgColor=hex_color)
def header_font(bold=True) -> Font:
return Font(name="Calibri", bold=bold, color=CLR_HEADER_FG, size=11)
def cell_font(bold=False, color="000000") -> Font:
return Font(name="Calibri", bold=bold, color=color, size=10)
def apply_header_row(ws, row: int, headers: list[str]):
"""Записывает строку заголовков с форматированием."""
for col, text in enumerate(headers, start=1):
c = ws.cell(row=row, column=col, value=text)
c.font = header_font()
c.fill = make_fill(CLR_HEADER_BG)
c.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
c.border = BORDER
def set_col_widths(ws, widths: list[int]):
for i, w in enumerate(widths, start=1):
ws.column_dimensions[get_column_letter(i)].width = w
def fmt_cell(ws, row, col, value, bold=False, bg=None, align="left", wrap=True, color="000000"):
c = ws.cell(row=row, column=col, value=value)
c.font = cell_font(bold=bold, color=color)
c.alignment = Alignment(horizontal=align, vertical="center", wrap_text=wrap)
c.border = BORDER
if bg:
c.fill = make_fill(bg)
return c
# ─── Вспомогательные функции для извлечения имён ────────────────────────────
def obj_display(obj: dict) -> str:
"""Возвращает строку вида 'hostname (ip/prefix)' или 'name' для группы."""
if "ip" in obj and "prefix" in obj:
return f"{obj.get('hostname', '')} ({obj['ip']}/{obj['prefix']})"
if "hostname" in obj:
return obj["hostname"]
if "name" in obj:
return obj["name"]
return str(obj)
def group_display(grp: dict) -> str:
return grp.get("name", "")
def svc_display(svc: dict) -> str:
return svc.get("name", "")
def list_to_str(items, fn) -> str:
if not items:
return ""
return "\n".join(fn(i) for i in items)
# ─── Лист: Объекты ───────────────────────────────────────────────────────────
def build_objects_sheet(wb: Workbook):
ws = wb.create_sheet("Объекты")
ws.freeze_panes = "A2"
headers = ["Имя", "Тип", "IP-адрес", "Префикс", "Шлюз", "Домен", "Описание"]
apply_header_row(ws, 1, headers)
set_col_widths(ws, [20, 10, 16, 8, 16, 16, 40])
all_objects = {}
for k, v in servers.items():
all_objects[k] = v
for k, v in nets.items():
all_objects[k] = v
for row_idx, (key, obj) in enumerate(all_objects.items(), start=2):
bg = CLR_WHITE if row_idx % 2 == 0 else CLR_ALT_BG
fmt_cell(ws, row_idx, 1, obj.get("hostname", key), bg=bg)
fmt_cell(ws, row_idx, 2, obj.get("type", ""), bg=bg, align="center")
fmt_cell(ws, row_idx, 3, obj.get("ip", ""), bg=bg, align="center")
fmt_cell(ws, row_idx, 4, str(obj.get("prefix", "")), bg=bg, align="center")
fmt_cell(ws, row_idx, 5, obj.get("gw", ""), bg=bg, align="center")
fmt_cell(ws, row_idx, 6, obj.get("domain", ""), bg=bg)
fmt_cell(ws, row_idx, 7, obj.get("description", ""), bg=bg)
ws.row_dimensions[1].height = 30
# ─── Лист: Группы объектов ───────────────────────────────────────────────────
def build_groups_sheet(wb: Workbook):
ws = wb.create_sheet("Группы объектов")
ws.freeze_panes = "A2"
headers = ["Имя группы", "Элементы группы"]
apply_header_row(ws, 1, headers)
set_col_widths(ws, [35, 60])
for row_idx, (key, grp) in enumerate(groups.items(), start=2):
bg = CLR_WHITE if row_idx % 2 == 0 else CLR_ALT_BG
items = grp.get("items", [])
items_str = "\n".join(i.get("hostname", str(i)) for i in items) if items else ""
fmt_cell(ws, row_idx, 1, grp.get("name", key), bold=True, bg=bg)
c = fmt_cell(ws, row_idx, 2, items_str, bg=bg)
# авто-высота строки по числу элементов
ws.row_dimensions[row_idx].height = max(15, 15 * max(1, len(items)))
ws.row_dimensions[1].height = 30
# ─── Лист: Сервисы ───────────────────────────────────────────────────────────
def build_services_sheet(wb: Workbook):
ws = wb.create_sheet("Сервисы")
ws.freeze_panes = "A2"
headers = ["Ключ", "Имя сервиса", "Протокол", "Порт источника", "Порт назначения"]
apply_header_row(ws, 1, headers)
set_col_widths(ws, [28, 38, 14, 18, 18])
for row_idx, (key, svc) in enumerate(services.items(), start=2):
bg = CLR_WHITE if row_idx % 2 == 0 else CLR_ALT_BG
fmt_cell(ws, row_idx, 1, key, bg=bg)
fmt_cell(ws, row_idx, 2, svc.get("name", ""), bg=bg)
fmt_cell(ws, row_idx, 3, svc.get("proto", ""), bg=bg, align="center")
fmt_cell(ws, row_idx, 4, svc.get("sport", ""), bg=bg, align="center")
fmt_cell(ws, row_idx, 5, svc.get("dport", ""), bg=bg, align="center")
ws.row_dimensions[1].height = 30
# ─── Лист: Группы сервисов ───────────────────────────────────────────────────
def build_service_groups_sheet(wb: Workbook):
ws = wb.create_sheet("Группы сервисов")
ws.freeze_panes = "A2"
headers = ["Ключ", "Имя группы", "Сервисы (имя | протокол | dport)"]
apply_header_row(ws, 1, headers)
set_col_widths(ws, [25, 35, 60])
for row_idx, (key, sg) in enumerate(service_groups.items(), start=2):
bg = CLR_WHITE if row_idx % 2 == 0 else CLR_ALT_BG
items = sg.get("items", [])
lines = []
for svc in items:
lines.append(
f"{svc.get('name','')} | {svc.get('proto','')} | {svc.get('dport','')}"
)
items_str = "\n".join(lines) if lines else ""
fmt_cell(ws, row_idx, 1, key, bg=bg)
fmt_cell(ws, row_idx, 2, sg.get("name", key), bold=True, bg=bg)
fmt_cell(ws, row_idx, 3, items_str, bg=bg)
ws.row_dimensions[row_idx].height = max(15, 15 * max(1, len(items)))
ws.row_dimensions[1].height = 30
# ─── Лист: Правила ───────────────────────────────────────────────────────────
def _extract_src_dst(lst) -> str:
"""Преобразует список src/dst (может содержать dict серверов или групп) в строку."""
if not lst:
return ""
parts = []
for item in lst:
if not isinstance(item, dict):
parts.append(str(item))
continue
# Это группа (есть ключ 'items') или сервер/сеть (есть ключ 'ip')
if "items" in item:
parts.append(item.get("name", "?"))
elif "ip" in item:
hostname = item.get("hostname", "")
ip = item.get("ip", "")
prefix = item.get("prefix", "")
parts.append(f"{hostname} ({ip}/{prefix})")
else:
parts.append(item.get("hostname") or item.get("name") or str(item))
return "\n".join(parts)
def _extract_services(svc_list, sg_list) -> str:
parts = []
if svc_list:
for s in svc_list:
if isinstance(s, dict):
parts.append(s.get("name", str(s)))
if sg_list:
for sg in sg_list:
if isinstance(sg, dict):
parts.append(f"[{sg.get('name', str(sg))}]")
return "\n".join(parts) if parts else ""
def build_rules_sheet(wb: Workbook):
ws = wb.create_sheet("Правила", 0) # первый лист
ws.freeze_panes = "A3"
headers = [
"", "Порядок", "Имя правила", "Описание",
"Источник", "Назначение", "Сервисы",
"Действие", "Лог", "IDP", "Affinity"
]
apply_header_row(ws, 1, headers)
set_col_widths(ws, [5, 8, 28, 40, 30, 30, 35, 10, 6, 6, 25])
rule_num = 0
for row_idx, rule in enumerate(rules, start=2):
rtype = rule.get("type", "rule")
if rtype == "span":
# Строка-разделитель (заголовок секции)
ws.merge_cells(
start_row=row_idx, start_column=1,
end_row=row_idx, end_column=len(headers)
)
c = ws.cell(row=row_idx, column=1, value=rule.get("name", ""))
c.font = Font(name="Calibri", bold=True, color=CLR_SPAN_FG, size=11)
c.fill = make_fill(CLR_SPAN_BG)
c.alignment = Alignment(horizontal="center", vertical="center")
c.border = BORDER
ws.row_dimensions[row_idx].height = 22
continue
# Обычное правило
rule_num += 1
action = rule.get("action", "")
if action == "allow":
bg = CLR_ALLOW_BG
elif action in ("deny", "drop", "reject"):
bg = CLR_DENY_BG
else:
bg = CLR_WHITE if row_idx % 2 == 0 else CLR_ALT_BG
src_str = _extract_src_dst(rule.get("src_list"))
dst_str = _extract_src_dst(rule.get("dst_list"))
svc_str = _extract_services(
rule.get("service_list"), rule.get("service_group_list")
)
affinity_str = ", ".join(rule.get("affinity", []))
fmt_cell(ws, row_idx, 1, rule_num, bg=bg, align="center")
fmt_cell(ws, row_idx, 2, rule.get("order", ""), bg=bg, align="center")
fmt_cell(ws, row_idx, 3, rule.get("name", ""), bg=bg, bold=True)
fmt_cell(ws, row_idx, 4, rule.get("description", ""), bg=bg)
fmt_cell(ws, row_idx, 5, src_str, bg=bg)
fmt_cell(ws, row_idx, 6, dst_str, bg=bg)
fmt_cell(ws, row_idx, 7, svc_str, bg=bg)
fmt_cell(ws, row_idx, 8, action, bg=bg, align="center", bold=True)
fmt_cell(ws, row_idx, 9, rule.get("log", ""), bg=bg, align="center")
fmt_cell(ws, row_idx, 10, rule.get("idp", ""), bg=bg, align="center")
fmt_cell(ws, row_idx, 11, affinity_str, bg=bg)
# Высота строки — по числу строк в самом длинном поле
max_lines = max(
len(src_str.split("\n")),
len(dst_str.split("\n")),
len(svc_str.split("\n")),
1
)
ws.row_dimensions[row_idx].height = max(18, 15 * max_lines)
ws.row_dimensions[1].height = 30
# ─── Главная функция ─────────────────────────────────────────────────────────
def main():
output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fw_report.xlsx")
wb = Workbook()
# Удаляем дефолтный лист
wb.remove(wb.active)
build_rules_sheet(wb)
build_objects_sheet(wb)
build_groups_sheet(wb)
build_services_sheet(wb)
build_service_groups_sheet(wb)
wb.save(output_file)
print(f"Отчёт сохранён: {output_file}")
if __name__ == "__main__":
main()