Commit 0e84b5e0 authored by Pierre-Elliott Bécue's avatar Pierre-Elliott Bécue
Browse files

[mac_prises] On envoie plus qu'un email par jour, qui contient un résumé...

[mac_prises] On envoie plus qu'un email par jour, qui contient un résumé qualitatif, et des détails en pièce jointe.
parent 46bf7e47
......@@ -8,45 +8,32 @@ hargneux = True
#: Si pour une chambre donnée, il y a plus de 300 entrées filaires
#: n'appartenant pas à l'adhérent propriétaire de la mac, on prévient.
max_inconnues_par_jour = 300
max_inconnues_par_jour = 480
#: Titre utilisé dans le corps du message
titre_mac_inconnue = u"Repérage de macs potentiellement non désirées dans les chambres suivantes."
#: Pour la recherche dans postgres
delay = { 'instant': '2 min',
'heuristique': '30 min',
delay = { 'instantanne': '2 min',
'moyen': '30 min',
'journalier': '1 day',
}
#: Contient trois dictionnaire. Le paramètre mac signifie "combien de chambres doivent voir la même mac pour que ça soit suspect"
#: Le paramètre chambre signifie "combien de macs doivent traverser une même chambre pour que ça soit suspect"
suspect = { 'instant':{'mac': 2, 'chambre': 2},
'heuristique':{'mac': 2, 'chambre': 2},
'journalier':{'mac': 2, 'chambre': 2},
suspect = { 'instantanne':2,
'moyen':2,
'journalier':2,
}
#: Contient trois dictionnaire. Le paramètre mac signifie "combien de chambres doivent voir la même mac pour que ça soit très suspect"
#: Le paramètre chambre signifie "combien de macs doivent traverser une même chambre pour que ça soit très suspect"
tres_suspect = { 'instant':{'mac': 3, 'chambre': 3},
'heuristique':{'mac': 3, 'chambre': 3},
'journalier':{'mac': 3, 'chambre': 3},
}
#: Le point central des analyses.
rapport_suspect = { 'instant':{'mac': 0.51, 'chambre': 0.51},
'heuristique':{'mac': 0.4, 'chambre': 0.55},
'journalier':{'mac': 0.2, 'chambre': 0.58},
rapport_suspect = { 'instantanne':0.51,
'moyen':0.4,
'journalier':0.2,
}
#: Titre des paragraphes suspects
titre_suspect = { 'instant':{'mac': u"Macs se baladant un peu trop entre les chambres (instantanné)", 'chambre': u"Chambres avec un peu trop de macs (instantanné)"},
'heuristique':{'mac': u"Macs se baladant un peu trop entre les chambres (délai moyen)", 'chambre': u"Chambres avec un peu trop de macs (délai moyen)"},
'journalier':{'mac': u"Macs s'étant peut-être un peu trop baladées aujourd'hui", 'chambre': u"Chambres avec un peu trop de macs sur un jour"},
titre_suspect = { 'instantanne':u"Macs se baladant un peu trop entre les chambres (instantanneanné)",
'moyen':u"Macs se baladant un peu trop entre les chambres (délai moyen)",
'journalier':u"Macs s'étant peut-être un peu trop baladées aujourd'hui",
}
#: Titre des paragraphes très suspects
titre_tres_suspect = { 'instant':{'mac': u"/!\ Spoof potentiel des macs suivantes dans les chambres ci-après (instantanné)", 'chambre': u"/!\ Les chambres ci-après contiennent étrangement trop de macs inconnues ! (instantanné)"},
'heuristique':{'mac': u"/!\ Spoof potentiel des macs suivantes dans les chambres ci-après (délai moyen)", 'chambre': u"/!\ Les chambres ci-après contiennent étrangement trop de macs inconnues ! (délai moyen)"},
'journalier':{'mac': u"/!\ Spoof probable des macs suivantes dans les chambres ci-après aujourd'hui !!", 'chambre': u"/!\ Les chambres suivantes contiennent trop de macs inconnues (sur un jour)"},
}
......@@ -2,14 +2,14 @@
# -*- coding: utf8 -*-
#
#
# PEB - 01/04/2012
# PEB - 01/04/2012 -> now()
#
import os, sys, re
import os, sys
import sys
import re
from commands import getstatusoutput
import time
# nécessite apparemment que l'objet conn soit bien créé lors de l'exec
# de annuaires_pg, il faut être root (ou dans je ne sais quel groupe)
......@@ -103,14 +103,7 @@ def __exec(cmd):
if __name__ == '__main__':
switchs = sys.argv[1:]
date = time.strftime('%F %T')
for switch in switchs:
macs = liste_chambres_macs(switch)
print macs
# fichier =
# for chambre in macs.keys():
# for mac in macs[chambre]:
#
# curseur.execute(requete, (date, chambre, mac))
......@@ -12,9 +12,14 @@ from config import mac_prise
from affich_tools import tableau
sys.path.append('/usr/scripts/lc_ldap')
import lc_ldap
import collections
ldap = lc_ldap.lc_ldap_local()
conn = psycopg2.connect(user='crans', database='mac_prises')
conn.set_session(autocommit = True)
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor)
membres_actifs = ldap.search('(|(droits=Cableur)(droits=Nounou)(droits=Apprenti)(droits=Bureau))')
chambres_ma = []
for membre_actif in membres_actifs:
......@@ -31,20 +36,12 @@ for club in clubs:
except:
pass
conn = psycopg2.connect(user='crans', database='mac_prises')
conn.set_session(autocommit = True)
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor)
requete = "SELECT * FROM signales WHERE date >= timestamp 'now' - interval '1 day';"
cur.execute(requete)
signales = cur.fetchall()
longueur = { 'mac': (24, 3),
'chambre': (10, 7),
}
titres = { 'mac':(u'mac', u'chambres'),
'chambre': (u'chambre', u'macs'),
}
longueur = (24, 7)
titres = (u'mac', u'chambres')
alignements = ('c', 'c')
def lin(x, y, z):
......@@ -53,87 +50,168 @@ def lin(x, y, z):
"""
return (float(x)/float(y)-1.0)/(float(z)-1.0)
def genere_comptage(duree, groupe, associes):
def genere_comptage(duree):
"""
Grosse fonction de hack pour ne pas écrire six fois le même formatage de sortie bidon.
Prend en argument :
* duree, qui peut valoir 'instant', 'heuristique', ou 'journalier', permet d'importer
les variables de config qui vont bien
* groupe, qui précise selon quoi on groupe (mac ou chambre)
* associes, qui contient l'autre champ
"""
global date
global Logs
pb_comptage_suspect = {}
output = ""
requete = "SELECT array_to_string(array_agg(DISTINCT date), ', ') AS dates , %(groupe)s, array_to_string(array_agg(DISTINCT %(associes)s), ', ') AS %(associes)ss, COUNT(DISTINCT %(associes)s) AS nb_%(associes)ss_distinctes, COUNT(%(associes)s) AS nb_%(associes)ss, COUNT(DISTINCT date) as nb_dates_distinctes, COUNT(DISTINCT %(groupe)s) as nb_%(groupe)ss_distinctes FROM correspondance WHERE date >= timestamp 'now' - interval '%(delay)s' GROUP BY %(groupe)s;" % { 'groupe': groupe, 'associes': associes, 'delay': mac_prise.delay[duree] }
requete = "SELECT array_to_string(array_agg(DISTINCT date), ', ') AS dates , mac, array_to_string(array_agg(DISTINCT chambre), ', ') AS chambres, COUNT(DISTINCT chambre) AS nb_chambres_distinctes, COUNT(chambre) AS nb_chambres, COUNT(DISTINCT date) as nb_dates_distinctes, COUNT(DISTINCT mac) as nb_macs_distinctes FROM correspondance WHERE date >= timestamp 'now' - interval '%(delay)s' GROUP BY mac;" % {'delay': mac_prise.delay[duree]}
cur.execute(requete)
fetched = cur.fetchall()
for entry in fetched:
if groupe == "mac":
machines = ldap.search('(macAddress=%s)' % entry[groupe])
if len(machines) > 0:
if isinstance(machines[0], lc_ldap.machineWifi):
continue
else:
machines = ldap.search('(macAddress=%s)' % entry['mac'])
if len(machines) > 0:
if isinstance(machines[0], lc_ldap.machineWifi):
continue
else:
continue
if entry['nb_'+associes+'s_distinctes'] >= mac_prise.suspect[duree][groupe]:
Logs.append(u"Recherche par %s, entrée suspecte : %s -> %s : %s distinctes sur %s dates distinctes\n" % (groupe, entry[groupe], entry[associes+'s'], entry['nb_'+associes+'s'], entry['nb_dates_distinctes']))
liste_associes = entry[associes+'s'].split(', ')
if entry['nb_chambres_distinctes'] >= mac_prise.suspect[duree]:
Logs.append(u"Recherche par %s, entrée suspecte : %s -> %s : %s distinctes sur %s dates distinctes\n" % ('mac', entry['mac'], entry['chambres'], entry['nb_chambres'], entry['nb_dates_distinctes']))
liste_chambres = entry['chambres'].split(', ')
# On calcule la "probabilité" qu'un truc ne soit pas clair concernant la chambre/mac
rapport = lin(entry['nb_'+associes+'s'], entry['nb_dates_distinctes'], float(entry['nb_'+associes+'s_distinctes']))
if rapport >= mac_prise.rapport_suspect[duree][groupe]:
Logs.append(u"Entrée ajoutée au tableau %s pour la recherche par %s, car rapport supérieur au seuil.\n\n" % (duree, groupe))
pb_comptage_suspect[entry[groupe]] = (liste_associes, rapport, mac_prise.rapport_suspect[duree][groupe])
rapport = lin(entry['nb_chambres'], entry['nb_dates_distinctes'], float(entry['nb_chambres_distinctes']))
if rapport >= mac_prise.rapport_suspect[duree]:
Logs.append(u"Entrée ajoutée au tableau %s, car rapport supérieur au seuil.\n\n" % (duree))
pb_comptage_suspect[entry['mac']] = (liste_chambres, rapport, mac_prise.rapport_suspect[duree])
else:
Logs.append(u"Entrée rejetée du tableau %s pour la recherche par %s, car rapport inférieur au seuil : (%s).\n\n" % (duree, groupe, rapport))
Logs.append(u"Entrée rejetée du tableau %s, car rapport inférieur au seuil : (%s).\n\n" % (duree, rapport))
else:
pass
if len(pb_comptage_suspect) > 0:
output += mac_prise.titre_suspect[duree][groupe] + "\n"
# On prend la longueur de la plus longue valeur, on s'assure que cette longueur fait celle de la légende, plus un entier de marge
longueur_max = max([len(", ".join(a[0])) for a in pb_comptage_suspect.values()] + [longueur[associes][1]]) + 4
largeurs = (longueur[groupe][0], longueur_max, 11, 9)
titre = (titres[groupe][0], titres[groupe][1], "rapport", "seuil")
alignement = (alignements[0], alignements[1], 'c', 'c')
for clef, valeur in pb_comptage_suspect.items():
requete = "INSERT INTO spotted (mac, type, chambre, date, rapport, seuil) VALUES(%s, %s, %s, %s, %s, %s);"
cur.execute(requete, (clef, duree, ", ".join(valeur[0]), date, valeur[1], valeur[2]))
return None
def summary():
"""
Fonction résumant les entrées "spotted" des dernières 24h
"""
output = u""
Logs = u""
requete = "SELECT * FROM spotted WHERE date >= timestamp 'now' - interval '1 day' ORDER BY date ASC;"
cur.execute(requete)
fetched = cur.fetchall()
liste_triee = collections.defaultdict(dict)
for entry in fetched:
chbres = entry['chambre'].split(', ')
chbres.sort()
chbres = tuple(chbres)
if liste_triee[entry['mac']].has_key(entry['type']):
if liste_triee[entry['mac']][entry['type']].has_key(chbres):
liste_triee[entry['mac']][entry['type']][chbres].append((entry['date'], entry['rapport'], entry['seuil']))
else:
liste_triee[entry['mac']][entry['type']][chbres] = []
else:
liste_triee[entry['mac']][entry['type']] = {}
for mac, ltype in liste_triee.items():
data = []
output += u"Il y a eu une activité suspecte concernant la mac %s :\n\n" % mac
Logs += u"Activité pour %s :\n" % mac
for dtype, dataset in ltype.items():
number = 0
chambres = set()
for chbres, dataframe in dataset.items():
chambres.update(chbres)
number += len(dataframe)
data += [[dtype, ", ".join(list(chbres)), donnee[0], donnee[1], donnee[2]] for donnee in dataframe]
output += u'Sur le relevé %s, la mac est apparue %s fois. Elle est apparue dans les chambres suivantes : %s.\n' % (dtype, number, ", ".join(list(chambres)))
output += u'Consulter les logs (en PJ) pour plus d\'informations.\n\n'
maxlen = max([len(a[1]) for a in data]) + 4
titres = (u'relevé', u'chambres', u'date', u'rapport', u'seuil')
alignements = ('c', 'c', 'c', 'c', 'c')
largeurs = (12, maxlen, 20, 10, 10)
Logs += tableau(data, titres, largeurs, alignements)
Logs += u"\n\n"
return output, Logs
def reperage_mac_inconnue():
"""
Fonction de repérage d'une mac qui ne devrait pas être
dans telle chambre, sur une plage de 24h, suivant un
paramètre de config pour le nombre d'occurrences.
"""
output = u""
probleme = {}
requete = "SELECT chambre, mac, COUNT(mac) as nb_min FROM correspondance WHERE date >= timestamp 'now' - interval '24 hours' GROUP BY chambre, mac ORDER BY chambre ASC;"
cur.execute(requete)
fetched = cur.fetchall()
liste_parsee = collections.defaultdict(dict)
for entry in fetched:
liste_parsee[entry['chambre']][entry['mac']] = int(entry['nb_min'])
for chambre in liste_parsee.keys():
if chambre in chambres_ma + chambres_clubs:
continue
for mac in liste_parsee[chambre].keys():
try:
proprio_associe = ldap.search('macAddress=%s' % mac)[0].proprio()
if str(proprio_associe['chbre'][0]).lower() == chambre.lower():
garbage = liste_parsee[chambre].pop(mac)
except:
pass
number = sum(liste_parsee[chambre].values())
if number >= mac_prise.max_inconnues_par_jour:
probleme[chambre] = (liste_parsee[chambre].keys(), number)
if len(probleme) > 0:
output += mac_prise.titre_mac_inconnue+"\n"
longueur_max = max([len(", ".join(a[0])) for a in probleme.values()] + [len("macs")]) + 2
largeurs = (len('chambre') + 2, longueur_max, len('compteur') + 2, len('seuil') + 2)
data = []
clefs = pb_comptage_suspect.keys()
clefs = probleme.keys()
clefs.sort()
for clef in clefs:
data.append([clef, ", ".join(pb_comptage_suspect[clef][0]), pb_comptage_suspect[clef][1], pb_comptage_suspect[clef][2]])
output += tableau(data, titre, largeurs, alignement)
data.append([clef, ", ".join(probleme[clef][0]), probleme[clef][1], mac_prise.max_inconnues_par_jour])
output += tableau(data, ('chambre', 'macs', 'compteur', 'seuil'), largeurs, ('c', 'c', 'c', 'c'))
output += u"\n\n\n"
return output
if __name__ == '__main__':
output = u"Détection de spoof potentiel\n\n\n"
coupure = len(output)
Logs = [u"Logs du script mac_prise_analyzer\n\n\n"]
output += genere_comptage('instant', 'mac', 'chambre')
output += genere_comptage('heuristique', 'mac', 'chambre')
output += genere_comptage('journalier', 'mac', 'chambre')
date = time.strftime('%F %T')
if len(sys.argv) >= 2:
output = u''
Logs = u''
if '--summary' in sys.argv or 'summary' in sys.argv:
# On envoie le résumé
output += u' *Résumé des apparitions de mac dans plusieurs chambres*\n\n'
texte, Logs = summary()
output += texte
output += u"\n\n"
if '--reperage' in sys.argv or 'reperage' in sys.argv:
# On envoie le repérage
output += u' *Repérage de spoof potentiel par comptage*\n\n'
output += reperage_mac_inconnue()
if time.localtime().tm_min % 30 == 0 and mac_prise.hargneux:
hargneux = True
else:
hargneux = False
if len(output) == coupure and not hargneux:
sys.exit(0)
message = """From: %(from)s
Logs = Logs.encode('UTF-8')
output = output.encode('UTF-8')
message = """From: %(from)s
To: %(to)s
Subject: %(subject)s
Subject: Résumé quotidien : mac_prises.
Content-Type: multipart/mixed; boundary="_424234545aaff-ffca234efff-556adceff5646_"
--_424234545aaff-ffca234efff-556adceff5646_
......@@ -141,9 +219,6 @@ Content-Type: text/plain; charset="UTF-8"
%(contenu)s
--
Script d'analyse mac_prise (en test)
--_424234545aaff-ffca234efff-556adceff5646_
Content-Type: text/plain; charset="UTF-8"
Content-Disposition: attachment; filename="logs_analyse"
......@@ -151,17 +226,46 @@ Content-Disposition: attachment; filename="logs_analyse"
%(logs)s
--_424234545aaff-ffca234efff-556adceff5646_--
"""
corps = message % { 'from': 'Spoofing watcher <spoof-watcher@crans.org>',
'to': 'test@lists.crans.org',
'subject': 'Analyse du spoofing',
'contenu': output,
'logs': "".join(Logs),
}
mail = smtplib.SMTP('localhost')
mailfrom = 'spoof-watcher@crans.org'
mailto = 'test@lists.crans.org'
mail.sendmail(mailfrom, mailto, corps.encode('utf-8'))
corps = message % { 'from': 'Spoofing watcher <spoof-watcher@crans.org>',
'to': 'test@lists.crans.org',
'subject': 'Résumé journalier ',
'contenu': output,
'logs': Logs,
}
mail = smtplib.SMTP('localhost')
mailfrom = 'spoof-watcher@crans.org'
mailto = 'test@lists.crans.org'
mail.sendmail(mailfrom, mailto, corps)
else:
Logs = [u"Logs du script mac_prise_analyzer\n\n\n"]
genere_comptage('instantanne')
genere_comptage('moyen')
genere_comptage('journalier')
if time.localtime().tm_min % 30 == 0 and mac_prise.hargneux:
hargneux = True
else:
hargneux = False
if hargneux:
message = """From: %(from)s
To: %(to)s
Subject: %(subject)s
Content-Type: text/plain; charset="UTF-8"
%(logs)s
"""
corps = message % { 'from': 'Spoofing watcher <spoof-watcher@crans.org>',
'to': 'test@lists.crans.org',
'subject': 'Logs de mac_prises_analyzer',
'logs': "".join(Logs),
}
mail = smtplib.SMTP('localhost')
mailfrom = 'spoof-watcher@crans.org'
mailto = 'test@lists.crans.org'
mail.sendmail(mailfrom, mailto, corps.encode('utf-8'))
#!/bin/sh
SUMMARY='/usr/scripts/surveillance/mac_prises/mac_prises_analyzer.py --summary --reperage'
psql -U crans mac_prises -c "DELETE FROM correspondance WHERE date <= timestamp 'now' - interval '2 days';" 1>/dev/null
psql -U crans mac_prises -c "DELETE FROM spotted WHERE date <= timestamp 'now' - interval '2 days';" 1>/dev/null
#!/usr/bin/env python
# -*- coding: utf8 -*-
import psycopg2
import psycopg2.extras
import sys
import smtplib
sys.path.append('/usr/scripts/gestion')
from config import mac_prise
from affich_tools import tableau
sys.path.append('/usr/scripts/lc_ldap')
import lc_ldap
import collections
ldap = lc_ldap.lc_ldap_local()
membres_actifs = ldap.search('(|(droits=Cableur)(droits=Nounou)(droits=Apprenti)(droits=Bureau))')
chambres_ma = []
for membre_actif in membres_actifs:
try:
chambres_ma.append(str(membre_actif['chbre'][0]).lower())
except:
pass
clubs = ldap.search('cid=*')
chambres_clubs = []
for club in clubs:
try:
chambres_clubs.append(str(club['chbre'][0]).lower())
except:
pass
conn = psycopg2.connect(user='crans', database='mac_prises')
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor)
def reperage_mac_inconnue():
"""
Fonction de repérage d'une mac qui ne devrait pas être
dans telle chambre, sur une plage de 24h, suivant un
paramètre de config pour le nombre d'occurrences.
Sans doute le truc le plus important, sera en tête du mail
"""
output = u""
probleme = {}
requete = "SELECT chambre, mac, COUNT(mac) as nb_min FROM correspondance WHERE date >= timestamp 'now' - interval '24 hours' GROUP BY chambre, mac ORDER BY chambre ASC;"
cur.execute(requete)
fetched = cur.fetchall()
liste_parsee = collections.defaultdict(dict)
for entry in fetched:
liste_parsee[entry['chambre']][entry['mac']] = int(entry['nb_min'])
for chambre in liste_parsee.keys():
if chambre in chambres_ma + chambres_clubs:
continue
for mac in liste_parsee[chambre].keys():
try:
proprio_associe = ldap.search('macAddress=%s' % mac)[0].proprio()
if str(proprio_associe['chbre'][0]).lower() == chambre.lower():
garbage = liste_parsee[chambre].pop(mac)
except:
pass
number = sum(liste_parsee[chambre].values())
if number >= mac_prise.max_inconnues_par_jour:
probleme[chambre] = (liste_parsee[chambre].keys(), number)
if len(probleme) > 0:
output += mac_prise.titre_mac_inconnue+"\n"
longueur_max = max([len(", ".join(a[0])) for a in probleme.values()] + [len("macs")]) + 2
largeurs = (len('chambre') + 2, longueur_max, len('compteur') + 2, len('seuil') + 2)
data = []
clefs = probleme.keys()
clefs.sort()
for clef in clefs:
data.append([clef, ", ".join(probleme[clef][0]), probleme[clef][1], mac_prise.max_inconnues_par_jour])
output += tableau(data, ('chambre', 'macs', 'compteur', 'seuil'), largeurs, ('c', 'c', 'c', 'c'))
output += u"\n\n\n"
return output
if __name__ == '__main__':
output = u'Repérage de spoof potentiel par comptage'
coupure = len(output)
output += reperage_mac_inconnue()
if len(output) == coupure and not mac_prise.hargneux:
sys.exit(0)
message = """From: %(from)s
To: %(to)s
Subject: %(subject)s
Content-Type: text/plain; charset="UTF-8"
%(contenu)s
--
Script d'analyse mac_prise (en test)
"""
corps = message % { 'from': 'Spoofing watcher <spoof-watcher@crans.org>',
'to': 'test@lists.crans.org',
'subject': 'Analyse horaire du spoofing',
'contenu': output,
}
mail = smtplib.SMTP('localhost')
mailfrom = 'spoof-watcher@crans.org'
mailto = 'test@lists.crans.org'
mail.sendmail(mailfrom, mailto, corps.encode('utf-8'))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment