FreeRadius-en sarearen ezarpenak DHCP bidez

FreeRadius-en sarearen ezarpenak DHCP bidez
Harpidedunei IP helbideak igortzea antolatzea heldu zen. Arazoaren baldintzak:

  • Ez dizugu baimena emateko zerbitzari bereizirik emango; konpontuko zara 😉
  • Harpidedunek sarearen ezarpenak jaso behar dituzte DHCP bidez
  • Sarea heterogeneoa da. Honek PON ekipamendua eta 82 aukera konfiguratuta dituzten etengailu arruntak eta hotspotekin WiFi oinarriak barne hartzen ditu
  • Datuak IP bat emateko baldintzaren batean sartzen ez badira, IP bat eman behar duzu "gonbidatu" saretik.

Alde ona: oraindik FreeBSD-n badago zerbitzari bat "funtzionatu" dezakeena, baina "urrun" dago ;), ez "sare honetan bertan".

Mikrotik izeneko gailu zoragarri bat ere badago. Sarearen diagrama orokorra honelako zerbait da:

FreeRadius-en sarearen ezarpenak DHCP bidez

Pentsatu ondoren, FreeRadius erabiltzea erabaki zen harpidedunei sareko ezarpenak emateko. Printzipioz, eskema ohikoa da: DHCP zerbitzaria gaitzen dugu Microtick-en, eta Radius Client bertan. DHCP zerbitzaria -> Radius Bezeroa -> Radius zerbitzariaren konexioa konfiguratzen dugu.

Ez dirudi zaila. Baina! Deabrua xehetasunetan dago. Alegia:

  • Eskema hau erabiliz PON OLT bat baimentzen denean, eskaera bat bidaltzen zaio FreeRadius-era, goiburuko MAC helbidearen berdina den Erabiltzaile-izena, MAC PON Onu-ren berdina den Agent-Circuit-Id eta pasahitz huts batekin.
  • 82 aukera duten etengailuetatik baimentzen duenean, FreeRadius-ek eskaera bat jasotzen du harpidedunaren gailuaren MACaren berdina den Erabiltzaile-izen huts batekin eta atributu gehigarriz beteta. errele etengailua eta harpideduna konektatuta dagoen portua.
  • WiFI puntuak dituzten harpidedun batzuk PAP-CHAP protokoloen bidez baimenduta daude
  • WIFI puntuetako harpidedun batzuk WIFI puntuaren MAC helbidearen pareko Erabiltzaile-izen batekin baimenduta daude, pasahitzik gabe.

Aurrekari historikoak: zer den "82 aukera" DHCPn

Informazio gehigarria transferitzeko aukera ematen duten DHCP protokoloaren aukera gehigarriak dira, adibidez Agent-Circuit-Id eta Agent-Remote-Id eremuetan. Normalean errele etengailuaren MAC helbidea eta harpideduna konektatuta dagoen ataka transmititzeko erabiltzen da. PON ekipoen edo WIFI oinarrizko estazioen kasuan, Agent-Circuit-Id eremuak ez du informazio erabilgarria (ez dago harpidedun atakarik). Kasu honetan DHCP funtzionamenduaren eskema orokorra honako hau da:

FreeRadius-en sarearen ezarpenak DHCP bidez

Pausoz pauso eskema honek honela funtzionatzen du:

  1. Erabiltzaile-ekipoak DHCP difusio-eskaera bat egiten du sarearen ezarpenak lortzeko
  2. Harpidedun-ekipoa zuzenean konektatuta dagoen gailuak (adibidez, etengailu bat, WiFi edo PON oinarrizko estazioa) pakete hau "atzematen" eta aldatzen du, aukera gehigarriak 82 aukera eta Errele-agentearen IP helbidea sartuz, eta gehiago transmititzen du. sarea.
  3. DHCP zerbitzariak eskaera onartzen du, erantzun bat sortzen du eta errelebo gailura bidaltzen du
  4. Errelebo gailuak erantzun paketea birbidaltzen du harpidedun gailura

Noski, guztiak ez du horren erraz funtzionatzen; zure sareko ekipoak horren arabera konfiguratu behar dituzu.

FreeRadius instalatzen

Noski, FreeRadius konfigurazio-ezarpenekin lor daiteke, baina zaila eta ez dago argia... batez ere, N hilabete igaro ondoren bertara joaten zarenean eta "denak funtzionatzen du". Horregatik, FreeRadius-erako gure baimen-modulua idaztea erabaki genuen Python-en. Baimen datuak MySQL datu-basetik hartuko ditugu. Ez du balio bere egitura deskribatzeak; dena den, bakoitzak "beretzat" egingo du. Bereziki, FreeRadius-erako sql moduluarekin eskaintzen den egitura hartu nuen, eta apur bat aldatu nuen harpidedun bakoitzeko mac eta portu eremua gehituz, saio-hasierako pasahitzaz gain.

Beraz, lehenik eta behin, instalatu FreeRadius:

cd /usr/ports/net/freeradius3
make config
make
install clean

Ezarpenetan, hautatu instalatzeko:

FreeRadius-en sarearen ezarpenak DHCP bidez

Python modulurako esteka sinbolikoa egiten dugu (hau da, "aktibatu"):

ln -s /usr/local/etc/raddb/mods-available/python /usr/local/etc/raddb/mods-enabled

Instalatu dezagun python-erako modulu gehigarri bat:

pip install mysql-connector

FreeRadius-en python moduluaren ezarpenetan, moduluen bilaketa-bideak zehaztu behar dituzu python_path aldagaian. Adibidez hau daukat:

python_path="/usr/local/etc/raddb/mods-config/python:/usr/local/lib/python2.7:/usr/local/lib/python27.zip:/usr/local/lib/python2.7:/usr/local/lib/python2.7/plat-freebsd12:/usr/local/lib/python2.7/lib-tk:/usr/local/lib/python2.7/lib-old:/usr/local/lib/python2.7/lib-dynload:/usr/local/lib/python2.7/site-packages"

Bideak aurki ditzakezu python interpretea abiarazi eta komandoak sartuz:

root@phaeton:/usr/local/etc/raddb/mods-enabled# python
Python 2.7.15 (default, Dec  8 2018, 01:22:25) 
[GCC 4.2.1 Compatible FreeBSD Clang 6.0.1 (tags/RELEASE_601/final 335540)] on freebsd12
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/local/lib/python27.zip', '/usr/local/lib/python2.7', '/usr/local/lib/python2.7/plat-freebsd12', '/usr/local/lib/python2.7/lib-tk', '/usr/local/lib/python2.7/lib-old', '/usr/local/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/site-packages']
>

Urrats hau ematen ez baduzu, python-en idatzitako eta FreeRadius-ek abiarazitako scriptek ez dituzte inportazioan zerrendatutako moduluak aurkituko. Horrez gain, moduluaren ezarpenetan baimena eta kontabilitatea deitzeko funtzioak iruzkintzea kendu behar duzu. Adibidez, modulu honek itxura hau du:

python {
    python_path="/usr/local/etc/raddb/mods-config/python:/usr/local/lib/python2.7:/usr/local/lib/python2.7/site-packages:/usr/local/lib/python27.zip:/usr/local/lib/python2.7:/usr/local/lib/python2.7/plat-freebsd12:/usr/local/lib/python2.7/lib-tk:/usr/local/lib/python2.7/lib-old:/usr/local/lib/python2.7/lib-dynload:/usr/local/lib/python2.7/site-packages"
    module = work
    mod_instantiate = ${.module}
    mod_detach = ${.module}

    mod_authorize = ${.module}
    func_authorize = authorize

    mod_authenticate = ${.module}
    func_authenticate = authenticate

    mod_preacct = ${.module}
    func_preacct = preacct

    mod_accounting = ${.module}
    func_accounting = accounting

    mod_checksimul = ${.module}
    mod_pre_proxy = ${.module}
    mod_post_proxy = ${.module}
    mod_post_auth = ${.module}
    mod_recv_coa = ${.module}
    mod_send_coa = ${.module}

}

work.py script-a (eta beste guztiak) /usr/local/etc/raddb/mods-config/python-en jarri behar da Hiru script ditut guztira.

lana.py:

#!/usr/local/bin/python
# coding=utf-8
import radiusd
import func
import sys
from pprint import pprint
mysql_host="localhost"
mysql_username="укацук"
mysql_password="ыукаыукаыук"
mysql_base="ыукаыкуаыу"
def instantiate(p):
print ("*** instantiate ***")
print (p)
# return 0 for success or -1 for failure
def authenticate(p):
print ("*** Аутенфикация!!***")
print (p)
def authorize(p):
radiusd.radlog(radiusd.L_INFO, '*** radlog call in authorize ***')    
conn=func.GetConnectionMysql(mysql_host, mysql_username, mysql_password, mysql_base);
param=func.ConvertArrayToNames(p);
pprint(param)
print ("*** Авторизация ***")
reply = ()
conf = ()
cnt=0
username="";mac="";
# сначала проверяем "как положено", по связке логин/пароль
if ("User-Name" in param) and ("User-Password" in param) :
print ("Вариант авторизации (1): есть логин-пароль")
pprint(param["User-Name"])
pprint(param["User-Password"])
pprint(conn)
print(sys.version_info)
print (radiusd.config)
sql="select radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where radcheck.username=%s and radcheck.value=%s"
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql,[param["User-Name"], param["User-Password"]]);
row = cursor.fetchone()	
while row is not None:    
cnt=cnt+1
username=row["username"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
# вариант, что User-Name - это МАС адрес БС,пароля и порта нет                
if ("User-Name" in param)  and ("User-Password" in param) and (cnt==0):
if param["User-Password"] =='':
if ":" in param["User-Name"]:
pprint(param["User-Name"])            
print ("Вариант авторизации (2): User-Name - это MAC адрес базовой станции, порта и пароля нет")
sql="select radreply.username,radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where REPLACE(radcheck.mac,':','') = REPLACE(REPLACE('"+str(param["User-Name"])+"','0x',''),':','') and radcheck.sw_port=''"
print (sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:                  
cnt=cnt+1
username=row["username"]
mac=param["User-Name"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
if ("Agent-Remote-Id" in param)  and ("User-Password" in param) and (cnt==0):
if param["User-Password"] =='':
pprint(param["Agent-Remote-Id"])            
print ("Вариант авторизации (2.5): Agent-Remote-Id - это MAC адрес PON оборудования")
sql="select radreply.username,radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where REPLACE(radcheck.mac,':','') = REPLACE(REPLACE('"+str(param["Agent-Remote-Id"])+"','0x',''),':','') and radcheck.sw_port=''"
print (sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:                  
cnt=cnt+1
username=row["username"]
mac=param["User-Name"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
#Вариант, что Agent-Remote-Id - это МАС адрес БС,пароля и порта нет и предыдущие варианты поиска IP результата не дали                
if ("Agent-Remote-Id" in param)  and ("User-Password" not in param) and (cnt==0):
pprint(param["Agent-Remote-Id"])            
print ("Вариант авторизации (3): Agent-Remote-Id - МАС базовой станции/пон. Порта в биллинге нет")
sql="select radreply.username,radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where REPLACE(radcheck.mac,':','') = REPLACE(REPLACE('"+str(param["Agent-Remote-Id"])+"','0x',''),':','') and radcheck.sw_port=''"
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:    
cnt=cnt+1
mac=param["Agent-Remote-Id"]
username=row["username"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
#Вариант, что предыдущие попытки результата не дали, но есть Agent-Remote-Id и Agent-Circuit-Id
if ("Agent-Remote-Id" in param)  and ("Agent-Circuit-Id" in param) and (cnt==0):
pprint(param["Agent-Remote-Id"])            
pprint(param["Agent-Circuit-Id"])            
print ("Вариант авторизации (4): авторизация по Agent-Remote-Id и Agent-Circuit-Id, в биллинге есть порт/мак")
sql="select radreply.username,radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where upper(radcheck.sw_mac)=upper(REPLACE('"+str(param["Agent-Remote-Id"])+"','0x','')) and upper(radcheck.sw_port)=upper(RIGHT('"+str(param["Agent-Circuit-Id"])+"',2)) and radcheck.sw_port<>''"
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:    
cnt=cnt+1
mac=param["Agent-Remote-Id"]
username=row["username"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
# если так до сих пор IP не получен, то выдаю иего из гостевой сети..
if cnt==0:      
print ("Ни один из вариантов авторизации не сработал, получаю IP из гостевой сети..")
ip=func.GetGuestNet(conn)      
if ip!="": 
cnt=cnt+1;
reply = reply+(("Framed-IP-Address",str(ip)), )
# если совсем всё плохо, то Reject
if cnt==0:
conf = ( ("Auth-Type", "Reject"), ) 
else:
#если авторизация успешная (есть такой абонент), то запишем историю авторизации
if username!="":
func.InsertToHistory(conn,username,mac, reply);
conf = ( ("Auth-Type", "Accept"), )             
pprint (reply)
conn=None;
return radiusd.RLM_MODULE_OK, reply, conf
def preacct(p):
print ("*** preacct ***")
print (p)
return radiusd.RLM_MODULE_OK
def accounting(p):
print ("*** Аккаунтинг ***")
radiusd.radlog(radiusd.L_INFO, '*** radlog call in accounting (0) ***')  
print (p)
conn=func.GetConnectionMysql(mysql_host, mysql_username, mysql_password, mysql_base);
param=func.ConvertArrayToNames(p);
pprint(param)  
print("Удалим старые сессии (более 20 минут нет аккаунтинга)");
sql="delete from radacct where TIMESTAMPDIFF(minute,acctupdatetime,now())>20"
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
conn.commit()
print("Обновим/добавим информацию о сессии")
if (("Acct-Unique-Session-Id" in param) and ("User-Name" in param) and ("Framed-IP-Address" in param)):
sql='insert into radacct (radacctid,acctuniqueid,username,framedipaddress,acctstarttime) values (null,"'+str(param['Acct-Unique-Session-Id'])+'","'+str(param['User-Name'])+'","'+str(param['Framed-IP-Address'])+'",now()) ON DUPLICATE KEY update acctupdatetime=now()'
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql)
conn.commit()
conn=None;
return radiusd.RLM_MODULE_OK
def pre_proxy(p):
print ("*** pre_proxy ***")
print (p)
return radiusd.RLM_MODULE_OK
def post_proxy(p):
print ("*** post_proxy ***")
print (p)
return radiusd.RLM_MODULE_OK
def post_auth(p):
print ("*** post_auth ***")
print (p)
return radiusd.RLM_MODULE_OK
def recv_coa(p):
print ("*** recv_coa ***")
print (p)
return radiusd.RLM_MODULE_OK
def send_coa(p):
print ("*** send_coa ***")
print (p)
return radiusd.RLM_MODULE_OK
def detach():
print ("*** На этом всё детишечки ***")
return radiusd.RLM_MODULE_OK

funtzio.py:

#!/usr/bin/python2.7
# coding=utf-8
import mysql.connector
from mysql.connector import Error
# Функция возвращает соединение с MySQL
def GetConnectionMysql(mysql_host, mysql_username, mysql_password, mysql_base):    
try:
conn = mysql.connector.connect(host=mysql_host,database=mysql_base,user=mysql_username,password=mysql_password)
if conn.is_connected(): print('---cоединение с БД '+mysql_base+' установлено')
except Error as e:
print("Ошибка: ",e);
exit(1);       
return conn
def ConvertArrayToNames(p):
mass={};
for z in p:
mass[z[0]]=z[1]
return mass
# Функция записывает историю соединения по известным данным
def InsertToHistory(conn,username,mac, reply):
print("--записываю для истории")
repl=ConvertArrayToNames(reply)
if "Framed-IP-Address" in repl:
sql='insert into radpostauth (username,reply,authdate,ip,mac,session_id,comment) values ("'+username+'","Access-Accept",now(),"'+str(repl["Framed-IP-Address"])+'","'+str(mac)+'","","")'
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)          
cursor.execute(sql);
conn.commit()
# Функция выдает последний по дате выдачи IP адрес из гостевой сети        
def GetGuestNet(conn):
ip="";id=0
sql="select * from guestnet order by dt limit 1"
print (sql)
cursor = conn.cursor(dictionary=True,buffered=True)          
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:    
ip=row["ip"]
id=row["id"]
row = cursor.fetchone()	          
if id>0:
sql="update guestnet set dt=now() where id="+str(id)
print (sql)
cursor = conn.cursor(dictionary=True,buffered=True)          
cursor.execute(sql);
conn.commit()
return ip         

radiusd.py:

#!/usr/bin/python2.7
# coding=utf-8
# from modules.h
RLM_MODULE_REJECT = 0
RLM_MODULE_FAIL = 1
RLM_MODULE_OK = 2
RLM_MODULE_HANDLED = 3
RLM_MODULE_INVALID = 4
RLM_MODULE_USERLOCK = 5
RLM_MODULE_NOTFOUND = 6
RLM_MODULE_NOOP = 7
RLM_MODULE_UPDATED = 8
RLM_MODULE_NUMCODES = 9
# from log.h
L_AUTH = 2
L_INFO = 3
L_ERR = 4
L_WARN = 5
L_PROXY = 6
L_ACCT = 7
L_DBG = 16
L_DBG_WARN = 17
L_DBG_ERR = 18
L_DBG_WARN_REQ = 19
L_DBG_ERR_REQ = 20
# log function
def radlog(level, msg):
import sys
sys.stdout.write(msg + 'n')
level = level

Kodean ikus dezakezun bezala, harpideduna identifikatzen saiatzen ari gara erabilgarri dauden metodo guztiak erabiliz bere harpidedunaren MAC helbide ezagunen edo 82 aukeraren konbinazioaren arabera, eta honek funtzionatzen ez badu, inoiz erabili den IP helbide zaharrena jaulkiko dugu "gonbidatua". ” sarea. Guneak gaituta dagoen karpetan script lehenetsia konfiguratzea besterik ez da geratzen, horrela python script-en beharrezko funtzioak izendatutako uneetan astindu daitezen. Izan ere, nahikoa da fitxategia formulariora ekartzea:

lehenetsi

server default {
listen {
type = auth
ipaddr = *
port = 0
limit {
max_connections = 16
lifetime = 0
idle_timeout = 30
}
}
listen {
ipaddr = *
port = 0
type = acct
limit {
}
}
listen {
type = auth
port = 0
limit {
max_connections = 1600
lifetime = 0
idle_timeout = 30
}
}
listen {
ipv6addr = ::
port = 0
type = acct
limit {
}
}
authorize {
python
filter_username
preprocess
expiration
logintime
}
authenticate {
Auth-Type PAP {
pap
python
}
Auth-Type CHAP {
chap
python
}
Auth-Type MS-CHAP {
mschap
python
}
eap
}
preacct {
preprocess
acct_unique
suffix
files
}
accounting {
python
exec
attr_filter.accounting_response
}
session {
}
post-auth {
update {
&reply: += &session-state:
}
exec
remove_reply_message_if_eap
Post-Auth-Type REJECT {
attr_filter.access_reject
eap
remove_reply_message_if_eap
}
Post-Auth-Type Challenge {
}
}
pre-proxy {
}
post-proxy {
eap
}
}

Saia gaitezen exekutatzen eta ikus dezagun zer sartzen den arazketa-erregistroan:

/usr/local/etc/rc.d/radiusd debug

Zer gehiago. FreeRadius konfiguratzean, komenigarria da bere funtzionamendua probatzea radclient utilitatearen bidez. Adibidez, baimena:

echo "User-Name=4C:5E:0C:2E:7F:15,Agent-Remote-Id=0x9845623a8c98,Agent-Circuit-Id=0x00010006" | radclient -x  127.0.0.1:1812 auth testing123

Edo kontua:

echo "User-Name=4C:5E:0C:2E:7F:15,Agent-Remote-Id=0x00030f26054a,Agent-Circuit-Id=0x00010002" | radclient -x  127.0.0.1:1813 acct testing123

Ohartarazi nahi dizut guztiz ezinezkoa dela horrelako eskema eta gidoiak "aldaketarik gabe" eskala "industrialean" erabiltzea. Nabarmena behintzat:

  • MAC helbidea "faltsutzea" posible da. Nahikoa da harpidedunak beste norbaiten MAC erregistratzea eta arazoak egongo dira
  • gonbidatuen sareak igortzearen logika kritikatik kanpo dago. Ez dago egiaztapenik ere "agian dagoeneko IP helbide bera duten bezeroak daude?"

Hau nire baldintzetan bereziki lan egiteko diseinatutako "cookie-ebakitzeko irtenbidea" besterik ez da, ezer gehiago. Ez epaitu zorrotz 😉

Iturria: www.habr.com

Gehitu iruzkin berria