UMGUM.COM 

Визуализация статистики ( Пишем простой сервис анализа журналов событий и представления свода количества успешно аутентифицировавшихся пользователей, сгруппированных по "realm". )

10 августа 2019  (обновлено 13 сентября 2019)

OS: "Linux Debian 8/9 (Jessie/Stretch)", "Linux Ubuntu 16/18 (Xenial/Bionic) LTS".
Application: "Freeradius v3", "Nginx", "Bash".

Задача: визуализировать статистику о количестве пользователей сегмента "Eduroam", без выдачи сведений о персоналиях.

Мы некоторое время размышляли, что хотелось бы видеть в статистическом срезе и пришли к тому, что свода перечня "realm"-ов и количества их пользователей, успешно аутентифицировавшихся за некоторые периоды времени, вполне достаточно. Этого легко добиться регулярным сканированием журналов событий аутентификации "Freeradius" и генерированием простейшей HTML-страницы с таблицей.

Результат выглядит примерно так (на этапе запуска сервиса, когда пользователей совсем не много):

размер: 320 400 640 800 1024 1280
Пример статистического среза количества пользователей сегмента "Eduroam".
Пример статистического среза количества пользователей сегмента "Eduroam".


Исходим от того, что конфигурация сервера аутентификации "Freeradius" соответствует приведённой в располагающейся рядом инструкции. Журналы событий формируются модулем "linelog", со строго определённым набором параметров.

Установка и настройка web-сервера.

Для отображения HTML-страницы со статистикой воспользуемся легковесным "Nginx":

# aptitude install nginx-light

Небольшая преднастройка сервиса:

# vi /etc/nginx/nginx.conf

user www-data www-data;
worker_processes auto;
....
http {
  ....
  # Велим Nginx не выдавать сведения о номере своей версии
  server_tokens off;
....

Настройки сайта самые простые, достаточные для отображения единственной статической HTML-страницы:

# vi /etc/nginx/sites-available/eduroam.example.net.conf

server {
  listen 80 default_server;
  server_name eduroam.example.net;

  access_log /var/log/nginx/eduroam.example.net.access.log;
  error_log /var/log/nginx/eduroam.example.net.error.log;

  # Явно указываем корень файловой структуры сайта
  root /var/www/eduroam.example.net/www;

  # Задаём перечень файлов, которые web-сервер должен выдать в отсутствии явного указания со стороны клиента
  index index.html;

  # Обслуживаем только внутрисетевых клиентов
  satisfy all;
  allow 10.0.0.0/8;
  allow 172.16.0.0/12;
  allow 192.168.0.0/16;
  deny  all;
}

Удаляем "сайт по умолчанию", применяем нашу конфигурацию, проверяем её корректность и перезапускаем web-сервер:

# rm /etc/nginx/sites-enabled/default
# ln -s /etc/nginx/sites-available/eduroam.example.net.conf /etc/nginx/sites-enabled/eduroam.example.net.conf
# nginx -t && /etc/init.d/nginx restart

Пишем скрипт анализа данных и визуализации статистики.

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

Заготовим месторасположение для скрипта и результирующей web-страницы:

# mkdir -p /var/www/cgi-bin/linelog
# mkdir -p /var/www/eduroam.example.net/www

Скрипт получился незамысловатый и даже в комментариях не нуждается, на мой вгляд - говорящих имён переменных вполне достаточно:

# vi /var/www/cgi-bin/linelog/view-make.sh && chmod ug+x /var/www/cgi-bin/linelog/view-make.sh

#!/bin/bash

unset BODY 2>/dev/null
BODY=${BODY}"<!DOCTYPE HTML>\n\
<html>\n\
<head>\n\
  <meta charset=\"utf-8\">\n\
  <font face=\"sans-serif\">\n\
  <meta http-equiv=\"refresh\" content=\"600\">\n\
  <title>Eduroam Gateway Usage Statistics (Example)</title>\n\
  <style>\n\
    a {text-decoration: none;}\n\
    a:visited {color: blue;}\n\
    table {text-align: left; border-collapse: collapse; margin: 0pt; padding: 0pt;}\n\
    tr {border: none; margin: 0pt; padding: 0pt;}\n\
    th {background-color: #F5F5F5; border: #C0C0C0 1px solid; text-align: center; font-size: 90%; margin: 0pt; padding-left: 4pt; padding-top: 2pt; padding-right: 4pt; padding-bottom: 2pt;}\n\
    td {border: #C0C0C0 1px solid; text-align: left; vertical-align: top; margin: 0pt; padding-left: 4pt; padding-top: 2pt; padding-right: 4pt; padding-bottom: 2pt;}\n\
  </style>\n\
</head>\n\
<body>\n\

<h2 style=\"color: #333333;\">Eduroam Gateway Usage Statistics (Example)</h2>\n\
<a href=\"https://eduroam.example.net\" style=\"font-size: 120%;\">eduroam.example.net</a>&nbsp;<span style=\"color: #808080;\">(this server)</span>
\n"

BODY=${BODY}"<table>\n"
BODY=${BODY}"<tr>\n"
BODY=${BODY}"<td style=\"border: none; padding-right: 15pt;\">\n"

BODY=${BODY}"<h3 style=\"color: #333333;\">Today:</h3>\n"
BODY=${BODY}"<table style='font-size: 12pt'>\n"
BODY=${BODY}"<tr>\n"
BODY=${BODY}"<th>Realm</th><th>Amount</th>\n"
BODY=${BODY}"</tr>\n"

ACCEPTED_TODAY=$(cat /var/log/freeradius/linelog/linelog*.log | grep -E 'Operator-Name = .?example.net' | grep 'Send-Accept' | grep -e "^$(date +%Y-%m-%d)" | awk -F ', ' '{print $2}' | awk 'match($0, /User-Name = .+/) {print substr($0, RSTART, RLENGTH);}' | awk -F '=' '{print $2}' | tr -d '[:blank:]' | sort | uniq --ignore-case)

REALMS_TODAY=( $(echo "${ACCEPTED_TODAY}" | awk -F '@' '{print $2}' | sort | uniq --ignore-case) )
for REALMS_TODAY_ITEM in ${REALMS_TODAY[@]} ; do
  BODY=${BODY}"<tr>\n"
  BODY=${BODY}"<td>$(echo "${REALMS_TODAY_ITEM}" | tr '[:upper:]' '[:lower:]')</td>"
  BODY=${BODY}"<td style=\"text-align: right;\">$(echo "${ACCEPTED_TODAY}" | grep -c -i "@${REALMS_TODAY_ITEM}")</td>"
  BODY=${BODY}"</tr>\n"
done
BODY=${BODY}"<tr><td style=\"border: none;\"></td><td style=\"text-align: right;\">$(echo "${ACCEPTED_TODAY}" | grep -c -i "@")</td></tr>"

BODY=${BODY}"<tr>\n"
BODY=${BODY}"<td colspan=\"3\" style=\"border: none;\">\n"
BODY=${BODY}"</td>\n"
BODY=${BODY}"</tr>\n"

ACCEPTED_EXT_TODAY=$(cat /var/log/freeradius/linelog/linelog*.log | grep -v -E 'Operator-Name = .?example.net' | grep 'Send-Accept' | grep -e "^$(date +'%Y-%m-%d')" | awk -F ', ' '{print $2}' | awk 'match($0, /User-Name = .+/) {print substr($0, RSTART, RLENGTH);}' | awk -F '=' '{print $2}' | tr -d '[:blank:]' | sort | uniq --ignore-case)
BODY=${BODY}"<tr><td>example.net (outside)</td><td style=\"text-align: right;\">$(echo "${ACCEPTED_EXT_TODAY}" | grep -c -i "@")</td></tr>"

BODY=${BODY}"<tr>\n"
BODY=${BODY}"<td colspan=\"3\" style=\"border: none;\">\n"
BODY=${BODY}"</td>\n"
BODY=${BODY}"</tr>\n"

REJECTED_TODAY=( $(cat /var/log/freeradius/linelog/linelog*.log | grep 'Send-Reject' | grep -e "^$(date +%Y-%m-%d)" | awk -F ', ' '{print $2}' | awk 'match($0, /User-Name = .+/) {print substr($0, RSTART, RLENGTH);}' | awk -F '=' '{print $2}' | tr -d '[:blank:]' | sort | uniq --ignore-case) )
for I in ${!REJECTED_TODAY[@]} ; do
  if echo "${ACCEPTED_TODAY}" | grep -qFx "${REJECTED_TODAY[$I]}" ; then
    unset REJECTED_TODAY[$I]
  fi
done
BODY=${BODY}"<tr><td>* (rejected)</td><td style=\"text-align: right; background-color: #ffcccc;\">$(echo "${REJECTED_TODAY[@]}" | wc -w)</td></tr>"

BODY=${BODY}"</table>\n"

BODY=${BODY}"</td>\n"
BODY=${BODY}"<td style=\"border: none; padding-right: 15pt;\">\n"

BODY=${BODY}"<h3 style=\"color: #333333;\">Last three days:</h3>\n"
BODY=${BODY}"<table style='font-size: 12pt'>\n"
BODY=${BODY}"<tr>\n"
BODY=${BODY}"<th>Realm</th><th>Amount</th>\n"
BODY=${BODY}"</tr>\n"

ACCEPTED_THREEDAY=$(cat /var/log/freeradius/linelog/linelog*.log | grep -E 'Operator-Name = .?example.net' | grep 'Send-Accept' | grep -e "^$(date +'%Y-%m-%d' --date '-2 days')\|$(date +'%Y-%m-%d' --date '-1 days')\|$(date +'%Y-%m-%d')" | awk -F ', ' '{print $2}' | awk 'match($0, /User-Name = .+/) {print substr($0, RSTART, RLENGTH);}' | awk -F '=' '{print $2}' | tr -d '[:blank:]' | sort | uniq --ignore-case)
REALMS_THREEDAY=( $(echo "${ACCEPTED_THREEDAY}" | awk -F '@' '{print $2}' | sort | uniq --ignore-case) )
for REALMS_THREEDAY_ITEM in ${REALMS_THREEDAY[@]} ; do
  BODY=${BODY}"<tr>\n"
  BODY=${BODY}"<td>$(echo "${REALMS_THREEDAY_ITEM}" | tr '[:upper:]' '[:lower:]')</td>"
  BODY=${BODY}"<td style=\"text-align: right;\">$(echo "${ACCEPTED_THREEDAY}" | grep -c -i "@${REALMS_THREEDAY_ITEM}")</td>"
  BODY=${BODY}"</tr>\n"
done
BODY=${BODY}"<tr><td style=\"border: none;\"></td><td style=\"text-align: right;\">$(echo "${ACCEPTED_THREEDAY}" | grep -c -i "@")</td></tr>"

BODY=${BODY}"<tr>\n"
BODY=${BODY}"<td colspan=\"3\" style=\"border: none;\">\n"
BODY=${BODY}"</td>\n"
BODY=${BODY}"</tr>\n"

ACCEPTED_EXT_THREEDAY=$(cat /var/log/freeradius/linelog/linelog*.log | grep -v -E 'Operator-Name = .?example.net' | grep 'Send-Accept' | grep -e "^$(date +'%Y-%m-%d' --date '-2 days')\|$(date +'%Y-%m-%d' --date '-1 days')\|$(date +'%Y-%m-%d')" | awk -F ', ' '{print $2}' | awk 'match($0, /User-Name = .+/) {print substr($0, RSTART, RLENGTH);}' | awk -F '=' '{print $2}' | tr -d '[:blank:]' | sort | uniq --ignore-case)
BODY=${BODY}"<tr><td>example.net (outside)</td><td style=\"text-align: right;\">$(echo "${ACCEPTED_EXT_THREEDAY}" | grep -c -i "@")</td></tr>"

BODY=${BODY}"</table>\n"

BODY=${BODY}"</td>\n"
BODY=${BODY}"<td style=\"border: none;\">\n"

BODY=${BODY}"<h3 style=\"color: #333333;\">Last week:</h3>\n"
BODY=${BODY}"<table style='font-size: 12pt'>\n"
BODY=${BODY}"<tr>\n"
BODY=${BODY}"<th>Realm</th><th>Amount</th>\n"
BODY=${BODY}"</tr>\n"

ACCEPTED_LASTWEEK=$(cat /var/log/freeradius/linelog/linelog*.log | grep -E 'Operator-Name = .?example.net' | grep 'Send-Accept' | grep -e "^$(date +'%Y-%m-%d' --date '-6 days')\|$(date +'%Y-%m-%d' --date '-5 days')\|$(date +'%Y-%m-%d' --date '-4 days')\|$(date +'%Y-%m-%d' --date '-3 days')\|$(date +'%Y-%m-%d' --date '-2 days')\|$(date +'%Y-%m-%d' --date '-1 days')\|$(date +'%Y-%m-%d')" | awk -F ', ' '{print $2}' | awk 'match($0, /User-Name = .+/) {print substr($0, RSTART, RLENGTH);}' | awk -F '=' '{print $2}' | tr -d '[:blank:]' | sort | uniq --ignore-case)
REALMS_LASTWEEK=( $(echo "${ACCEPTED_LASTWEEK}" | awk -F '@' '{print $2}' | sort | uniq --ignore-case) )
for REALMS_LASTWEEK_ITEM in ${REALMS_LASTWEEK[@]} ; do
  BODY=${BODY}"<tr>\n"
  BODY=${BODY}"<td>$(echo "${REALMS_LASTWEEK_ITEM}" | tr '[:upper:]' '[:lower:]')</td>"
  BODY=${BODY}"<td style=\"text-align: right;\">$(echo "${ACCEPTED_LASTWEEK}" | grep -c -i "@${REALMS_LASTWEEK_ITEM}")</td>"
  BODY=${BODY}"</tr>\n"
done
BODY=${BODY}"<tr><td style=\"border: none;\"></td><td style=\"text-align: right;\">$(echo "${ACCEPTED_LASTWEEK}" | grep -c -i "@")</td></tr>"

BODY=${BODY}"<tr>\n"
BODY=${BODY}"<td colspan=\"3\" style=\"border: none;\">\n"
BODY=${BODY}"</td>\n"
BODY=${BODY}"</tr>\n"

ACCEPTED_EXT_LASTWEEK=$(cat /var/log/freeradius/linelog/linelog*.log | grep -v -E 'Operator-Name = .?example.net' | grep 'Send-Accept' | grep -e "^$(date +'%Y-%m-%d' --date '-6 days')\|$(date +'%Y-%m-%d' --date '-5 days')\|$(date +'%Y-%m-%d' --date '-4 days')\|$(date +'%Y-%m-%d' --date '-3 days')\|$(date +'%Y-%m-%d' --date '-2 days')\|$(date +'%Y-%m-%d' --date '-1 days')\|$(date +'%Y-%m-%d')" | awk -F ', ' '{print $2}' | awk 'match($0, /User-Name = .+/) {print substr($0, RSTART, RLENGTH);}' | awk -F '=' '{print $2}' | tr -d '[:blank:]' | sort | uniq --ignore-case)
BODY=${BODY}"<tr><td>example.net (outside)</td><td style=\"text-align: right;\">$(echo "${ACCEPTED_EXT_LASTWEEK}" | grep -c -i "@")</td></tr>"

BODY=${BODY}"</table>\n"

BODY=${BODY}"</td>\n"
BODY=${BODY}"</tr>\n"
BODY=${BODY}"</table>\n"

BODY=${BODY}"<br /><span style=\"font-size: 90%; color: #808080;\">Collection time: "$(date +'%Y-%m-%d %H:%M')" (updated hourly)</span>\n"

BODY=${BODY}"\n</body>\n</html>"
  echo -e "${BODY}" 2>/dev/null > /var/www/eduroam.example.net/www/index.html
  chown www-data:www-data /var/www/eduroam.example.net/www/index.html

exit ${?}

Хоть мы и не затрагиваем здесь приватных данных, но для порядка закрываем доступ к скриптам и файлам web-сайта посторонним:

# chown -R www-data:www-data /var/www/eduroam.example.net/www /var/www/cgi-bin/linelog
# chmod -R o-rwx /var/www/eduroam.example.net/www /var/www/cgi-bin/linelog

Я наладил запуск скрипта ежечасно:

# vi /etc/crontab

....
# Regularly start collecting statistics on the Eduroam Gateway
0 */1  * * *  root /var/www/cgi-bin/linelog/view-make.sh &


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


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