Rdeirection BDE

This commit is contained in:
2026-05-04 11:16:20 +02:00
parent 99300db2ca
commit f3af1428af
14 changed files with 605 additions and 83 deletions

16
.env
View File

@@ -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<cuisine@bw-paris-saclay.com>
EMAIL_MEUDON=meudon@domo91.fr
EMAIL_DEFAULT=services@domo91.fr

View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<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="sourceFolder" forTests="false" />
</component>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<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>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<DefaultCasing>exact</DefaultCasing>
<DefaultEngine>InnoDB</DefaultEngine>

View 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
View 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"

View File

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

23
TestMysqlConnector.py Normal file
View 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
View 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")})

View File

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

View File

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