UMGUM.COM (лучше) 

Zabbix + Freeradius ( Мониторинг состояния компонентов сервиса аутентификации "Freeradius". )

2 сентября 2019

OS: "Linux Debian 8/9 (Jessie/Stretch)", "Linux Ubuntu 16/18 (Xenial/Bionic) LTS".
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".

Получившееся полностью автоматизированное решение отслеживает состояние локального сервера и связей с внешними серверами проксирования по следующим позициям:

Наличие процесса сервиса "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).


Предварительная подготовка.

На стороне сервера аутентификации должен быть установлен "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

Определяем параметры "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

Установленной по умолчанию двухсекундной задержки при ожидании ответа от "Zabbix-Agent"-а на практике недостаточно - продлеваем до пяти секунд:

# vi /etc/zabbix/zabbix_agentd.conf

....
### Option: Timeout
Timeout=5
....

Подстраховываясь, закрываем к настройкам и скриптам "Zabbix" доступ посторонним:

# chown -R zabbix:zabbix /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

Проверяем синтаксическую корректность конфигурации 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

#!/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 ${?}

Пример получаемого в ответ на запрос JSON-файла для "Zabbix LLD", с обнаруженными активными объектами мониторинга:

{
  "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

#!/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 ${?}

Аналогично предыдущим этапам проверяем корректность прохождения запросов к "Freeradius" от сервера мониторинга "Zabbix":

# sudo -u zabbix -s zabbix_get -s eg2.nsu.ru -k "freeradius.stat[node-1-upstream,status]"

Эксплуатация.

Как легко понять из вышеприведённых скриптов механизм актуализации перечня объектов мониторинга происходит полностью автоматически максимум спустя один час после изменения конфигурации сервера "Freeradius" - это удобно. Каких-то особых замечаний к схеме нет, она легко дополняется желаемыми параметрами, хотя лично мне более ничего ни разу не понадобилось.


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )