Ограничение доступа на основе объектов
Материал из BiTel WiKi
Версия от 10:04, 24 апреля 2012; Snark (Обсуждение | вклад)
Ограничение доступа пользователей на основе RFC 4679 (здесь рассмотрен случай с IP адресом свича и номером порта) привязанных к объектам.
ВНИМАНИЕ! Эта статья еще не закончена!
Словарь RADIUS-а:
<vendor name="ADSL-Forum" code="3561"> <attribute name="ADSL-Agent-Circuit-Id" code="1" type="string" /> <attribute name="ADSL-Agent-Remote-Id" code="2" type="string" /> <attribute name="Actual-Data-Rate-Upstream" code="129" type="integer" /> <attribute name="Actual-Data-Rate-Downstream" code="130" type="integer" /> <attribute name="Minimum-Data-Rate-Upstream" code="131" type="integer" /> <attribute name="Minimum-Data-Rate-Downstream" code="132" type="integer" /> <attribute name="Attainable-Data-Rate-Upstream" code="133" type="integer" /> <attribute name="Attainable-Data-Rate-Downstream" code="134" type="integer" /> <attribute name="Maximum-Data-Rate-Upstream" code="135" type="integer" /> <attribute name="Maximum-Data-Rate-Downstream" code="136" type="integer" /> <attribute name="Minimum-Data-Rate-Upstream-Low-Power" code="137" type="integer" /> <attribute name="Minimum-Data-Rate-Downstream-Low-Power" code="138" type="integer" /> <attribute name="Maximum-Interleaving-Delay-Upstream" code="139" type="integer" /> <attribute name="Actual-Interleaving-Delay-Upstream" code="140" type="integer" /> <attribute name="Maximum-Interleaving-Delay-Downstream" code="141" type="integer" /> <attribute name="Actual-Interleaving-Delay-Downstream" code="142" type="integer" /> <attribute name="Access-Loop-Encapsulation" code="144" type="octets" /> <attribute name="IWF-Session" code="252" type="octets" /> </vendor>
Скрипт предобработки:
import bitel.billing.server.radius.*; import java.util.*; import java.util.regex.*; /* удаляем атрибуты RFC 2865 (http://tools.ietf.org/html/rfc2868) т.к. у нас они реально не используются, а в логах только место занимают :) */ if (request.hasAttribute(64)) request.removeAttributes(64); // Tunnel-Type if (request.hasAttribute(65)) request.removeAttributes(65); // Tunnel-Medium-Type if (request.hasAttribute(66)) request.removeAttributes(66); // Tunnel-Client-Endpoint if (request.hasAttribute(90)) request.removeAttributes(90); // Tunnel-Client-Auth-ID if (request.hasAttribute(91)) request.removeAttributes(91); // Tunnel-Server-Auth-ID // атрибут Called-Station-Id меня не интересует - удаляем if (request.hasAttribute(30)) request.removeAttributes(30); /* модифицируем атрибут NAS-Port-Id RFC 2869 (http://tools.ietf.org/html/rfc2869) attribute name="NAS-Port-Id" type="string" code="87" */ if (request.hasAttribute(87)) { value = request.getStringAttribute(87); // атрибут имеет вид: NAS-Port-Id = vlan123 // где: 123 - VLAN ID if (value.indexOf("vlan") >= 0) { // удаляем слово vlan, дабы атрибут имел вид: NAS-Port-Id = 123 request.setStringAttribute(87, value.replaceFirst("vlan", "")); } } else { // юзеры без VLAN не нужны request.setOption("service_time", -1); } /* модифицируем атрибут ADSL-Agent-Circuit-Id RFC 4679 (http://tools.ietf.org/html/rfc4679) vendor name="ADSL-Forum" code="3561" attribute name="ADSL-Agent-Circuit-Id" code="1" type="string" */ adslAgentCircuitId = request.getVendorAttributes(3561, 1); // java.util.List<RadiusVendorAttribute> (bitel.billing.server.radius.RadiusPacket) if (adslAgentCircuitId != null) { attribute = adslAgentCircuitId.get(0); value = attribute.getStringValue().toLowerCase(); // атрибут имеет вид: ADSL-Agent-Circuit-Id = 0a:0b:0c:0d:0e:0f::255.255.255.255::1 // где: 0a:0b:0c:0d:0e:0f - МАС адрес пользователя, 255.255.255.255 - IP адрес свича, 1 - порт свича if (value.matches("^(?:[\\da-f]{2}:){5}[\\da-f]{2}::(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)::\\d{1,2}$")) { // удаляем из атрибута МАС и заменяем двойное двоеточие "::" одинарным ":" circuitId = value.substring(19).replace("::", ":"); // теперь атрибут будет имеет вид: // ADSL-Agent-Circuit-Id = 255.255.255.255:1 // где: 255.255.255.255 - IP адрес свича, 1 - порт свича attribute.setStringValue(circuitId); } }
Скрипт на событие RADIUS аутентификации:
import bitel.billing.server.contract.bean.*; import bitel.billing.server.processor.event.*; import bitel.billing.server.radius.*; import bitel.billing.server.contract.object.bean.*; import bitel.billing.common.*; import bitel.billing.server.script.bean.event.*; /** * Вбивает в лог ошибок радиус запрос сессии чтобы в мониторе было видно. * Иногда может показать ошибочный лог (очень редко, но я должен предупредить), но я не виноват что Бител нас не любит и заставляет делать такие костыли :) * * @param event RadiusAuthenticationEvent * @param con подключение к БД * @param cid ID договора * @param err код ошибки */ private void writeError(RadiusAuthenticationEvent event, java.sql.Connection con, int cid, int err) { date = event.getGenerateTime(); tableDate = TimeUtils.format(date, "yyyyMM"); logDate = TimeUtils.format(date, "yyyy-MM-dd HH:mm:ss"); mid = event.getModuleID(); request = event.getRequest(); lid = event.getLogin().getId(); login = request.getStringAttribute(1); nasId = request.getNAS().getID(); qw = "INSERT INTO log_error_" + mid + "_" + tableDate + " (dt, cid, lid, login, nas_id, error_code, log_rec_id) SELECT ?, ?, ?, ?, ?, ?, (SELECT MAX(id) FROM log_server_" + mid + "_" + tableDate + " WHERE requests LIKE '%User-Name=" + login + "\n%')"; ps = con.prepareStatement(qw); ps.setString(1, logDate); ps.setInt(2, cid); ps.setInt(3, lid); ps.setString(4, login); ps.setInt(5, nasId); ps.setInt(6, err); ps.executeUpdate(); } RadiusPacket request = event.getRequest(); // bitel.billing.server.processor.event.RadiusAuthenticationEvent RadiusPacket response = event.getResponse(); // bitel.billing.server.processor.event.RadiusAuthenticationEvent int errorCode = event.getErrorCode(); // bitel.billing.server.processor.event.RadiusAuthenticationEvent Contract contract = event.getContract(); // bitel.billing.server.processor.event.RadiusAuthenticationEvent long contractGroups = contract.getGroups(); // bitel.billing.server.contract.bean.Contract int contractId = contract.getID(); // bitel.billing.server.contract.bean.Contract // Эти параметры берутся из клиента int objectConfigTypeId = 1; // тип объекта - Ethernet int objectConfigSwitchIpId = 1; // параметр объекта - IP адрес свича int objectConfigSwitchPortId = 2; // параметр объекта - Номер порта // получаем атрибут ADSL-Agent-Circuit-Id adslAgentCircuitId = request.getVendorAttributes(3561, 1); // java.util.List<RadiusVendorAttribute> (bitel.billing.server.radius.RadiusPacket) // Это дефолтные значения для объекта - они будут использованы если Ваше железо не прислало эти данные String circuitIdIp = "0.0.0.0"; // IP адрес свича String circuitIdPort = "0"; // порт свича if (adslAgentCircuitId != null) { /* я не стал делать тут итерации в духе этой: for (RadiusVendorAttribute i : adslAgentCircuitId) { value = i.getStringValue().toLowerCase(); } или этой: ListIterator i = adslAgentCircuitId.listIterator(); while (i.hasNext()) { value = i.next().getStringValue(); } т.к. в списке всего 1 элемент и проще его получить через get() */ // после скрипта предобработки атрибут имеет вид: // ADSL-Agent-Circuit-Id = 255.255.255.255:1 // где: 255.255.255.255 - IP адрес свича, 1 - порт свича circuitId = adslAgentCircuitId.get(0).getStringValue(); delimIdx = circuitId.indexOf(":"); circuitIdIp = circuitId.substring(0, delimIdx); circuitIdPort = circuitId.substring(delimIdx + 1); } int switchIp = IPUtils.convertStringIPtoInt(circuitIdIp); int switchPort = Integer.parseInt(circuitIdPort); // получаем списки с IP адресами свичей и портов listValueManager = new ListValueManager(con); configSwitchIpList = listValueManager.getValues(objectConfigSwitchIpId); // java.util.List<ListValue> (bitel.billing.server.contract.object.bean.ListValueManager) configSwitchPortList = listValueManager.getValues(objectConfigSwitchPortId); // java.util.List<ListValue> (bitel.billing.server.contract.object.bean.ListValueManager) // перебираем полученные списки, чтобы узнать есть они в базе или нет, если есть - их ID-ы пригодятся в дальнейшем int configSwitchIp = 0; int configSwitchIpId = 0; int configSwitchPort = 0; int configSwitchPortId = 0; for (ListValue i : configSwitchIpList) { configSwitchIp = IPUtils.convertStringIPtoInt(i.getTitle()); if (switchIp == configSwitchIp) { configSwitchIpId = i.getId(); // int (bitel.billing.server.contract.object.bean.ListValue) break; } } for (ListValue i : configSwitchPortList) { configSwitchPort = Integer.parseInt(i.getTitle()); if (switchPort == configSwitchPort) { configSwitchPortId = i.getId(); // int (bitel.billing.server.contract.object.bean.ListValue) break; } } // ни свича ни порта в базе нет - отбиваем юзера // отбиваем, потому что массовые звонки в ТП - лучший способ узнать что кто-то нарушил процедуру установки нового свича и дать этому кому-то по башке :) if (configSwitchIpId == 0 || configSwitchPortId == 0) { error("Ahtung! No switch IP " + circuitIdIp + " or port " + circuitIdPort + " in object's catalog!"); // удаляем из ответа ненужные там атрибуты if (response.hasAttribute(6)) response.removeAttributes(6); // Service-Type if (response.hasAttribute(7)) response.removeAttributes(7); // Framed-Protocol if (response.hasAttribute(8)) response.removeAttributes(8); // Framed-IP-Address if (response.hasAttribute(85)) response.removeAttributes(85); // Acct-Interim-Interval response.setPacketType(RadiusPacket.AUTHENTICATION_REJECT); writeError(event, con, contractId, 35); // код ошибки: 35 - Истек срок жизни карточного договора return; } boolean validUser = false; // изначально юзер "не правильный" и "правильным" он станет только когда мы его проверим :) // работаем только с тему к кого все ОК, т.е. нет ошибки баланса и т.п. if (errorCode < 1) { // получаем список объектов договора objectManager = new ObjectManager(con); contractObjectList = objectManager.getObjectList(contractId); // java.util.List<ContractObject> (bitel.billing.server.contract.object.bean.ObjectManager) paramValueManager = new ParamValueManager(con); if (contractObjectList.isEmpty()) { // у договора нет объектов // добавляем объект contractObject = new ContractObject(); contractObject.setTypeId(objectConfigTypeId); contractObject.setContractId(contractId); contractObject.setDate1(TimeUtils.convertDateToCalendar(contract.getDateFrom())); contractObject.setTitle(listValueManager.getValue(configSwitchIpId).getTitle() + ":" + listValueManager.getValue(configSwitchPortId).getTitle()); objectManager.updateObject(contractObject); // обновляем значения списков contractObjectId = contractObject.getId(); contractObjectSwitchIp = new ListParamValue(); contractObjectSwitchIp.setObjectId(contractObjectId); contractObjectSwitchIp.setParamId(objectConfigSwitchIpId); contractObjectSwitchIp.setValue(configSwitchIpId); paramValueManager.updateParamValue(contractObjectSwitchIp); contractObjectSwitchPort = new ListParamValue(); contractObjectSwitchPort.setObjectId(contractObjectId); contractObjectSwitchPort.setParamId(objectConfigSwitchPortId); contractObjectSwitchPort.setValue(configSwitchPortId); paramValueManager.updateParamValue(contractObjectSwitchPort); // событие EventProcessor.getProcessor().addEvent(new ContractAddObjectEvent(contractObject)); // это хороший, годный юзер - делаем его "правильным" validUser = true; } else { // у договора есть объекты for (ContractObject i : contractObjectList) { objectId = i.getId(); objectSwitchIpListParamValue = paramValueManager.getListParamValue(objectId, objectConfigSwitchIpId).getValue(); objectSwitchIpListValue = listValueManager.getValue(objectSwitchIpListParamValue).getTitle(); objectSwitchIp = IPUtils.convertStringIPtoInt(objectSwitchIpListValue); objectSwitchPortListParamValue = paramValueManager.getListParamValue(objectId, objectConfigSwitchPortId).getValue(); objectSwitchPortListValue = listValueManager.getValue(objectSwitchPortListParamValue).getTitle(); objectSwitchPort = Integer.parseInt(objectSwitchPortListValue); if (switchIp == objectSwitchIp && switchPort == objectSwitchPort) { validUser = true; break; } else { validUser = false; } } contractLoginCount = 0; // кол-во алиасов в dialup contractObjectCount = contractObjectList.size(); // кол-во объектов договора // считаем кол-во алиасов в dialup qw = "SELECT COUNT(login) AS cnt FROM user_login_" + event.getModuleID() + " WHERE cid = ? AND date2 IS NULL"; ps = con.prepareStatement(qw); ps.setInt(1, contractId); rs = ps.executeQuery(); if (rs.first()) { contractLoginCount = rs.getInt("cnt"); } // юзер "пришел" не оттуда, откуда ждали, но т.к. кол-во рабочих логинов больше чем кол-во объектов то создаем новый объект if (!validUser && contractLoginCount > contractObjectCount) { contractObject = new ContractObject(); contractObject.setTypeId(objectConfigTypeId); contractObject.setContractId(contractId); contractObject.setDate1(TimeUtils.convertDateToCalendar(contract.getDateFrom())); contractObject.setTitle(listValueManager.getValue(configSwitchIpId).getTitle() + ":" + listValueManager.getValue(configSwitchPortId).getTitle()); objectManager.updateObject(contractObject); contractObjectId = contractObject.getId(); contractObjectSwitchIp = new ListParamValue(); contractObjectSwitchIp.setObjectId(contractObjectId); contractObjectSwitchIp.setParamId(objectConfigSwitchIpId); contractObjectSwitchIp.setValue(configSwitchIpId); paramValueManager.updateParamValue(contractObjectSwitchIp); contractObjectSwitchPort = new ListParamValue(); contractObjectSwitchPort.setObjectId(contractObjectId); contractObjectSwitchPort.setParamId(objectConfigSwitchPortId); contractObjectSwitchPort.setValue(configSwitchPortId); paramValueManager.updateParamValue(contractObjectSwitchPort); EventProcessor.getProcessor().addEvent(new ContractAddObjectEvent(contractObject)); validUser = true; } // кол-во рабочих логинов меньше чем кол-во объектов - чтобы не было путаницы удаляем все объекты и создаем новый // ситуация довольно редкая - возникает только когда клиент отказывается от доп. логина(ов) if (contractLoginCount < contractObjectCount) { for (ContractObject i : contractObjectList) { objectId = i.getId(); paramValueManager.deleteActivityParamValues(objectId); objectManager.deleteObject(objectId); EventProcessor.getProcessor().addEvent(new ContractDeleteObjectEvent(i)); } contractObject = new ContractObject(); contractObject.setTypeId(objectConfigTypeId); contractObject.setContractId(contractId); contractObject.setDate1(TimeUtils.convertDateToCalendar(contract.getDateFrom())); contractObject.setTitle(listValueManager.getValue(configSwitchIpId).getTitle() + ":" + listValueManager.getValue(configSwitchPortId).getTitle()); objectManager.updateObject(contractObject); contractObjectId = contractObject.getId(); contractObjectSwitchIp = new ListParamValue(); contractObjectSwitchIp.setObjectId(contractObjectId); contractObjectSwitchIp.setParamId(objectConfigSwitchIpId); contractObjectSwitchIp.setValue(configSwitchIpId); paramValueManager.updateParamValue(contractObjectSwitchIp); contractObjectSwitchPort = new ListParamValue(); contractObjectSwitchPort.setObjectId(contractObjectId); contractObjectSwitchPort.setParamId(objectConfigSwitchPortId); contractObjectSwitchPort.setValue(configSwitchPortId); paramValueManager.updateParamValue(contractObjectSwitchPort); EventProcessor.getProcessor().addEvent(new ContractAddObjectEvent(contractObject)); validUser = true; } } } // этот юзер по прежнему "не правильный" (см. начало) и поэтому отбиваем его if (!validUser) { // удаляем из ответа ненужные там атрибуты if (response.hasAttribute(6)) response.removeAttributes(6); // Service-Type if (response.hasAttribute(7)) response.removeAttributes(7); // Framed-Protocol if (response.hasAttribute(8)) response.removeAttributes(8); // Framed-IP-Address if (response.hasAttribute(85)) response.removeAttributes(85); // Acct-Interim-Interval response.setPacketType(RadiusPacket.AUTHENTICATION_REJECT); writeError(event, con, contractId, 22); // код ошибки: 22 - Запрещён вход на данный телефон return; }
Настройка PPPoE Circuit Id Insertion в свичах D-Link (проверено на DES-1228/ME, DES-3028 и DES-3526):
config pppoe circuit_id_insertion ports <клиентские порты> state enable
config pppoe circuit_id_insertion ports <up/downlink порты> state disable
config pppoe circuit_id_insertion state enable