Netwerkinstellings vanaf FreeRadius via DHCP

Netwerkinstellings vanaf FreeRadius via DHCP
Die taak het aangebreek om die uitreiking van IP-adresse aan intekenare te reël. Toestande van die probleem:

  • Ons sal nie vir jou 'n aparte bediener vir magtiging gee nie - jy sal klaarkom 😉
  • Intekenare moet netwerkinstellings via DHCP ontvang
  • Die netwerk is heterogeen. Dit sluit PON-toerusting en gereelde skakelaars met gekonfigureerde Opsie 82 en WiFi-basisse met hotspots in
  • As die data nie onder enige van die voorwaardes vir die uitreiking van 'n IP val nie, moet jy 'n IP van die "gas" netwerk uitreik

Aan die goeie kant: daar is nog 'n bediener op FreeBSD wat kan "werk", maar dit is "ver" ;), nie "reg op hierdie netwerk nie".

Daar is ook 'n wonderlike toestel genaamd Mikrotik. Die algemene netwerkdiagram is iets soos volg:

Netwerkinstellings vanaf FreeRadius via DHCP

Na 'n paar gedagtes is besluit om FreeRadius te gebruik om netwerkinstellings aan intekenare uit te reik. In beginsel is die skema normaal: ons aktiveer die DHCP-bediener op Microtick, en Radius Client daarop. Ons konfigureer die DHCP-bediener -> Radius-kliënt -> Radius-bedienerverbinding.

Dit lyk nie moeilik nie. Maar! Die duiwel is in die besonderhede. Naamlik:

  • Wanneer 'n PON OLT met behulp van hierdie skema gemagtig word, word 'n versoek na FreeRadius gestuur met 'n Gebruikersnaam gelykstaande aan die MAC-adres van die koppunt, 'n Agent-Kring-ID gelykstaande aan die MAC PON Onu en 'n leë wagwoord.
  • By magtiging vanaf skakelaars met opsie 82, ontvang FreeRadius 'n versoek met 'n leë Gebruikersnaam gelykstaande aan die MAC van die intekenaar se toestel en gevul met bykomende eienskappe Agent-Circuit-Id en Agent-Remote-Id wat onderskeidelik weer die MAC van bevat die aflosskakelaar en die poort waaraan die intekenaar gekoppel is.
  • Sommige intekenare met WiFI-punte word deur PAP-CHAP-protokolle gemagtig
  • Sommige intekenare van WIFI-punte word gemagtig met 'n Gebruikersnaam gelykstaande aan die MAC-adres van die WIFI-punt, sonder 'n wagwoord.

Historiese agtergrond: wat is "Opsie 82" in DHCP

Dit is bykomende opsies vir die DHCP-protokol wat jou toelaat om bykomende inligting oor te dra, byvoorbeeld in die Agent-Circuit-Id en Agent-Remote-Id-velde. Tipies gebruik om die MAC-adres van die aflosskakelaar en die poort waaraan die intekenaar gekoppel is, oor te dra. In die geval van PON-toerusting of WIFI-basisstasies, bevat die Agent-Circuit-Id-veld nie nuttige inligting nie (daar is geen intekenaarpoort nie). Die algemene skema van DHCP-werking in hierdie geval is soos volg:

Netwerkinstellings vanaf FreeRadius via DHCP

Stap vir stap werk hierdie skema soos volg:

  1. Die gebruikertoerusting doen 'n DHCP-uitsaaiversoek om netwerkinstellings te verkry
  2. Die toestel (byvoorbeeld 'n skakelaar, WiFi- of PON-basisstasie) waaraan die intekenaartoerusting direk gekoppel is, "onderskep" hierdie pakkie en verander dit, wat addisionele opsies Opsie 82 en Relay-agent IP-adres daarin bekendstel, en versend dit verder oor die netwerk.
  3. Die DHCP-bediener aanvaar die versoek, genereer 'n antwoord en stuur dit na die aflostoestel
  4. Die aflostoestel stuur die antwoordpakket aan na die intekenaartoestel

Natuurlik werk dit alles nie so maklik nie; jy moet jou netwerktoerusting dienooreenkomstig opstel.

Installeer FreeRadius

Natuurlik kan dit bereik word met die FreeRadius-konfigurasie-instellings, maar dit is moeilik en onduidelik ... veral as jy na N maande soontoe gaan en "alles werk." Daarom het ons besluit om ons eie magtigingsmodule vir FreeRadius in Python te skryf. Ons sal magtigingsdata van die MySQL-databasis neem. Daar is geen sin om die struktuur daarvan te beskryf nie; in elk geval sal almal dit "vir hulself" maak. Ek het veral die struktuur wat met die sql-module vir FreeRadius aangebied word, geneem en dit effens verander deur 'n Mac- en poortveld vir elke intekenaar by te voeg, bykomend tot die aanmeldwagwoord.

So, installeer eers FreeRadius:

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

Kies in die instellings om te installeer:

Netwerkinstellings vanaf FreeRadius via DHCP

Ons maak 'n simskakel na die luislangmodule (d.w.s. "skakel dit aan"):

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

Kom ons installeer 'n bykomende module vir python:

pip install mysql-connector

In die python-module-instellings vir FreeRadius, moet jy die module-soekpaaie in die python_path-veranderlike spesifiseer. Ek het byvoorbeeld hierdie:

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"

U kan die paaie uitvind deur die python-tolk te begin en die opdragte in te voer:

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']
>

As jy nie hierdie stap doen nie, sal skrifte wat in python geskryf is en deur FreeRadius geloods is, nie die modules vind wat in invoer gelys is nie. Daarbenewens moet jy die funksies vir die oproep van magtiging en rekeningkunde in die module-instellings verwyder. Byvoorbeeld, hierdie module lyk soos volg:

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}

}

Die work.py script (en al die ander) moet geplaas word in /usr/local/etc/raddb/mods-config/python Ek het drie skrifte in totaal.

work.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

func.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

Soos u uit die kode kan sien, probeer ons om die intekenaar te identifiseer deur alle beskikbare metodes te gebruik deur sy bekende intekenaar-MAC-adresse of Opsie 82-kombinasie, en as dit nie werk nie, gee ons die oudste IP-adres wat ooit gebruik is van die "gas" uit. ” netwerk. Al wat oorbly, is om die verstekskrif in die webwerwe-geaktiveerde vouer op te stel, sodat die nodige funksies van die python-skrip op die aangewese oomblikke sal draai. Trouens, dit is genoeg om die lêer na die vorm te bring:

verstek

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
}
}

Kom ons probeer om dit uit te voer en kyk wat kom in die ontfoutingslogboek:

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

Wat nog. Wanneer u FreeRadius opstel, is dit gerieflik om die werking daarvan te toets met behulp van die radclient-nutsding. Byvoorbeeld magtiging:

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

Of rekening:

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

Ek wil jou waarsku dat dit absoluut onmoontlik is om so 'n skema en skrifte "sonder veranderinge" op 'n "industriële" skaal te gebruik. Ten minste merkbaar:

  • dit is moontlik om die MAC-adres te "vervals". Dit is genoeg vir die intekenaar om iemand anders se MAC te registreer en daar sal probleme wees
  • die logika van die uitreik van gasnetwerke is bo kritiek. Daar is nie eers 'n tjek "is daar dalk reeds kliënte met dieselfde IP-adres?"

Hierdie is net 'n "koekiesnyer-oplossing" wat ontwerp is om spesifiek in my toestande te werk, niks meer nie. Moenie streng oordeel nie 😉

Bron: will.com

Voeg 'n opmerking