Rdeirection BDE
This commit is contained in:
16
.env
16
.env
@@ -1,18 +1,4 @@
|
|||||||
DB_HOST=162.19.78.131
|
DB_HOST=192.168.1.100
|
||||||
DB_USER=SG
|
DB_USER=SG
|
||||||
DB_PASSWORD=A*Rb2Q@i72m8tM6@!$*
|
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<cuisine@bw-paris-saclay.com>
|
|
||||||
EMAIL_MEUDON=meudon@domo91.fr
|
|
||||||
EMAIL_DEFAULT=services@domo91.fr
|
|
||||||
4
.idea/Fichiers_perso.iml
generated
4
.idea/Fichiers_perso.iml
generated
@@ -1,7 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$" />
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.11 virtualenv at C:\Users\miche\PycharmProjects\Gestion_sondes\.venv" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.11 virtualenv at C:\Users\miche\PycharmProjects\Gestion_sondes\.venv" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
2
.idea/dataSources.local.xml
generated
2
.idea/dataSources.local.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="dataSourceStorageLocal" created-in="PY-252.25557.178">
|
<component name="dataSourceStorageLocal" created-in="PY-261.23567.174">
|
||||||
<data-source name="SG" uuid="068e6b2a-97f7-4f17-ae58-a11df8c5f332">
|
<data-source name="SG" uuid="068e6b2a-97f7-4f17-ae58-a11df8c5f332">
|
||||||
<database-info product="MariaDB" version="10.11.11-MariaDB-0+deb12u1" jdbc-version="4.2" driver-name="MariaDB Connector/J" driver-version="3.3.3" dbms="MARIADB" exact-version="10.11.11" exact-driver-version="3.3">
|
<database-info product="MariaDB" version="10.11.11-MariaDB-0+deb12u1" jdbc-version="4.2" driver-name="MariaDB Connector/J" driver-version="3.3.3" dbms="MARIADB" exact-version="10.11.11" exact-driver-version="3.3">
|
||||||
<extra-name-characters>#@</extra-name-characters>
|
<extra-name-characters>#@</extra-name-characters>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<dataSource name="SG">
|
<dataSource name="SG">
|
||||||
<database-model serializer="dbm" dbms="MARIADB" family-id="MARIADB" format-version="4.53">
|
<database-model serializer="dbm" dbms="MARIADB" family-id="MARIADB" format-version="4.55">
|
||||||
<root id="1">
|
<root id="1">
|
||||||
<DefaultCasing>exact</DefaultCasing>
|
<DefaultCasing>exact</DefaultCasing>
|
||||||
<DefaultEngine>InnoDB</DefaultEngine>
|
<DefaultEngine>InnoDB</DefaultEngine>
|
||||||
|
|||||||
23
.idea/dataSources/data_sources_history.xml
generated
Normal file
23
.idea/dataSources/data_sources_history.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<DataSourcesHistory>
|
||||||
|
<DataSourceFromHistory isRemovedFromProject="false">
|
||||||
|
<data-source source="LOCAL" name="SG" uuid="068e6b2a-97f7-4f17-ae58-a11df8c5f332">
|
||||||
|
<database-info product="MariaDB" version="10.11.11-MariaDB-0+deb12u1" jdbc-version="4.2" driver-name="MariaDB Connector/J" driver-version="3.3.3" dbms="MARIADB" exact-version="10.11.11" exact-driver-version="3.3">
|
||||||
|
<extra-name-characters>#@</extra-name-characters>
|
||||||
|
<identifier-quote-string>`</identifier-quote-string>
|
||||||
|
</database-info>
|
||||||
|
<case-sensitivity plain-identifiers="exact" quoted-identifiers="exact" />
|
||||||
|
<driver-ref>mariadb</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mariadb://162.19.78.131:3306/SG</jdbc-url>
|
||||||
|
<secret-storage>master_key</secret-storage>
|
||||||
|
<user-name>SG</user-name>
|
||||||
|
<schema-mapping>
|
||||||
|
<introspection-scope>
|
||||||
|
<node kind="schema" qname="@" />
|
||||||
|
</introspection-scope>
|
||||||
|
</schema-mapping>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</DataSourceFromHistory>
|
||||||
|
</DataSourcesHistory>
|
||||||
69
FICHIERS SUPERVISOR.txt
Normal file
69
FICHIERS SUPERVISOR.txt
Normal file
@@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ conn = mysql.connector.connect(
|
|||||||
host=os.getenv("DB_HOST"),
|
host=os.getenv("DB_HOST"),
|
||||||
user=os.getenv("DB_USER"),
|
user=os.getenv("DB_USER"),
|
||||||
password=os.getenv("DB_PASSWORD"),
|
password=os.getenv("DB_PASSWORD"),
|
||||||
database="SG"
|
database="Societe_Generale"
|
||||||
)
|
)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
|||||||
23
TestMysqlConnector.py
Normal file
23
TestMysqlConnector.py
Normal file
@@ -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)
|
||||||
55
Test_SMS.py
Normal file
55
Test_SMS.py
Normal file
@@ -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")})
|
||||||
@@ -82,10 +82,10 @@ def on_message(_client, _userdata, message):
|
|||||||
|
|
||||||
|
|
||||||
# Configuration du client MQTT
|
# 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_port = 1883 # Vérifiez si votre broker utilise un autre port
|
||||||
mqtt_user = "Bwps"
|
mqtt_user = "sondes"
|
||||||
mqtt_password = "scJ5ACj2keRfI^"
|
mqtt_password = "3J@bjYP0"
|
||||||
mqtt_topic = "Tracker/sondes"
|
mqtt_topic = "Tracker/sondes"
|
||||||
|
|
||||||
client = mqtt.Client()
|
client = mqtt.Client()
|
||||||
|
|||||||
@@ -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
|
|
||||||
# ============================================================================
|
|
||||||
222
telegram_watch.py
Normal file
222
telegram_watch.py
Normal file
@@ -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()
|
||||||
20
test_synology_chat.py
Normal file
20
test_synology_chat.py
Normal file
@@ -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))
|
||||||
183
watcher.py
Normal file
183
watcher.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user