Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Nounous
re2o
Commits
d6149880
Commit
d6149880
authored
Dec 11, 2016
by
root
Browse files
Merge branch 'master' of
https://gitlab.rezometz.org/rezo/re2o
parents
bdd6d29f
e266a854
Changes
4
Hide whitespace changes
Inline
Side-by-side
freeradius_utils/auth.py
0 → 100644
View file @
d6149880
# ⁻*- mode: python; coding: utf-8 -*-
"""
Backend python pour freeradius.
Ce fichier contient la définition de plusieurs fonctions d'interface à
freeradius qui peuvent être appelées (suivant les configurations) à certains
moment de l'authentification, en WiFi, filaire, ou par les NAS eux-mêmes.
Inspirés d'autres exemples trouvés ici :
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
"""
import
logging
import
netaddr
import
radiusd
# Module magique freeradius (radiusd.py is dummy)
import
os
import
binascii
import
hashlib
import
subprocess
import
os
,
sys
from
ast
import
literal_eval
as
make_tuple
#: Serveur radius de test (pas la prod)
TEST_SERVER
=
bool
(
os
.
getenv
(
'DBG_FREERADIUS'
,
False
))
## -*- Logging -*-
class
RadiusdHandler
(
logging
.
Handler
):
"""Handler de logs pour freeradius"""
def
emit
(
self
,
record
):
"""Process un message de log, en convertissant les niveaux"""
if
record
.
levelno
>=
logging
.
WARN
:
rad_sig
=
radiusd
.
L_ERR
elif
record
.
levelno
>=
logging
.
INFO
:
rad_sig
=
radiusd
.
L_INFO
else
:
rad_sig
=
radiusd
.
L_DBG
radiusd
.
radlog
(
rad_sig
,
record
.
msg
)
# Initialisation d'un logger (pour logguer unifié)
logger
=
logging
.
getLogger
(
'auth.py'
)
logger
.
setLevel
(
logging
.
DEBUG
)
formatter
=
logging
.
Formatter
(
'%(name)s: [%(levelname)s] %(message)s'
)
handler
=
RadiusdHandler
()
handler
.
setFormatter
(
formatter
)
logger
.
addHandler
(
handler
)
def
radius_event
(
fun
):
"""Décorateur pour les fonctions d'interfaces avec radius.
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 (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)
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
):
if
type
(
auth_data
)
==
dict
:
data
=
auth_data
else
:
data
=
dict
()
for
(
key
,
value
)
in
auth_data
or
[]:
# Beware: les valeurs scalaires sont entre guillemets
# Ex: Calling-Station-Id: "une_adresse_mac"
data
[
key
]
=
value
.
replace
(
'"'
,
''
)
try
:
# TODO s'assurer ici que les tuples renvoyés sont bien des (str,str)
# rlm_python ne digère PAS les unicodes
return
fun
(
data
)
except
Exception
as
err
:
logger
.
error
(
'Failed %r on data %r'
%
(
err
,
auth_data
))
raise
return
new_f
@
radius_event
def
instantiate
(
*
_
):
"""Utile pour initialiser les connexions ldap une première fois (otherwise,
do nothing)"""
logger
.
info
(
'Instantiation'
)
if
TEST_SERVER
:
logger
.
info
(
'DBG_FREERADIUS is enabled'
)
@
radius_event
def
authorize
(
data
):
"""Fonction qui aiguille entre nas, wifi et filaire pour authorize
On se contecte de faire une verification basique de ce que contien la requète
pour déterminer la fonction à utiliser"""
if
data
.
get
(
'Service-Type'
,
''
)
==
u
'NAS-Prompt-User'
or
data
.
get
(
'Service-Type'
,
''
)
==
u
'Administrative-User'
:
return
authorize_user
(
data
)
else
:
return
authorize_fil
(
data
)
@
radius_event
def
authorize_user
(
data
):
nas
=
data
.
get
(
'NAS-IP-Address'
,
None
)
nas_id
=
data
.
get
(
'NAS-Identifier'
,
None
)
user
=
data
.
get
(
'User-Name'
,
None
)
password
=
data
.
get
(
'User-Password'
,
None
)
out
=
subprocess
.
check_output
([
'/usr/bin/python3'
,
'/var/www/re2o/freeradius_utils/authenticate_user.py'
,
user
,
password
])
if
out
[:
-
1
]
==
u
"TRUE"
:
if
data
.
get
(
'Service-Type'
,
''
)
==
u
'NAS-Prompt-User'
:
logger
.
info
(
u
"Access of user %s on %s (%s)"
%
(
user
,
nas
,
nas_id
))
elif
data
.
get
(
'Service-Type'
,
''
)
==
u
'Administrative-User'
:
logger
.
info
(
u
"Enable manager for %s on %s (%s)"
%
(
user
,
nas
,
nas_id
))
return
(
radiusd
.
RLM_MODULE_UPDATED
,
(),
(
(
"Auth-Type"
,
"Accept"
),
),
)
else
:
return
(
radiusd
.
RLM_MODULE_UPDATED
,
(),
(
(
"Auth-Type"
,
"Reject"
),
),
)
@
radius_event
def
authorize_fil
(
data
):
"""
Check le challenge chap, et accepte.
"""
return
(
radiusd
.
RLM_MODULE_UPDATED
,
(),
(
(
"Auth-Type"
,
"Accept"
),
),
)
@
radius_event
def
post_auth
(
data
):
# On cherche quel est le type de machine, et quel sites lui appliquer
if
data
.
get
(
'NAS-Port-Type'
,
''
)
==
u
'Ethernet'
:
return
post_auth_fil
(
data
)
elif
u
"Wireless"
in
data
.
get
(
'NAS-Port-Type'
,
''
):
return
post_auth_wifi
(
data
)
@
radius_event
def
post_auth_wifi
(
data
):
"""Appelé une fois que l'authentification est ok.
On peut rajouter quelques éléments dans la réponse radius ici.
Comme par exemple le vlan sur lequel placer le client"""
port
,
vlan_name
,
reason
=
decide_vlan
(
data
,
True
)
mac
=
data
.
get
(
'Calling-Station-Id'
,
None
)
log_message
=
'(wifi) %s -> %s [%s%s]'
%
\
(
port
,
mac
,
vlan_name
,
(
reason
and
u
': '
+
reason
).
encode
(
'utf-8'
))
logger
.
info
(
log_message
)
# Si NAS ayant des mapping particuliers, à signaler ici
vlan_id
=
config
.
vlans
[
vlan_name
]
# WiFi : Pour l'instant, on ne met pas d'infos de vlans dans la réponse
# les bornes wifi ont du mal avec cela
if
WIFI_DYN_VLAN
:
return
(
radiusd
.
RLM_MODULE_UPDATED
,
(
(
"Tunnel-Type"
,
"VLAN"
),
(
"Tunnel-Medium-Type"
,
"IEEE-802"
),
(
"Tunnel-Private-Group-Id"
,
'%d'
%
vlan_id
),
),
()
)
return
radiusd
.
RLM_MODULE_OK
@
radius_event
def
post_auth_fil
(
data
):
"""Idem, mais en filaire.
"""
nas
=
data
.
get
(
'NAS-IP-Address'
,
None
)
port
=
data
.
get
(
'NAS-Port'
,
None
)
mac
=
data
.
get
(
'Calling-Station-Id'
,
None
)
# Hack, à cause d'une numérotation cisco baroque
port
=
port
[
-
2
:]
out
=
subprocess
.
check_output
([
'/usr/bin/python3'
,
'/var/www/re2o/freeradius_utils/authenticate_filaire.py'
,
nas
,
port
,
mac
])
sw_name
,
reason
,
vlan_id
=
make_tuple
(
out
)
log_message
=
'(fil) %s -> %s [%s%s]'
%
\
(
sw_name
+
u
":"
+
port
,
mac
,
vlan_id
,
(
reason
and
u
': '
+
reason
).
encode
(
'utf-8'
))
logger
.
info
(
log_message
)
# Filaire
return
(
radiusd
.
RLM_MODULE_UPDATED
,
(
(
"Tunnel-Type"
,
"VLAN"
),
(
"Tunnel-Medium-Type"
,
"IEEE-802"
),
(
"Tunnel-Private-Group-Id"
,
'%d'
%
int
(
vlan_id
)),
),
()
)
@
radius_event
def
dummy_fun
(
_
):
"""Do nothing, successfully. (C'est pour avoir un truc à mettre)"""
return
radiusd
.
RLM_MODULE_OK
def
detach
(
_
=
None
):
"""Appelé lors du déchargement du module (enfin, normalement)"""
print
"*** goodbye from auth.py ***"
return
radiusd
.
RLM_MODULE_OK
freeradius_utils/authenticate_filaire.py
0 → 100755
View file @
d6149880
import
os
,
sys
proj_path
=
"/var/www/re2o/"
# This is so Django knows where to find stuff.
os
.
environ
.
setdefault
(
"DJANGO_SETTINGS_MODULE"
,
"re2o.settings"
)
sys
.
path
.
append
(
proj_path
)
# This is so my local_settings.py gets loaded.
os
.
chdir
(
proj_path
)
# This is so models get loaded.
from
django.core.wsgi
import
get_wsgi_application
application
=
get_wsgi_application
()
import
argparse
from
machines.models
import
Interface
,
IpList
from
topologie.models
import
Room
,
Port
,
Switch
from
users.models
import
User
from
re2o.settings
import
RADIUS_VLAN_DECISION
VLAN_NOK
=
RADIUS_VLAN_DECISION
[
'VLAN_NOK'
]
VLAN_OK
=
RADIUS_VLAN_DECISION
[
'VLAN_OK'
]
def
decide_vlan
(
switch_ip
,
port_number
,
mac_address
):
# Get port from switch and port number
switch
=
Switch
.
objects
.
filter
(
switch_interface
=
Interface
.
objects
.
filter
(
ipv4
=
IpList
.
objects
.
filter
(
ipv4
=
switch_ip
)))
if
switch
:
sw_name
=
str
(
switch
[
0
].
switch_interface
)
port
=
Port
.
objects
.
filter
(
switch
=
switch
[
0
],
port
=
port_number
)
if
port
:
port
=
port
[
0
]
if
port
.
radius
==
'NO'
:
# Aucune authentification sur ce port
decision
=
(
sw_name
,
"Pas d'authentification sur ce port"
,
VLAN_OK
)
elif
port
.
radius
==
'BLOQ'
:
# Prise désactivée
decision
=
(
sw_name
,
'Port desactive'
,
VLAN_NOK
)
elif
port
.
radius
==
'COMMON'
:
# Authentification par mac
interface
=
Interface
.
objects
.
filter
(
mac_address
=
mac_address
)
if
not
interface
:
decision
=
(
sw_name
,
'Mac not found'
,
VLAN_NOK
)
elif
not
interface
[
0
].
is_active
():
decision
=
(
sw_name
,
'Machine non active / adherent non cotisant'
,
VLAN_NOK
)
else
:
decision
=
(
sw_name
,
'Machine OK'
,
VLAN_OK
)
elif
port
.
radius
==
'STRICT'
:
if
port
.
room
:
user
=
User
.
objects
.
filter
(
room
=
Room
.
objects
.
filter
(
name
=
port
.
room
))
if
not
user
:
decision
=
(
sw_name
,
'Chambre non cotisante'
,
VLAN_NOK
)
elif
not
user
[
0
].
has_access
():
decision
=
(
sw_name
,
'Resident desactive'
,
VLAN_NOK
)
else
:
# Verification de la mac
interface
=
Interface
.
objects
.
filter
(
mac_address
=
mac_address
)
if
not
interface
:
decision
=
(
sw_name
,
'Chambre Ok, but mac not found'
,
VLAN_NOK
)
elif
not
interface
[
0
].
is_active
():
decision
=
(
sw_name
,
'Chambre Ok, but machine non active / adherent non cotisant'
,
VLAN_NOK
)
else
:
decision
=
(
sw_name
,
'Machine OK, Proprio OK'
,
VLAN_OK
)
else
:
decision
=
(
sw_name
,
'Chambre inconnue'
,
VLAN_NOK
)
else
:
decision
=
(
sw_name
,
'VLAN forced'
,
int
(
port
.
radius
))
else
:
decision
=
(
sw_name
,
'port not found!'
,
VLAN_OK
)
else
:
decision
=
(
'?'
,
'switch not found!'
,
VLAN_OK
)
return
decision
if
__name__
==
'__main__'
:
parser
=
argparse
.
ArgumentParser
(
description
=
'Decide radius vlan attribution'
)
parser
.
add_argument
(
'switch_ip'
,
action
=
"store"
)
parser
.
add_argument
(
'port_number'
,
action
=
"store"
,
type
=
int
)
parser
.
add_argument
(
'mac_address'
,
action
=
"store"
)
args
=
parser
.
parse_args
()
print
(
decide_vlan
(
args
.
switch_ip
,
args
.
port_number
,
args
.
mac_address
))
freeradius_utils/authenticate_user.py
0 → 100755
View file @
d6149880
import
os
,
sys
proj_path
=
"/var/www/re2o/"
# This is so Django knows where to find stuff.
os
.
environ
.
setdefault
(
"DJANGO_SETTINGS_MODULE"
,
"re2o.settings"
)
sys
.
path
.
append
(
proj_path
)
# This is so my local_settings.py gets loaded.
os
.
chdir
(
proj_path
)
# This is so models get loaded.
from
django.core.wsgi
import
get_wsgi_application
application
=
get_wsgi_application
()
import
argparse
from
django.contrib.auth
import
authenticate
from
users.models
import
User
def
authorize_user
(
user
,
password
):
user
=
authenticate
(
username
=
user
,
password
=
password
)
if
user
:
if
User
.
objects
.
get
(
pseudo
=
user
):
return
"TRUE"
else
:
return
"FALSE"
else
:
return
"FALSE"
if
__name__
==
'__main__'
:
parser
=
argparse
.
ArgumentParser
(
description
=
'Authorize user'
)
parser
.
add_argument
(
'user'
,
action
=
"store"
)
parser
.
add_argument
(
'password'
,
action
=
"store"
)
args
=
parser
.
parse_args
()
print
(
authorize_user
(
args
.
user
,
args
.
password
))
freeradius_utils/modules/rlm_python_re2o.conf
0 → 120000
View file @
d6149880
../
rlm_python_re2o
.
conf
\ No newline at end of file
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment