воскресенье, 14 июля 2019 г.

Установка и настройка cgit

cgit - это веб-интерфейс для доступа к git-репозиториям,​ который написан на Си и в целом аналогичен приложению Gitweb, написанному на Perl. Поскольку оба приложения очень похожи друг на друга, не стану притворяться и скажу прямо: эта статья является почти дословным повтором статьи про Gitweb, с необходимой поправкой на специфику cgit. Как и в случае с Gitweb, для эксперимента решил попробовать настроить cgit поверх связки nginx и uWSGI.

Установка пакетов

Установим необходимые пакеты:​
# apt-get install nginx-light uwsgi cgit

Настройка uwsgi

Первым делом настроим сервер приложений uWSGI для запуска CGI-сценария. Для этого создадим файл /​etc/​uwsgi/​apps-available/​cgit.ini со следующим содержимым:​
[uwsgi]

procname = uwsgi-cgit
procname-master = uwsgi-cgit-master

plugins = cgi
processes = 1
threads = 4
cgi = /cgit/=/usr/lib/cgit/cgit.cgi
cgi-timeout = 5
Теперь нужно подключить использование этого файла:
# cd /​etc/​uwsgi/​apps-enabled/​
# ln -s /​etc/​uwsgi/​apps-available/​cgit.ini .
Перезапустим uWSGI, чтобы его новые настройки вступили в силу:
# systemctl restart uwsgi.service

Настройка cgit

Теперь настроим файл конфигурации самого приложения cgit. Я буду настраивать приложение в подкаталоге веб-сервера /cgit/, поэтому у меня файл конфигурации /​etc/​cgitrc принял следующий вид:
favicon=/cgit/static/favicon.ico
css=/cgit/static/cgit.css
logo=/cgit/static/cgit.png

snapshots=tar.gz tar.bz2 zip
remove-suffix=1
enable-index-links=1
enable-index-owner=0

scan-path=/var/lib/cgit/
Будьте внимательны при редактировании файла конфигурации: порядок некоторых опций имеет значение. В частности, опция snapshots работает только при указании перед опцией scan-path.

Кроме указания пути к каталогу с проектами при помощи опции scan-path, имеется возможность описать каждый проект индивидуально и даже сгруппировать проекты. За подробностями обращайтесь к странице руководства cgitrc.

Настройка nginx

Наконец,​ теперь пришла очередь настраивать веб-сервер nginx. Для настройки откроем файл в каталоге /​etc/​nginx/​sites-enabled/,​ найдём секцию сервера и впишем в неё следующие настройки:​
location /cgit {
  return 301 $scheme://$host$request_uri/;
}

location /cgit/static/ {
  autoindex off;
  alias /usr/share/cgit/;
}

location /cgit/ {
  uwsgi_pass unix:/run/uwsgi/app/cgit/socket;
  include uwsgi_params;
  uwsgi_modifier1 9;
}
Перезагрузим nginx, чтобы добавленные нами настройки вступили в силу:
# systemctl reload nginx.service

Публикация репозиториев

Как можно увидеть в файле конфигурации /etc/cgitrc, созданном нами ранее, репозитории git должны размещаться в каталоге /​var/​lib/​cgit/​. Создадим этот каталог и назначим права на него:
# mkdir /var/lib/cgit
# chown www-data:www-data /var/lib/cgit
# chmod o= /var/lib/cgit
В этом каталоге нужно создать подкаталоги с расширением .git, содержащие так называемые «голые» (bare) репозитории. Обычный репозиторий, в котором ведётся разработка, содержит внутри себя текущие версии файлов, над которыми идёт работа, и каталог .git. Каталог .git, по-сути, и является «голым» репозиторием. Для создания «голого» репозитория из обычного можно воспользоваться такими командами:
# cd /var/lib/cgit
# git clone --bare /home/stupin/git/view3d view3d.git
И не забудьте поменять права доступа на «голый» репозиторий:
# cd /var/lib/cgit
# chown -R www-data:www-data view3d
# chmod -R o= view3d.git

Тестирование cgit

Теперь можно запустить браузер и проверить доступность приложения. У меня стартовая страница приложения выглядела следующим образом:​

Как видно из снимка экрана,​ ссылки на странице не дружественны для человека,​ что, впрочем,​ не мешает выполнять основную задачу,​ для которой это веб-приложение и было создано:​ просматривать git-репозитории через браузер.

Использованные материалы

воскресенье, 7 июля 2019 г.

Установка и настройка Gitweb

Gitweb - это веб-интерфейс для доступа к git-репозиториям, который написан на Perl и поставляется вместе с исходными текстами git. Gitweb рассчитан на запуск через веб-сервер Apache, но я Apache не использую и для эксперимента решил попробовать настроить Gitweb поверх связки nginx и uWSGI.

Установка пакетов

Несмотря на то, что в репозитории Debian имеется отдельный пакет с именем gitweb, всё необходимое для запуска gitweb есть в пакете git. Единственное, чего может не хватить - это модуля CGI.pm. Его можно установить из пакета libcgi-pm-perl. Установим необходимые пакеты:
# apt-get install nginx-light uwsgi git libcgi-pm-perl

Настройка uwsgi

Первым делом настроим сервер приложений uWSGI для запуска CGI-сценария. Для этого создадим файл /etc/uwsgi/apps-available/gitweb.ini со следующим содержимым:
[uwsgi]

procname = uwsgi-gitweb
procname-master = uwsgi-gitweb-master

plugins = cgi
processes = 1
threads = 4
cgi = /usr/share/gitweb/gitweb.cgi
cgi-timeout = 5
Теперь нужно подключить использование этого файла:
# cd /etc/uwsgi/apps-enabled/
# ln -s /etc/uwsgi/apps-available/gitweb.ini .
Перезапустим uWSGI, чтобы его новые настройки вступили в силу:
# systemctl restart uwsgi.service

Настройка Gitweb

Теперь настроим файл конфигурации самого приложения Gitweb. Я буду настраивать приложение в подкаталоге веб-сервера /gitweb/, поэтому у меня файл конфигурации /etc/gitweb.conf принял следующий вид:
# path to git projects (.git)
$projectroot = "/var/lib/git";

# directory to use for temp files
$git_temp = "/tmp";

# target of the home link on top of all pages
$home_link = $my_uri || "/gitweb/";

# html text to include at home page
$home_text = "indextext.html";

# file with project list; by default, simply scan the projectroot dir.
$projects_list = $projectroot;

# stylesheet to use
@stylesheets = ("/gitweb/static/gitweb.css");

# javascript code for gitweb
$javascript = "/gitweb/static/gitweb.js";

# logo to use
$logo = "/gitweb/static/git-logo.png";

# the 'favicon'
$favicon = "/gitweb/static/git-favicon.png";

# git-diff-tree(1) options to use for generated patches
#@diff_opts = ("-M");
@diff_opts = ();

Настройка nginx

Наконец, теперь пришла очередь настраивать веб-сервер nginx. Для настройки откроем файл в каталоге /etc/nginx/sites-enabled/, найдём секцию сервера и впишем в неё следующие настройки:
# Gitweb
location /gitweb/static/ {
  autoindex off;
  alias /usr/share/gitweb/static/;
}

location /gitweb/ {
  uwsgi_pass unix:/run/uwsgi/app/gitweb/socket;
  include uwsgi_params;
  uwsgi_modifier1 9;
  uwsgi_param PATH_INFO $1;
  uwsgi_param GITWEB_CONFIG /etc/gitweb.conf;
}
Перезагрузим nginx, чтобы добавленные нами настройки вступили в силу:
# systemctl reload nginx.service

Размещение репозиториев

Репозитории git, доступ к которым обеспечивает Gitweb, должны располагаться в каталоге /var/lib/git/. Я поместил туда несколько репозиториев для того, чтобы проверить работу приложения. Не забудьте дать права доступа к файлам репозиториев пользователю www-data и группе www-data, например, при помощи следующих команд:
# cd /var/lib/git/
# chown -R www-data:www-data *
В этом каталоге нужно создать подкаталоги с расширением .git, содержащие так называемые "голые" (bare) репозитории. Обычный репозиторий, в котором ведётся разработка, содержит внутри себя текущие версии файлов, над которыми идёт работа, и каталог .git. Каталог .git, по-сути, и является "голым" репозиторием. Для создания "голого" репозитория из обычного можно воспользоваться такими командами:
# cd /var/lib/git
# git clone --bare /home/stupin/git/view3d view3d.git
И не забудьте поменять права доступа на "голый" репозиторий:
# cd /var/lib/git
# chown -R www-data:www-data view3d.git
# chmod -R o= view3d

Тестирование Gitweb

Теперь можно запустить браузер и проверить доступность приложения. У меня стартовая страница приложения выглядела следующим образом:

Как видно из снимка экрана, ссылки на странице не дружественны для человека, что, впрочем, не мешает выполнять основную задачу, для которой это веб-приложение и было создано: просматривать git-репозитории через браузер.

Использованные материалы

воскресенье, 30 июня 2019 г.

Exim и Dovecot quota-status

5 лет назад я написал заметку Exim и Dovecot без SQL, в конце которой имелся такой вот абзац:
Для проверки квот на этапе RCPT сеанса ESMTP можно было бы воспользоваться не только самописным скриптом, но и сервисом проверки квот quota-status, который появился в Dovecot 2.2 (в Debian Wheezy поставляется Dovecot версии 2.1.7), благо Exim позволяет отправлять запросы в юникс-сокеты и читать из них ответ.
С момента написания той заметки я по-прежнему пользуюсь тем почтовым сервером, настройка которого была описана в статье. Сервер пережил несколько обновлений операционной системы, и Dovecot сейчас обновлён до версии 2.2.27.

Не так давно некто Slavko в комментариях к той заметке спросил, удалось ли мне прикрутить quota-satus к Exim. Я попробовал и у меня получилось. В ходе дальнейшей переписки в комментариях найденное решение было улучшено, а также была подтверждена его пригодность для промышленной эксплуатации на серверах с большим потоком входящих писем.

Объясню вкратце, зачем нужен quota-status и в чём сложности его интеграции с Exim.

Когда Dovecot и Exim настраиваются с использованием базы данных SQL, имеется возможность хранить информацию о текущем использовании квот почтового ящика в базе данных. Эту информацию можно использовать в почтовом сервере Exim на этапе приёма писем, чтобы не принимать к доставке письма на переполненные почтовые ящики.

При настройке связки Dovecot и Exim без использования базы данных SQL такой простой способ проверки квот пропадает. Exim не может проверить квоту почтового ящика получателя и принимает письмо к доставке. Если места в ящике нет, Dovecot не принимает письмо от Exim и Exim вынужден слать отправителю письма ответное письмо с сообщением об ошибке - рикошет.

В Dovecot имеется плагин quota-status, который реализует сервис для проверки превышения квоты получателем письма. Небольшая проблема заключается в том, что этот сервис рассчитан на работу с Postfix, т.к. сервис реализует протокол, используемый Postfix.

Однако, в Exim - это не простой почтовый сервер. Т.к. он обладает богатыми возможностями по фильтрации писем, его можно называть своего рода фреймворком для реализации SMTP-серверов. В частности, Exim позволяет передавать запросы в TCP- и Unix-сокеты и читать ответы из них. Этим можно воспользоваться для того, чтобы попытаться воспользоваться сервисом quota-status, который предоставляется Dovecot.

Настройка Dovecot

В файл /etc/dovecot/conf.d/90-quota.conf вписываем:
plugin {
  quota_status_success = OK 
  quota_status_nouser = NOUSER
  quota_status_overquota = OVER
}

service quota-status {
  executable = quota-status -p postfix
  
  unix_listener exim-quota-status {
    mode = 0660
    user = Debian-exim
    group = Debian-exim
  }
  
  client_limit = 1
}
Осталось перезапустить Dovecot:
# systemctl restart dovecot.service
Проверить работу сервиса можно при помощи утилиты socat. В примере ниже проиллюстрирована проверка статуса двух почтовых ящиков:
# socat STDIO UNIX:/var/run/dovecot/exim-quota-status 
recipient=xxx@stupin.su

action=OK

recipient=yyy@stupin.su

action=OVER
Как видно, с почтовым ящиком xxx@stupin.su всё в порядке, а вот у почтового ящика yyy@stupin.su квота превышена.

Настройка Exim

В файл конфигурации /etc/exim4/exim4.conf перед ACL грейлистинга вставляем такую проверку:
defer message = 422 Mailbox $local_part@$domain is over quota
         domains = +local_domains
         condition = ${if eq{${extract{action}\
                                      {${readsocket{/var/run/dovecot/exim-quota-status}\
                                                   {size=$message_size\nrecipient=$local_part@$domain\n\n}\
                                                   {5s}\
                                                   { }\
                                                   {action=FAIL}}}}}\
                            {OVER}\
                            {yes}\
                            {no}}
Указанное выше условие отправляет в сокет /var/run/dovecot/exim-quota-status три строки. В первой строке указан размер письма, во второй - его получатель, третья строка - пустая. Пустая строка сигнализирует о завершении запроса. Дальше в течение 5 секунд ожидается ответ. Если ответ поступил, то все переводы строк в ответе заменяются на пробелы. Если ответ не поступил, то вместо ответа дальше будет использоваться строка "action=FAIL". В результате должна получиться строка, в которой содержится ассоциативный массив, в котором разделителями элементов являются пробелы, а разделителями ключей и значений - знаки "равно", вот такая:
key1=value1 key2=value2 key3=value3
Выражение extract извлекает из этого словаря значение ключа action. Если значение равно OVER, то считается, что ящик переполнен и отправителю сообщается, что он должен отложить письмо в очередь, т.к. в данный момент почтовый ящик одного из получателей переполнен.

Осталось перезапустить Exim:
# systemctl restart exim4.service
Письма на переполненный ящик отбиваются с таким сообщением в журнале:
2019-06-11 22:48:31 H=forward100p.mail.yandex.net [77.88.28.100] X=TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256 CV=no F= temporarily rejected RCPT : 422 Mailbox yyy@stupin.su is over quota

Пригодность решения к промышленному применению

У Slavko возникли опасения, что Exim будет закрывать подключение к Dovecot по истечение 5 секунд даже в тех случаях, когда Dovecot уже ответил на запрос. Такая задержка может привести к проблемам при приёме большого потока входящих писем.

Судя по описанию readsocket на странице Chapter 11 - String expansions, Exim пишет запрос и сразу закрывает ту половинку сокета, которая используется для передачи данных в направлении Dovecot. После этого он читает ответ из оставшейся половинки сокета, в которую Dovecot пишет ответ для Exim. Чтобы Exim не закрывал свою половинку сокета, через которую отправляет данные в Dovecot, нужно после таймаута явным образом указать опцию shutdown=no, вот так: {5s:shutdown=no}. Поведение по умолчанию в нашем случае как раз подходящее, поэтому эту опцию писать не нужно.

Если посмотреть со стороны Dovecot, то он может отреагировать на поведение Exim одним из двух способов:
  1. либо сразу обнаружить закрытие половинки сокета и закрыть вторую половинку, не отправляя ответ,
  2. либо сначала отправить ответ, а потом закрыть свою половинку сокета.
Во-первых, я попробовал сымитировать ситуацию при помощи printf и socat:
# printf "recipient=yyy@stupin.su\n\n" | socat STDIO UNIX:/var/run/dovecot/exim-quota-status 
action=OVER
Как видно, ответ пришёл.

Во-вторых, я попробовал оттрассировать процесс dovecot/quota-status -p postfix при помощи strace и увидел, что сразу после ответа клиентское подключение через Unix-сокет закрывается. "Невооружённым взглядом" заметно, что таймаута в 5 секунд нет, т.к. вся проверка отрабатывает меньше чем за секунду.

Так что за образование очередей из писем, ожидающих проверки квоты ящиков адресатов можно не беспокоиться.

Отклонение адресата вместо отказа от письма

Я пробовал указывать выражение discard, чтобы отбрасывать только тех получателей, ящики которых переполнены. Но если вместо defer написать discard, то в журналах почтового сервера появляются ошибки следующего вида:
2019-06-11 22:35:32 configured error code starts with incorrect digit (expected 2) in "422 Mailbox yyy@stupin.su is over quota"
2019-06-11 22:35:32 H=forward104j.mail.yandex.net [5.45.198.247] F= RCPT : discarded by RCPT ACL: 422 Mailbox ууу@stupin.su is over quota
Exim считает, что его неправильно настроили, т.к. ответ должен начинаться с цифры 2.

Описанная конфигурация тестировалось на Debian Stretch. Готов выслушать замечания или дополнения к описанной конфигурации.

Использованные материалы

воскресенье, 23 июня 2019 г.

Контроль использования пулов подключений в PgBouncer при помощи Zabbix

Я уже рассказывал о PgBouncer'е в одной из своих прошлых заметок: Проксирование запросов к PostgreSQL через PgBouncer. PgBouncer - это прокси-сервер для СУБД PostgreSQL, который устанавливает постоянные подключения к серверу PostgreSQL и распределяет по этим постоянным подключениям поступающие подключения от клиентов.

Для уменьшения потребления оперативной памяти нужно стремиться к тому, чтобы пул подключений к серверу PostgreSQL было как можно меньше, т.к. каждое подключение к серверу порождает запуск ещё одного процесса для его обслуживания. С другой стороны, если чрезмерно уменьшить количество подключений к серверу PostgreSQL, то приложения могут успешно устанавливать подключения к PgBouncer'у, но долгое время ожидать освобождения подключения к серверу PostgreSQL, прежде чем смогут выполнить запрос.

Подключения к самому PgBouncer'у обычно обходятся очень дёшево, поэтому нет особого смысла уменьшать их количество, но тем не менее, может оказаться полезно отслеживать и их, т.к. это может помочь в диагностике внутренних проблем в приложении.

Для контроля входящих подключений к PgBouncer'у и использования пула подключений PgBouncer'а к серверу PostgreSQL я решил воспользоваться Zabbix.

Настройка пользователя

Для съёма статистики будем использовать пользователя mon с паролем mon. Впишем в файл /etc/pgbouncer/userlist.txt нового пользователя:
"mon" "mon"
Теперь нужно разрешить этому пользователю просматривать статистику. Для этого откроем файл /etc/pgbouncer/pgbouncer.ini, найдём опцию stats_users. По умолчанию она закомментирована. В таком случае нужно её раскомментировать и прописать одного-единственного пользователя mon. Если же она уже раскомментирована, тогда нужно добавить пользователя mon через запятую к списку имеющихся:
stats_users = mon
Осталось перезагрузить pgbouncer, чтобы новый пользователь получил доступ к статистике PgBouncer'а:
# systemctl reload pgbouncer.service

Скрипт для Zabbix-агента

Скрипт для обнаружения пулов подключений написан на языке командной строки, использует только утилиту psql для подключения к PgBouncer'у и язык awk для разбора результатов запросов:
#!/bin/sh

MON_USER=mon
MON_PASSWORD=mon
PGBOUNCER_PORT=6432

COMMAND=$1
USER=$2
DB=$3

if [ "x$COMMAND" = "xdiscover" ]
then
    env PGPASSWORD=$MON_PASSWORD psql -p $PGBOUNCER_PORT -U $MON_USER pgbouncer -t -c 'show pools;' \
      | awk -F'|' 'BEGIN { printf "{\"data\":["; n=0; }
                   /\|/ { if (n != 0)
                            printf ",";
                          gsub(" ", "", $1);
                          gsub(" ", "", $2);
                          printf "{\"{#PGBOUNCER_DATABASE}\": \"" $1 "\", \"{#PGBOUNCER_USER}\": \"" $2 "\"}";
                          n++; }
                   END { printf "]}"; }'
else
  env PGPASSWORD=$MON_PASSWORD psql -p $PGBOUNCER_PORT -U $MON_USER pgbouncer -t -c 'show pools;' \
    | awk -F'|' \
          -v DB=$DB \
          -v USER=$USER \
          -v COMMAND=$COMMAND '/\|/ { gsub(" ", "", $1);
                                      gsub(" ", "", $2);
                                      if (($1 == DB) && ($2 == USER))
                                      {
                                        if (COMMAND == "cl_active")
                                        {
                                          gsub(" ", "", $3);
                                          print $3;
                                        }
                                        else if (COMMAND == "cl_waiting")
                                        {
                                          gsub(" ", "", $4);
                                          print $4;
                                        }
                                        else if (COMMAND == "cl_total")
                                        {
                                          print $3 + $4;
                                        }
                                        else if (COMMAND == "sv_active")
                                        {
                                          gsub(" ", "", $5);
                                          print $5;
                                        }
                                        else if (COMMAND == "sv_idle")
                                        {
                                          gsub(" ", "", $6);
                                          print $6;
                                        }
                                        else if (COMMAND == "sv_used")
                                        {
                                          gsub(" ", "", $7);
                                          print $7;
                                        }
                                        else if (COMMAND == "sv_tested")
                                        {
                                          gsub(" ", "", $8);
                                          print $8;
                                        }
                                        else if (COMMAND == "sv_login")
                                        {
                                          gsub(" ", "", $9);
                                          print $9;
                                        }
                                        else if (COMMAND == "sv_total")
                                        {
                                          print $5 + $6 + $7 + $8 + $9;
                                        }
                                        else if (COMMAND == "maxwait")
                                        {
                                          gsub(" ", "", $10);
                                          print $10;
                                        }
                                      }
                                    }'
fi
Я поместил его в файле /etc/zabbix/pgbouncer.sh и выставил права доступа, позволяющие редактировать его только пользователю root, а читать и выполнять - пользователями из группы zabbix:
# chown root:zabbix /etc/zabbix/pgbouncer.sh
# chmod u=rwx,g=rx,o= /etc/zabbix/pgbouncer.sh
Как можно понять из указанных выше команд, у остальных пользователей доступа к файлу нет. Сделано это на случай, если нужно будет поменять пароль пользователя mon на более безопасный, чтобы никто не мог увидеть этот пароль, посмотрев тело скрипта.

Настройка Zabbix-агента

Для того, чтобы использовать данные скрипта из Zabbix'а, нужно добавить в конфигурацию Zabbix-агента /etc/zabbix/zabbix_agentd.conf на том компьютере, где установлен PgBouncer, следующие строчки:
UserParameter=pgbouncer.pool.discover,/etc/zabbix/pgbouncer.sh discover
UserParameter=pgbouncer.pool.cl_active[*],/etc/zabbix/pgbouncer.sh cl_active $1 $2
UserParameter=pgbouncer.pool.cl_waiting[*],/etc/zabbix/pgbouncer.sh cl_waiting $1 $2
UserParameter=pgbouncer.pool.cl_total[*],/etc/zabbix/pgbouncer.sh cl_total $1 $2
UserParameter=pgbouncer.pool.sv_active[*],/etc/zabbix/pgbouncer.sh sv_active $1 $2
UserParameter=pgbouncer.pool.sv_idle[*],/etc/zabbix/pgbouncer.sh sv_idle $1 $2
UserParameter=pgbouncer.pool.sv_used[*],/etc/zabbix/pgbouncer.sh sv_used $1 $2
UserParameter=pgbouncer.pool.sv_tested[*],/etc/zabbix/pgbouncer.sh sv_tested $1 $2
UserParameter=pgbouncer.pool.sv_login[*],/etc/zabbix/pgbouncer.sh sv_login $1 $2
UserParameter=pgbouncer.pool.sv_total[*],/etc/zabbix/pgbouncer.sh sv_total $1 $2
UserParameter=pgbouncer.pool.maxwait[*],/etc/zabbix/pgbouncer.sh maxwait $1 $2
После изменения конфигурации нужно перезапустить Zabbix-агента:
# systemctl restart zabbix-agent.service

Шаблоны Zabbix

Я подготовил два шаблона для Zabbix 3.4:
В обоих шаблонах имеется элемент данных для низкоуровневого обнаружения пулов подключений. Обнаружение происходит раз в час:

В шаблонах настроены прототипы элементов данных, позволяющих контролировать количество подключений от клиентов, количество подключений к серверу, а также время ожидания клиента в очереди до получения доступа к одному из свободных подключений к серверу:

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

Примеры графиков

Количество клиентских подключений:

Количество подключений к серверу:

Время ожидания клиента в очереди в ожидании свободного подключения к серверу:

воскресенье, 16 июня 2019 г.

Контроль количества процессов приложений uWSGI через Zabbix

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

В uwsgi новых версий есть множество разных способов контролировать его работу, но в имеющейся у меня версии 1.2.3 из репозитория Debian Wheezy поддерживается не так-то много вариантов для его контроля. Я остановился на варианте с механизмом Stats Server.

Настройка сокет-файлов

Чтобы включить Stats Server для каждого из приложений, нужно добавить соответствующую опцию в конфигурацию каждого из приложений в файлах /etc/uwsgi/apps-available/. Однако, чтобы не редактировать много файлов конфигурации, можно изменить файл с настройками по умолчанию. Копируем из файла /usr/share/uwsgi/conf/default.ini настройки в файл /etc/uwsgi/uwsgi.ini и добавляем опцию:
stats = /run/uwsgi/%(deb-confnamespace)/%(deb-confname)/stats
В результате у меня получился файл с таким содержимым:
[uwsgi]
autoload = true
master = true
workers = 2
no-orphans = true
pidfile = /run/uwsgi/%(deb-confnamespace)/%(deb-confname)/pid
socket = /run/uwsgi/%(deb-confnamespace)/%(deb-confname)/socket
stats = /run/uwsgi/%(deb-confnamespace)/%(deb-confname)/stats
chmod-socket = 660
log-date = true
uid = www-data
gid = www-data
Теперь пропишем использование этого файла с настройками по умолчанию в файле /etc/default/uwsgi, прописав в него соответствующую настройку:
INHERITED_CONFIG=/etc/uwsgi/uwsgi.ini
Теперь нужно перезапустить все приложения:
# /etc/init.d/uwsgi restart
В каталогах приложений /var/run/uwsgi/app/<приложение>/ должны появиться файлы stats.

Скрипт обнаружения и опроса

К сожалению, права доступа к этому файлу точно такие же, как у файла сокета и поменять их через файл конфигурации приложения нельзя, поэтому для доступа к файлу статистики от имени пользователя zabbix придётся либо воспользоваться sudo, либо включить пользователя zabbix в группу www-data. Я воспользовался sudo. Однако, прежде чем перейти к настройкам sudo и Zabbix-агента, нужно написать скрипт, с помощью которого Zabbix-агент будет извлекать необходимые данные из сокетов статистики.

У меня получился скрипт на языке Python со следующим содержимым:
#!/usr/bin/python

import sys, os, socket, json

def discover():
    data = []
    for app in os.listdir('/run/uwsgi/app/'):
        data.append({'{#UWSGI_APP}': app})
    data = {'data': data}
    return json.dumps(data)

def read_stats(unix_socket):
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    try:
        s.connect(unix_socket)
    except socket.error:
        return None

    data = ''
    while True:
        buf = s.recv(4096)
        if len(buf) < 1:
            break
        data += buf
    s.close()

    data = data.decode('UTF-8')
    return json.loads(data)

def analyze_stats(data):
    listen_queue = data['listen_queue']
    listen_queue_errors = data['listen_queue_errors']
    
    total = 0
    idle = 0
    busy = 0
    for worker in data['workers']:
        total += 1
        if worker['status'] == 'idle':
            idle += 1
        elif worker['status'] == 'busy':
            busy += 1

    pidle = idle * 100.0 / total
    pbusy = busy * 100.0 / total

    return listen_queue, listen_queue_errors, total, idle, busy, pidle, pbusy

if __name__ == '__main__':
    if len(sys.argv) == 2:
        if sys.argv[1] == 'discover':
            print discover()
    elif len(sys.argv) == 3:
        app = sys.argv[2]
        data = read_stats('/run/uwsgi/app/%s/stats' % app)
        if data is None:
            print 'No stats'
            sys.exit()

        listen_queue, listen_queue_errors, total, idle, busy, pidle, pbusy = analyze_stats(data)
        if sys.argv[1] == 'listen_queue':
            print listen_queue
        elif sys.argv[1] == 'listen_queue_errors':
            print listen_queue_errors
        elif sys.argv[1] == 'total':
            print total
        elif sys.argv[1] == 'idle':
            print idle
        elif sys.argv[1] == 'busy':
            print busy
        elif sys.argv[1] == 'pidle':
            print pidle
        elif sys.argv[1] == 'pbusy':
            print pbusy
Я поместил этот скрипт в файл /etc/zabbix/uwsgi.py. Не забудьте поменять права доступа к файлу:
# chown root:root /etc/zabbix/uwsgi.py
# chmod ugo=rx /etc/zabbix/uwsgi.py

Настройка sudo и Zabbix-агента

Теперь дадим пользователю zabbix права на запуск этого скрипта от имени пользователя root. Запускаем visudo и добавляем в файл конфигурации две строчки:
Defaults:zabbix !requiretty
zabbix ALL=(www-data:ALL) NOPASSWD:/etc/zabbix/uwsgi.py *
Отредактируем файл конфигурации Zabbix-агента /etc/zabbix/zabbix_agentd.conf:
UserParameter=uwsgi.discover,/usr/bin/sudo -u www-data /etc/zabbix/uwsgi.py discover
UserParameter=uwsgi.total[*],/usr/bin/sudo -u www-data /etc/zabbix/uwsgi.py total $1
UserParameter=uwsgi.idle[*],/usr/bin/sudo -u www-data /etc/zabbix/uwsgi.py idle $1
UserParameter=uwsgi.busy[*],/usr/bin/sudo -u www-data /etc/zabbix/uwsgi.py busy $1
UserParameter=uwsgi.pidle[*],/usr/bin/sudo -u www-data /etc/zabbix/uwsgi.py pidle $1
UserParameter=uwsgi.pbusy[*],/usr/bin/sudo -u www-data /etc/zabbix/uwsgi.py pbusy $1
UserParameter=uwsgi.listen_queue[*],/usr/bin/sudo -u www-data /etc/zabbix/uwsgi.py listen_queue $1
UserParameter=uwsgi.listen_queue_errors[*],/usr/bin/sudo -u www-data /etc/zabbix/uwsgi.py listen_queue_errors $1
И перезапустим Zabbix-агента, чтобы новые настройки вступили в силу:
# /etc/init.d/zabbix-agentd restart

Шаблоны Zabbix

Я подготовил два шаблона для Zabbix 3.4:
В обоих шаблонах имеется элемент данных для низкоуровневого обнаружения, который находит все имеющиеся сокет-файлы для статистики приложений. Обнаружение происходит раз в час:

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

Пример графика

Приведу пример графика с реального сервера:

Из графиков видно, что приложения maps и bottle иногда используют 100% рабочих процессов, а вот приложение ncc постоянно использует не более 30% рабочих процессов.

воскресенье, 9 июня 2019 г.

Исправление ручного закрытия проблем в Zabbix 3.4

В Zabbix версии 3.2 появилась возможность закрывать проблемы вручную, которая бывает очень полезной, если невозможно сформулировать выражение, при котором проблема должна закрываться автоматически. Например, это может оказаться полезно в триггерах, которые срабатывают при появлении определённой строчке в каком-либо журнале, или если информация о проблеме поступает в виде трапа в элемент данных типа Zabbix-траппер или SNMP trap, особенно если сообщения о восстановлении не предусмотрены или потерялись.

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

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

Для ручного закрытия проблемы нужно написать сообщение, объясняющее причину ручного закрытия проблемы, и отметить соответствующую галочку:

Однако, реализована эта функция оказалась довольно странным образом: чтобы воспользоваться галочкой, нужно иметь права редактирования группы узлов, на одном из которых может сработать проблема, требующая ручного закрытия. Даже если предположить, что пользователь, желающий закрыть проблему, не захочет менять настройки наблюдаемых узлов или удалять их, у него остаётся возможность закрыть любую проблему, которая ему не нравится, если у него есть доступ на редактирование соответствующего узла. Для этого нужно всего лишь зайти в настройки триггера, который породил проблему, выставить галочку, разрешающую ручное закрытие, закрыть проблему и вернуть галочку обратно. На мой взгляд, польза от такой настройки сомнительна, потому что пользователь, имеющий доступ к редактированию настроек, и раньше мог закрыть любую проблему. Для этого ему нужно было всего лишь временно поменять выражение в триггере и вернуть его обратно, или удалить и создать триггер заново. Новая галочка ручного закрытия лишь дала возможность сделать это чуть-чуть корректнее :)

Итак, думаю что вам уже стало понятно, что лучше дать возможность всем закрывать видимые ими проблемы вручную, чем давать им доступ на редактирование настроек узлов. Для этого пересоберём пакет для Debian, внеся в исходные тексты веб-интерфейса соответствующие исправления.

Добавим официальные репозитории с файлами для сборки пакетов в файл /etc/apt/sources.list:
deb-src http://repo.zabbix.com/zabbix/3.4/debian stretch main
Установим ключ репозитория Zabbix и обновим список доступных пакетов:
# wget http://repo.zabbix.com/zabbix-official-repo.key -O - | apt-key add -
# apt-get update
Установим необходимые для сборки пакетов Zabbix зависимости и скачаем файлы для сборки пакетов:
# apt-get build-dep zabbix
# apt-get source zabbix
Ставим пакеты с инструментами, которые пригодятся нам для пересборки пакета:
# apt-get install dpkg-dev devscripts fakeroot
Переходим в каталог zabbix-3.4.12-1+stretch, в который распаковались исходные тексты пакета, скачиваем и накладываем заплатку:
# cd zabbix-3.4.12-1+stretch
# wget http://stupin.su/files/zabbix3_4_12_manual_close_event_acknowledge.diff
# patch -Np0 < zabbix3_4_12_manual_close_event_acknowledge.diff
Текст самой заплатки:
Description: Fixed manual closing problems for users without rights to edit trigger
Fixed manual closing problems for users without rights to edit trigger

Author: Vladimir Stupin <vladimir@stupin.su>
Last-Update: <2019-03-01>

--- zabbix-3.4.12-1+wheezy.orig/frontends/php/app/controllers/CControllerAcknowledgeEdit.php
+++ zabbix-3.4.12-1+wheezy/frontends/php/app/controllers/CControllerAcknowledgeEdit.php
@@ -132,7 +132,6 @@ class CControllerAcknowledgeEdit extends
                        'output' => [],
                        'triggerids' => $triggerids,
                        'filter' => ['manual_close' => ZBX_TRIGGER_MANUAL_CLOSE_ALLOWED],
-                       'editable' => true,
                        'preservekeys' => true
                ]);
--- zabbix-3.4.12-1+wheezy.orig/app/controllers/CControllerAcknowledgeCreate.php        2019-03-01 16:15:28.931300319 +0500
+++ zabbix-3.4.12-1+wheezy/app/controllers/CControllerAcknowledgeCreate.php     2019-03-01 16:15:14.211550047 +0500
@@ -87,7 +87,6 @@
                                'output' => [],
                                'triggerids' => $triggerids,
                                'filter' => ['manual_close' => ZBX_TRIGGER_MANUAL_CLOSE_ALLOWED],
-                               'editable' => true,
                                'preservekeys' => true
                        ]);
--- zabbix-3.4.12-1+wheezy.orig/include/classes/api/services/CEvent.php 2019-03-01 16:18:17.636438283 +0500
+++ zabbix-3.4.12-1+wheezy/include/classes/api/services/CEvent.php      2019-03-01 16:18:24.272325613 +0500
@@ -860,7 +860,6 @@
                        'eventids' => $eventids,
                        'source' => EVENT_SOURCE_TRIGGERS,
                        'object' => EVENT_OBJECT_TRIGGER,
-                       'editable' => true
                ]);
 
                if ($events_count != count($events)) {
Если всё прошло успешно, то можно внести комментарии к изменениям. Запускаем команду для редактирования журнала изменений:
# dch -i
И приводим последнюю запись к подобному виду:
zabbix (1:3.4.12-1+stretch-p2) UNRELEASED; urgency=low

  * Non-maintainer upload.
  * Fixed manual closing problems for users without rights to edit trigger

 -- Vladimir Stupin <vladimir@stupin.su>  Thu, 07 Mar 2019 11:04:14 +0500
Теперь можно собрать двоичные пакеты и исправленный пакет с исходными текстами:
# dpkg-buildpackage -us -uc -rfakeroot
В каталоге выше появятся собранные пакеты, которые можно установить при помощи dpkg. Нас прежде всего интересует пересобранный пакет с веб-интерфейсом Zabbix:
# dpkg -i zabbix-frontend-php_3.4.12-1+stretch-p2_all.deb

воскресенье, 2 июня 2019 г.

Пересборка libiksemel для решения проблемы JABBER tls handshake failed в Zabbix

После обновления операционной системы Debian с Wheezy до Stretch перестала работать отправка сообщений через Jabber. При обновлении пакет libiksemel3 обновился с версии 1.2-4 до версии 1.4-3. В журнале сервера Zabbix /var/log/zabbix/zabbix_server.log обнаружились строчки вида:
1769:20190422:083154.224 JABBER: [monitoring@jabber.server.ru/xxx] tls handshake failed
В интернете можно найти массу примеров того, как люди справляются с подобными проблемами:
Как видно, большинство статей ограничивается обходными решениями. Лишь в третьей статье сделана попытка справиться с источником проблем. Проблема эта, правда, была другая и её решение тоже не было корректным: вместо увеличения таймаута автор статьи по-сути просто отключил проверку таймаута.

Поскольку раньше отправка сообщений в Jabber у меня работала без нареканий, то я сразу заподозрил, что в библиотеке libiksemel, при помощи которой Zabbix отправляет сообщения в Jabber, повысились требования к безопасности используемых протоколов и шифров. Для начала я решил узнать, какие протоколы и шифры поддерживает Jabber-сервер.

Jabber-сервер по умолчанию принимает подключения от клиентов на TCP-порт 5222. Через этот порт принимаются как подключения без шифрования, так и с шифрованием. При подключении клиент узнаёт о возможностях сервера и может согласовать с ним шифрование данных, если сервер такую возможность позволяет. К сожалению, я не знаю, как проверить список поддерживаемых протоколов и шифров, подключившись на этот порт.

Однако, для совместимости со старыми клиентами Jabber-сервер обычно принимает подключения также и на порт 5223, на котором сразу после подключения нужно согласовать шифрование, а затем через зашифрованное соединение уже обмениваться данными по протоколу XMPP.

libiksemel для установки защищённых подключений использует библиотеку GNU TLS. Попробуем воспользоваться утилитой командной строки gnutls-cli-debug, использующей эту библиотеку, для проверки возможностей Jabber-сервера. Для этого установим пакет gnutls-bin:
# apt-get install gnutls-bin
Вызываем утилиту для получения списка возможностей Jabber-сервера:
$ gnutls-cli-debug -p 5223 jabber.server.ru
Утилита выводит следующую информацию:
Warning: getservbyport(5223) failed. Using port number as service.
GnuTLS debug client 3.5.8
Checking jabber.server.ru:5223
                             for SSL 3.0 (RFC6101) support... yes
                        whether we need to disable TLS 1.2... no
                        whether we need to disable TLS 1.1... no
                        whether we need to disable TLS 1.0... no
                        whether %NO_EXTENSIONS is required... no
                               whether %COMPAT is required... no
                             for TLS 1.0 (RFC2246) support... yes
                             for TLS 1.1 (RFC4346) support... no
                                  fallback from TLS 1.1 to... TLS 1.0
                             for TLS 1.2 (RFC5246) support... no
                                  fallback from TLS 1.6 to... failed (server requires fallback dance)
              for inappropriate fallback (RFC7507) support... yes
                               for certificate chain order... sorted
                  for safe renegotiation (RFC5746) support... yes
                     for Safe renegotiation support (SCSV)... yes
                    for encrypt-then-MAC (RFC7366) support... no
                   for ext master secret (RFC7627) support... no
                           for heartbeat (RFC6520) support... no
                       for version rollback bug in RSA PMS... no
                  for version rollback bug in Client Hello... no
            whether the server ignores the RSA PMS version... no
whether small records (512 bytes) are tolerated on handshake... yes
    whether cipher suites not in SSL 3.0 spec are accepted... yes
whether a bogus TLS record version in the client hello is accepted... yes
         whether the server understands TLS closure alerts... no
            whether the server supports session resumption... no
                      for anonymous authentication support... no
                      for ephemeral Diffie-Hellman support... no
                   for ephemeral EC Diffie-Hellman support... no
                             for curve SECP256r1 (RFC4492)... no
                             for curve SECP384r1 (RFC4492)... no
                             for curve SECP521r1 (RFC4492)... no
           for curve X25519 (draft-ietf-tls-rfc4492bis-07)... no
                  for AES-128-GCM cipher (RFC5288) support... no
                  for AES-128-CCM cipher (RFC6655) support... no
                for AES-128-CCM-8 cipher (RFC6655) support... no
                  for AES-128-CBC cipher (RFC3268) support... yes
             for CAMELLIA-128-GCM cipher (RFC6367) support... no
             for CAMELLIA-128-CBC cipher (RFC5932) support... yes
                     for 3DES-CBC cipher (RFC2246) support... yes
                  for ARCFOUR 128 cipher (RFC2246) support... yes
            for CHACHA20-POLY1305 cipher (RFC7905) support... no
                                       for MD5 MAC support... yes
                                      for SHA1 MAC support... yes
                                    for SHA256 MAC support... no
                              for ZLIB compression support... no
                     for max record size (RFC6066) support... no
                for OCSP status response (RFC6066) support... no
              for OpenPGP authentication (RFC6091) support... no
Как видно, сервер поддерживает только протоколы SSL 3.0 и TLS 1.0, а протоколы TLS 1.1 и TLS 1.2 не поддерживаются. Не понял я только, откуда в выводе утилиты взялся TLS 1.6.

Теперь распакуем пакет с исходными текстами, чтобы проверить, какие настройки используются:
# apt-get source libiksemel3
В файле src/stream.c имеется строчка:
const char *priority_string = "SECURE256:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2";
Видно, что Jabber-сервер поддерживает только протоколы SSL3 и TLS1.0, а libiksemel требует, чтобы сервер поддерживал как минимум TLS1.2. Безопасность - это, конечно, хорошо, но, как мне кажется, не стоит бежать впереди паровоза и выставлять настройки более жёсткие, чем это принято в библиотеке GNU TLS по умолчанию.

Прежде чем менять строку приоритетов и пересобирать пакет, можно протестировать новую строку приоритетов при помощи такой команды:
$ gnutls-cli --priority 'NORMAL:-VERS-SSL3.0' -p 5223 jabber.server.ru
Если подключение устанавливается успешно с указанной строкой приоритетов, можно продолжать.

Я поменял строку приоритетов следующим образом:
const char *priority_string = "NORMAL:-VERS-SSL3.0";
Вы можете оставить там только слово NORMAL, т.к. я добавил запрет использовать протокол SSL3.0 для того, чтобы принудить клиента использовать протокол TLS1.0, поддерживаемый используемым мной сервером Jabber. Подробнее о формате строки приоритетов можно почитать, например, здесь: 7.9 Priority strings

Подтверждаем изменения, формируя из них заплату:
# dpkg-source --commit
В качестве имени заплаты я указал строчку downgrade_gnutls_priority_string_for_jabber.server.ru

У меня получился такая заплатка:
Description: Downgrade GNU TLS priority string for jabber.server.ru
 Downgrade GNU TLS priority string for jabber.server.ru
Author: Vladimir Stupin <vladimir@stupin.su>
Last-Update: 2019-04-22

--- libiksemel-1.4.orig/src/stream.c
+++ libiksemel-1.4/src/stream.c
@@ -63,7 +63,7 @@ tls_pull (iksparser *prs, char *buffer,
 static int
 handshake (struct stream_data *data)
 {
-       const char *priority_string = "SECURE256:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2";
+       const char *priority_string = "NORMAL:-VERS-SSL3.0";
        int ret;

        if (gnutls_global_init () != 0)
Запускаем команду для обновления журнала изменений пакета:
# dch -i
Добавляем такую запись:
libiksemel (1.4-3+b1u1) UNRELEASED; urgency=medium

  * Downgrade GNU TLS priority string for jabber.server.ru

 -- Vladimir Stupin   Mon, 22 Apr 2019 10:29:11 +0300
Осталось собрать пакеты с исходными текстами и двоичные пакеты:
# dpkg-buildpackage -us -uc -rfakeroot
Можно установить libiksemel из собранных пакетов, перезапустить сервер Zabbix и проверить отправку сообщений в Jabber. У меня всё заработало.

воскресенье, 26 мая 2019 г.

Установка, настройка и использование Jappix - веб-клиента Jabber

В статье Настройка Jabber-сервера Prosody с аутентификацией пользователей через Dovecot SASL я описал настройку BOSH. BOSH расшифровывается как Bidirectional-streams Over Synchronous HTTP - двунаправленные потоки данных через синхронный HTTP. Этот протокол позволяет клиентам Jabber взаимодействовать с сервером Jabber через протокол HTTP. Особенно полезной эта функция оказывается для веб-клиентов Jabber, одним из которых и является программа Jappix. Кроме того, что название этой программы похоже на название системы мониторинга Zabbix, можно сказать разве лишь следующее:
  • написана на PHP,
  • её нет в репозиториях Debian,
  • активная разработка программы не ведётся уже более двух лет - последняя правка в репозитории с исходными текстами датируется 26 января 2017 года.
Последний пункт был ещё не настолько очевиден в октябре 2017 года, когда я, по сути, и готовил эту статью.

Т.к. веб-приложения нет в репозиториях Debian, я подготовил скрипт для сборки deb-пакета:
#!/bin/sh

wget --quiet wget https://github.com/jappix/jappix/archive/master.zip -O jappix-master.zip
unzip -q jappix-master.zip
rm jappix-master.zip

PACKAGE=jappix
DATETIME=`date "+%Y%m%d%H%M%S"`
VERSION="0.$DATETIME"
DIR=${PACKAGE}_${VERSION}

mkdir -p $DIR/DEBIAN

# Формируем описание пакета
cat - > $DIR/DEBIAN/control <<END
Package: $PACKAGE
Version: $VERSION
Architecture: all
Depends: apache2 | httpd, php, php-common (>= 5.5.0+dfsg-10~), php-curl, php-gd
Maintainer: Vladimir Stupin <vladimir@stupin.su>
Description: Jappix, a full-featured XMPP web-client
 Jappix, a full-featured XMPP web-client
 Homepage: https://github.com/jappix/jappix
END

chmod 755 $DIR/DEBIAN/control

cat - > $DIR/DEBIAN/postinst <<END
#!/bin/sh
# postinst script for jappix

set -e

# summary of how this script can be called:
#        * <postinst> \`configure' <most-recently-configured-version>
#        * <old-postinst> \`abort-upgrade' <new version>
#        * <conflictor's-postinst> \`abort-remove' \`in-favour' <package>
#          <new-version>
#        * <deconfigured's-postinst> \`abort-deconfigure' \`in-favour'
#          <failed-install-package> <version> \`removing'
#          <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package

case "\$1" in
  configure)
        # Change permissions to etc files
        if ! dpkg-statoverride --list /etc/tmpfiles.d/jappix.conf >/dev/null; then
                chown -R root:root /etc/tmpfiles.d/jappix.conf
                chmod 644 /etc/tmpfiles.d/jappix.conf
        fi

        # Change permissions to application files
        if ! dpkg-statoverride --list /usr/share/jappix >/dev/null; then
                chown -R root:root /usr/share/jappix
                chmod 755 /usr/share/jappix
                find /usr/share/jappix -type d | xargs chmod 755
                find /usr/share/jappix -type f | xargs chmod 644
        fi

        # Create data directory
        if [ ! -d "/var/lib/jappix" ]; then
                install -d -o www-data -g www-data -m 770 /var/lib/jappix;
        fi
        if ! dpkg-statoverride --list /var/lib/jappix >/dev/null; then
                chown -R www-data:www-data /var/lib/jappix
                chmod 770 /var/lib/jappix
                find /var/lib/jappix -type d | xargs chmod 770
                find /var/lib/jappix -type f | xargs chmod 660
        fi

        # Create logs directory
        if [ ! -d "/var/log/jappix" ]; then
                install -d -o www-data -g www-data -m 770 /var/log/jappix;
        fi

        # Create tmp directory
        systemd-tmpfiles --create >/dev/null 2>&1
  ;;
  abort-upgrade|abort-remove|abort-deconfigure)
  ;;
  *)
    echo "postinst called with unknown argument \\\`\$1'" >&2
    exit 1
  ;;
esac
END

chmod 0755 $DIR/DEBIAN/postinst

# Формируем содержимое пакета
mkdir -p $DIR/usr/share/jappix/
cp -R jappix-master/* $DIR/usr/share/jappix/
rm -R jappix-master

# Выносим каталог /usr/share/jappix/store в /var/lib/jappix
mkdir -p $DIR/var/lib/
mv $DIR/usr/share/jappix/store $DIR/var/lib/jappix
ln -s /var/lib/jappix $DIR/usr/share/jappix/store

# Выносим каталог /usr/share/jappix/tmp в /tmp/jappix
rm -R $DIR/usr/share/jappix/tmp
ln -s /tmp/jappix $DIR/usr/share/jappix/tmp

mkdir -p $DIR/etc/tmpfiles.d/
cat - > $DIR/etc/tmpfiles.d/jappix.conf <<END
d    /tmp/jappix/          0770 www-data www-data 10m -
d    /tmp/jappix/avatar/   0770 www-data www-data 10m -
d    /tmp/jappix/archives/ 0770 www-data www-data 10m -
d    /tmp/jappix/send/     0770 www-data www-data 10m -
d    /tmp/jappix/cache/    0770 www-data www-data 10m -
END

# Выносим каталог /usr/share/jappix/log в /var/log/jappix
rm -R $DIR/usr/share/jappix/log
ln -s /var/log/jappix $DIR/usr/share/jappix/log

FILE=${PACKAGE}_${VERSION}_all.deb

# Формируем пакет
dpkg-deb -b $DIR $FILE >/dev/null 2>&1
rm -R $DIR
Сохраним этот скрипт под именем make_jappix.sh, сделаем его исполняемым и запустим:
$ chown +x ./make_jappix.sh
$ ./make_jappix.sh
Если всё пройдёт успешно, то в текущем каталоге появится deb-файл. Во время подготовки статьи к публикации собранный мной deb-файл назывался jappix_0.20190321184054_all.deb. Установим его в систему:
# dpkg -i /home/stupin/jappix_0.20190321184054_all.deb
Если в системе не установлены необходимые зависимости для этого пакета, то исправить это можно, установив недостающие пакеты вручную и завершив установку недостающих зависимостей при помощи пары команд:
# apt-get install nginx-full php-fpm
# apt-get install -f
Настройка BOSH уже была описана, поэтому не буду повторяться и приступлю сразу к настройке приложения Jappix. Во-первых, если у вас ещё не настроен пул процессов PHP-FPM, то можно настроить его, создав файл в каталоге /etc/php/7.0/fpm/pool.d/. Я создал файл /etc/php/7.0/fpm/pool.d/www07.conf со следующим содержимым:
[www07]
user = www07
group = www-data

listen = /var/run/www07.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

;chroot = /home/www07/
chdir = /usr/share/jappix/

pm = ondemand
pm.max_children = 2
pm.process_idle_timeout = 10m

;access.log = /var/log/php5-fpm.access.log

php_flag[log_errors] = On
;php_value[error_log] = /var/log/php5-fpm.error.log
php_value[error_reporting] = E_ALL & ~E_NOTICE

php_value[post_max_size] = 8M
php_value[upload_max_filesize] = 8M
php_value[date.timezone] = Asia/Yekaterinburg
Теперь нужно перезагрузить php-fpm, чтобы настройки нового пула процессов вступили в силу:
# systemctl reload php7.0-fpm.service
Теперь можно приступить к настройке nginx. Я вписал в файл /etc/nginx/sites-enabled/default в секцию server следующие настройки:
location /jappix/ {
  alias /usr/share/jappix/;
  index index.php;
}

location ~ ^/jappix/.+\.php$ {
  root /usr/share/jappix/;
 
  include fastcgi.conf;

  fastcgi_pass unix:/var/run/www07.sock;
  fastcgi_split_path_info ^/jappix(/.+\.php)(.*)$;
}

location ~ /jappix/(app|i18n|log|test|tmp|tools)/ {
  deny all;
}
Осталось перезагрузить nginx, чтобы он узнал о новых настройках:
# systemctl reload nginx.service
Теперь можно вооружиться браузером и приступить к настройке только что установленного приложения.

Начальная настройка Jappix:



Внимание! В примере ниже включена опция шифрования. Подумайте о том, стоит ли включать её. Во-первых, при использовании HTTPS, требование шифрования избыточно, т.к. шифрование уже осуществляется на уровне сеанса HTTPS. Во-вторых - требование шифрования может вызвать проблемы, т.к. со стороны сервера может быть выставлена настройка, запрещающая устанавливать шифрованные соединения через HTTPS. В частности, клиент Pidgin с графическим интерфейсом, при указании адреса BOSH пытается установить шифрованное подключение по протоколу XMPP через уже зашифрованное подключение HTTPS. Поскольку в Pidgin нет опции для безусловного отключения требования шифрования, то заставить его работать через BOSH не удаётся. Если вы всё-же включили шифрование, то отключить его позже можно через файл /var/lib/jappix/conf/main.xml, для этого нужно вписать значение off в строчку "<encryption>on</encryption>" и сохранить изменения.



Вход в Jappix:


Настройка профиля пользователя Jappix:






На снимке ниже программа предлагает создать публичный профиль на сайте, который уже не существует. Стоит отказаться от предложения. На мой взгляд, не стоило бы соглашаться, даже если сайт по-прежнему работал бы.

Окно программы:

Вход в комнату:






Поиск пользователя в каталоге VJUD - Virtual Jabber User Directory:


Включить/отключить возможность поиска своей учётной записи в каталоге VJUD:





Поскольку через VJUD мне ни разу не удалось найти что-либо, возможно поддержка VJUD в Prosody, описанная в прошлой статье, просто сломана.

Учётная запись из примеров - экспериментальная. Не стоит писать мне сообщения на неё, т.к. я пока не пользуюсь ей активно и она в любой момент может оказаться неисправной вместе с сервером Jabber. Лучше пишите комментарии или письма.

Если кто-то может подсказать альтернативные веб-клиенты Jabber, которые продолжают активно поддерживаться, и поделиться опытом их использования, прошу написать в комментарии.

воскресенье, 19 мая 2019 г.

Настройка Jabber-сервера Prosody с аутентификацией пользователей через Dovecot SASL

Мода на сети обмена мгновенными сообщениями приходит и уходит. Сначала это был ICQ, потом Skype, в последнее время были популярны WhatsApp и Viber, а теперь многие пользователи отказываются от них в пользу Telegram. Все эти сети - централизованные и принадлежат определённому хозяину, через серверы которого проходит вся переписка. Этот хозяин может изменить условия использования сервиса, никого не предупреждая.

Например, в ICQ было запрещено использовать сторонние клиенты, протокол был закрыт и часто менялся. Официальный клиент показывал пользователям рекламу, а из-за смены протокола неофициальные клиенты часто переставали работать. Самое же интересное в этой сети заключалось в том, что ICQ по пользовательскому соглашению, имел право пользоваться содержанием переписки пользователей по своему усмотрению. Сейчас, когда ICQ купил Mail.Ru Group, насколько мне известно, многие ограничения сняты: можно использовать неофициальные клиенты, протокол не меняют, рекламу не показывают. Однако, мода успела смениться и приток новых пользователей в ICQ сейчас в значительной мере иссяк, а многие прежние пользователи уже перескочили на другую, более модную альтернативу. Тогда это был Skype.

В Skype тоже имелись свои особенности. Например, версии Skype для Linux имели ограниченный функционал, отстающий от версий для Windows. Поддерживался только клиент для 32-битной платформы Linux.

WhatsApp невозможно пользоваться, не имея достаточно современного мобильного телефона с подключением к сети интернет. Нельзя установить на компьютер программу для общения в этой сети, не устанавливая программу на мобильный телефон. Все пользователи классических телефонов типа Nokia оказываются в пролёте. Это особенно удивительно, что за основу этой сети был взят протокол XMPP и одна из реализаций Jabber-серверов. Надо очень постараться, чтобы превратить изначально демократичные средства связи в какой-то жуткий тоталитаризм.

Telegram значительно лучше всех своих предшественников по крайней мере в том, что его протокол открыт. В этой сети официально можно создавать свои программы-боты, для чего предусмотрено свой собственный интерфейс прикладных программ - API.

Но все эти сети изначально не предусматривают главной свободы - свободы создать свой собственный сервер. Jabber - это распределённая сеть для обмена мгновенными сообщениями, которая работает по принципам, сходным с теми, на которых работает электронная почта: любой пользователь сети интернет, имеющий постоянное подключение с постоянным IP-адресом, может настроить собственный сервер, который сможет пересылать сообщения на другие серверы и принимать от них сообщения. Протокол, по которому взаимодействую клиенты и серверы, открыт и называется XMPP. Идентификаторы пользователей похожи на адреса электронной почты и сокращённо называются JID, что полностью звучит как Jabber ID - Jabber-идентификатор.

Существует несколько популярных Jabber-серверов. Настройка одного из них, который называется Prosody, и будет рассмотрена в этой статье. Сервер написан на языке Lua и выгодно отличается от конкурентов умеренным использованием системных ресурсов. Если вам нужно настроить Jabber-сервер для небольшого количества пользователей, то этот сервер будет идеальным выбором.

Базовая настройка сервера

Установим пакет, в котором находится Jabber-сервер Prosody:
# apt-get install prosody
По умолчанию включено обслуживание домена localhost. Отключим его, удалив ссылку /etc/prosody/conf.d/localhost.cfg.lua, которая указывает на файл /etc/prosody/conf.avail.localhost.cfg.lua:
# rm /etc/prosody/conf.d/localhost.cfg.lua
В файле /etc/prosody/conf.avail/example.com.cfg.lua есть пример настроек для домена example.com. По образу и подобию этого файла создадим файл для своего домена. В моём случае это будет домен stupin.su, а файл будет называться /etc/prosody/conf.avail/stupin.su.cfg.lua:
VirtualHost "stupin.su"
В этой конфигурации настроено обслуживание домена stupin.su - всё предельно просто.

Чуть ниже имеется строчка, отключающая обработку этого домена:
enabled = false
Нужно удалить эту строчку, или заменить значение на true.

Теперь нужно включить использование этого файла, создав символическую ссылку на него в каталоге /etc/prosody/conf.d:
# cd /etc/prosody/conf.d
# ln -s /etc/prosody/conf.avail/stupin.su.cfg.lua .
Отключим пока что глобальную поддержку SSL. Для этого нужно открыть основной файл конфигурации /etc/prosody/prosody.cfg.lua, найти в нём глобальные настройки SSL и закомментировать их, приведя к следующему виду:
-- These are the SSL/TLS-related settings. If you don't want
-- to use SSL/TLS, you may comment or remove this
-- ssl = {
--      key = "/etc/prosody/certs/localhost.key";
--      certificate = "/etc/prosody/certs/localhost.crt";
--}
В том же файле можно указать список учётных записей, которые будут обладать правами администраторов на сервере:
admins = { "vladimir@stupin.su" }
Перезапустим сервер Prosody, чтобы его новые настройки вступили в силу:
# systemctl restart prosody.service
В моей системе IPv6 был отключен, из-за чего Prosody запуститься не мог. Чтобы Prosody не пытался открывать на прослушивание адреса IPv6, можно добавить в файл конфигурации /etc/prosody/prosody.cfg.lua явное указание на список прослушиваемых интерфейсов. В моём случае нужно открывать на прослушивание все адреса IPv4, поэтому я добавил в конфигурацию такой фрагмент:
interfaces = { "*" }
Теперь добавим нового пользователя при помощи такой команды, которая дважды спросит пароль нового пользователя:
# prosodyctl adduser vladimir@stupin.su
Можно добавить нового пользователя, указав его пароль прямо в командной строке. Эту команду удобно использовать из сценариев, например, для массового заведения пользователей:
# prosodyctl register vladimir stupin.su pa$$w0rd

Настройка DNS

У меня есть только один Jabber-сервер, для которого я создам соответствующую A-запись:
jabber          IN A 83.83.185.185
Настроим теперь записи в DNS, чтобы Jabber-клиенты и другие Jabber-серверы знали, куда надо устанавливать подключение. В простейшем случае нужно добавить всего две SRV-записи: одна укажет адрес и порт подключения клиентов, а другая - для соединений серверов между собой:
_xmpp-client._tcp IN SRV 0 0 5222 jabber.stupin.su.
_xmpp-server._tcp IN SRV 0 0 5269 jabber.stupin.su.
Цифры 0 0 задают приоритет и вес соответствующего сервера. Прежде всего будет использоваться тот сервер, приоритет которого выше - сервер с меньшим числом приоритета. Среди серверов с одинаковым приоритетом, используемый выбирается случайным образом, однако предпочтение отдаётся тем, вес которых выше. Вес сервера по отношению к сумме весов всех серверов данного приоритета и будет вероятностью выбора этого сервера.

Подробности можно прочитать здесь: SRV Records

Проверка работы сервера

Для проверки настроек я воспользуюсь Jabber-клиентом Pidgin. Если у вас он ещё не установлен, то установить его можно из стандартных репозиториев дистрибутива, например, такой командой:
# apt-get install pidgin
Теперь запускаем программу через меню:

В запустившейся программе находим меню "Уч. записи" и выбираем в нём пункт "Управление учётными записями":

В открывшемся диалоговом окне "Учётные записи" нажимаем кнопку "Добавить...":

В открывшемся диалоговом окне "Добавить учётную запись" выбираем из выпадающего списка протокол XMPP:

И заполняем оставшиеся поля. В поле "Имя пользователя" вводим часть JID, находящуюся слева от символа @, а в поле "Домен", соответственно, вводим часть JID, которая находится справа от символа @. Одной и той же учётной записью можно пользоваться на нескольких компьютерах одновременно. Чтобы отличать друг от друга такие компьютеры, на каждом из них можно задать своё собственное имя, которое называется ресурсом. Например, компьютер, на котором я установил Pidgin стоит дома, поэтому я прописываю в поле "Ресурс" имя "home". В поле "Пароль" введём пароль и отметим ниже галочку "Запомнить пароль", чтобы его не нужно было вводить при каждом подключении к серверу. Наконец, в поле "Локальный псевдоним" можно ввести своё имя, которое будет отображаться в вашем окне переписки с другим пользователем:

Поскольку на сервере пока не настроены функции шифрования, перейдём на вкладку "Дополнительно" и выберем из выпадающего списка в поле "Безопасность соединения" значение "Если доступно, использовать шифрование", после чего нажимаем кнопку "Добавить":

В диалоговом окне "Учётные записи" появится новая, только что добавленная нами учётная запись. Закроем диалоговое окно, нажав на кнопку "Закрыть":

Теперь Pidgin установил подключение от имени только что созданной учётной записи, список контактов у которой пока что пуст:

Добавим свою собственную учётную запись в список контактов. Для этого в меню "Собеседники" выберем пункт "Добавить собеседника...":

В открывшемся диалоговом окне "Добавить собеседника" вводим в поле "Имя пользователя собеседника" JID нашей учётной записи, а в поле "Псевдоним" вводим имя, под которым эта учётная запись будет фигурировать в переписке. После этого нажимаем кнопку "Добавить":

В списке контактов появится новый собеседник. Щёлкнем дважды по строчке с его именем:

Откроется окно беседы. Наберём в нём тестовый текст и отправим его:

Отправленный текст вернётся в ответ:

Вот мы и убедились, что сервер и учётная запись функционируют. Приятно поговорить с умным человеком, но можно попробовать добавить собеседников из другого домена, на другом Jabber-сервере. Для этой проверки я воспользовался двумя своими учётными записями в разных доменах. Получилось такое вот раздвоение личности - беседуют доктор Джекилл и мистер Хайд:

Некоторые серверы могут отказываться использовать незащищённые соединения и требуют, чтобы соединение было защищёно сертификатом, подписанным в одном из доверенных удостоверяющих центров. Поэтому сразу после проверки работоспособности Jabber-сервера первым делом стоит приступить к настройке SSL.

Включение epoll вместо select

По умолчанию Prosody использует системный вызов select для обработки событий на TCP-подключениях с клиентами и серверами. Этот системный вызов не обладает высокой эффективностью, вместо него в Linux был создан новый системный вызов epoll, который достигает более высокой эффективности из-за отсутствия необходимости передавать между ядром и пользовательским приложением объёмные структуры данных.

Если в журнале /var/log/prosody/prosody.log имеется сообщение следующего вида, то это значит, что используется системный вызов select:
Mar 31 00:12:07 startup info    Prosody is using the select backend for connection handling
Для использования системного вызова epoll необходимо установить в систему дополнительный пакет lua-event:
# apt-get install lua-event
И включить его использование, добавив в файл конфигурации /etc/prosody/prosody.cfg.lua дополнительную опцию:
use_libevent = true
После изменения файла конфигурации осталось перезапустить Prosody, чтобы он начал использовать системный вызов epoll:
# systemctl restart prosody.service
После перезапуска в журнале /var/log/prosody/prosody.log должны появиться сообщения следующего вида, свидетельствующие об использовании системного вызова epoll:
Mar 31 00:20:02 startup info    Prosody is using the libevent epoll backend for connection handling

Настройка SSL

В целях отладки можно воспользоваться самозаверенным сертификатом, который можно сгенерировать при помощи одной команды:
# cd /etc/prosody/certs/
# openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout stupin.su.key -out stupin.su.crt
# chown root:prosody stupin.su.key stupin.su.crt
# chmod u=rw,g=r,o= stupin.su.key stupin.su.crt
Однако, для постоянного использования лучше взять сертификат, заверенный удостоверяющим центром, которому доверяют разработчики большинства операционных систем и популярных программ. Я воспользуюсь сертификатами, полученными в удостоверяющем центре Let's Encrypt. Описание процедуры получения сертификатов можно найти в моей статье Получение SSL-сертификата от удостоверяющего центра Let's Encrypt в Debian Stretch.

Скопируем сертификат и приватный ключ в каталог Prosody и выставим соответствующие права доступа:
cat /etc/letsencrypt/live/stupin.su/privkey.pem > /etc/prosody/certs/stupin.su.key
cat /etc/letsencrypt/live/stupin.su/fullchain.pem > /etc/prosody/certs/stupin.su.crt
chown root:prosody /etc/prosody/certs/stupin.su.key /etc/prosody/certs/stupin.su.crt
chmod u=rw,g=r,o= /etc/prosody/certs/stupin.su.key /etc/prosody/certs/stupin.su.crt

Изменим файл /etc/prosody/conf.d/stupin.su.cfg.lua следующим образом:
VirtualHost "stupin.su"
    ssl = {
           certificate = "/etc/prosody/certs/stupin.su.crt";
           key = "/etc/prosody/certs/stupin.su.key";
    }
Осталось перезапустить сервер Prosody, чтобы новые настройки вступили в силу:
# systemctl restart prosody.service
Чтобы при продлении сертификата он вместе с приватным ключом автоматически копировался в каталог /etc/prosody/certs/, я настрою скрипт, вызываемый в процессе продления сертификата. Пропишем в файл конфигурации /etc/letsencrypt/cli.ini следующее содержимое (если файла ещё нет, то его нужно создать):
renew-hook /etc/letsencrypt/renewhook.sh
Теперь создадим сам скрипт /etc/letsencrypt/renewhook.sh со следующим содержимым:
#!/bin/sh

set -e

for domain in $RENEWED_DOMAINS; do
    case $domain in
        stupin.su)
            chmod o= `readlink -f "$RENEWED_LINEAGE/privkey.pem"`

            daemon_cert_root=/etc/prosody/certs
            umask 077
            cat "$RENEWED_LINEAGE/fullchain.pem" > "$daemon_cert_root/$domain.crt"
            cat "$RENEWED_LINEAGE/privkey.pem" > "$daemon_cert_root/$domain.key"
            chown root:prosody "$daemon_cert_root/$domain.crt" \
                               "$daemon_cert_root/$domain.key"
            chmod u=rw,g=r,o= "$daemon_cert_root/$domain.crt" \
                              "$daemon_cert_root/$domain.key"
            systemctl restart prosody.service > /dev/null
            ;;
    esac
done
И выставим права доступа к нему:
# chown root:root /etc/letsencrypt/renewhook.sh
# chmod u=rwx,go= /etc/letsencrypt/renewhook.sh

Усиление безопасности

Чтобы не позволять подключаться к серверу клиентам, которые не поддерживают шифрование, в файл /etc/prosody/prosody.cfg.lua можно прописать такую опцию:
c2s_require_encryption = true
Чтобы требовать у серверов предоставить сертификат, подписанный удостоверяющим центром, входящим в список доверенных, можно прописать в файл /etc/prosody/prosody.cfg.lua такую опцию:
s2s_secure_auth = true
Если вы включили эту опцию, то можете задать список доменов, у сертификатов которых подпись проверяться не будет. Для этого в файл /etc/prosody/prosody.cfg.lua надо добавить опцию со списком доменов:
s2s_insecure_domains = { "gmail.com" }
Если же опция s2c_secure_auth не была включена, то можно задать список доменов, у которых всё же будет проверяться наличие подписи от одного из удостоверяющих центров, входящих в список доверенных. Для этого в файл /etc/prosody/prosody.cfg.lua нужно добавить опцию со списком таких доменов:
s2s_secure_domains = { "jabber.org" }
В статье Advanced SSL/TLS configuration есть ряд рекомендаций по усилению безопасности. В частности, можно запретить использование небезопасных протоколов SSLv2 и SSLv3, небезопасной функции сжатия трафика в TLSv1. Для этого в секции ssl файла /etc/prosody/conf.avail/stupin.su.cfg.lua добавим такие опции:
options = { "no_sslv3", "no_sslv2", "no_ticket", "no_compression", "cipher_server_preference", "single_dh_use", "single_ecdh_use" };
ciphers = "HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL";
Также не рекомендуется использовать RC4: IETF решил окончательно избавиться от RC4. Для его отключения можно добавить в настройку ciphers соответствующую строку "!RC4". В Prosody начиная с версии 0.9.2 RC4 отключен по умолчанию. Однако если вы решили поменять опции по умолчанию, то нужно прописать эту опцию явно.

По умолчанию в Prosody используются наиболее оптимальные настройки SSL. Страница с рекомендациями специально окрашена в цвета, неприятные для чтения, чтобы отпугнуть людей, бездумно копирующих рекомендации из первого попавшегося источника. Бездумное копирование настроек во многих случаях может привести к снижению безопасности системы, т.к. статьи в интернете быстро устаревают и рекомендованные в статье настройки могут оказаться менее безопасными, чем настройки по умолчанию.
Рекомендуется также настроить опцию протокола TLS под названием Perfect forward secrecy, которая позволяет защитить сеансовые ключи даже в том случае, если скомпрометированы долговременные ключи. Однако для Forward Secrecy необходима поддержка алгоритма Диффи-Хеллмана, которая отсутствует в пакете lua-sec версий 0.4.x. К счастью, в Debian Stretch поставляется lua-sec версии 0.6, поэтому мы можем настроить эту опцию.

Ключ для протокола Диффи-Хеллмана можно сгенерировать следующим образом:
# cd /etc/prosody/certs/
# openssl dhparam -out stupin.su.dh-2048.pem 2048
# chown prosody:prosody stupin.su.dh-2048.pem
# chmod u=rw,go= stupin.su.dh-2048.pem
Осталось добавить сгенерированный долговременный ключ в секции ssl файла /etc/prosody/conf.avail/stupin.su.cfg.lua:
dhparam = "/etc/prosody/certs/stupin.su.dh-2048.pem";
После всех изменений нужно перезапустить сервер Prosody, чтобы новые настройки вступили в силу:
# systemctl restart prosody.service
Проверить степень безопасности Jabber-сервера можно на сайте check.messaging.one.

Аутентификация через Dovecot SASL

Для Prosody имеется неофициальный модуль, позволяющий использовать для аутентификации пользователей сервис SASL от Dovecot. Использование SASL от Dovecot позволит использовать одни и те же учётные записи с одними и теми же паролями как для доступа к почтовому ящику, так и для доступа к серверу Jabber. Если Dovecot был настроен так, что использует информацию в базе данных PostfixAdmin, то PostfixAdmin можно использовать для создания, удаления и изменения одновременно как почтовых ящиков, так и одноимённых учётных записей Jabber. Пример такой настройки Dovecot можно посмотреть в моей статье: Установка и настройка Dovecot.

Скачать исходные тексты модуля вручную можно следующим образом:
  • Переходим на сайт http://hg.prosody.im/
  • Находим репозиторий prosody-modules, переходим в него.
  • Переходим в меню files.
  • Находим в списке каталог mod_auth_dovecot, переходим в него.
  • Жмём по ссылке gz и скачиваем по ней файл.
Чтобы воспользоваться неофициальным модулем, сначала мы соберём пакет для Debian. Для скачивания исходных текстов модуля и сборки deb-пакета я подготовил скрипт:
#!/bin/sh

wget --quiet --content-disposition http://hg.prosody.im/prosody-modules/archive/tip.tar.gz/mod_auth_dovecot/

COMMIT=`ls Community-Modules-*.tar.gz | head -n1 | sed 's/Community-Modules-\(.*\).tar.gz/\1/'`
tar xzf Community-Modules-$COMMIT.tar.gz
rm Community-Modules-$COMMIT.tar.gz

PACKAGE=prosody-mod-auth-dovecot
DATETIME=`date "+%Y%m%d%H%M%S"`
VERSION="0.$DATETIME"
DIR=${PACKAGE}_${VERSION}

mkdir -p $DIR/DEBIAN

# Формируем описание пакета
cat - > $DIR/DEBIAN/control <<END
Package: $PACKAGE
Version: $VERSION
Architecture: all
Maintainer: Vladimir Stupin <vladimir@stupin.su>
Description: Prosody module mod_auth_dovecot
 Prosody module mod_auth_dovecot
 Homepage: http://prosody.im
END

chmod 755 $DIR/DEBIAN/control

# Формируем содержимое пакета
mkdir -p $DIR/usr/lib/prosody/modules/
cp -R Community-Modules-$COMMIT/mod_auth_dovecot/auth_dovecot/* $DIR/usr/lib/prosody/modules/
rm -R Community-Modules-$COMMIT

FILE=${PACKAGE}_${VERSION}_all.deb

# Формируем пакет
dpkg-deb -b $DIR $FILE >/dev/null 2>&1
rm -R $DIR
Назначим этому файлу права для выполнения:
$ chmod +x make_prosody_mod_auth_dovecot.sh
И запустим его:
$ ./make_prosody_mod_auth_dovecot.sh
Собранный пакет с именем типа prosody-mod-auth-dovecot_0.57d4680b86a4.deb устанавливаем командой:
# dpkg -i prosody-mod-auth-dovecot*.deb
Осталось настроить Dovecot так, чтобы он создавал при запуске Unix-сокет, доступный для Prosody, а Prosody настроить так, чтобы этот модуль был включен и обращался к Unix-сокету Dovecot.

Открываем файл /etc/dovecot/conf.d/10-master.conf на редактирование, находим секцию service auth и вписываем в неё:
unix_listener prosody-auth-client {
    mode = 0660
    user = prosody
    group = prosody
}
Перезапустим Dovecot, чтобы он узнал новые настройки и создал нужный нам Unix-сокет:
# systemctl restart dovecot.service
Открываем файл /etc/prosody/prosody.cfg.lua на редактирование, находим секцию modules_enabled и вписываем модуль:
"auth_dovecot";
Находим ниже строку authentication и вместо неё вписываем следующие три строки:
authentication = "dovecot"
dovecot_auth_socket = "/var/run/dovecot/prosody-auth-client"
auth_append_host = true
Перезапустим Prosody, чтобы он начал использовать новый модуль:
# systemctl restart prosody.service
Можно проверять работу учётной записи.

Настройка конференций

Чтобы пользователи могли создавать конференции, нужно добавить в файл с настройками домена /etc/prosody/conf.avail/stupin.su.cfg.lua одну строчку:
Component "conference.stupin.su" "muc"
Чтобы комнаты на сервере могли создавать только администраторы, можно включить в файле /etc/prosody/conf.avail/stupin.su.cfg.lua у модуля конференций дополнительную опцию restrict_room_creation:
Component "conference.stupin.su" "muc"
    restrict_room_creation = true
Чтобы комнаты могли создавать только пользователи, имеющие учётную запись на сервере, можно присвоить этой опции значение "local":
Component "conference.stupin.su" "muc"
    restrict_room_creation = "local"
Перезапустим сервер Prosody, чтобы новые настройки вступили в силу:
# systemctl restart prosody.service
Теперь нужно прописать в DNS, какой сервер будет обслуживать домен конференций conference.stupin.su. Делается это точно так же, как и для основного домена. Единственное отличие заключается в том, что в этом случае нужна только запись о серверном порте:
_xmpp-server._tcp.conference IN SRV 0 5 5269 jabber.stupin.su.
Чтобы проверить работу конференций, воспользуемся Pidgin. Подключимся к нашему серверу под нашей учётной записью и в меню "Собеседники" выберем пункт "Добавить чат...":

В открывшемся диалоговом окне "Добавить чат" в поле "Имя комнаты" введём имя, которое будет стоять слева от символа собачки. В поле "Сервер" введём имя домена для конференций - conference.stupin.su. Если вы хотите, чтобы в конференцию не смогли заходить люди, не знающие пароля от комнаты, то его можно задать в поле "Пароль":

Окно конференции выглядит следующим образом:

В конференциях можно использовать команды. Список команд можно узнать, введя команду "/help".

Ниже изображена справка, выведенная командой "/help":

Чтобы уточнить информацию о какой-то конкретной команде, можно ввести команду "/help" и указать ей в качестве аргумента имя команды, о которой мы хотим получить справку. Например, наведём справки о команде "/topic", для чего введём команду "/help topic".

Ниже можно увидеть справку по команде "/topic":

Команда "/topic" позволяет менять тему комнаты. Попробуем её поменять командой "/topic Моя комната":

Аналогичным образом наведём справки по команде "/invite" и пригласим в комнату нового посетителя.:

Посетитель получит приглашение в комнату в виде диалогового окна:

Если приглашённый пользователь принимает приглашение, то он появится в списке посетителей слева:

Итак, конференции работают. С помощью команд можно сделать комнату постоянной, чтобы она не пропадала, если пользователь, создавший её, вдруг отключится. Посетители могут аналогичным образом зарезервировать своё имя, чтобы никто его не занял в их отсутствие. Можно менять тему комнаты, приглашать новых посетителей, давать им дополнительные права.

SOCKS5-прокси

При передаче файлов от одного собеседника к другому, они устанавливают друг с другом прямое соединение. Один из них в этом случае будет выступать в роли сервера, ожидая подключения со стороны другого. Однако, может так случиться, что оба собеседника не имеют "белых" IP-адресов в интернете, а оба находятся за NAT-серверами. В таком случае ни один из собеседников не сможет выступать в роли сервера, но они оба могут выступать только в роли клиента и могут установить куда-то подключение.

Чтобы выйти из затруднительной ситуации, можно прибегнуть к помощи третьей стороны, которая обладает "белым" IP-адресом и будет ожидать подключения от обоих собеседников, транслируя их пакеты друг к другу. В роли такой третьей стороны может выступать прокси-сервер, работающий по протоколу SOCKS5. Более того - таким прокси-сервером может быть сам Jabber-сервер Prosody, если на нём включить соответствующий модуль - proxy65. Займёмся его настройкой. Для этого откроем файл конфигурации нашего домена /etc/prosody/conf.avail/stupin.su.cfg.lua и добавим в него следующие строчки:
Component "proxy.stupin.su" "proxy65"
    proxy65_acl = { "stupin.su" }
Первая строчка задаёт доменное имя SOCKS5-прокси, вторая строчка ограничивает Jabber-идентификаторы пользователей, которым разрешено пользоваться этим прокси. В данном случае прокси находится по адресу proxy.stupin.su и будет обслуживать только пользователей, Jabber-идентификатор которых находится в домене stupin.su. Перезапустим Jabber-сервер Prosody, чтобы его настройки вступили в силу:
# systemctl restart prosody.service

Настройка BOSH

Протокол BOSH позволяет клиентам подключаться к Jabber-серверу по протоколу HTTP или HTTPS. BOSH расшифровывается как Bidirectional-streams Over Synchronous HTTP - двунаправленные потоки данных через синхронный HTTP. Этот протокол обычно применяется либо веб-приложениями, выполняющими функции Jabber-клиента, либо обычными приложениями, если системный администратор запретил подключения к клиентскому порту Jabber-сервера. Для настройки BOSH в Prosody воспользуемся соответствующей статьёй: Setting up a BOSH server. В отличие от рассмотренных выше модулей, этот модуль можно включить только глобально. Для этого откроем файл /etc/prosody/prosody.cfg.lua и раскомментируем в нём строчку с модулем bosh:
modules_enabled = {
        ...
        -- HTTP modules
                "bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
                --"http_files"; -- Serve static files from a directory over HTTP
        ...
};
Если Jabber-клиент написан на JavaScript и выполняется в браузере, то могут возникнуть проблемы. Обычно такие скрипты не имеют прав подключаться к адресам, отличным от адреса веб-сайта, на котором они находятся. Поэтому, если для подключения к Jabber-серверу используется веб-приложение, работающее на другом адресе (на другом порту или домене), то такое приложение не будет работать. Чтобы выйти из этой затруднительной ситуации, мы воспользуемся возможностью проксирования запросов, имеющейся в веб-сервере nginx. Веб-сервер будет принимать запросы по тому же адресу, где работает веб-приложение, и будет проксировать их на встроенный в Prosody веб-сервер.

Модуль bosh использует функции модуля http - встроенного веб-сервера Prosody. Для того, чтобы BOSH был доступен только со стороны nginx, работающего на одном компьютере с Prosody, добавим в файл /etc/prosody/prosody.cfg следующие строчки с настройками модуля http:
http_ports = { 5280 }
http_interfaces = { "127.0.0.1" }
https_ports = { }
https_interfaces = { }
Эти строчки предписывают встроенному веб-серверу ожидать подключений по протоколу HTTP на TCP-порту 5280, на IP-адресе 127.0.0.1. Протокол HTTPS отключается. Чтобы BOSH не требовал подключения от клиентов по протоколу HTTPS, нужно добавить в файл /etc/prosody/prosody.cfg.lua следующую опцию:
consider_bosh_secure = true
Если вы настроили nginx на другом компьютере, то нужно не только поправить опцию http_interfaces, чтобы она прослушивала внешний IP-адрес, но и добавить следующую опцию trusted_proxies, в настройках которой указать IP-адреса, с которых будут приходить запросы, проксируемые nginx. Эта опция нужна, чтобы Prosody доверял информации из заголовков HTTP-запросов, которые сообщают реальный IP-адрес клиента. Например, если nginx будет обращаться к серверу Prosody с IP-адреса 80.80.80.80, то в файл /etc/prosody/prosody.cfg.lua нужно будет добавить такую опцию:
trusted_proxies = { "80.80.80.80" }
Перезапустим сервер Prosody, чтобы его новые настройки вступили в силу:
# systemctl restart prosody.service
Теперь приступим к настройке nginx. Пропишем в конфигурацию nginx в соответствующие секции серверов настройки для проксирования запросов к BOSH:
location /bosh {
  proxy_pass  http://localhost:5280/http-bind;
  proxy_set_header Host $host;
  proxy_buffering off;
  tcp_nodelay on;
}
И перезагрузим конфигурацию:
# systemctl reload nginx.service
Осталось сообщить Jabber-клиентам, где искать BOSH. Для этого воспользуемся документом XEP-0156: Discovering Alternative XMPP Connection Methods и пропишем в DNS запись, через которую клиенты смогут найти ссылку к BOSH:
_xmppconnect      IN TXT "_xmpp-client-xbosh=https://stupin.su/bosh"
Если в Jabber-клиенте Pidgin открыть настройки учётной записи:

И прописать в поле "BOSH-адрес" ссылку, по которой доступен BOSH на Jabber-сервере:

То Jabber-клиент переключится на использование только протокола BOSH. В журналах веб-сервера можно будет увидеть записи, подобные указанным ниже:
127.0.0.1 - - [11/Aug/2017:23:18:51 +0500] "POST /bosh HTTP/1.1" 200 148 "-" "Pidgin 2.12.0 (libpurple 2.12.0)"
127.0.0.1 - - [11/Aug/2017:23:18:51 +0500] "POST /bosh HTTP/1.1" 200 238 "-" "Pidgin 2.12.0 (libpurple 2.12.0)"
127.0.0.1 - - [11/Aug/2017:23:19:49 +0500] "POST /bosh HTTP/1.1" 200 148 "-" "Pidgin 2.12.0 (libpurple 2.12.0)"
127.0.0.1 - - [11/Aug/2017:23:19:49 +0500] "POST /bosh HTTP/1.1" 200 238 "-" "Pidgin 2.12.0 (libpurple 2.12.0)"
127.0.0.1 - - [11/Aug/2017:23:20:47 +0500] "POST /bosh HTTP/1.1" 200 148 "-" "Pidgin 2.12.0 (libpurple 2.12.0)"
127.0.0.1 - - [11/Aug/2017:23:20:47 +0500] "POST /bosh HTTP/1.1" 200 238 "-" "Pidgin 2.12.0 (libpurple 2.12.0)"

Настройка pubsub

PubSub - это сервис публикации-подписки (Publish-Subscribe). Позволяет организовывать узлы распространения информации, подобные RSS-лентам. На каждом узле могут добавляться или удаляться элементы информации. Клиенты Jabber могут подписываться на обновления и при добавлении, удалении или изменении элементов информации на узле, получат уведомление.

Поддерживается эта функция далеко не всеми клиентами Jabber, да и польза от неё довольно сомнительна. Но лучше изучить её, чтобы при возникновении какой-то специфичной задачи, можно было вспомнить об этой функции и воспользоваться ей, если она похожа на решение этой задачи.

Откроем файл конфигурации нашего домена /etc/prosody/conf.avail/stupin.su.cfg.lua и добавим в него следующие строчки:
Component "pubsub.stupin.su" "pubsub"
    admins = { }
    default_admin_affilation = "owner"
    autocreate_on_subscribe = false
    autocreate_on_publish = false
    pubsub_max_items = 256
Назначение настроек в примере следующее:
  • admins - задаёт дополнительных администраторов, помимо администраторов сервера, которые получат возможность управлять сервисом pubsub,
  • default_admin_affilation - связи между администраторами и администрируемыми узлами (список возможных значений найти не удалось),
  • autocreate_on_subscribe - указывает, нужно ли создавать новый узел, если узла нет, но на него кто-то хочет подписаться,
  • autocreate_on_publish - указывает, нужно ли создавать новый узел, если узла нет, но кто-то пытается опубликовать на нём новый элемент,
  • pubsub_max_items - ограничивает максимальное количество элементов на узле.
Перезапустим Jabber-сервер Prosody, чтобы его настройки вступили в силу:
# systemctl restart prosody.service

Настройка vjud

vjud - виртуальный каталог пользователей Jabber (Virtual Jabber User Directory), позволяет искать информацию о пользователях через каталог.

Для настройки этой функции понадобится установить в систему дополнительный пакет - prosody-modules, т.к. нужный нам модуль находится в этом пакете. Установим:
# apt-get install prosody-modules
Теперь настроим компонент vjud, для чего откроем файл конфигурации нашего домена /etc/prosody/conf.avail/stupin.su.cfg.lua и добавим в него следующие строчки:
Component "vjud.stupin.su" "vjud"
    vjud_mode = "opt-in"
Строчка vjud_mode указывает на то, что публиковаться в каталоге будет информация только тех пользователей, которые дали на это согласие.

Перезапустим Jabber-сервер Prosody, чтобы его настройки вступили в силу:
# systemctl restart prosody.service