freeradius/auth.py : implémente auth filaire
This commit is contained in:
parent
366814f8de
commit
e45546afdb
1 changed files with 145 additions and 114 deletions
|
@ -7,18 +7,6 @@
|
||||||
# qui peuvent être appelées (suivant les configurations) à certains moment de
|
# qui peuvent être appelées (suivant les configurations) à certains moment de
|
||||||
# l'éxécution.
|
# l'éxécution.
|
||||||
#
|
#
|
||||||
# Une telle fonction prend un uniquement argument, qui est une liste de tuples
|
|
||||||
# (clé, valeur)
|
|
||||||
# et renvoie un triplet dont les composantes sont :
|
|
||||||
# * le code de retour (voir radiusd.RLM_MODULE_* )
|
|
||||||
# * un tuple de couples (clé, valeur) pour les valeurs de réponse
|
|
||||||
# (access ok et autres trucs du genre)
|
|
||||||
# * un tuple de couples (clé, valeur) pour les valeurs internes à mettre à jour
|
|
||||||
# (mot de passe par exemple)
|
|
||||||
#
|
|
||||||
# Voir des exemples plus complets ici:
|
|
||||||
# https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
|
|
||||||
|
|
||||||
import lc_ldap.shortcuts
|
import lc_ldap.shortcuts
|
||||||
from lc_ldap.crans_utils import escape as escape_ldap
|
from lc_ldap.crans_utils import escape as escape_ldap
|
||||||
import lc_ldap.crans_utils
|
import lc_ldap.crans_utils
|
||||||
|
@ -28,24 +16,16 @@ import radiusd
|
||||||
import netaddr
|
import netaddr
|
||||||
import traceback
|
import traceback
|
||||||
from gestion.gen_confs.trigger import trigger_generate_cochon as trigger_generate
|
from gestion.gen_confs.trigger import trigger_generate_cochon as trigger_generate
|
||||||
|
import annuaires_pg
|
||||||
|
|
||||||
# Voilà, pour faire marcher le V6Only, il faut se retirer l'ipv4 de sa machine
|
# Voilà, pour faire marcher le V6Only, il faut se retirer l'ipv4 de sa machine
|
||||||
# ou en enregistrer une nouvelle (sans ipv4) avec une autre mac. Moi j'ai la
|
# ou en enregistrer une nouvelle (sans ipv4) avec une autre mac. Moi j'ai la
|
||||||
# flemme donc je hardcode les MAC qui doivent toujours être placées sur le vlan v6only
|
# flemme donc je hardcode les MAC qui doivent toujours être placées sur le vlan v6only
|
||||||
test_v6 = [
|
test_v6 = [
|
||||||
u'dc:9f:db:5c:c3:ea', # polynice-wlan0
|
u'00:26:c7:a6:9e:16', # cerveaulent (machine de b2moo)
|
||||||
#u'00:15:6d:ee:e8:f8', # atree-wlan0
|
|
||||||
u'00:26:c7:a6:9e:16', # cerveaulent
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO (à metre dans bcfg2)
|
USERNAME_SUFFIX = '.wifi.crans.org'
|
||||||
#setfacl -m u:freerad:rx /etc/crans/
|
|
||||||
#setfacl -m u:freerad:rx /etc/crans/secrets
|
|
||||||
#setfacl -m u:freerad:r /etc/crans/secrets/dhcp.py
|
|
||||||
#setfacl -m u:freerad:r /etc/crans/secrets/secrets.py
|
|
||||||
#setfacl -m u:freerad:r /etc/crans/secrets/trigger-generate.pub
|
|
||||||
#setfacl -m m::r /etc/crans/secrets/trigger-generate
|
|
||||||
#setfacl -m u:freerad:r /etc/crans/secrets/trigger-generate
|
|
||||||
|
|
||||||
bl_reject = [u'bloq']
|
bl_reject = [u'bloq']
|
||||||
bl_isolement = [u'virus', u'autodisc_virus', u'autodisc_p2p', u'ipv6_ra']
|
bl_isolement = [u'virus', u'autodisc_virus', u'autodisc_p2p', u'ipv6_ra']
|
||||||
|
@ -59,41 +39,65 @@ use_ldap_admin = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5,
|
||||||
use_ldap = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5,
|
use_ldap = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5,
|
||||||
constructor=lc_ldap.shortcuts.lc_ldap_anonymous)
|
constructor=lc_ldap.shortcuts.lc_ldap_anonymous)
|
||||||
|
|
||||||
@use_ldap
|
def radius_event(f):
|
||||||
def get_machines(auth_data, conn):
|
"""Décorateur pour les fonctions d'interfaces avec radius.
|
||||||
mac = None
|
Une telle fonction prend un uniquement argument, qui est une liste de tuples
|
||||||
username = None
|
(clé, valeur) et renvoie un triplet dont les composantes sont :
|
||||||
|
* le code de retour (voir radiusd.RLM_MODULE_* )
|
||||||
|
* un tuple de couples (clé, valeur) pour les valeurs de réponse (accès ok
|
||||||
|
et autres trucs du genre)
|
||||||
|
* un tuple de couples (clé, valeur) pour les valeurs internes à mettre à
|
||||||
|
jour (mot de passe par exemple)
|
||||||
|
Voir des exemples plus complets ici:
|
||||||
|
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
|
||||||
|
|
||||||
|
On se contente avec ce décorateur (pour l'instant) de convertir la liste de
|
||||||
|
tuples en entrée en un dictionnaire."""
|
||||||
|
|
||||||
|
def new_f(auth_data):
|
||||||
|
data = dict()
|
||||||
|
for (key, value) in auth_data or []:
|
||||||
# Beware: les valeurs scalaires sont entre guillemets
|
# Beware: les valeurs scalaires sont entre guillemets
|
||||||
# Calling-Station-Id: "une_adresse_mac"
|
# Ex: Calling-Station-Id: "une_adresse_mac"
|
||||||
for (key, value) in auth_data:
|
data[key] = value.replace('"', '')
|
||||||
if key == 'Calling-Station-Id':
|
return f(data)
|
||||||
|
|
||||||
|
return new_f
|
||||||
|
|
||||||
|
@use_ldap
|
||||||
|
def get_machines(data, conn):
|
||||||
|
"""Obtient la liste de machine essayant actuellement de se connecter"""
|
||||||
|
mac = data.get('Calling-Station-Id', None)
|
||||||
|
if mac:
|
||||||
try:
|
try:
|
||||||
value = value.decode('ascii', 'ignore').replace('"','')
|
mac = lc_ldap.crans_utils.format_mac(mac.decode('ascii', 'ignore'))
|
||||||
mac = lc_ldap.crans_utils.format_mac(value)
|
|
||||||
except:
|
except:
|
||||||
radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
|
radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
|
||||||
if key == 'User-Name':
|
mac = None
|
||||||
username = escape_ldap(value.decode('ascii', 'ignore').replace('"',''))
|
username = data.get('User-Name', None)
|
||||||
|
if username:
|
||||||
# TODO
|
username = escape_ldap(username.decode('ascii', 'ignore'))
|
||||||
# format username (strip .wifi.crans.org)
|
if username.endswith(USERNAME_SUFFIX):
|
||||||
|
username = username[:-len(USERNAME_SUFFIX)]
|
||||||
|
|
||||||
base = u'(objectclass=machine)'
|
base = u'(objectclass=machine)'
|
||||||
|
|
||||||
if mac is None:
|
if mac is None:
|
||||||
radiusd.radlog(radiusd.L_ERR, 'Cannot read client MAC from AP !')
|
radiusd.radlog(radiusd.L_ERR, 'Cannot read client MAC from AP !')
|
||||||
return []
|
if username is None:
|
||||||
|
radiusd.radlog(radiusd.L_ERR, 'Cannot read client User-Name !')
|
||||||
|
|
||||||
|
# Sanitize all the things
|
||||||
mac = escape_ldap(mac)
|
mac = escape_ldap(mac)
|
||||||
username = escape_ldap(username)
|
username = escape_ldap(username)
|
||||||
|
|
||||||
|
# Liste de filtres ldap à essayer
|
||||||
search_strats = [
|
search_strats = [
|
||||||
# Case 1: Search by mac (reported by AP)
|
# Case 1: Search by mac (reported by AP)
|
||||||
u'(&%s(macAddress=%s))' % (base, mac),
|
u'(&%s(macAddress=%s))' % (base, mac),
|
||||||
# Case 2: unregistered mac
|
# Case 2: unregistered mac
|
||||||
u'(&%s(macAddress=<automatique>)(host=%s.wifi.crans.org))' %
|
u'(&%s(macAddress=<automatique>)(host=%s%s))' %
|
||||||
(base, username),
|
(base, username, USERNAME_SUFFIX),
|
||||||
]
|
]
|
||||||
|
|
||||||
for filter_s in search_strats:
|
for filter_s in search_strats:
|
||||||
|
@ -103,50 +107,65 @@ def get_machines(auth_data, conn):
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_prise(auth_data):
|
def get_prise_chbre(data):
|
||||||
"""Extrait la prise"""
|
"""Extrait la prise (filaire) et la chambre correspondante.
|
||||||
## Regarder dans
|
Par convention, le nom d'une chambre commence (lettre du bâtiment) par une
|
||||||
|
majuscule, tandis que la prise correspondante commence par une miniscule.
|
||||||
|
"""
|
||||||
## Filaire: NAS-Identifier => contient le nom du switch (batm-3.adm.crans.org)
|
## Filaire: NAS-Identifier => contient le nom du switch (batm-3.adm.crans.org)
|
||||||
## Nas-Port => port du switch (ex 42)
|
## Nas-Port => port du switch (ex 42)
|
||||||
|
|
||||||
|
# Lettre du bâtiment (C, B, A, etc, en majuscule)
|
||||||
|
bat_name = None
|
||||||
|
|
||||||
|
# Numéro du switch
|
||||||
|
bat_num = None
|
||||||
|
|
||||||
|
# Port sur le switch
|
||||||
|
port = None
|
||||||
|
|
||||||
|
nas = data.get('Nas-Identifier', None)
|
||||||
|
if nas:
|
||||||
|
if nas.startswith('bat'):
|
||||||
|
nas = value.split('.', 1)[0]
|
||||||
|
try:
|
||||||
|
bat_name = nas[3].upper()
|
||||||
|
bat_num = int(nas.split('-', 1)[1])
|
||||||
|
except IndexError, ValueError:
|
||||||
|
pass
|
||||||
|
port = data.get('Nas-Port', None)
|
||||||
|
if port:
|
||||||
|
port = int(value)
|
||||||
|
|
||||||
|
if bat_num is not None and bat_name and port:
|
||||||
|
prise = bat_name.lower() + "%01d%02d" % (bat_num, port)
|
||||||
|
try:
|
||||||
|
chbre = bat_name + annuaires_pg.reverse(bat_name, prise[1:])[0]
|
||||||
|
except IndexError:
|
||||||
|
chbre = None
|
||||||
|
return prise, chbre
|
||||||
|
|
||||||
|
def get_ap(data):
|
||||||
|
"""Extrait la prise (wifi)"""
|
||||||
## WiFi: NAS-Identifier => vide
|
## WiFi: NAS-Identifier => vide
|
||||||
## Nas-Port => numéro sur l'interface
|
## Nas-Port => numéro sur l'interface
|
||||||
## Nas-IP-Address => adresse IP de la borne
|
## Nas-IP-Address => adresse IP de la borne
|
||||||
is_wifi = True
|
|
||||||
bat_name = None
|
|
||||||
bat_num = None
|
|
||||||
port = None
|
|
||||||
|
|
||||||
for (key, value) in auth_data:
|
|
||||||
if key == 'NAS-Identifier':
|
|
||||||
if value.startswith('bat'):
|
|
||||||
nas = value.split('.', 1)[0]
|
|
||||||
try:
|
|
||||||
bat_name = nas[3]
|
|
||||||
bat_num = nas.split('-', 1)[1]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if key == 'Nas-Port':
|
|
||||||
port = int(value)
|
|
||||||
|
|
||||||
if bat_num and bat_name and port:
|
|
||||||
return bat_name + "%01d%02d" % (bat_num, port)
|
|
||||||
|
|
||||||
|
|
||||||
@use_ldap_admin
|
@use_ldap_admin
|
||||||
def register_mac(auth_data, machine, conn):
|
def register_mac(data, machine, conn):
|
||||||
for (key, value) in auth_data:
|
"""Enregistre la mac actuelle sur une machine donnée."""
|
||||||
if key == 'Calling-Station-Id':
|
mac = data.get('Calling-Station-Id', None)
|
||||||
try:
|
|
||||||
value = value.decode('ascii', 'ignore').replace('"','')
|
|
||||||
mac = lc_ldap.crans_utils.format_mac(value)
|
|
||||||
except:
|
|
||||||
radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
|
|
||||||
|
|
||||||
if mac is None:
|
if mac is None:
|
||||||
radiusd.radlog(radiusd.L_ERR, 'Cannot find MAC')
|
radiusd.radlog(radiusd.L_ERR, 'Cannot find MAC')
|
||||||
return
|
return
|
||||||
mac = unicode(mac.lower())
|
mac = mac.decode('ascii', 'ignore').replace('"','')
|
||||||
|
try:
|
||||||
|
mac = lc_ldap.crans_utils.format_mac(value)
|
||||||
|
except:
|
||||||
|
radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
|
||||||
|
return
|
||||||
|
|
||||||
with conn.search(unicode(machine.dn.split(',',1)[0]), mode='rw')[0] as machine:
|
with conn.search(unicode(machine.dn.split(',',1)[0]), mode='rw')[0] as machine:
|
||||||
radiusd.radlog(radiusd.L_INFO, 'Registering mac %s' % mac)
|
radiusd.radlog(radiusd.L_INFO, 'Registering mac %s' % mac)
|
||||||
machine['macAddress'] = mac
|
machine['macAddress'] = mac
|
||||||
|
@ -158,6 +177,7 @@ def register_mac(auth_data, machine, conn):
|
||||||
trigger_generate('komaz')
|
trigger_generate('komaz')
|
||||||
radiusd.radlog(radiusd.L_INFO, 'done ! (triggered komaz)')
|
radiusd.radlog(radiusd.L_INFO, 'done ! (triggered komaz)')
|
||||||
|
|
||||||
|
@radius_event
|
||||||
@use_ldap_admin
|
@use_ldap_admin
|
||||||
@use_ldap
|
@use_ldap
|
||||||
def instantiate(p, *conns):
|
def instantiate(p, *conns):
|
||||||
|
@ -165,15 +185,16 @@ def instantiate(p, *conns):
|
||||||
do nothing)"""
|
do nothing)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@radius_event
|
||||||
@use_ldap
|
@use_ldap
|
||||||
def wifi_authorize(auth_data, conn):
|
def wifi_authorize(data, conn):
|
||||||
"""Section authorize pour le wifi
|
"""Section authorize pour le wifi
|
||||||
(NB: le filaire est en accept pour tout le monde)
|
(NB: le filaire est en accept pour tout le monde)
|
||||||
Éxécuté avant l'authentification proprement dite. On peut ainsi remplir les
|
Éxécuté avant l'authentification proprement dite. On peut ainsi remplir les
|
||||||
champs login et mot de passe qui serviront ensuite à l'authentification
|
champs login et mot de passe qui serviront ensuite à l'authentification
|
||||||
(MschapV2/PEAP ou MschapV2/TTLS)"""
|
(MschapV2/PEAP ou MschapV2/TTLS)"""
|
||||||
|
|
||||||
items = get_machines(auth_data)
|
items = get_machines(data)
|
||||||
|
|
||||||
if not items:
|
if not items:
|
||||||
radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Nobody found')
|
radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Nobody found')
|
||||||
|
@ -185,8 +206,7 @@ def wifi_authorize(auth_data, conn):
|
||||||
machine = items[0]
|
machine = items[0]
|
||||||
|
|
||||||
if '<automatique>' in machine['macAddress']:
|
if '<automatique>' in machine['macAddress']:
|
||||||
register_mac(auth_data, machine)
|
register_mac(data, machine)
|
||||||
|
|
||||||
|
|
||||||
proprio = machine.proprio()
|
proprio = machine.proprio()
|
||||||
if isinstance(proprio, lc_ldap.objets.AssociationCrans):
|
if isinstance(proprio, lc_ldap.objets.AssociationCrans):
|
||||||
|
@ -212,19 +232,22 @@ def wifi_authorize(auth_data, conn):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@radius_event
|
||||||
@use_ldap
|
@use_ldap
|
||||||
def post_auth(auth_data, conn):
|
def post_auth(data, conn):
|
||||||
"""Appelé une fois que l'authentification est ok.
|
"""Appelé une fois que l'authentification est ok.
|
||||||
On peut rajouter quelques éléments dans la réponse radius ici.
|
On peut rajouter quelques éléments dans la réponse radius ici.
|
||||||
Comme par exemple le vlan sur lequel placer le client"""
|
Comme par exemple le vlan sur lequel placer le client"""
|
||||||
|
|
||||||
|
is_wifi = False
|
||||||
vlan_name = None
|
vlan_name = None
|
||||||
reason = ''
|
reason = ''
|
||||||
identity = "" #TODO
|
identity = "" #TODO
|
||||||
prise = "" #TODO
|
prise = ""
|
||||||
items = get_machines(auth_data)
|
chbre = None
|
||||||
|
items = get_machines(data)
|
||||||
decision = 'adherent',''
|
decision = 'adherent',''
|
||||||
|
|
||||||
if not items:
|
if not items:
|
||||||
decision = 'accueil', 'Machine inconnue'
|
decision = 'accueil', 'Machine inconnue'
|
||||||
|
|
||||||
|
@ -233,6 +256,7 @@ def post_auth(auth_data, conn):
|
||||||
|
|
||||||
if isinstance(machine, lc_ldap.objets.machineWifi):
|
if isinstance(machine, lc_ldap.objets.machineWifi):
|
||||||
decision = 'wifi', ''
|
decision = 'wifi', ''
|
||||||
|
is_wifi = True
|
||||||
|
|
||||||
if not machine['ipHostNumber'] or unicode(machine['macAddress'][0]) in test_v6:
|
if not machine['ipHostNumber'] or unicode(machine['macAddress'][0]) in test_v6:
|
||||||
decision = 'v6only', 'No IPv4'
|
decision = 'v6only', 'No IPv4'
|
||||||
|
@ -246,33 +270,39 @@ def post_auth(auth_data, conn):
|
||||||
if bl.value['type'] in bl_accueil:
|
if bl.value['type'] in bl_accueil:
|
||||||
decision = 'accueil', unicode(bl)
|
decision = 'accueil', unicode(bl)
|
||||||
|
|
||||||
|
# Filaire : protection anti-"squattage"
|
||||||
|
if not is_wifi:
|
||||||
|
# Si l'adhérent n'est pas membre actif, il doit se brancher depuis la
|
||||||
|
# prise d'un autre adhérent à jour de cotisation
|
||||||
|
prise, chbre = get_prise_chbre(data)
|
||||||
|
if proprio['droits']:
|
||||||
|
decision = decision[0], decision[1] + ' (force MA)'
|
||||||
|
elif chbre is None:
|
||||||
|
decision = "accueil", "Chambre inconnue"
|
||||||
|
else:
|
||||||
|
chbre = escape_ldap(chbre)
|
||||||
|
hebergeurs = conn.search(u'(&(chambre=%s)(cid=*)(aid=*))' % chbre)
|
||||||
|
for hebergeur in hebergeurs:
|
||||||
|
if not hebergeur.blacklist_actif():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
decision = "accueil", "Hébergeur blacklisté"
|
||||||
|
|
||||||
|
# Unpack and log
|
||||||
|
if chbre is not None:
|
||||||
|
prise += '/' + chbre
|
||||||
|
|
||||||
vlan_name, reason = decision
|
vlan_name, reason = decision
|
||||||
vlan = vlans[vlan_name]
|
vlan = vlans[vlan_name]
|
||||||
radiusd.radlog(radiusd.L_INFO, 'auth.py: %s -> %s [%s%s]' %
|
radiusd.radlog(radiusd.L_INFO, 'auth.py: %s -> %s [%s%s]' %
|
||||||
(prise, identity, vlan_name, (reason and u': ' + reason).encode(u'utf-8'))
|
(prise, identity, vlan_name, (reason and u': ' + reason).encode(u'utf-8'))
|
||||||
)
|
)
|
||||||
|
|
||||||
#<!>
|
# WiFi : Pour l'instant, on ne met pas d'infos de vlans dans la réponse
|
||||||
#
|
# les bornes wifi ont du mal avec cela
|
||||||
# # Si l'adhérent n'est pas membre actif, il doit se brancher depuis la prise
|
if is_wifi:
|
||||||
# # d'un autre adhérent à jour de cotisation
|
|
||||||
# if not proprio.droits():
|
|
||||||
# try:
|
|
||||||
# chbre = prise[0] + annuaires_pg.reverse(prise[0], prise[1:])[0]
|
|
||||||
# except IndexError:
|
|
||||||
# return (0, "Chambre inconnue", "accueil")
|
|
||||||
# hebergeurs = conn.search('chambre=' + chbre)
|
|
||||||
# for hebergeur in hebergeurs['adherent'] + hebergeurs['club']:
|
|
||||||
# if paiement_ok(hebergeur):
|
|
||||||
# break
|
|
||||||
# else:
|
|
||||||
# return (0, "Hébergeur non à jour", "accueil")
|
|
||||||
#
|
|
||||||
#<!>
|
|
||||||
# Pour l'instant, on ne met pas d'infos de vlans dans la réponse
|
|
||||||
return radiusd.RLM_MODULE_OK
|
return radiusd.RLM_MODULE_OK
|
||||||
|
|
||||||
# This is dead code (for now)
|
|
||||||
return (radiusd.RLM_MODULE_UPDATED,
|
return (radiusd.RLM_MODULE_UPDATED,
|
||||||
(
|
(
|
||||||
("Tunnel-Type", "VLAN"),
|
("Tunnel-Type", "VLAN"),
|
||||||
|
@ -282,6 +312,7 @@ def post_auth(auth_data, conn):
|
||||||
()
|
()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@radius_event
|
||||||
def dummy_fun(p):
|
def dummy_fun(p):
|
||||||
return radiusd.RLM_MODULE_OK
|
return radiusd.RLM_MODULE_OK
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue