Application: "Freeradius v3", "Zabbix v3.4".
Задача: наладить посредством системы мониторинга "Zabbix" отслеживание текущего состояния сервиса аутентификации "Freeradius", хранения статистики и уведомления о недоступности критически важных компонентов.
Общий принцип действия выработанного решения таков:
1. Раз в час "Zabbix" обращается за списком актуальных объектов мониторинга к "Zabbix Agent"-у на стороне сервера "Freeradius", ожидая его в JSON-массиве.
2. Для полученного перечня объектов мониторинга сервером "Zabbix", в соответствии с заготовками в специализированном шаблоне, по спецификации "Low-Level Discovery (LLD)" создаются необходимые элементы.
3. Практически все запросы обрабатываются запускаемыми "Zabbix Agent"-ом самодельными скриптами, извлекающими данные через CLI-утилиты "Freeradius".
2. Для полученного перечня объектов мониторинга сервером "Zabbix", в соответствии с заготовками в специализированном шаблоне, по спецификации "Low-Level Discovery (LLD)" создаются необходимые элементы.
3. Практически все запросы обрабатываются запускаемыми "Zabbix Agent"-ом самодельными скриптами, извлекающими данные через CLI-утилиты "Freeradius".
Получившееся полностью автоматизированное решение отслеживает состояние локального сервера и связей с внешними серверами проксирования по следующим позициям:
Наличие процесса сервиса "Freeradius" (item/trigger, every 30sec);
Статус локального сервера аутентификации (item/trigger, every 2min);
Статистика запросов "Accepted" и "Rejected" локального сервера аутентификации (item/graph, every 60sec);
Статус каждого соединения с сервером проксирования по отдельности (item/trigger, every 5min);
Статистика запросов "Accepted" и "Rejected" каждого соединения с сервером проксирования по отдельности (item/graph, every 60sec).
Статус локального сервера аутентификации (item/trigger, every 2min);
Статистика запросов "Accepted" и "Rejected" локального сервера аутентификации (item/graph, every 60sec);
Статус каждого соединения с сервером проксирования по отдельности (item/trigger, every 5min);
Статистика запросов "Accepted" и "Rejected" каждого соединения с сервером проксирования по отдельности (item/graph, every 60sec).
Предварительная подготовка.
На стороне сервера аутентификации должен быть установлен "Zabbix Agent" и CLI-утилиты для "Freeradius", а также утилита проверки синтаксиса JSON:
# aptitude install zabbix-agent freeradius-utils jq
Перед всеми дальнейшими работами потребуется применить заранее подготовленный мною специализированный шаблон для системы мониторинга:
Очень желательно сразу проверить возможность прохождения запроса от сервера мониторинга "Zabbix" к запущенному на стороне сервера аутентификации "Zabbix Agent"-у:
# sudo -u zabbix -s zabbix_get -s freeradius.example.net -k "proc.num[freeradius]"
Настраиваем "Zabbix Agent".
Учитывая то, что процедуры сбора данных и отправки их на сервер мониторинга осуществляется клиентом "Zabbix", то конфигурационные файл и скрипты расположим в его директории:
# mkdir -p /etc/zabbix/scripts
# mkdir -p /etc/zabbix/zabbix_agents.conf.d
# mkdir -p /etc/zabbix/zabbix_agents.conf.d
Определяем параметры "Zabbix-Agent"-а, запросы к которым будут обслуживаться внешними приложениями:
# vi /etc/zabbix/zabbix_agentd.conf.d/freeradius.conf
UserParameter=freeradius.discovery, /etc/zabbix/scripts/freeradius_discovery.sh
UserParameter=freeradius.stat[*], /etc/zabbix/scripts/freeradius_stat.sh $1 $2 $3 $4
UserParameter=freeradius.stat[*], /etc/zabbix/scripts/freeradius_stat.sh $1 $2 $3 $4
Установленной по умолчанию двухсекундной задержки при ожидании ответа от "Zabbix-Agent"-а на практике недостаточно - продлеваем до пяти секунд:
# vi /etc/zabbix/zabbix_agentd.conf
....
### Option: Timeout
Timeout=5
....
### Option: Timeout
Timeout=5
....
Подстраховываясь, закрываем к настройкам и скриптам "Zabbix" доступ посторонним:
# chown -R zabbix:zabbix /etc/zabbix
# chmod o-rwx /etc/zabbix
# chmod o-rwx /etc/zabbix
Для применения изменений в конфигурации "Zabbix Agent" необходимо перезапустить:
# /etc/init.d/zabbix-agent restart
Разрешаем "Zabbix-Agent"-у запуск ограниченного перечня операции в контексте сервиса "Freeradius":
# vi /etc/sudoers.d/zabbix-agent
zabbix ALL=(freerad:freerad) NOPASSWD: /bin/cat /etc/freeradius/3.0/*
zabbix ALL=(freerad:freerad) NOPASSWD: /usr/sbin/radmin,/usr/bin/radclient
zabbix ALL=(freerad:freerad) NOPASSWD: /usr/sbin/radmin,/usr/bin/radclient
Проверяем синтаксическую корректность конфигурации SUDO:
# visudo -cf /etc/sudoers.d/zabbix-agent
Настраиваем "Freeradius".
Активируем виртуальный сервер предоставляющий только на "локальной сетевой петле" статистические данные "Freeradius" (настройки по умолчанию вполне приемлемы):
# ln -s /etc/freeradius/3.0/sites-available/status /etc/freeradius/3.0/sites-enabled/status
Для применения изменений в конфигурации "Freeradius" необходимо перезапустить:
# freeradius -C -X && systemctl restart freeradius
Пишем и тестируем скрипт "Zabbix Auto Discovery (LLD)".
Подготовим простейший Bash-скрипт, по запросу подключающийся к "Freeradius", запрашивающий перечень объектов мониторинга и формирующий соответствующий требования "Zabbix Auto Discovery" JSON-массив:
# cd /etc/zabbix/scripts
# vi ./freeradius_discovery.sh && chown zabbix:zabbix ./freeradius_discovery.sh && chmod ug+x,o-rwx ./freeradius_discovery.sh
# vi ./freeradius_discovery.sh && chown zabbix:zabbix ./freeradius_discovery.sh && chmod ug+x,o-rwx ./freeradius_discovery.sh
#!/bin/bash
# usage: ./freeradius_discovery.sh
# Задаём переменные рабочего окружения
CNFD="/etc/freeradius/3.0"
LOG="/var/log/zabbix-agent/zabbix-freeradius-error.log"
DATE=$(date +"%Y-%m-%d.%H:%M:%S")
# Проверяем наличие ожидаемых утилит
[ -x "$(command -v jq)" ] || { echo "${DATE}: Не обнаружены необходимые для работы утилиты. Процедура создания списка активных серверов проксирования прервана." >> ${LOG}; exit 1; }
# Получаем выборкой из конфигурационного файла перечень серверов проксирования
UPLINKS=$(sudo -u freerad cat ${CNFD}/proxy.conf 2>/dev/null | grep -v -i -E '^(#|[[:blank:]]*#)' | tr '\n' ' ' | tr '\r' ' ' | sed 's/ \{1,\}/ /g' | grep -o -P "home_server\s?[^\s]+\s?{.*?}" | sed -n "s/^.*home_server\s*\(\S*\).*$/\1/p")
# Если перечень серверов проксирования не пуст, то формируем JSON для "Zabbix Low-Level Discovery (LLD)"
if [ ! -z "${UPLINKS}" ] ; then
# Задаём начало JSON
JSONZLLD="{\"data\":["
FIRST=1
# Перебираем блоки конфигурации
for UPLINK in ${UPLINKS} ; do
# Проставляем разделитель между элементами JSON
if [ ${FIRST} == 0 ] ; then
JSONZLLD=${JSONZLLD}","
fi
FIRST=0
# Вводим имя задания в качестве элмента JSON
JSONZLLD=${JSONZLLD}"{\"{#UPLINK}\":\"${UPLINK}\"}"
done
# Завершаем JSON
JSONZLLD=${JSONZLLD}"]}"
# Проверяем синтаксическую корректность JSON
if jq -e . 1>/dev/null 2>&1 <<< "${JSONZLLD}" ; then
# Отдаём JSON на STDOUT
echo ${JSONZLLD}
fi
fi
exit ${?}
# usage: ./freeradius_discovery.sh
# Задаём переменные рабочего окружения
CNFD="/etc/freeradius/3.0"
LOG="/var/log/zabbix-agent/zabbix-freeradius-error.log"
DATE=$(date +"%Y-%m-%d.%H:%M:%S")
# Проверяем наличие ожидаемых утилит
[ -x "$(command -v jq)" ] || { echo "${DATE}: Не обнаружены необходимые для работы утилиты. Процедура создания списка активных серверов проксирования прервана." >> ${LOG}; exit 1; }
# Получаем выборкой из конфигурационного файла перечень серверов проксирования
UPLINKS=$(sudo -u freerad cat ${CNFD}/proxy.conf 2>/dev/null | grep -v -i -E '^(#|[[:blank:]]*#)' | tr '\n' ' ' | tr '\r' ' ' | sed 's/ \{1,\}/ /g' | grep -o -P "home_server\s?[^\s]+\s?{.*?}" | sed -n "s/^.*home_server\s*\(\S*\).*$/\1/p")
# Если перечень серверов проксирования не пуст, то формируем JSON для "Zabbix Low-Level Discovery (LLD)"
if [ ! -z "${UPLINKS}" ] ; then
# Задаём начало JSON
JSONZLLD="{\"data\":["
FIRST=1
# Перебираем блоки конфигурации
for UPLINK in ${UPLINKS} ; do
# Проставляем разделитель между элементами JSON
if [ ${FIRST} == 0 ] ; then
JSONZLLD=${JSONZLLD}","
fi
FIRST=0
# Вводим имя задания в качестве элмента JSON
JSONZLLD=${JSONZLLD}"{\"{#UPLINK}\":\"${UPLINK}\"}"
done
# Завершаем JSON
JSONZLLD=${JSONZLLD}"]}"
# Проверяем синтаксическую корректность JSON
if jq -e . 1>/dev/null 2>&1 <<< "${JSONZLLD}" ; then
# Отдаём JSON на STDOUT
echo ${JSONZLLD}
fi
fi
exit ${?}
Пример получаемого в ответ на запрос JSON-файла для "Zabbix LLD", с обнаруженными активными объектами мониторинга:
{
"data":[
{"{#UPLINK}":"node-1-upstream"},
{"{#UPLINK}":"node-2-upstream"},
{"{#UPLINK}":"neighbor-1-moscow"},
{"{#UPLINK}":"neighbor-1-kiev"}
]
}
"data":[
{"{#UPLINK}":"node-1-upstream"},
{"{#UPLINK}":"node-2-upstream"},
{"{#UPLINK}":"neighbor-1-moscow"},
{"{#UPLINK}":"neighbor-1-kiev"}
]
}
Проверяем корректность отрабатывания скрипта "Auto Discovery (LLD)":
# sudo -u zabbix -s zabbix_get -s freeradius.example.net -k "freeradius.discovery"
Пишем и тестируем скрипт получения запрашиваемых параметров.
Создаём специализированный Bash-скрипт, принимающий от "Zabbix Agent"-а запросы на получение данных по ряду интересующих нас параметров, обращающийся к CLI-утилитам "Freeradius" и нормализующий их перед выдачей:
# cd /etc/zabbix/scripts
# vi ./freeradius_stat.sh && chown zabbix:zabbix ./freeradius_stat.sh && chmod ug+x,o-rwx ./freeradius_stat.sh
# vi ./freeradius_stat.sh && chown zabbix:zabbix ./freeradius_stat.sh && chmod ug+x,o-rwx ./freeradius_stat.sh
#!/bin/bash
# usage: ./freeradius_stat.sh "targetObjectName" "objectParam" {Level}
# Задаём переменные рабочего окружения
CNFD="/etc/freeradius/3.0"
LOG="/var/log/zabbix-agent/zabbix-freeradius-error.log"
DATE=$(date +"%Y-%m-%d.%H:%M:%S")
unset ANSWER
#
# Задаём известные параметры подключения к серверу статистики на "локальной петле"
# (учитывая то, что сервис доступен только изнутри несущего сервера не вижу необходимости изменять параметры аутентификации)
LIPADDR="127.0.0.1"
LPORT="18121"
LSECRET="adminsecret"
# Проверяем наличие ожидаемых утилит
[ -x "$(command -v radmin)" ] && [ -x "$(command -v radclient)" ] || { echo "${DATE}: Не обнаружены необходимые для работы утилиты. Процедура создания списка активных серверов проксирования прервана." >> ${LOG}; exit 1; }
# Проверяем корректность вводимых данных и выводим подсказку в журнал событий при необходимости
OITARGET="${1}"
OIPARAM="${2}"
#
[ ! "${OITARGET}" ] && { echo "${DATE}: Запрос: \"$(basename $0) $@\". Не указан субъект. Процедура запроса статуса прервана." >> ${LOG}; exit 1; }
[ ! "${OIPARAM}" ] && { echo "${DATE}: Запрос: \"$(basename $0) $@\". Не указан запрашиваемый параметр. Процедура запроса статуса прервана." >> ${LOG}; exit 1; }
# В зависимости от целевого объекта формируем набор параметров подключения к таковому
if [[ "${OITARGET}" == "local" ]] ; then
# Применяем для запроса известные параметры сервера на "локальной петле"
OIPADDR=${LIPADDR}
OPORT=${LPORT}
OSECRET=${LSECRET}
OSCHECK="status-server"
OSTYPE="1"
else
# Получаем выборкой из конфигурационного файла "Freeradius" блок описания целевого сервера проксирования
CNFBLOCK=$(sudo -u freerad cat ${CNFD}/proxy.conf 2>/dev/null | grep -v -i -E '^(#|[[:blank:]]*#)' | tr '\n' ' ' | tr '\r' ' ' | sed 's/ \{1,\}/ /g' | grep -o -P "home_server\s?${OITARGET}+\s?{.*?}")
if [ ! -z "${CNFBLOCK}" ] ; then
# Вычленяем ожидаемые параметры сервера проксирования
OIPADDR=$(echo ${CNFBLOCK} | sed -n "s/^.*ipaddr\s*=\s*\(\S*\)\s*.*$/\1/p")
OPORT=$(echo ${CNFBLOCK} | sed -n "s/^.*port\s*=\s*\(\S*\)\s*.*$/\1/p")
OSECRET=$(echo ${CNFBLOCK} | sed -n "s/^.*secret\s*=\s*\(\S*\)\s*.*$/\1/p")
OSCHECK=$(echo ${CNFBLOCK} | sed -n "s/^.*status_check\s*=\s*\(\S*\)\s*.*$/\1/p")
OSTYPE="131"
fi
fi
# Продолжаем, если параметры подключения к целевому серверу обнаружены
if [[ ! -z "${OIPADDR}" && ! -z "${OPORT}" && ! -z "${OSECRET}" && ! -z "${OSCHECK}" ]] ; then
# Отрабатываем запрос статуса сервера
if [ "${OIPARAM}" == "status" ] ; then
# Запрашиваем состояние сервера напрямую, если это разрешено в конфигурации
# (первый этап проверки, подключением к серверу извне)
if [[ "${OSCHECK}" == "status-server" ]] ; then
OSTATUS=$(echo "Message-Authenticator = 0x00, Response-Packet-Type = Access-Accept" | radclient -x -r1 -t3 ${OIPADDR}:${OPORT} status ${OSECRET} 2>/dev/null | grep -i 'Received Access-Accept')
if [ ! -z "${OSTATUS}" ] ; then
OSTATUS="ok"
else
OSTATUS="failed"
fi
fi
# Проверяем статус сервера проксирования, установленный "Freeradius" в процессе работы
# (второй этап проверки, основанной на статистике работы с сервером)
[ "${OITARGET}" != "local" ] && OSTATE=$(sudo -u freerad radmin -e "show home_server state ${OIPADDR} ${OPORT}" 2>/dev/null)
# Суммируем результаты проверки статуса и выдаём ответ
if [[ -z "${OSTATUS}" || "${OSTATUS}" == "ok" ]] && [[ -z "${OSTATE}" || "${OSTATE}" == "alive" || "${OSTATE}" == "unknown" ]] ; then
ANSWER="ok"
else
ANSWER="failed"
fi
elif [ "${OIPARAM}" == "statistics-accepts" ] ; then
# Отдаём статистику успешных аутентификаций (линейная, не за определённый период)
ANSWER=$(echo "Message-Authenticator = 0x00, FreeRADIUS-Statistics-Type = ${OSTYPE}, FreeRADIUS-Stats-Server-IP-Address = ${OIPADDR}, FreeRADIUS-Stats-Server-Port = ${OPORT}" | radclient -x -r1 -t2 ${LIPADDR}:${LPORT} status ${LSECRET} | grep -i 'Access-Accepts' | awk -F '=' '{print $2}' | tr -d '[:space:]')
elif [ "${OIPARAM}" == "statistics-rejects" ] ; then
# Отдаём статистику отказов в аутентификации (линейная, не за определённый период)
ANSWER=$(echo "Message-Authenticator = 0x00, FreeRADIUS-Statistics-Type = ${OSTYPE}, FreeRADIUS-Stats-Server-IP-Address = ${OIPADDR}, FreeRADIUS-Stats-Server-Port = ${OPORT}" | radclient -x -r1 -t2 ${LIPADDR}:${LPORT} status ${LSECRET} | grep -i 'Access-Rejects' | awk -F '=' '{print $2}' | tr -d '[:space:]')
else
# Явно уведомляем, что запрашиваемый параметр неизвестен
ANSWER="failed"
fi
else
# Явно уведомляем, что целевой объект неизвестен
ANSWER="failed"
fi
# Отдаём значение запрашиваемого параметра (или ничего не отвечаем, если запрашиваемый параметр отсутствует)
[ ! -z "${ANSWER}" ] && echo ${ANSWER}
exit ${?}
# usage: ./freeradius_stat.sh "targetObjectName" "objectParam" {Level}
# Задаём переменные рабочего окружения
CNFD="/etc/freeradius/3.0"
LOG="/var/log/zabbix-agent/zabbix-freeradius-error.log"
DATE=$(date +"%Y-%m-%d.%H:%M:%S")
unset ANSWER
#
# Задаём известные параметры подключения к серверу статистики на "локальной петле"
# (учитывая то, что сервис доступен только изнутри несущего сервера не вижу необходимости изменять параметры аутентификации)
LIPADDR="127.0.0.1"
LPORT="18121"
LSECRET="adminsecret"
# Проверяем наличие ожидаемых утилит
[ -x "$(command -v radmin)" ] && [ -x "$(command -v radclient)" ] || { echo "${DATE}: Не обнаружены необходимые для работы утилиты. Процедура создания списка активных серверов проксирования прервана." >> ${LOG}; exit 1; }
# Проверяем корректность вводимых данных и выводим подсказку в журнал событий при необходимости
OITARGET="${1}"
OIPARAM="${2}"
#
[ ! "${OITARGET}" ] && { echo "${DATE}: Запрос: \"$(basename $0) $@\". Не указан субъект. Процедура запроса статуса прервана." >> ${LOG}; exit 1; }
[ ! "${OIPARAM}" ] && { echo "${DATE}: Запрос: \"$(basename $0) $@\". Не указан запрашиваемый параметр. Процедура запроса статуса прервана." >> ${LOG}; exit 1; }
# В зависимости от целевого объекта формируем набор параметров подключения к таковому
if [[ "${OITARGET}" == "local" ]] ; then
# Применяем для запроса известные параметры сервера на "локальной петле"
OIPADDR=${LIPADDR}
OPORT=${LPORT}
OSECRET=${LSECRET}
OSCHECK="status-server"
OSTYPE="1"
else
# Получаем выборкой из конфигурационного файла "Freeradius" блок описания целевого сервера проксирования
CNFBLOCK=$(sudo -u freerad cat ${CNFD}/proxy.conf 2>/dev/null | grep -v -i -E '^(#|[[:blank:]]*#)' | tr '\n' ' ' | tr '\r' ' ' | sed 's/ \{1,\}/ /g' | grep -o -P "home_server\s?${OITARGET}+\s?{.*?}")
if [ ! -z "${CNFBLOCK}" ] ; then
# Вычленяем ожидаемые параметры сервера проксирования
OIPADDR=$(echo ${CNFBLOCK} | sed -n "s/^.*ipaddr\s*=\s*\(\S*\)\s*.*$/\1/p")
OPORT=$(echo ${CNFBLOCK} | sed -n "s/^.*port\s*=\s*\(\S*\)\s*.*$/\1/p")
OSECRET=$(echo ${CNFBLOCK} | sed -n "s/^.*secret\s*=\s*\(\S*\)\s*.*$/\1/p")
OSCHECK=$(echo ${CNFBLOCK} | sed -n "s/^.*status_check\s*=\s*\(\S*\)\s*.*$/\1/p")
OSTYPE="131"
fi
fi
# Продолжаем, если параметры подключения к целевому серверу обнаружены
if [[ ! -z "${OIPADDR}" && ! -z "${OPORT}" && ! -z "${OSECRET}" && ! -z "${OSCHECK}" ]] ; then
# Отрабатываем запрос статуса сервера
if [ "${OIPARAM}" == "status" ] ; then
# Запрашиваем состояние сервера напрямую, если это разрешено в конфигурации
# (первый этап проверки, подключением к серверу извне)
if [[ "${OSCHECK}" == "status-server" ]] ; then
OSTATUS=$(echo "Message-Authenticator = 0x00, Response-Packet-Type = Access-Accept" | radclient -x -r1 -t3 ${OIPADDR}:${OPORT} status ${OSECRET} 2>/dev/null | grep -i 'Received Access-Accept')
if [ ! -z "${OSTATUS}" ] ; then
OSTATUS="ok"
else
OSTATUS="failed"
fi
fi
# Проверяем статус сервера проксирования, установленный "Freeradius" в процессе работы
# (второй этап проверки, основанной на статистике работы с сервером)
[ "${OITARGET}" != "local" ] && OSTATE=$(sudo -u freerad radmin -e "show home_server state ${OIPADDR} ${OPORT}" 2>/dev/null)
# Суммируем результаты проверки статуса и выдаём ответ
if [[ -z "${OSTATUS}" || "${OSTATUS}" == "ok" ]] && [[ -z "${OSTATE}" || "${OSTATE}" == "alive" || "${OSTATE}" == "unknown" ]] ; then
ANSWER="ok"
else
ANSWER="failed"
fi
elif [ "${OIPARAM}" == "statistics-accepts" ] ; then
# Отдаём статистику успешных аутентификаций (линейная, не за определённый период)
ANSWER=$(echo "Message-Authenticator = 0x00, FreeRADIUS-Statistics-Type = ${OSTYPE}, FreeRADIUS-Stats-Server-IP-Address = ${OIPADDR}, FreeRADIUS-Stats-Server-Port = ${OPORT}" | radclient -x -r1 -t2 ${LIPADDR}:${LPORT} status ${LSECRET} | grep -i 'Access-Accepts' | awk -F '=' '{print $2}' | tr -d '[:space:]')
elif [ "${OIPARAM}" == "statistics-rejects" ] ; then
# Отдаём статистику отказов в аутентификации (линейная, не за определённый период)
ANSWER=$(echo "Message-Authenticator = 0x00, FreeRADIUS-Statistics-Type = ${OSTYPE}, FreeRADIUS-Stats-Server-IP-Address = ${OIPADDR}, FreeRADIUS-Stats-Server-Port = ${OPORT}" | radclient -x -r1 -t2 ${LIPADDR}:${LPORT} status ${LSECRET} | grep -i 'Access-Rejects' | awk -F '=' '{print $2}' | tr -d '[:space:]')
else
# Явно уведомляем, что запрашиваемый параметр неизвестен
ANSWER="failed"
fi
else
# Явно уведомляем, что целевой объект неизвестен
ANSWER="failed"
fi
# Отдаём значение запрашиваемого параметра (или ничего не отвечаем, если запрашиваемый параметр отсутствует)
[ ! -z "${ANSWER}" ] && echo ${ANSWER}
exit ${?}
Аналогично предыдущим этапам проверяем корректность прохождения запросов к "Freeradius" от сервера мониторинга "Zabbix":
# sudo -u zabbix -s zabbix_get -s freeradius.example.net -k "freeradius.stat[node-1-upstream,status]"
Эксплуатация.
Как легко понять из вышеприведённых скриптов механизм актуализации перечня объектов мониторинга происходит полностью автоматически максимум спустя один час после изменения конфигурации сервера "Freeradius" - это удобно. Каких-то особых замечаний к схеме нет, она легко дополняется желаемыми параметрами, хотя лично мне более ничего ни разу не понадобилось.