Add cybersecurity/Network Reputation Service/script-nrs.py
This commit is contained in:
712
cybersecurity/Network Reputation Service/script-nrs.py
Normal file
712
cybersecurity/Network Reputation Service/script-nrs.py
Normal file
@@ -0,0 +1,712 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script d'Audit Firewall - Network Reputation Service
|
||||||
|
Auteur: Converti depuis PowerShell
|
||||||
|
Date: 04/09/2025
|
||||||
|
Version: 1.3 (Python)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import time
|
||||||
|
import getpass
|
||||||
|
from typing import List, Dict, Any, Optional, Tuple
|
||||||
|
import math
|
||||||
|
|
||||||
|
# Import des bibliothèques avec gestion des erreurs
|
||||||
|
try:
|
||||||
|
from colorama import init, Fore, Back, Style
|
||||||
|
init(autoreset=True)
|
||||||
|
COLORAMA_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
COLORAMA_AVAILABLE = False
|
||||||
|
print("Attention: colorama n'est pas installé. Les couleurs ne seront pas disponibles.")
|
||||||
|
print("Installez avec: pip install colorama")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from tqdm import tqdm
|
||||||
|
TQDM_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
TQDM_AVAILABLE = False
|
||||||
|
print("Attention: tqdm n'est pas installé. Les barres de progression ne seront pas disponibles.")
|
||||||
|
print("Installez avec: pip install tqdm")
|
||||||
|
|
||||||
|
# Configuration globale
|
||||||
|
TIMEOUT_DEFAULT = 10
|
||||||
|
GRADE_COLORS = {
|
||||||
|
'A+': '#28a745', 'A': '#52b83a', 'B+': '#7ac92e', 'B': '#a3da23',
|
||||||
|
'C+': '#cceb17', 'C': '#f5f90c', 'D+': '#f7d808', 'D': '#f9b604',
|
||||||
|
'E+': '#fb9500', 'E': '#fd7300', 'F+': '#ff5100', 'F': '#dc3545'
|
||||||
|
}
|
||||||
|
|
||||||
|
BLOCK_KEYWORDS = [
|
||||||
|
"site bloqué", "access denied", "filtrage web",
|
||||||
|
"Access Denied", "Site Blocked", "blocked", "forbidden"
|
||||||
|
]
|
||||||
|
|
||||||
|
BROWSER_HEADERS = {
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
||||||
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||||
|
"Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7"
|
||||||
|
}
|
||||||
|
|
||||||
|
def write_color_output(text: str, color: str = "WHITE") -> None:
|
||||||
|
"""Affiche du texte en couleur si colorama est disponible"""
|
||||||
|
if not COLORAMA_AVAILABLE:
|
||||||
|
print(text)
|
||||||
|
return
|
||||||
|
|
||||||
|
color_map = {
|
||||||
|
"RED": Fore.RED,
|
||||||
|
"GREEN": Fore.GREEN,
|
||||||
|
"YELLOW": Fore.YELLOW,
|
||||||
|
"BLUE": Fore.BLUE,
|
||||||
|
"CYAN": Fore.CYAN,
|
||||||
|
"MAGENTA": Fore.MAGENTA,
|
||||||
|
"WHITE": Fore.WHITE,
|
||||||
|
"GRAY": Fore.LIGHTBLACK_EX
|
||||||
|
}
|
||||||
|
|
||||||
|
color_code = color_map.get(color.upper(), Fore.WHITE)
|
||||||
|
print(f"{color_code}{text}")
|
||||||
|
|
||||||
|
def prerequisites() -> bool:
|
||||||
|
"""Vérifie les prérequis du script"""
|
||||||
|
write_color_output("\n === Vérification des prérequis ===", "CYAN")
|
||||||
|
|
||||||
|
# Vérification du fichier JSON
|
||||||
|
script_dir = Path(__file__).parent
|
||||||
|
json_file = script_dir / "file-nrs.json"
|
||||||
|
|
||||||
|
if not json_file.exists():
|
||||||
|
write_color_output("ERREUR: Le fichier 'file-nrs.json' n'existe pas!", "RED")
|
||||||
|
write_color_output("Veuillez télécharger ou créer le fichier depuis:", "YELLOW")
|
||||||
|
write_color_output("https://gitea.tips-of-mine.com/Tips-Of-Mine/Powershell/src/branch/main/cybersecurity/Network%20Reputation%20Service/file-nrs.json", "BLUE")
|
||||||
|
write_color_output("Le fichier doit être placé dans le même dossier que ce script.", "YELLOW")
|
||||||
|
return False
|
||||||
|
|
||||||
|
write_color_output("✓ Fichier 'file-nrs.json' trouvé", "GREEN")
|
||||||
|
|
||||||
|
# Vérification des modules Python requis
|
||||||
|
required_modules = {
|
||||||
|
'requests': 'requests',
|
||||||
|
'colorama': 'colorama',
|
||||||
|
'tqdm': 'tqdm'
|
||||||
|
}
|
||||||
|
|
||||||
|
missing_modules = []
|
||||||
|
for module_name, pip_name in required_modules.items():
|
||||||
|
try:
|
||||||
|
__import__(module_name)
|
||||||
|
write_color_output(f"✓ Module {module_name} disponible", "GREEN")
|
||||||
|
except ImportError:
|
||||||
|
write_color_output(f"⚠ Module {module_name} manquant", "YELLOW")
|
||||||
|
missing_modules.append(pip_name)
|
||||||
|
|
||||||
|
if missing_modules:
|
||||||
|
write_color_output(f"Modules manquants (optionnels): {', '.join(missing_modules)}", "YELLOW")
|
||||||
|
write_color_output(f"Installez avec: pip install {' '.join(missing_modules)}", "YELLOW")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def calculate_category_score(results: List[Dict]) -> float:
|
||||||
|
"""Calcule le score d'une catégorie"""
|
||||||
|
if not results:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
correct_results = sum(1 for result in results if result['is_correct'])
|
||||||
|
return round((correct_results / len(results)) * 100, 2)
|
||||||
|
|
||||||
|
def convert_score_to_grade(score: float) -> str:
|
||||||
|
"""Convertit un score en note"""
|
||||||
|
if score >= 95: return 'A+'
|
||||||
|
elif score >= 90: return 'A'
|
||||||
|
elif score >= 85: return 'B+'
|
||||||
|
elif score >= 80: return 'B'
|
||||||
|
elif score >= 75: return 'C+'
|
||||||
|
elif score >= 70: return 'C'
|
||||||
|
elif score >= 65: return 'D+'
|
||||||
|
elif score >= 60: return 'D'
|
||||||
|
elif score >= 55: return 'E+'
|
||||||
|
elif score >= 50: return 'E'
|
||||||
|
elif score >= 45: return 'F+'
|
||||||
|
else: return 'F'
|
||||||
|
|
||||||
|
def get_url_status(url: str, expected_action: str, proxy_url: Optional[str] = None,
|
||||||
|
proxy_auth: Optional[Tuple[str, str]] = None, timeout: int = TIMEOUT_DEFAULT) -> Dict:
|
||||||
|
"""Teste une URL et détermine si elle est bloquée ou autorisée"""
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'url': url,
|
||||||
|
'expected': expected_action,
|
||||||
|
'actual_result': 'Indéterminé',
|
||||||
|
'test_status': 'Échec',
|
||||||
|
'score': None,
|
||||||
|
'status_code': '',
|
||||||
|
'details': '',
|
||||||
|
'response_time': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configuration des proxies
|
||||||
|
proxies = {}
|
||||||
|
if proxy_url:
|
||||||
|
proxies = {
|
||||||
|
'http': proxy_url,
|
||||||
|
'https': proxy_url
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configuration de l'authentification proxy
|
||||||
|
auth = None
|
||||||
|
if proxy_auth:
|
||||||
|
auth = proxy_auth
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
url,
|
||||||
|
headers=BROWSER_HEADERS,
|
||||||
|
timeout=timeout,
|
||||||
|
proxies=proxies,
|
||||||
|
auth=auth,
|
||||||
|
verify=True,
|
||||||
|
allow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
response_time = round(time.time() - start_time, 2)
|
||||||
|
result['response_time'] = str(response_time)
|
||||||
|
result['status_code'] = str(response.status_code)
|
||||||
|
|
||||||
|
# Vérification des mots-clés de blocage dans le contenu
|
||||||
|
keyword_found = any(keyword.lower() in response.text.lower() for keyword in BLOCK_KEYWORDS)
|
||||||
|
|
||||||
|
if keyword_found:
|
||||||
|
result['actual_result'] = "Bloqué"
|
||||||
|
result['details'] = "Page de blocage détectée."
|
||||||
|
else:
|
||||||
|
result['actual_result'] = "Autorisé"
|
||||||
|
result['details'] = "Le site a été atteint sans blocage."
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
result['actual_result'] = "Bloqué (Timeout)"
|
||||||
|
result['details'] = f"La requête a expiré après {timeout}s, indiquant un blocage probable."
|
||||||
|
result['response_time'] = str(timeout)
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
result['actual_result'] = "Erreur de Connexion"
|
||||||
|
result['details'] = "Impossible de joindre le serveur"
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
result['actual_result'] = "Erreur Script"
|
||||||
|
result['details'] = f"Erreur inattendue: {str(e)}"
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result['actual_result'] = "Erreur Script"
|
||||||
|
result['details'] = f"Erreur inattendue: {str(e)}"
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Détermination si le test est conforme
|
||||||
|
is_blocked = result['actual_result'] in ['Bloqué', 'Bloqué (Timeout)']
|
||||||
|
should_be_blocked = expected_action == 'block'
|
||||||
|
|
||||||
|
if (is_blocked and should_be_blocked) or (not is_blocked and not should_be_blocked):
|
||||||
|
result['test_status'] = "Conforme"
|
||||||
|
result['score'] = 1
|
||||||
|
result['details'] += " (Résultat conforme à l'attendu)"
|
||||||
|
else:
|
||||||
|
result['test_status'] = "Non Conforme"
|
||||||
|
result['score'] = 0
|
||||||
|
result['details'] += f" (NON CONFORME; Attendu: {expected_action}; Obtenu: {result['actual_result']})"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def check_categories(categories: Dict, proxy_url: Optional[str] = None,
|
||||||
|
proxy_auth: Optional[Tuple[str, str]] = None, timeout: int = TIMEOUT_DEFAULT) -> List[Dict]:
|
||||||
|
"""Teste toutes les catégories et URLs"""
|
||||||
|
|
||||||
|
all_results = []
|
||||||
|
categories_data = categories.get('categorie', [])
|
||||||
|
|
||||||
|
write_color_output(f"\n=== Début du test de {len(categories_data)} catégories ===", "CYAN")
|
||||||
|
|
||||||
|
# Barre de progression principale si tqdm est disponible
|
||||||
|
category_progress = None
|
||||||
|
if TQDM_AVAILABLE:
|
||||||
|
category_progress = tqdm(categories_data, desc="Catégories", unit="cat")
|
||||||
|
|
||||||
|
for category_idx, category in enumerate(categories_data):
|
||||||
|
if not TQDM_AVAILABLE:
|
||||||
|
write_color_output(f"\n=== Test de la catégorie: {category['nom']} ({category_idx + 1}/{len(categories_data)}) ===", "CYAN")
|
||||||
|
|
||||||
|
category_results = []
|
||||||
|
urls = category.get('urls', [])
|
||||||
|
|
||||||
|
# Barre de progression pour les URLs si tqdm est disponible
|
||||||
|
url_progress = None
|
||||||
|
if TQDM_AVAILABLE:
|
||||||
|
url_progress = tqdm(urls, desc=f"URLs de '{category['nom']}'", unit="url", leave=False)
|
||||||
|
|
||||||
|
for url_idx, url_obj in enumerate(urls):
|
||||||
|
if not TQDM_AVAILABLE:
|
||||||
|
write_color_output(f"Test de: {url_obj['url']} ({url_idx + 1}/{len(urls)})", "YELLOW")
|
||||||
|
|
||||||
|
result = get_url_status(
|
||||||
|
url_obj['url'],
|
||||||
|
url_obj['expected_action'],
|
||||||
|
proxy_url,
|
||||||
|
proxy_auth,
|
||||||
|
timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
test_result = {
|
||||||
|
'category': category['nom'],
|
||||||
|
'category_id': category.get('id', ''),
|
||||||
|
'url': url_obj['url'],
|
||||||
|
'reputation': url_obj.get('reputation', ''),
|
||||||
|
'expected_action': url_obj['expected_action'],
|
||||||
|
'status': result['actual_result'],
|
||||||
|
'status_code': result['status_code'],
|
||||||
|
'response_time': result['response_time'],
|
||||||
|
'error': result['details'],
|
||||||
|
'score': result['score'],
|
||||||
|
'is_correct': result['test_status'] == "Conforme"
|
||||||
|
}
|
||||||
|
|
||||||
|
if not TQDM_AVAILABLE:
|
||||||
|
status_color = "GREEN" if test_result['is_correct'] else "RED"
|
||||||
|
write_color_output(
|
||||||
|
f" → Résultat: {test_result['status']} | Attendu: {test_result['expected_action']} | Correct: {test_result['is_correct']}",
|
||||||
|
status_color
|
||||||
|
)
|
||||||
|
|
||||||
|
category_results.append(test_result)
|
||||||
|
all_results.append(test_result)
|
||||||
|
|
||||||
|
if url_progress:
|
||||||
|
url_progress.update(1)
|
||||||
|
|
||||||
|
if url_progress:
|
||||||
|
url_progress.close()
|
||||||
|
|
||||||
|
category_score = calculate_category_score(category_results)
|
||||||
|
category_grade = convert_score_to_grade(category_score)
|
||||||
|
|
||||||
|
if not TQDM_AVAILABLE:
|
||||||
|
write_color_output(f"Score de la catégorie '{category['nom']}': {category_score}% (Note: {category_grade})", "MAGENTA")
|
||||||
|
|
||||||
|
if category_progress:
|
||||||
|
category_progress.set_postfix({
|
||||||
|
'Score': f"{category_score}%",
|
||||||
|
'Grade': category_grade
|
||||||
|
})
|
||||||
|
category_progress.update(1)
|
||||||
|
|
||||||
|
if category_progress:
|
||||||
|
category_progress.close()
|
||||||
|
|
||||||
|
return all_results
|
||||||
|
|
||||||
|
def generate_html_report(results: List[Dict], output_path: Path) -> None:
|
||||||
|
"""Génère le rapport HTML"""
|
||||||
|
write_color_output("Génération du rapport HTML...", "YELLOW")
|
||||||
|
|
||||||
|
# Calcul des scores par catégorie
|
||||||
|
categories_dict = {}
|
||||||
|
for result in results:
|
||||||
|
cat_name = result['category']
|
||||||
|
if cat_name not in categories_dict:
|
||||||
|
categories_dict[cat_name] = []
|
||||||
|
categories_dict[cat_name].append(result)
|
||||||
|
|
||||||
|
category_scores = []
|
||||||
|
for cat_name, cat_results in categories_dict.items():
|
||||||
|
score = calculate_category_score(cat_results)
|
||||||
|
grade = convert_score_to_grade(score)
|
||||||
|
|
||||||
|
category_scores.append({
|
||||||
|
'category': cat_name,
|
||||||
|
'score': score,
|
||||||
|
'grade': grade,
|
||||||
|
'color': GRADE_COLORS[grade],
|
||||||
|
'total_urls': len(cat_results),
|
||||||
|
'correct_results': sum(1 for r in cat_results if r['is_correct']),
|
||||||
|
'results': cat_results
|
||||||
|
})
|
||||||
|
|
||||||
|
# Score global
|
||||||
|
global_score = calculate_category_score(results)
|
||||||
|
global_grade = convert_score_to_grade(global_score)
|
||||||
|
global_color = GRADE_COLORS[global_grade]
|
||||||
|
|
||||||
|
# Génération du HTML
|
||||||
|
current_time = datetime.now().strftime('%d/%m/%Y à %H:%M')
|
||||||
|
|
||||||
|
html_content = f"""<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Audit Firewall - Network Reputation Service</title>
|
||||||
|
<style>
|
||||||
|
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||||
|
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f5f5; color: #333; line-height: 1.6; }}
|
||||||
|
.container {{ max-width: 1200px; margin: 0 auto; padding: 20px; }}
|
||||||
|
h1 {{ text-align: center; color: #2c3e50; margin-bottom: 10px; font-size: 2.5em; }}
|
||||||
|
.subtitle {{ text-align: center; color: #666; margin-bottom: 30px; font-size: 1.1em; }}
|
||||||
|
.section {{ background: white; margin: 20px 0; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
|
||||||
|
.section h2 {{ color: #2c3e50; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #3498db; }}
|
||||||
|
|
||||||
|
/* Score global */
|
||||||
|
.global-score {{ display: flex; align-items: center; justify-content: center; margin: 30px 0; }}
|
||||||
|
.score-box {{ background-color: {global_color}; color: white; width: 150px; height: 150px; border-radius: 15px; display: flex; flex-direction: column; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(0,0,0,0.2); margin-right: 40px; }}
|
||||||
|
.score-grade {{ font-size: 3em; font-weight: bold; }}
|
||||||
|
.score-percent {{ font-size: 1.5em; margin-top: 5px; }}
|
||||||
|
.score-label {{ font-size: 1em; margin-top: 5px; opacity: 0.9; }}
|
||||||
|
.stats {{ text-align: left; }}
|
||||||
|
.stats div {{ margin: 8px 0; font-size: 1.2em; }}
|
||||||
|
|
||||||
|
/* Tableau des scores */
|
||||||
|
table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
|
||||||
|
th, td {{ padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }}
|
||||||
|
th {{ background-color: #3498db; color: white; font-weight: bold; }}
|
||||||
|
tr:nth-child(even) {{ background-color: #f9f9f9; }}
|
||||||
|
tr:hover {{ background-color: #e8f4fd; }}
|
||||||
|
.grade-cell {{ font-weight: bold; color: white; text-align: center; border-radius: 5px; }}
|
||||||
|
|
||||||
|
/* Catégories en grille */
|
||||||
|
.categories-grid {{ display: flex; flex-wrap: wrap; gap: 20px; margin: 20px 0; }}
|
||||||
|
.category-item {{ flex: 1; min-width: 280px; max-width: calc(25% - 15px); background: white; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); overflow: hidden; }}
|
||||||
|
.category-header {{ padding: 15px; cursor: pointer; transition: background-color 0.3s; }}
|
||||||
|
.category-header:hover {{ background-color: #f8f9fa; }}
|
||||||
|
.category-title {{ font-size: 1.2em; font-weight: bold; margin-bottom: 8px; color: #2c3e50; }}
|
||||||
|
.category-score {{ display: inline-block; padding: 5px 15px; border-radius: 20px; color: white; font-weight: bold; font-size: 0.9em; }}
|
||||||
|
.category-content {{ display: none; padding: 0 15px 15px; }}
|
||||||
|
.category-content.active {{ display: block; animation: slideDown 0.3s ease; }}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes slideDown {{ from {{ opacity: 0; transform: translateY(-10px); }} to {{ opacity: 1; transform: translateY(0); }} }}
|
||||||
|
|
||||||
|
/* Tables détaillées */
|
||||||
|
.detail-table {{ font-size: 0.9em; }}
|
||||||
|
.detail-table th {{ font-size: 0.8em; }}
|
||||||
|
.correct {{ background-color: #d4edda !important; color: #155724; }}
|
||||||
|
.incorrect {{ background-color: #f8d7da !important; color: #721c24; }}
|
||||||
|
|
||||||
|
/* Barème */
|
||||||
|
.grading-scale {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 10px; }}
|
||||||
|
.grade-item {{ display: flex; align-items: center; padding: 10px; border-radius: 8px; background-color: #f8f9fa; }}
|
||||||
|
.grade-badge {{ width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-weight: bold; color: white; margin-right: 15px; }}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {{
|
||||||
|
.global-score {{ flex-direction: column; }}
|
||||||
|
.score-box {{ margin-right: 0; margin-bottom: 20px; }}
|
||||||
|
.category-item {{ max-width: 100%; }}
|
||||||
|
}}
|
||||||
|
|
||||||
|
/* Bouton toggle */
|
||||||
|
.toggle-btn {{ float: right; font-size: 1.2em; transition: transform 0.3s; }}
|
||||||
|
.toggle-btn.active {{ transform: rotate(180deg); }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>🛡️ Audit Firewall - Network Reputation Service</h1>
|
||||||
|
<p class="subtitle">Rapport généré le {current_time}</p>
|
||||||
|
|
||||||
|
<!-- Résumé Exécutif -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>📊 Résumé Exécutif</h2>
|
||||||
|
<div class="global-score">
|
||||||
|
<div class="score-box">
|
||||||
|
<div class="score-grade">{global_grade}</div>
|
||||||
|
<div class="score-percent">{global_score}%</div>
|
||||||
|
<div class="score-label">Score Global</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats">
|
||||||
|
<div><strong>📋 Total des URLs testées :</strong> {len(results)}</div>
|
||||||
|
<div><strong>✅ URLs correctement filtrées :</strong> {sum(1 for r in results if r['is_correct'])}</div>
|
||||||
|
<div><strong>🎯 Taux de réussite :</strong> {round((sum(1 for r in results if r['is_correct']) / len(results)) * 100, 2)}%</div>
|
||||||
|
<div><strong>📅 Date du test :</strong> {datetime.now().strftime('%d/%m/%Y %H:%M')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scores par Catégorie -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>📈 Scores par Catégorie</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Catégorie</th>
|
||||||
|
<th>Score (%)</th>
|
||||||
|
<th>Note</th>
|
||||||
|
<th>URLs Totales</th>
|
||||||
|
<th>Résultats Corrects</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>"""
|
||||||
|
|
||||||
|
# Ajout des lignes du tableau
|
||||||
|
for cat_score in category_scores:
|
||||||
|
html_content += f"""
|
||||||
|
<tr>
|
||||||
|
<td><strong>{cat_score['category']}</strong></td>
|
||||||
|
<td>{cat_score['score']}%</td>
|
||||||
|
<td><span class="grade-cell" style="background-color: {cat_score['color']}; padding: 5px 10px; border-radius: 5px;">{cat_score['grade']}</span></td>
|
||||||
|
<td>{cat_score['total_urls']}</td>
|
||||||
|
<td>{cat_score['correct_results']}</td>
|
||||||
|
</tr>"""
|
||||||
|
|
||||||
|
html_content += """
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Détail des Tests par Catégorie -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>🔍 Détail des Tests par Catégorie</h2>
|
||||||
|
<div class="categories-grid">"""
|
||||||
|
|
||||||
|
# Ajout des catégories
|
||||||
|
for idx, cat_score in enumerate(category_scores, 1):
|
||||||
|
html_content += f"""
|
||||||
|
<div class="category-item">
|
||||||
|
<div class="category-header" onclick="toggleCategory('cat-{idx}')">
|
||||||
|
<div class="category-title">{cat_score['category']} <span class="toggle-btn" id="btn-cat-{idx}">▼</span></div>
|
||||||
|
<span class="category-score" style="background-color: {cat_score['color']};">{cat_score['grade']} - {cat_score['score']}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content" id="cat-{idx}">
|
||||||
|
<table class="detail-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>Réputation</th>
|
||||||
|
<th>Action Attendue</th>
|
||||||
|
<th>Statut</th>
|
||||||
|
<th>Correct</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>"""
|
||||||
|
|
||||||
|
# Ajout des résultats de chaque URL
|
||||||
|
for result in cat_score['results']:
|
||||||
|
row_class = "correct" if result['is_correct'] else "incorrect"
|
||||||
|
correct_text = "✅ Oui" if result['is_correct'] else "❌ Non"
|
||||||
|
url_display = result['url'][:30] + ("..." if len(result['url']) > 30 else "")
|
||||||
|
|
||||||
|
html_content += f"""
|
||||||
|
<tr class="{row_class}">
|
||||||
|
<td title="{result['url']}">{url_display}</td>
|
||||||
|
<td>{result['reputation']}</td>
|
||||||
|
<td>{result['expected_action']}</td>
|
||||||
|
<td>{result['status']}</td>
|
||||||
|
<td>{correct_text}</td>
|
||||||
|
</tr>"""
|
||||||
|
|
||||||
|
html_content += """
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>"""
|
||||||
|
|
||||||
|
html_content += """
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Barème de Notation -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>📏 Barème de Notation</h2>
|
||||||
|
<div class="grading-scale">"""
|
||||||
|
|
||||||
|
# Ajout du barème de notation
|
||||||
|
grading_scale = [
|
||||||
|
{'grade': 'A+', 'score': '95-100%', 'interpretation': 'Excellent / Parfait', 'color': '#28a745'},
|
||||||
|
{'grade': 'A', 'score': '90-95%', 'interpretation': 'Très bon niveau de filtrage', 'color': '#52b83a'},
|
||||||
|
{'grade': 'B+', 'score': '85-90%', 'interpretation': 'Très bon', 'color': '#7ac92e'},
|
||||||
|
{'grade': 'B', 'score': '80-85%', 'interpretation': 'Bon, quelques ajustements nécessaires', 'color': '#a3da23'},
|
||||||
|
{'grade': 'C+', 'score': '75-80%', 'interpretation': 'Assez bon', 'color': '#cceb17'},
|
||||||
|
{'grade': 'C', 'score': '70-75%', 'interpretation': 'Moyen, lacunes importantes', 'color': '#f5f90c'},
|
||||||
|
{'grade': 'D+', 'score': '65-70%', 'interpretation': 'Passable', 'color': '#f7d808'},
|
||||||
|
{'grade': 'D', 'score': '60-65%', 'interpretation': 'Faible, filtrage inefficace', 'color': '#f9b604'},
|
||||||
|
{'grade': 'E+', 'score': '55-60%', 'interpretation': 'Très faible', 'color': '#fb9500'},
|
||||||
|
{'grade': 'E', 'score': '50-55%', 'interpretation': 'Insuffisant', 'color': '#fd7300'},
|
||||||
|
{'grade': 'F+', 'score': '45-50%', 'interpretation': 'Critique', 'color': '#ff5100'},
|
||||||
|
{'grade': 'F', 'score': '0-45%', 'interpretation': 'Très faible, action immédiate requise', 'color': '#dc3545'}
|
||||||
|
]
|
||||||
|
|
||||||
|
for grade_info in grading_scale:
|
||||||
|
html_content += f"""
|
||||||
|
<div class="grade-item">
|
||||||
|
<div class="grade-badge" style="background-color: {grade_info['color']};">{grade_info['grade']}</div>
|
||||||
|
<div>
|
||||||
|
<strong>{grade_info['score']}</strong><br>
|
||||||
|
<span style="color: #666;">{grade_info['interpretation']}</span>
|
||||||
|
</div>
|
||||||
|
</div>"""
|
||||||
|
|
||||||
|
html_content += """
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleCategory(categoryId) {
|
||||||
|
const content = document.getElementById(categoryId);
|
||||||
|
const btn = document.getElementById('btn-' + categoryId);
|
||||||
|
|
||||||
|
if (content.classList.contains('active')) {
|
||||||
|
content.classList.remove('active');
|
||||||
|
btn.classList.remove('active');
|
||||||
|
content.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
content.classList.add('active');
|
||||||
|
btn.classList.add('active');
|
||||||
|
content.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation au chargement
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const items = document.querySelectorAll('.category-item');
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
item.style.opacity = '1';
|
||||||
|
item.style.transform = 'translateY(0)';
|
||||||
|
}, index * 100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
# Écriture du fichier HTML
|
||||||
|
try:
|
||||||
|
with open(output_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(html_content)
|
||||||
|
write_color_output(f"✓ Rapport généré: {output_path}", "GREEN")
|
||||||
|
except Exception as e:
|
||||||
|
write_color_output(f"ERREUR: Impossible de générer le rapport: {str(e)}", "RED")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Fonction principale"""
|
||||||
|
parser = argparse.ArgumentParser(description="Script d'Audit Firewall - Network Reputation Service")
|
||||||
|
parser.add_argument("--proxy-url", help="URL du proxy à utiliser (ex: 'http://proxy.domaine.com:8080')")
|
||||||
|
parser.add_argument("--proxy-auth", action="store_true", help="Utiliser l'authentification pour le proxy")
|
||||||
|
parser.add_argument("--timeout", type=int, default=TIMEOUT_DEFAULT, help="Délai d'attente en secondes pour chaque requête")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
write_color_output("""
|
||||||
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||||
|
║ AUDIT FIREWALL - NETWORK REPUTATION SERVICE ║
|
||||||
|
║ Version 1.3 (Python) ║
|
||||||
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||||
|
""", "CYAN")
|
||||||
|
|
||||||
|
# Vérification des prérequis
|
||||||
|
if not prerequisites():
|
||||||
|
write_color_output("ERREUR: Les prérequis ne sont pas satisfaits. Arrêt du script.", "RED")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Chargement du fichier JSON
|
||||||
|
try:
|
||||||
|
script_dir = Path(__file__).parent
|
||||||
|
json_file = script_dir / "file-nrs.json"
|
||||||
|
|
||||||
|
with open(json_file, 'r', encoding='utf-8') as f:
|
||||||
|
categories = json.load(f)
|
||||||
|
|
||||||
|
write_color_output(f"✓ Fichier JSON chargé avec {len(categories.get('categorie', []))} catégories", "GREEN")
|
||||||
|
except Exception as e:
|
||||||
|
write_color_output(f"ERREUR: Impossible de charger le fichier JSON: {str(e)}", "RED")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Configuration de l'authentification proxy si nécessaire
|
||||||
|
proxy_auth = None
|
||||||
|
if args.proxy_auth and args.proxy_url:
|
||||||
|
write_color_output(f"Configuration de l'authentification pour le proxy {args.proxy_url}", "YELLOW")
|
||||||
|
username = input("Nom d'utilisateur: ")
|
||||||
|
password = getpass.getpass("Mot de passe: ")
|
||||||
|
proxy_auth = (username, password)
|
||||||
|
|
||||||
|
# Création du dossier de sortie
|
||||||
|
current_time = datetime.now()
|
||||||
|
report_date = current_time.strftime("%A %d %B %Y-%H %M")
|
||||||
|
script_dir = Path(__file__).parent
|
||||||
|
reports_dir = script_dir / "Rapports"
|
||||||
|
output_dir = reports_dir / report_date
|
||||||
|
|
||||||
|
reports_dir.mkdir(exist_ok=True)
|
||||||
|
output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
write_color_output(f"\nDossier de sortie: {output_dir}", "YELLOW")
|
||||||
|
|
||||||
|
# Début des tests
|
||||||
|
write_color_output("\n=== DÉBUT DES TESTS ===", "GREEN")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
all_results = check_categories(
|
||||||
|
categories,
|
||||||
|
args.proxy_url,
|
||||||
|
proxy_auth,
|
||||||
|
args.timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
duration_str = f"{int(duration // 3600):02d}:{int((duration % 3600) // 60):02d}:{int(duration % 60):02d}"
|
||||||
|
|
||||||
|
write_color_output("\n=== RÉSULTATS FINAUX ===", "GREEN")
|
||||||
|
write_color_output(f"\nTemps total d'exécution: {duration_str}", "YELLOW")
|
||||||
|
write_color_output(f"Total des URLs testées: {len(all_results)}", "YELLOW")
|
||||||
|
|
||||||
|
# Calcul du score global
|
||||||
|
global_score = calculate_category_score(all_results)
|
||||||
|
global_grade = convert_score_to_grade(global_score)
|
||||||
|
|
||||||
|
write_color_output(f"\n\nScore global: {global_score}% - Note: {global_grade}", "MAGENTA")
|
||||||
|
|
||||||
|
# Génération du rapport HTML
|
||||||
|
report_path = output_dir / "Audit_Firewall_Report.html"
|
||||||
|
generate_html_report(all_results, report_path)
|
||||||
|
|
||||||
|
# Sauvegarde des résultats en JSON
|
||||||
|
json_results_path = output_dir / "Results.json"
|
||||||
|
try:
|
||||||
|
with open(json_results_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(all_results, f, indent=2, ensure_ascii=False)
|
||||||
|
write_color_output(f"✓ Résultats sauvegardés: {json_results_path}", "GREEN")
|
||||||
|
except Exception as e:
|
||||||
|
write_color_output(f"ERREUR: Impossible de sauvegarder les résultats: {str(e)}", "RED")
|
||||||
|
|
||||||
|
write_color_output("\n=== AUDIT TERMINÉ ===", "GREEN")
|
||||||
|
write_color_output(f"\nRapport disponible à: {report_path}", "CYAN")
|
||||||
|
|
||||||
|
# Ouverture automatique du rapport
|
||||||
|
try:
|
||||||
|
open_report = input("\nVoulez-vous ouvrir le rapport maintenant? (O/N): ").strip().lower()
|
||||||
|
if open_report in ['o', 'oui', 'y', 'yes']:
|
||||||
|
import webbrowser
|
||||||
|
webbrowser.open(f"file://{report_path.absolute()}")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nAu revoir!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
write_color_output("\n\nScript interrompu par l'utilisateur.", "YELLOW")
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
write_color_output(f"ERREUR FATALE: {str(e)}", "RED")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
Reference in New Issue
Block a user