From f3af1428af26f43f30dd0145b11b84642857041e Mon Sep 17 00:00:00 2001 From: Michel Date: Mon, 4 May 2026 11:16:20 +0200 Subject: [PATCH] Rdeirection BDE --- .env | 16 +- .idea/Fichiers_perso.iml | 4 +- .idea/dataSources.local.xml | 2 +- .../068e6b2a-97f7-4f17-ae58-a11df8c5f332.xml | 2 +- .idea/dataSources/data_sources_history.xml | 23 ++ FICHIERS SUPERVISOR.txt | 69 ++++++ Societe_Generale.py | 2 +- TestMysqlConnector.py | 23 ++ Test_SMS.py | 55 +++++ Tracker.py | 6 +- Vider_Logs.py | 61 ----- telegram_watch.py | 222 ++++++++++++++++++ test_synology_chat.py | 20 ++ watcher.py | 183 +++++++++++++++ 14 files changed, 605 insertions(+), 83 deletions(-) create mode 100644 .idea/dataSources/data_sources_history.xml create mode 100644 FICHIERS SUPERVISOR.txt create mode 100644 TestMysqlConnector.py create mode 100644 Test_SMS.py delete mode 100644 Vider_Logs.py create mode 100644 telegram_watch.py create mode 100644 test_synology_chat.py create mode 100644 watcher.py diff --git a/.env b/.env index 99faa96..971f9e3 100644 --- a/.env +++ b/.env @@ -1,18 +1,4 @@ -DB_HOST=162.19.78.131 +DB_HOST=192.168.1.100 DB_USER=SG DB_PASSWORD=A*Rb2Q@i72m8tM6@!$* - -MQTT_HOST=162.19.78.131 -MQTT_USER=sondes -MQTT_PASSWORD=3J@bjYP0 - -# === EMAIL SMTP ===# === EMAIL SMTP === -SMTP_HOST=smtp.mail.ovh.net -SMTP_PORT=465 -SMTP_USER=alertes_saclay@domo91.fr -SMTP_PASSWORD=Kdpke674y23Feq^H -EMAIL_FROM=alertes_saclay@domo91.fr -EMAIL_SACLAY=Cuisine -EMAIL_MEUDON=meudon@domo91.fr -EMAIL_DEFAULT=services@domo91.fr \ No newline at end of file diff --git a/.idea/Fichiers_perso.iml b/.idea/Fichiers_perso.iml index 39a8e58..5c9e85e 100644 --- a/.idea/Fichiers_perso.iml +++ b/.idea/Fichiers_perso.iml @@ -1,7 +1,9 @@ - + + + diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml index 5a26e28..5d40b72 100644 --- a/.idea/dataSources.local.xml +++ b/.idea/dataSources.local.xml @@ -1,6 +1,6 @@ - + #@ diff --git a/.idea/dataSources/068e6b2a-97f7-4f17-ae58-a11df8c5f332.xml b/.idea/dataSources/068e6b2a-97f7-4f17-ae58-a11df8c5f332.xml index 49567be..f3b4eee 100644 --- a/.idea/dataSources/068e6b2a-97f7-4f17-ae58-a11df8c5f332.xml +++ b/.idea/dataSources/068e6b2a-97f7-4f17-ae58-a11df8c5f332.xml @@ -1,6 +1,6 @@ - + exact InnoDB diff --git a/.idea/dataSources/data_sources_history.xml b/.idea/dataSources/data_sources_history.xml new file mode 100644 index 0000000..4172d8f --- /dev/null +++ b/.idea/dataSources/data_sources_history.xml @@ -0,0 +1,23 @@ + + + + + #@ + ` + + + mariadb + true + org.mariadb.jdbc.Driver + jdbc:mariadb://162.19.78.131:3306/SG + master_key + SG + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/FICHIERS SUPERVISOR.txt b/FICHIERS SUPERVISOR.txt new file mode 100644 index 0000000..93cffd1 --- /dev/null +++ b/FICHIERS SUPERVISOR.txt @@ -0,0 +1,69 @@ +FICHIERS SUPERVISOR + + + +[program:Outils] +directory=/home/debian/Outils +command=/home/debian/Outils/.venv/bin/streamlit run app/users.py --server.port=8503 --server.address=162.19.78.131 --server.headless=true +user=debian +autostart=true +autorestart=true +startsecs=5 +stopsignal=TERM +stopasgroup=true +killasgroup=true +stdout_logfile=/var/log/users.out.log +stderr_logfile=/var/log/users.err.log +environment=PYTHONUNBUFFERED="1" + + + +[program:tracker] +command=/home/debian/Gestion_sondes/venv/bin/streamlit run /home/debian/Gestion_sondes/Outils/tracker.py --server.headless true --server.address 162.19.78.131 --server.po> +directory=/home/debian/Gestion_sondes +user=debian +autostart=true +autorestart=true +startsecs=8 +environment=PYTHONUNBUFFERED="1" +stdout_logfile=/home/debian/Gestion_sondes/Logs/tracker.out.log +stderr_logfile=/home/debian/Gestion_sondes/Logs/tracker.err.log + + + + +[program:log_viewer] +directory=/home/debian/Gestion_sondes/Outils +command=/home/debian/Outils/myenv/bin/streamlit run Logs.py --server.port=8515 +autostart=true +autorestart=true +stderr_logfile=/home/debian/Gestion_sondes/Logs/log_viewer_err.log +stdout_logfile=/home/debian/Gestion_sondes/Logs/log_viewer_out.log +user=debian +environment=PATH="/home/debian/Outils/myenv/bin",HOME="/home/debian" + + +program:cuisine_meudon] +command=/home/debian/Gestion_sondes/myenv/bin/python -u /home/debian/Gestion_sondes/app/Mqtt_meudon.py +directory=/home/debian/Gestion_sondes +user=debian +autostart=true +autorestart=true +stdout_logfile=/home/debian/Gestion_sondes/Logs/cuisine_meudon.out.log +stderr_logfile=/home/debian/Gestion_sondes/Logs/cuisine_meudon.err.log +environment=PYTHONUNBUFFERED="1" + + +[program:cuisine_saclay] +command=/home/debian/Gestion_sondes/myenv/bin/python -u /home/debian/Gestion_sondes/app/Mqtt_saclay.py +directory=/home/debian/Gestion_sondes +user=debian +autostart=true +autorestart=true +stdout_logfile=/home/debian/Gestion_sondes/Logs/cuisine_saclay.out.log +stderr_logfile=/home/debian/Gestion_sondes/Logs/cuisine_saclay.err.log +environment=PYTHONUNBUFFERED="1" + + + + diff --git a/Societe_Generale.py b/Societe_Generale.py index fd166dd..b090724 100644 --- a/Societe_Generale.py +++ b/Societe_Generale.py @@ -15,7 +15,7 @@ conn = mysql.connector.connect( host=os.getenv("DB_HOST"), user=os.getenv("DB_USER"), password=os.getenv("DB_PASSWORD"), - database="SG" + database="Societe_Generale" ) cursor = conn.cursor() diff --git a/TestMysqlConnector.py b/TestMysqlConnector.py new file mode 100644 index 0000000..1276b7d --- /dev/null +++ b/TestMysqlConnector.py @@ -0,0 +1,23 @@ +import mysql.connector + +try: + conn = mysql.connector.connect( + host="192.168.1.100", + port=3306, + user="Michel", + password="wuP^wu&6xjx61bh*kjS^", + database="TestDB" + ) + + cursor = conn.cursor() + cursor.execute("SELECT VERSION();") + version = cursor.fetchone() + + print("Connexion OK") + print("Version MariaDB :", version[0]) + + cursor.close() + conn.close() + +except mysql.connector.Error as err: + print("Erreur MySQL :", err) \ No newline at end of file diff --git a/Test_SMS.py b/Test_SMS.py new file mode 100644 index 0000000..06943da --- /dev/null +++ b/Test_SMS.py @@ -0,0 +1,55 @@ +import os, time +from dotenv import load_dotenv +import ovh + +load_dotenv() + +client = ovh.Client( + endpoint=os.getenv("OVH_ENDPOINT", "ovh-eu"), + application_key=os.getenv("OVH_APPLICATION_KEY"), + application_secret=os.getenv("OVH_APPLICATION_SECRET"), + consumer_key=os.getenv("OVH_CONSUMER_KEY"), +) + +service = os.getenv("OVH_SMS_SERVICE") +sender = os.getenv("OVH_SMS_SENDER") +to = os.getenv("TEST_RECEIVER") + +message = "ALERTE TEST DOMO91 (transactionnel)." + +# Envoi immédiat (pas de differedDelivery) +payload = { + "sender": sender, + "receivers": [to], + "message": message, + "priority": "high", + "coding": "7bit", + "class": "phoneDisplay", + "noStopClause": True, # => H24 si autorisé par OVH + "senderForResponse": False, # True si vous utilisez un numéro et attendez des réponses + "validityPeriod": 2880, # minutes où le SMS reste valable (2 jours) + "tag": "test-ovh", +} + +print("Envoi en cours…") +resp = client.post(f"/sms/{service}/jobs", **payload) +# La réponse contient des taskIds pour suivre l'état +print("Réponse OVH:", resp) + +task_ids = resp.get("ids", []) +if task_ids: + task_id = task_ids[0] + # Boucle de suivi simple (optionnelle) + while True: + task = client.get(f"/sms/{service}/jobs/{task_id}") + print("Etat tâche:", task.get("status")) + if task.get("status") in ("done", "error", "cancelled"): + break + time.sleep(2) + + # On peut aussi lister les SMS sortants pour voir le statut final + outgoing_ids = client.get(f"/sms/{service}/outgoing") + if outgoing_ids: + last_id = outgoing_ids[-1] + last = client.get(f"/sms/{service}/outgoing/{last_id}") + print("Dernier SMS:", {k: last[k] for k in ("creationDatetime","receiver","status","messageId")}) diff --git a/Tracker.py b/Tracker.py index 6d47f1f..c8a472c 100644 --- a/Tracker.py +++ b/Tracker.py @@ -82,10 +82,10 @@ def on_message(_client, _userdata, message): # Configuration du client MQTT -mqtt_broker = "46.105.92.116" +mqtt_broker = "162.19.78.131" mqtt_port = 1883 # Vérifiez si votre broker utilise un autre port -mqtt_user = "Bwps" -mqtt_password = "scJ5ACj2keRfI^" +mqtt_user = "sondes" +mqtt_password = "3J@bjYP0" mqtt_topic = "Tracker/sondes" client = mqtt.Client() diff --git a/Vider_Logs.py b/Vider_Logs.py deleted file mode 100644 index 44d3ad2..0000000 --- a/Vider_Logs.py +++ /dev/null @@ -1,61 +0,0 @@ -import paramiko -# Configuration -hostname = "162.19.78.131" -port = 22 -username = "debian" -password = "lpZwixbBUFtGY" -chemin_script = "/home/debian/Gestion_sondes/app/Vider_Logs.py" - -# Connexion SSH avec clé + passphrase -try: - print("Connexion SSH en cours...") - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(hostname, port=port, username=username, password=password) - - # Exécution du script distant - print(f"Exécution du script : {chemin_script}") - stdin, stdout, stderr = ssh.exec_command(f"python3 {chemin_script}") - - print("\n--- Résultat ---") - for ligne in stdout: - print(ligne.strip()) - - erreurs = stderr.read().decode() - if erreurs: - print("\n--- Erreurs ---") - print(erreurs) - - ssh.close() - print("\n✅ Terminé avec succès.") -except Exception as e: - print(f"❌ Erreur : {e}") -# --- FIN DU CODE ACTIF --- - -# ============================================================================ -# 👇 CI-DESSOUS : VERSION DU SCRIPT À DÉPLOYER SUR LE VPS (à copier/coller) -# ============================================================================ - -""" -#!/usr/bin/env python3 -import os - -racine_logs = "/var/log" - -nb_vides = 0 -for dossier, _, fichiers in os.walk(racine_logs): - for fichier in fichiers: - if fichier.endswith(".log"): - try: - with open(os.path.join(dossier, fichier), "w") as f: - pass - nb_vides += 1 - except Exception: - pass - -print(f"{nb_vides} fichiers log vidés.") -""" - -# ============================================================================ -# FIN DU SCRIPT -# ============================================================================ \ No newline at end of file diff --git a/telegram_watch.py b/telegram_watch.py new file mode 100644 index 0000000..a6e13d4 --- /dev/null +++ b/telegram_watch.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +import argparse +import ipaddress +import shutil +import subprocess +import sys +from collections import defaultdict + +import geoip2.database +from rich.console import Console +from rich.table import Table +from rich import box + +console = Console() + + +def is_public_ip(ip: str) -> bool: + try: + addr = ipaddress.ip_address(ip) + return not ( + addr.is_private + or addr.is_loopback + or addr.is_multicast + or addr.is_link_local + or addr.is_reserved + ) + except ValueError: + return False + + +def geo_lookup(reader, ip: str): + try: + r = reader.country(ip) + iso = r.country.iso_code or "??" + name = r.country.name or "Inconnu" + return iso, name + except Exception: + return "??", "Inconnu" + + +def run_tshark_capture(interface: str, duration: int, output_pcap: str): + if shutil.which("tshark") is None: + console.print("[red]tshark n'est pas installé ou pas dans le PATH.[/red]") + sys.exit(1) + + cmd = [ + "tshark", + "-i", interface, + "-a", f"duration:{duration}", + "-w", output_pcap, + ] + + console.print( + f"[cyan]Capture en cours sur {interface} pendant {duration} s...[/cyan]\n" + "[yellow]Envoie maintenant tes messages Telegram.[/yellow]" + ) + result = subprocess.run(cmd) + if result.returncode != 0: + console.print("[red]La capture TShark a échoué.[/red]") + sys.exit(result.returncode) + + +def extract_remote_ips(pcap_file: str, local_ip: str = None): + fields = ["-e", "ip.src", "-e", "ip.dst", "-e", "tcp.dstport", "-e", "udp.dstport"] + + cmd = [ + "tshark", + "-r", pcap_file, + "-T", "fields", + "-E", "separator=|", + "-E", "occurrence=f", + *fields, + ] + + proc = subprocess.run(cmd, capture_output=True, text=True) + if proc.returncode != 0: + console.print("[red]Impossible de relire le fichier pcapng.[/red]") + console.print(proc.stderr) + sys.exit(proc.returncode) + + stats = defaultdict(lambda: {"count": 0, "ports": set(), "direction": set()}) + + for line in proc.stdout.splitlines(): + parts = line.split("|") + if len(parts) < 4: + continue + + src = parts[0].strip() + dst = parts[1].strip() + tcp_port = parts[2].strip() + udp_port = parts[3].strip() + + if not src or not dst: + continue + + if local_ip: + if src == local_ip and is_public_ip(dst): + remote = dst + direction = "sortant" + elif dst == local_ip and is_public_ip(src): + remote = src + direction = "entrant" + else: + continue + else: + candidates = [] + if is_public_ip(src): + candidates.append((src, "entrant")) + if is_public_ip(dst): + candidates.append((dst, "sortant")) + for remote, direction in candidates: + stats[remote]["count"] += 1 + stats[remote]["direction"].add(direction) + if tcp_port: + stats[remote]["ports"].add(f"tcp/{tcp_port}") + if udp_port: + stats[remote]["ports"].add(f"udp/{udp_port}") + continue + + stats[remote]["count"] += 1 + stats[remote]["direction"].add(direction) + if tcp_port: + stats[remote]["ports"].add(f"tcp/{tcp_port}") + if udp_port: + stats[remote]["ports"].add(f"udp/{udp_port}") + + return stats + + +def render_table(stats, mmdb_path: str): + reader = geoip2.database.Reader(mmdb_path) + + table = Table( + title="IP observées pendant l'activité Telegram", + box=box.SIMPLE_HEAVY, + show_lines=False + ) + table.add_column("IP", style="bold") + table.add_column("Pays") + table.add_column("Code") + table.add_column("Direction") + table.add_column("Ports") + table.add_column("Paquets", justify="right") + table.add_column("Alerte") + + rows = [] + for ip, data in stats.items(): + iso, country = geo_lookup(reader, ip) + non_fr = iso != "FR" + rows.append(( + non_fr, + ip, + country, + iso, + ",".join(sorted(data["direction"])), + ", ".join(sorted(data["ports"]))[:80], + str(data["count"]), + "NON FR" if non_fr else "" + )) + + rows.sort(key=lambda r: (not r[0], r[1])) + + non_fr_count = 0 + for non_fr, ip, country, iso, direction, ports, count, alert in rows: + if non_fr: + non_fr_count += 1 + table.add_row( + f"[red]{ip}[/red]", + f"[red]{country}[/red]", + f"[red]{iso}[/red]", + f"[red]{direction}[/red]", + f"[red]{ports}[/red]", + f"[red]{count}[/red]", + f"[bold red]{alert}[/bold red]", + ) + else: + table.add_row( + ip, country, iso, direction, ports, count, "" + ) + + console.print(table) + console.print( + f"\n[bold]Total IP publiques observées :[/bold] {len(rows)}\n" + f"[bold red]IP non françaises :[/bold red] {non_fr_count}" + ) + + +def main(): + parser = argparse.ArgumentParser( + description="Surveille les IP vues pendant une activité Telegram et met en évidence les IP non françaises." + ) + parser.add_argument("-i", "--interface", default="ens3", help="Interface réseau, ex: ens3") + parser.add_argument("-d", "--duration", type=int, default=20, help="Durée de capture en secondes") + parser.add_argument("-o", "--output", default="telegram-test.pcapng", help="Fichier de capture") + parser.add_argument("--local-ip", help="IP locale à filtrer, ex: 192.168.1.112") + parser.add_argument( + "--mmdb", + default="/usr/share/GeoIP/GeoLite2-Country.mmdb", + help="Chemin vers GeoLite2-Country.mmdb" + ) + parser.add_argument( + "--read-only", + action="store_true", + help="N'effectue pas de capture, relit seulement le fichier -o" + ) + + args = parser.parse_args() + + if not args.read_only: + run_tshark_capture(args.interface, args.duration, args.output) + + stats = extract_remote_ips(args.output, args.local_ip) + + if not stats: + console.print("[yellow]Aucune IP publique trouvée dans cette capture.[/yellow]") + sys.exit(0) + + render_table(stats, args.mmdb) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/test_synology_chat.py b/test_synology_chat.py new file mode 100644 index 0000000..a1280a3 --- /dev/null +++ b/test_synology_chat.py @@ -0,0 +1,20 @@ +import json +import requests + +WEBHOOK_URL = "https://mj91.fr/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=UN7nhD70vrhrHFh1VeDdOpsklIHiIFRop2qB7b6YusMEY3clY3R8CXe4hFzz4KKc" + +payload = { + "text": "✅ Test Python vers Synology Chat" +} + +try: + r = requests.post( + WEBHOOK_URL, + data={"payload": json.dumps(payload, ensure_ascii=False)}, + timeout=10, + verify=True + ) + print("HTTP:", r.status_code) + print("Réponse:", r.text) +except Exception as e: + print("Erreur:", repr(e)) \ No newline at end of file diff --git a/watcher.py b/watcher.py new file mode 100644 index 0000000..0950c1e --- /dev/null +++ b/watcher.py @@ -0,0 +1,183 @@ +import json +import os +import sqlite3 +import time +from datetime import datetime +from ipaddress import ip_address + +import geoip2.database + +DB_PATH = "/host_logs/synolog/.SYNOCONNDB" +GEOIP_DB = "/config/GeoLite2-Country.mmdb" +STATE_FILE = "/data/state.json" +OUTPUT_LOG = "/logs/watcher.log" + +POLL_SECONDS = 60 + +KNOWN_USERS = { + "Michel", + "Samsung_A13", + "Aurélie", + "Gilberte", + "Chloé", +} + +WHITELIST_IPS = { + "192.168.1.254", + "192.168.1.90", + "192.168.1.80", +} + +WATCH_CHAT_ONLY = True + +def load_state(): + if os.path.exists(STATE_FILE): + try: + with open(STATE_FILE, "r", encoding="utf-8") as f: + return json.load(f) + except Exception: + return {} + return {} + +def save_state(state): + with open(STATE_FILE, "w", encoding="utf-8") as f: + json.dump(state, f, indent=2) + +def log_event(message): + ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + line = f"[{ts}] {message}\n" + with open(OUTPUT_LOG, "a", encoding="utf-8") as f: + f.write(line) + print(line, end="") + +def is_private_ip(value: str) -> bool: + try: + ip = ip_address(value) + return ip.is_private or ip.is_loopback + except Exception: + return False + +def get_country_code(reader, ip: str): + if not ip or is_private_ip(ip): + return "PRIVATE" + try: + response = reader.country(ip) + return response.country.iso_code or "UNKNOWN" + except Exception: + return "UNKNOWN" + +def fetch_new_rows(last_id: int): + if not os.path.exists(DB_PATH): + log_event(f"ERREUR base introuvable: {DB_PATH}") + return [] + + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + + query = """ + SELECT id, time, level, username, msg, user, uid, ip, protocol, token, useragent + FROM logs + WHERE id > ? + ORDER BY id ASC + """ + + rows = conn.execute(query, (last_id,)).fetchall() + conn.close() + return rows + +def classify_event(row, reader): + username = (row["username"] or "").strip() + ip = (row["ip"] or "").strip() + protocol = (row["protocol"] or "").strip() + msg = (row["msg"] or "").strip() + useragent = (row["useragent"] or "").strip() + + if WATCH_CHAT_ONLY and "Synology_Chat" not in useragent: + return None + + reasons = [] + level = "INFO" + country = get_country_code(reader, ip) + + if username not in KNOWN_USERS: + level = "ALERTE" + reasons.append("user_inconnu") + + if ip in WHITELIST_IPS: + reasons.append("ip_whitelist") + country = "PRIVATE" + + if is_private_ip(ip): + reasons.append("ip_privee") + else: + reasons.append("ip_publique") + + if country == "FR": + reasons.append("pays_fr") + if level == "INFO": + level = "INFO" + elif country == "UNKNOWN": + reasons.append("pays_inconnu") + if level != "ALERTE": + level = "SURVEILLANCE" + else: + reasons.append(f"pays_{country}") + if level != "ALERTE": + level = "SUSPECT" + + if "failed" in msg.lower(): + reasons.append("echec_connexion") + level = "ALERTE" + + return { + "id": row["id"], + "dt": datetime.fromtimestamp(row["time"]).strftime("%Y-%m-%d %H:%M:%S"), + "username": username, + "ip": ip, + "protocol": protocol, + "country": country, + "level": level, + "reasons": ",".join(reasons), + "msg": msg, + } + +def main(): + if not os.path.exists(GEOIP_DB): + raise FileNotFoundError(f"Base GeoIP introuvable: {GEOIP_DB}") + + log_event("Démarrage ipwatcher sqlite + GeoIP") + state = load_state() + last_id = state.get("last_id", 0) + + with geoip2.database.Reader(GEOIP_DB) as reader: + while True: + try: + rows = fetch_new_rows(last_id) + for row in rows: + last_id = row["id"] + event = classify_event(row, reader) + if event is None: + continue + + log_event( + f"{event['level']} " + f"id={event['id']} " + f"dt={event['dt']} " + f"user={event['username']} " + f"ip={event['ip']} " + f"country={event['country']} " + f"protocol={event['protocol']} " + f"reasons={event['reasons']} " + f"msg={event['msg']}" + ) + + state["last_id"] = last_id + save_state(state) + + except Exception as e: + log_event(f"ERREUR traitement sqlite: {e}") + + time.sleep(POLL_SECONDS) + +if __name__ == "__main__": + main() \ No newline at end of file