Доброго времени суток, коллеги. В наличии есть Cisco ASA 5512 с настроенным сервером IPSEC туннелей к которому цепляются пользователи для доступа в корпоративную сеть. Поступила задача — выводить список активных пользователей в мониторинге, а также вести логирование кто, когда, с какого адреса и с каким профилем цеплялся.
Полазил по интернету, ничего подходящего для моей задачи не нашёл (может конечно плохо искал), и решил написать скрипт, который парсит вывод SNMP и складывает в таблицу.
Структура таблицы MySQL:
Сам скрипт обработки:
Логика работы:
Скрипт запускается каждый 30 секунд через crone и опрашивает по SNMP оборудование. Так как логинов пользователей в открытом виде Cisco не хранит, то необходимо вытащить логины из динамически формируемого SNMP OID. Каждый символ логин хранится с помощью ASCII кода в части SNMP OID (это почерпнул отсюда).
После отработки функции get_user() переменная $x принимает значение в следующем формате:
Описание значений можно посмотреть тут.
Дальше скрипт проверяет — есть ли логин в таблице Logins. Если такого логина нет — добавляет его туда, если есть — получает его id_l. Затем, смотрит — есть ли у этого логина открытые сессии с неустановленным датой окончания. Если нет — создаёт в таблице sessions новую запись.
Затем скрипт получает из базы список пользователей, у которых есть незавершённые сессии. И проверяет наличие логинов в опросе. Если логина нет в опросе или номер сессии не совпадает с той, которая в базе (s_index) — ему проставляется время завершения сессии.
Готов к замечаниям/исправлениям/доработкам/вопросам.
UPD: добавил strtolower() в функциях парсинга логина и в проверке существования логина в активных сессиях (почти в конце уже). Иначе, если логин содержит заглавные буквы, тогда некорректно работает всё.
Полазил по интернету, ничего подходящего для моей задачи не нашёл (может конечно плохо искал), и решил написать скрипт, который парсит вывод SNMP и складывает в таблицу.
Структура таблицы MySQL:
CREATE DATABASE `vpn_log` /*!40100 DEFAULT CHARACTER SET latin1 */;
CREATE TABLE `logins` (
`id_l` int(10) unsigned NOT NULL AUTO_INCREMENT,
`login` varchar(90) NOT NULL,
`id_s` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id_l`),
UNIQUE KEY `id_l_UNIQUE` (`id_l`),
UNIQUE KEY `login_UNIQUE` (`login`)
) ENGINE=InnoDB AUTO_INCREMENT=121 DEFAULT CHARSET=latin1;
CREATE TABLE `sessions` (
`id_s` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_l` int(10) unsigned NOT NULL,
`time_start` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`time_end` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`ip_source` varchar(45) NOT NULL,
`ip_lan` varchar(45) NOT NULL,
`s_index` int(10) unsigned NOT NULL,
`profile` varchar(255) NOT NULL,
PRIMARY KEY (`id_s`),
UNIQUE KEY `id_s_UNIQUE` (`id_s`)
) ENGINE=InnoDB AUTO_INCREMENT=2923 DEFAULT CHARSET=latin1;
Сам скрипт обработки:
#!/usr/bin/php
<?php
##Database settings
$settings['sql_host']="localhost";
$settings['sql_db']="vpn_log";
$settings['sql_user']="vpn_log";
$settings['sql_password']="vpn_log";
#Подключение к базе#
function connectdb(){
global $settings;
$dbconn = mysqli_connect(
$settings['sql_host'],
$settings['sql_user'],
$settings['sql_password'],
$settings['sql_db']
)
or die('Could not connect: ' . mysqli_connect_errno());
return $dbconn;
}
#Получение списка пользователей и параметров IPSEC сессий#
function get_users(){
$ret = snmp3_real_walk(
'10.10.10.10', #IP адрес Cisco ASA
'snmpuser', #SNMP авторизация
'authNoPriv', #
'MD5', #
'authpassword', #
'', #
'', #
'1.3.6.1.4.1.9.9.392.1.3.21' # ветка OID в которой расположены пользователи
);
$result = [];
$user = [];
foreach ($ret as $oid=>$value){
$re = '/(SNMPv2-SMI::enterprises\.9\.9\.392\.1\.3\.21\.1\.[0-9]{1,2}\.[0-9]{1,2}\.)([\.0-9]*)\.([0-9]{4,10})/'; # регулярное выражение, которое парсит вывод и выцепляет логины пользователей
$str = $oid;
preg_match_all($re, $str, $matches);
$oid = explode(".",$matches[2][0]);
$value = explode(": ",$value);
$login = "";
foreach ($oid as $chr){
$login.=chr($chr);
};
$result[strtolower($login)][$matches[3][0]][] = @str_ireplace("\"","",$value[1]);
};
return $result;
#на выходе получаем массив из объектов вида [login][s_index][value]
};
$x = get_users();
$connect = connectdb();
foreach ($x as $user=>$sessions){
$user = addslashes($user);
foreach ($sessions as $session=>$value ){
if (preg_match("/((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)/",$value[7]) and preg_match("/((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)/",$value[5])){
$sql = "SELECT * from logins where login = '$user'";
$result = mysqli_query($connect, $sql);
$res = mysqli_fetch_array($result);
$time = date("Y-m-d H:i:s",time()-$value[3]);
$id_l = $res['id_l'];
if (mysqli_num_rows($result)==0){
$sql_1 = "INSERT INTO logins value ('','".$user."','')";
$result_1 = mysqli_query($connect, $sql_1);
$id_l = mysqli_insert_id($connect);
$sql_1 = "INSERT INTO sessions (id_l,time_start,ip_source,ip_lan,s_index, profile)value ('$id_l','$time','$value[7]','$value[5]','$session', '$value[0]')";
$result_1 = mysqli_query($connect, $sql_1);
$id_s = mysqli_insert_id($connect);
}
else{
$sql_1 = "SELECT * from sessions where (id_l = (select id_l from logins where login = '$user'))and(s_index = '$session')and(time_end = '0')";
$result_1 = mysqli_query($connect, $sql_1);
if (mysqli_num_rows($result_1)==0){
$sql_2 = "INSERT INTO sessions (id_l,time_start,ip_source,ip_lan,s_index, profile)value ('$id_l','$time','$value[7]','$value[5]','$session', '$value[0]')";
$result_2 = mysqli_query($connect, $sql_2);
}
}
}
}
}
$sql = "SELECT l.login,s.s_index FROM logins as l left join sessions as s on l.id_l=s.id_l where (s.time_end=0)";
$rw = mysqli_query($connect,$sql);
$result = mysqli_fetch_array($rw);
while ($result['s_index']>0){
if (@!$x[strtolower($result['login'])][$result['s_index']][0]){
$sql_1 = "UPDATE sessions SET time_end = '".date("Y-m-d H:i:s",time())."' where s_index='".$result['s_index']."'";
mysqli_query($connect, $sql_1);
};
$result = mysqli_fetch_array($rw);
};
mysqli_close($connect);
?>
Логика работы:
Скрипт запускается каждый 30 секунд через crone и опрашивает по SNMP оборудование. Так как логинов пользователей в открытом виде Cisco не хранит, то необходимо вытащить логины из динамически формируемого SNMP OID. Каждый символ логин хранится с помощью ASCII кода в части SNMP OID (это почерпнул отсюда).
После отработки функции get_user() переменная $x принимает значение в следующем формате:
[login] => Array
(
[s_index] => Array
(
[0] => profile_name
.............................
[34] => 0
)
)
Описание значений можно посмотреть тут.
Дальше скрипт проверяет — есть ли логин в таблице Logins. Если такого логина нет — добавляет его туда, если есть — получает его id_l. Затем, смотрит — есть ли у этого логина открытые сессии с неустановленным датой окончания. Если нет — создаёт в таблице sessions новую запись.
Затем скрипт получает из базы список пользователей, у которых есть незавершённые сессии. И проверяет наличие логинов в опросе. Если логина нет в опросе или номер сессии не совпадает с той, которая в базе (s_index) — ему проставляется время завершения сессии.
Готов к замечаниям/исправлениям/доработкам/вопросам.
UPD: добавил strtolower() в функциях парсинга логина и в проверке существования логина в активных сессиях (почти в конце уже). Иначе, если логин содержит заглавные буквы, тогда некорректно работает всё.
Поделиться с друзьями
epicf4il
Еще можно парсить логи асы. В части SVC они достаточно информативны, их много и они имеют соответсвующие ID, что облегчает процесс получения только необходимых сообщений.
NvAriec
Изначально хотел сделать через парсинг логов, но когда настроил phplogcon чтобы посмотреть что и как там происходит — не увидел в логах ни единого упоминания об IP адресе, выдаваемого клиенту. Может плохо смотрел.
Креденшелы только на чтение. К тому же можно ограничить выдаваемые ветки для этого пользователя.
epicf4il
Скорее всего, просто не заметили сообщения 722051
NvAriec
Может быть просто уровень логирования был не тот или может какие дополнительные настройки необходимо было сделать, но таких логов 100% не видел. Делал поиск по подсети, которую получают клиенты и ничего не находилось.
epicf4il
Как видно из примеров, для сообщений 722051 и 113019 достаточно 4-ого уровня severity (warnings). Для более детальных потребуется 6-ой или уже 7-ой.
NvAriec
Включал последний уровень для дебага.
Будет время — покопаюсь ещё, может переделаю и дополню вторым вариантом)
neumeika
мда, а вот radius для AAA не катит?
NvAriec
Катит. Он и настроен. Вся авторизация проходит через Active Directory. Не пойму что Вы предлагаете.
gotch
Складывать логи радиуса в SQL?
neumeika
нативно :)
но велосипеды выгодно отличаются простотой/удобством/скоростью
Openmsk
Я писал скрипт на powershell который запускался когда пользователь логинится, в логе венды есть и адрес откуда пользователь пришёл.
В этом же скрипте не большое HTML тело, которое отправляется на почту пользователю и СБ, там видно кто, когда, откуда пришёл.
Завтра могу показать рыбу скрипта если надо
neumeika
скрипт по крону, али на IAS изобразили, или в скуле какой-то триггер?
Openmsk
sql не используется.
шедулер на радиус сервере запускается как только в логе появляется событие, и запускает скрипт, который хватает это событие и раскладывает на нужные нам поля.
VFedorV
у нас безопасник настроил OSSIM (парсит логи ASA, которые ему пересылаются syslog-ом) грамотно:
ultraElephant
Недавно делал менее красивое решение, но для заббикса.
В конце — ссылка на скрипт и шаблон, если кому нужно.
NvAriec
Не поверите, как раз потом свою базу пихал в Zabbix отдельной страницей)
Спасибо за ссылку.