UMGUM.COM 

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

17 декабря 2019  (обновлено 15 марта 2020)

OS: "Linux Ubuntu 16/18 (Xenial/Bionic) LTS".
Application: "LDAP 389-AS/DS v1.3", "Apache2", "Bash".

Задача: визуализировать статистику о количестве подключений пользователей к LDAP-сервису, без выдачи сведений о персоналиях.

Статистика нам потребовалась для отслеживания процесса миграции со старого LDAP-сервера на новые инстансы, с попутной нормализацией политики использования учётных записей. От описываемого здесь сервиса требовалось лишь показать таблицы с перечнями комбинаций "источник подключения - пользователь" для каждого инстанса. Этого легко добиться регулярным сканированием журналов событий "389-DS" и генерированием простейшей HTML-страницы с таблицей.

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


Настройка web-сервера "Apache2".

В комплекте с дистрибутивом "389-AS" уже поставляется web-сервер "Apache2" - им и воспользуемся, дабы не плодить сущности.

Привычно чуть поднастроим web-сервер, велев ему поменьше о себе рассказывать всем встречным:

# vi /etc/apache2/conf-enabled/security.conf

....
ServerSignature Off
....
ServerTokens Prod
....

Для нашего локального web-сервиса устойчивость к большим нагрузкам ни к чему, а потому уменьшим аппетиты "Apache2", велев ему запускать поменьше потоков:

# vi /etc/apache2/mods-enabled/mpm_event.conf

....
<IfModule mpm_event_module>
  StartServers      1
  MinSpareThreads   5
  MaxSpareThreads   25
  ThreadLimit       32
  ThreadsPerChild   12
  MaxRequestWorkers 150
  MaxConnectionsPerChild 0
</IfModule>
....

Опишем конфигурацию простейшего сайта с публикацией HTML-странички - но только посредством HTTPS, на случай, если понадобится наладить на входе аутентификацию:

# vi /etc/apache2/sites-available/ldap0.example.net.conf

<VirtualHost ldap0.example.net:80>
  ServerName ldap0.example.net
  Redirect / https://ldap0.example.net/
</VirtualHost>

<VirtualHost ldap0.example.net:443>
  ServerName ldap0.example.net

  ErrorLog ${APACHE_LOG_DIR}/ldap0.example.net.error.log
  CustomLog ${APACHE_LOG_DIR}/ldap0.example.net.access.log combined

  <IfModule mod_ssl.c>
    SSLEngine on
    SSLProtocol all -SSLv2
    SSLHonorCipherOrder on
    SSLCipherSuite HIGH:MEDIUM:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4:!aECDH:+SHA1:+MD5:+HIGH:+MEDIUM
    SSLOptions +StrictRequire
    SSLCompression off
    SSLCACertificateFile /etc/ssl/apache2/intermediate.crt
    SSLCertificateFile /etc/ssl/apache2/example.net.crt
    SSLCertificateKeyFile /etc/ssl/apache2/example.net.key.decrypt
  </IfModule>

  DocumentRoot /var/www/ldap0.example.net/www

  <Directory /var/www/ldap0.example.net/www>
    Options None
    AllowOverride None
    Order Deny,Allow
    Deny from all
    Allow from 10.0.0.0/8
    Allow from 172.16.0.0/12
    Allow from 192.168.0.0/16
  </Directory>

</VirtualHost>

Активируем "Apache2" (в процессе настройки "389-AS" мы его отключили) и SSL-модуль:

# systemctl enable apache2
# a2enmod ssl
# /etc/init.d/apache2 start

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

# rm /etc/apache2/sites-enabled/000-default.conf
# rm /etc/apache2/sites-enabled/default-ssl.conf
# ln -s /etc/apache2/sites-available/ldap0.example.net.conf /etc/apache2/sites-enabled/ldap0.example.net.conf
# apache2 -t && /etc/init.d/apache2 reload

Настройка LDAP-сервера "389-DS".

В нагруженных LDAP-серверах журналы событий быстро разрастаются и становятся неудобными для частого к ним обращения, из-за размера, в "389-DS" по умолчанию ограниченного 100MB. При этом в день их запросто набегает по триста-пятьсот мегабайт. Чтобы облегчить процедуру выборки свежих данных, есть смысл уменьшить размер файлов журналов с одновременным увеличением их количества:

$ ldapmodify -x -h 127.0.0.1 -D "cn=Directory Manager" -W

dn: cn=config
changetype: modify
replace: nsslapd-accesslog-maxlogsperdir
nsslapd-accesslog-maxlogsperdir: 25

replace: nsslapd-accesslog-logrotationtimeunit:
nsslapd-accesslog-logrotationtimeunit: day

replace: nsslapd-accesslog-logrotationtime:
nsslapd-accesslog-logrotationtime: 1

replace: nsslapd-accesslog-maxlogsize
nsslapd-accesslog-maxlogsize: 50

^d

В старых инсталляциях "389-AS/DS" события фиксируются в журналах с указанием времени с точностью только до секунд, чего явно недостаточно для последующего анализа - в одну секунду могут несколько подключения и аутентификаций состоятся. Исправляем это, увеличивая точность фиксирования времени события до наносекунд:

$ ldapmodify -x -h 127.0.0.1 -D "cn=Directory Manager" -W

dn: cn=config
changetype: modify
replace: nsslapd-logging-hr-timestamps-enabled
nsslapd-logging-hr-timestamps-enabled: on

^d

О формате даты и времени в журналах "389-DS".

Дату и время события "389-DS" фиксирует в неудобном для автоматизированной обработки американском виде, и лучше всего сразу преобразовывать его к строке ("YYYY-MM-DD HH:MM:SS.SSS") в формате "ISO8601", одинаково хорошо воспринимаемом людьми и обрабатываемом СУБД вроде "SQLite" - в "Bash" это делается примерно так:

$ echo "[20/Dec/2019:10:09:40.377075423 +0700] conn=1948" | sed -E "s/\[([0-9]{2})\/([A-Za-z]{3})\/([0-9]{4}):([0-9]{2}):([0-9]{2}):([0-9]{2})\.([0-9]+)\s([\+0-9]+)\].*/\3-"$(echo \\2 | date +%m)"-\1 \4:\5:\6.\7 \8/"

2019-12-20 10:09:40.377075423 +0700

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

Подход с простым чтением журнала события "389-DS", вычленением интересующих нас данных, формирования по ним статистики с последующим непосредственным отображением оказался крайне неэффективен - слишком большие у находящегося под наблюдением сервиса файлы журналов. Самая большая часть времени - до 99% от общего - уходит на чтение файлов журналов. Пришлось придумать способ ступенчатой загрузки интересующих нас событий (вначале всех, и потом лишь свежие данные, порционно) в промежуточную БД "SQLite", а уже оттуда SQL-запросами вынимать нужную нам статистику.

Устанавливаем APT-пакет с нужной мини-СУБД:

# aptitude install sqlite3

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

# mkdir -p /usr/local/etc/ds-logstat
# mkdir -p /var/log/ds-logstat
# mkdir -p /var/www/ldap0.example.net/www

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

# vi /usr/local/etc/ds-logstat/main.conf

# Имя LDAP-инстанса, журналы событий которого будут читаться
# (автоматически будет добавлен префикс "/var/log/slapd-")
INSTANCE="ldap0-example-net"

# Директория для файлов "базы данных" сервиса
DATADIR="/var/log/ds-logstat"

# Задаём имя файла "базы данных"
DBF="${DATADIR}/main.db"

# Формируем строки запуска и конфигурации "SQLite3"
SQLEXE=`which sqlite3`
SQLCFG="/usr/local/etc/ds-logstat/.sqliterc"

# Задаём региональный формат для вычленения даты из журнала событий "389-DS"
export LC_TIME="en_US.UTF-8"

# Явно указываем использовать как разделитель символ "переноса строки"
IFS=$'\n'

Подготавливаем для программного клиента "SQLite" набор параметров, которые сделают подключение немного стабильнее и быстрее:

# vi /usr/local/etc/ds-logstat/.sqliterc

-- Suppress output service messages
.output /dev/null
-- Lock DB file, making it unavailable for other
PRAGMA locking_mode = EXCLUSIVE;
PRAGMA journal_mode = MEMORY;
.output stdout

Сам bash-скрипт сбора и визуализации статистики получился немаленький - пора бы уже на "Python" или "Go" начать такое писать - но все этапы я прокомментировал в коде, так что должно быть всё очевидно:

# vi /usr/local/bin/ds-logstat.sh && chmod ug+x /usr/local/bin/ds-logstat.sh

#!/bin/bash

# Загружаем переменные конфигурации
. /usr/local/etc/ds-logstat/main.conf

# Принимаем аргументы
LEVEL=${1} # {total|last}

# Описываем функцию пакетной записи
function sql-drop {
  SQL_PACK_LINE=""

  # Добавляем к пакету данных сведения о подключениях
  if [ -s "${BUF_CONNS_PACK}" ] ; then
    SQL_PACK_LINE="INSERT OR REPLACE INTO ll_conns (date, conn, ip) VALUES "$(cat "${BUF_CONNS_PACK}" | sed 's/,$//')";"
    echo -n "" > "${BUF_CONNS_PACK}"
  fi

  # Добавляем к пакету данных сведения об аутентификациях
  if [ -s "${BUF_BINDS_PACK}" ] ; then
    SQL_PACK_LINE=${SQL_PACK_LINE}"INSERT OR REPLACE INTO ll_binds (date, conn, binddn) VALUES "$(cat "${BUF_BINDS_PACK}" | sed 's/,$//')";"
    echo -n "" > "${BUF_BINDS_PACK}"
  fi

  # Отправляем SQL-пакет на запись в "базу данных"
  ${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "${SQL_PACK_LINE}"
  [ "${?}" -eq "0" ] || { echo -e "\nError writing to SQLite database.\n"; return ${?}; }

return ${?}
}

# Создаём временные буферные файлы
BUF_CONNS_PACK=$(mktemp --tmpdir=/dev/shm);
BUF_BINDS_PACK=$(mktemp --tmpdir=/dev/shm);

# Создаём заготовки таблиц в БД
# (для отладки) ${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "DROP TABLE IF EXISTS ll_conns; DROP TABLE IF EXISTS ll_binds;"
${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "CREATE TABLE IF NOT EXISTS ll_conns (date INT PRIMARY KEY, conn INT, ip TEXT); CREATE TABLE IF NOT EXISTS ll_binds (date INT PRIMARY KEY, conn INT, binddn TEXT);"

# Задаём ограничение выборки данных по времени создания записи (в минутах)
if [ "${LEVEL}" == "total" ] ; then
  CTLIMIT="10080" # week
elif [ "${LEVEL}" == "last" ] ; then
  CTLIMIT="480" # 8 hour
else
  CTLIMIT="120" # 2 hour
fi

# Выбираем и перебираем строки журнала из текстового файла
COUNT="0" ; COUNT_PACK="0"
find /var/log/dirsrv/slapd-${INSTANCE} -type f -mmin -${CTLIMIT} -name 'access*' -exec grep -ie 'fd=.*slot=.*connection\|conn=.*bind.*dn=.*' {} \; | \
while read STRING ; do

  # Показываем статистику по чтению данных
  (( COUNT ++ ))
  let "FLAG_COUNT = ${COUNT} % 10"
  [ "${FLAG_COUNT}" == "0" ] && echo -n "."

  # Извлекаем и преобразуем к формату "ISO8601" дату фиксирования события
  LOG_DATE=$(echo "${STRING}" | awk 'match($0, /^\[(.*)\]\s/, result) {print result[1];}' | sed -E "s/([0-9]{2})\/([A-Za-z]{3})\/([0-9]{4})(:)/\3-"$(echo \\2 | date +%m)"-\1 /")

  # Проверяем корректность распознавания даты и времени события
  date "+%Y-%m-%d %H:%M:%S.%N %z" -d "${LOG_DATE}" > /dev/null 2>&1 || continue

  # Переводим строку даты и времени в число наносекунд от "unix-эпохи"
  # (SQLite3 не поддерживает операции со строковыми датами)
  LOG_DATE_NSEC=$(date +%s -d "${LOG_DATE}")$(date +%N -d "${LOG_DATE}")

  # Обрабатываем только строки в интересующем нас диапазоне даты и времени
  # (в секунде - 1000000000 наносекунд; вычисляем разницу в минутах)
  DATEDIFF=$(( ( $(date +%s)$(date +%N) - ${LOG_DATE_NSEC} ) / 60000000000 ))
  [[ "${CTLIMIT}" -eq "0" || "${DATEDIFF}" -le "${CTLIMIT}" ]] || continue

  # Откидываем записи с датами "из будущего"
  [[ "${DATEDIFF}" -lt "0" ]] && continue

  # Выявляем событие установления подключения
  if echo "${STRING}" | grep -qi 'fd=.*slot=.*connection' ; then
    CONN_CID=$(echo "${STRING}" | awk 'match($0, /\sconn=([0-9]*)\s/, result) {print result[1];}')
    CONN_IP=$(echo "${STRING}" | awk 'match($0, /\sfrom\s([0-9\.]*)\s/, result) {print result[1];}')

    # Не обрабатываем некоторые события
    [ "${CONN_IP}" == "127.0.0.1" ] && continue

    # Добавляем в пакет данных сведения сопоставления IP-адреса клиента и номера соединения
    echo -n "("${LOG_DATE_NSEC}", "${CONN_CID}", '"${CONN_IP}"')," >> "${BUF_CONNS_PACK}"
    (( COUNT_PACK ++ ))

  else # (если это не событие подключения - значит аутентификации (BIND)

    BIND_CID=$(echo "${STRING}" | awk 'match($0, /\sconn=([0-9]*)\s/, result) {print result[1];}')
    BIND_DN=$(echo "${STRING}" | awk 'match($0, /\sdn=\"(.*)\"\s/, result) {print result[1];}')

    # Не обрабатываем некоторые события
    [ "${BIND_DN}" == "cn=Replication Manager,cn=config" ] && continue

    # Маскируем идентификаторы пользователей (анонимизируем статистику)
    BIND_DN=$(echo "${BIND_DN}" | sed -e 's/^uid=.*,ou=People/uid=*****,ou=People/I')

    # Добавляем в пакет данных сведения сопоставления номера соединения клиента и BindDN
    echo -n "("${LOG_DATE_NSEC}", "${BIND_CID}", '"${BIND_DN}"')," >> "${BUF_BINDS_PACK}"
    (( COUNT_PACK ++ ))
  fi

  # Регулярно сбрасываем в БД накапливаемые данные
  let "FLAG_COUNT_PACK = ${COUNT_PACK} % 100"
  if [[ "${COUNT_PACK}" != "0" && "${FLAG_COUNT_PACK}" == "0" ]] ; then
    COUNT_PACK="0"
    echo -n "*"
    sql-drop
  fi

done
echo

# Сбрасываем в "базу данных" накопившиеся данные
sql-drop

# Начинаем рисовать HTML-страницу
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>LDAP 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;\">LDAP Usage Statistics (Example)</h2>\n\
<a href=\"https://ldap0.example.net\" style=\"font-size: 120%;\">ldap0.example.net</a>&nbsp;<span style=\"color: #808080;\">(this server)</span><br />\n\
<a href=\"https://ldap1.example.net\" style=\"font-size: 120%;\">ldap1.example.net</a>
\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>Client IP</th><th>Bind DN</th><th>Amount</th>\n"
BODY=${BODY}"</tr>\n"

# Вычисляем дату и время за сутки до выборки, в наносекундах
NSTIME_ONE_DAY="$(date +%s -d '-24 hour')000000000"

# Вычленяем перечень IP-адресов и перебираем их
CONN_IPS=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT DISTINCT ip FROM ll_conns WHERE date > "${NSTIME_ONE_DAY}" ORDER BY ip ASC;" 2>/dev/null)
for CONN_IPS_ITEM in ${CONN_IPS[@]} ; do

  # (дата и время в наносекундах; аутентификация ожидается не позже, чем через минуту после соединения)

  # Вычленяем перечень DN-ов и перебираем их
  BINDS=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT DISTINCT binddn FROM ll_conns INNER JOIN ll_binds ON ll_conns.conn = ll_binds.conn WHERE ip = '"${CONN_IPS_ITEM}"' AND ll_conns.date > "${NSTIME_ONE_DAY}" AND (ll_binds.date - ll_conns.date) >= 0 AND (ll_binds.date - ll_conns.date) < 60000000000;" 2>/dev/null)
  for BINDS_ITEM in ${BINDS[@]} ; do

    # Выборкой из БД подсчитываем количество подключений BinDN-а с IP-адреса
    BINDS_COUNT=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT COUNT(binddn) FROM ll_conns INNER JOIN ll_binds ON ll_conns.conn = ll_binds.conn WHERE ip = '"${CONN_IPS_ITEM}"' AND binddn = '"${BINDS_ITEM}"' AND ll_conns.date > "${NSTIME_ONE_DAY}" AND (ll_binds.date - ll_conns.date) >= 0 AND (ll_binds.date - ll_conns.date) < 60000000000;" 2>/dev/null)

    # Непустые записи показываем
    if [ ! -z "${BINDS_ITEM}" ] ; then

      # Отображаем данные в табличной строке
      BODY=${BODY}"<tr>\n"
      BODY=${BODY}"<td>$(echo "${CONN_IPS_ITEM}")</td>"
      BODY=${BODY}"<td>$(echo "${BINDS_ITEM}" | tr '[:upper:]' '[:lower:]')</td>"
      BODY=${BODY}"<td style=\"text-align: right;\">$(echo "${BINDS_COUNT}")</td>"
      BODY=${BODY}"</tr>\n"
    fi
  done
done
BODY=${BODY}"</table>\n"

# Вычисляем дату и время за трое суток до выборки, в наносекундах
NSTIME_THREE_DAY="$(date +%s -d '-3 days')000000000"

# Выясняем, имеются ли данные за указанный период
CHECK_EXST=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT COUNT(ip) FROM ll_conns WHERE date > "${NSTIME_THREE_DAY}" AND date < "${NSTIME_ONE_DAY}";" 2>/dev/null)
if [ "${CHECK_EXST}" -ge "1" ] ; then

  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>Client IP</th><th>Bind DN</th><th>Amount</th>\n"
  BODY=${BODY}"</tr>\n"

  # Вычленяем перечень IP-адресов и перебираем их
  CONN_IPS=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT DISTINCT ip FROM ll_conns WHERE date > "${NSTIME_THREE_DAY}" ORDER BY ip ASC;" 2>/dev/null)
  for CONN_IPS_ITEM in ${CONN_IPS[@]} ; do

    # (дата и время в наносекундах; аутентификация ожидается не позже, чем через минуту после соединения)

    # Вычленяем перечень DN-ов и перебираем их
    BINDS=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT DISTINCT binddn FROM ll_conns INNER JOIN ll_binds ON ll_conns.conn = ll_binds.conn WHERE ip = '"${CONN_IPS_ITEM}"' AND ll_conns.date > "${NSTIME_THREE_DAY}" AND (ll_binds.date - ll_conns.date) >= 0 AND (ll_binds.date - ll_conns.date) < 60000000000;" 2>/dev/null)
    for BINDS_ITEM in ${BINDS[@]} ; do

      # Выборкой из БД подсчитываем количество подключений BinDN-а с IP-адреса
      BINDS_COUNT=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT COUNT(binddn) FROM ll_conns INNER JOIN ll_binds ON ll_conns.conn = ll_binds.conn WHERE ip = '"${CONN_IPS_ITEM}"' AND binddn = '"${BINDS_ITEM}"' AND ll_conns.date > "${NSTIME_THREE_DAY}" AND (ll_binds.date - ll_conns.date) >= 0 AND (ll_binds.date - ll_conns.date) < 60000000000;" 2>/dev/null)

      # Непустые записи показываем
      if [ ! -z "${BINDS_ITEM}" ] ; then

        # Отображаем данные в табличной строке
        BODY=${BODY}"<tr>\n"
        BODY=${BODY}"<td>$(echo "${CONN_IPS_ITEM}")</td>"
        BODY=${BODY}"<td>$(echo "${BINDS_ITEM}" | tr '[:upper:]' '[:lower:]')</td>"
        BODY=${BODY}"<td style=\"text-align: right;\">$(echo "${BINDS_COUNT}")</td>"
        BODY=${BODY}"</tr>\n"
      fi
   done
  done
  BODY=${BODY}"</table>\n"
fi

# Вычисляем дату и время за неделю до выборки, в наносекундах
NSTIME_SEVEN_DAY="$(date +%s -d '-7 days')000000000"

# Выясняем, имеются ли данные за указанный период
CHECK_EXST=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT COUNT(ip) FROM ll_conns WHERE date > "${NSTIME_SEVEN_DAY}" AND date < "${NSTIME_THREE_DAY}";" 2>/dev/null)
if [ "${CHECK_EXST}" -ge "1" ] ; then

  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>Client IP</th><th>Bind DN</th><th>Amount</th>\n"
  BODY=${BODY}"</tr>\n"

  # Вычленяем перечень IP-адресов и перебираем их
  CONN_IPS=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT DISTINCT ip FROM ll_conns WHERE date > "${NSTIME_SEVEN_DAY}" ORDER BY ip ASC;" 2>/dev/null)
  for CONN_IPS_ITEM in ${CONN_IPS[@]} ; do

    # (дата и время в наносекундах; аутентификация ожидается не позже, чем через минуту после соединения)

    # Вычленяем перечень DN-ов и перебираем их
    BINDS=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT DISTINCT binddn FROM ll_conns INNER JOIN ll_binds ON ll_conns.conn = ll_binds.conn WHERE ip = '"${CONN_IPS_ITEM}"' AND ll_conns.date > "${NSTIME_SEVEN_DAY}" AND (ll_binds.date - ll_conns.date) >= 0 AND (ll_binds.date - ll_conns.date) < 60000000000;" 2>/dev/null)
    for BINDS_ITEM in ${BINDS[@]} ; do

      # Выборкой из БД подсчитываем количество подключений BinDN-а с IP-адреса
      BINDS_COUNT=$(${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "SELECT COUNT(binddn) FROM ll_conns INNER JOIN ll_binds ON ll_conns.conn = ll_binds.conn WHERE ip = '"${CONN_IPS_ITEM}"' AND binddn = '"${BINDS_ITEM}"' AND ll_conns.date > "${NSTIME_SEVEN_DAY}" AND (ll_binds.date - ll_conns.date) >= 0 AND (ll_binds.date - ll_conns.date) < 60000000000;" 2>/dev/null)

      # Непустые записи показываем
      if [ ! -z "${BINDS_ITEM}" ] ; then

        # Отображаем данные в табличной строке
        BODY=${BODY}"<tr>\n"
        BODY=${BODY}"<td>$(echo "${CONN_IPS_ITEM}")</td>"
        BODY=${BODY}"<td>$(echo "${BINDS_ITEM}" | tr '[:upper:]' '[:lower:]')</td>"
        BODY=${BODY}"<td style=\"text-align: right;\">$(echo "${BINDS_COUNT}")</td>"
        BODY=${BODY}"</tr>\n"
      fi
   done
  done
  BODY=${BODY}"</table>\n"
fi

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

BODY=${BODY}"<h3 style=\"color: #333333;\">For LDAP customers:</h3>\n"
BODY=${BODY}"&nbsp;&nbsp;<a href=\"ss-rootCA.crt\" target=\"_blank\">Self-Signed Root CA certificat</a><br />"

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

# Удаляем временные буферные файлы
rm -f "${BUF_CONNS_PACK}"
rm -f "${BUF_BINDS_PACK}"

# Удаляем ненужные для статистики сведения
${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "DELETE FROM ll_conns WHERE date < "${NSTIME_SEVEN_DAY}";" 2>/dev/null
${SQLEXE} -batch -init "${SQLCFG}" "${DBF}" "DELETE FROM ll_binds WHERE date < "${NSTIME_SEVEN_DAY}";" 2>/dev/null

exit ${?}

Таким образом, из кода уже наверняка понятно, что первоначально нужно запустить длительный процесс загрузки всех доступных данных:

# /usr/local/bin/ds-logstat.sh total

В процессе наладки может понадобиться запросить загрузку данных за восемь последних часов:

# /usr/local/bin/ds-logstat.sh last

Ну а обычный запрос по расписанию раз в час будет забирать данные последних двух часов:

# /usr/local/bin/ds-logstat.sh

Я наладил запуск скрипта ежечасно в обычном режиме (забор данных последних двух часов):

# vi /etc/crontab

....
# Regularly start collecting statistics on the LDAP "389-DS"
0 */1  * * *  root /usr/local/bin/ds-logstat.sh 1>/dev/null &


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


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