Возникла задача обновить сервера LinOTP, которые используются в качестве сервиса 2х факторной авторизации. В текущей конфигурации сервиса, в качестве бекенда используется pgpool+postgresql, и новой реализации решил отказаться от этого, так как жизнь с pgpool накладывает дополнительный overhead.

linotp-centos8-schemes.png (Схема нового кластера)

Установка PostgreSQL

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

[All Servers]# yum module enable -y postgresql:13
[All Servers]# yum install postgresql -y postgresql-server

Проинициализируем базу данных:

[All Servers]# /usr/bin/postgresql-setup --initdb
 * Initializing database in '/var/lib/pgsql/data'
 * Initialized, logs are in /var/lib/pgsql/initdb_postgresql.log

Запускаем базу, и ставим сервис в автозагрузку:

[All Servers]# systemctl enable --now postgresql

На данном этапе нам нужно отрыть доступ между двумя инстансами бд, для реализации задачи по копированию базы на вторичный инстанс. Итак, для начала отредактируем файл конфигурации - postgresql.conf:

[All Servers]# vi /var/lib/pgsql/data/postgresql.conf
--
# Адрес, с которого слушает сервис
listen_addresses = 'localhost,<NODE IP>'    

# Порт, на котором слушает сервис
port = 5432

Теперь создаем базу данных и пользователя с правами на эту базу:

[All Servers]# sudo -u postgres psql
# Задаем механизм шифрования паролей
postgres=# set password_encryption = 'scram-sha-256';

# Создаем базу и пользователя
postgres=# create database linotp_db;
postgres=# create user linotp_user with encrypted password 'pass';

# Даем права пользователю на базу
postgres=# grant all privileges on database linotp_db to linotp_user;

Разрешим доступ к базе, для наших серверов.

[All Servers]# vi /var/lib/pgsql/data/pg_hba.conf
---
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    linotp_db       linotp_user     127.0.0.1/32            scram-sha-256
host    linotp_db       linotp_user     192.168.110.11/32        scram-sha-256
host    linotp_db       linotp_user     192.168.110.12/32        scram-sha-256

Перезапускаем сервис с базой:

[All Servers]# systemctl restart postgresql

На фаерволе не забываем открыть порт:

[All Servers]# firewall-cmd --add-port=5432/tcp --permanent
[All Servers]# firewall-cmd --reload

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

[All Servers]# psql -h 192.168.110.12 -U linotp_user linotp_db -p 5432

Как я ранее говорил, в этой конфигурации я решил отказаться от использования pgpool-кластера, и использовать базовые инструменты по бекапированию/восстановлению базы. Хоть и получается костыль, но рациональней так =) Напишем скриптец, который будет снимать бекап с основного сервера и восстанавливать его в реплику.

#!/bin/bash

DB_SRV="<IP-АДРЕС ГЛАВНОГО СЕРВЕРА>"
DB_PORT="5432"
DB_USER="linotp_user"
DB_NAME="linotp_db"

BACKUP_PATH="/var/lib/pgsql/backups"
BACKUP_NAME="$(date +%d%m%Y%H).dump.tar"


function restoreDump() {
        echo "---"
        echo "Restore fresh database dump..."

        psql -h 127.0.0.1 -p $DB_PORT -U $DB_USER -d $DB_NAME -f "$BACKUP_PATH/$BACKUP_NAME"
}


function backupDatabase() {
        echo "---"
        echo "Create new backup..."

        if [[ ! -f "$BACKUP_PATH/$BACKUP_NAME" ]]; then
                pg_dump -U $DB_USER -h $DB_SRV -p $DB_PORT $DB_NAME > "$BACKUP_PATH/$BACKUP_NAME" && echo "Backup was been created!"

                restoreDump
        else
                echo "Backup is already up to date!"
        fi
}


nc -z -w5 $DB_SRV $DB_PORT < /dev/null

if [ $? = 0 ]; then
        backupDatabase
else
        echo "Problem connect to $DB_SRV port $DB_PORT"
fi

В начале скрипта перечисляется блок с переменными, в которых обозначаем данные подключения к базе. Следом идет две функции restoreDump() и backupDatabase(), как понятно из названия одна восстанавливает бекап, другая снимает бекап с мастер сервера. И далее описывает entry-часть скрипта, мы проверяем доступность основного сервера, если он доступен запускаем функцию по бекапу базы. Если недоступен, выходим из сценария.

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

[postgres@lotp02 ~]$ vim .pgpass
---
127.0.0.1:5432:linotp_db:linotp_user:<PASSWORD>
192.168.110.11:5432:linotp_db:linotp_user:<PASSWORD>

Даем права на чтение/запись только владельцу:

[postgres@lotp02 ~]$ chmod 600 .pgpass

Ну и теперь можем запустить скриптец, для проверки:

[postgres@lotp02 ~]$ bash scripts/backup_db.sh
Connection to lotp01.office.local (192.168.110.11) 5432 port [tcp/postgres] succeeded!
---
Create new backup...
Backup is already up to date!

Отлично, теперь для выполнения сценария не нужно будет указывать пароль для пользователя. Теперь остается только добавить скрипт на исполнение в cron, в файл /etc/crontab добавляем строчку:

0 */5 * * * postgres bash -c '/var/lib/pgsql/scripts/backup_db.sh'

(Каждые 5 часов будет запускаться скриптец)

Установка LinOTP сервиса

На данный момент в rpm-репозитории LinOTP есть пакеты поддерживаемые только на CentOS7. Мы же используем 8 версию центоси, и будет собирать сервис из пакетов. (Установка выполняется на всех серверах)

В первую очередь установим все записимости, сервис написан на второй версии python. Ставим необходимые для сборки пакеты.

[All Servers]# yum install epel-release
[All Servers]# yum install -y python2 python2-virtualenv python2-wheel git openssl-devel swig openldap-devel libsodium gcc

Создаем новое виртуальное окружение, куда в дальшейшем поставим зависимости.:

[All Servers]# cd /srv/
[All Servers - srv]# virtualenv-2 linotp

Переключаемся в окружение, и ставим пакеты.:

[All Servers - srv]# source /srv/linotp/bin/activate
(linotp) [All Servers - srv]# pip install --upgrade pip pip-tools
(linotp) [All Servers - srv]# pip install pillow pillow-pil m2crypto psycopg2-binary

Теперь клонируем репозиторий LinOTP.

[All Servers - srv]# cd /srv/linotp
(linotp) [All Servers - linotp]# git clone https://github.com/LinOTP/LinOTP

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

(linotp) [All Servers -  linotp]# cd LinOTP/
(linotp) [All Servers -  LinOTP]# git tag | grep release/2 | sort --version-sort
---
release/2.12.2
release/2.12.3
release/2.12.4
release/2.12.5
release/2.12.6

Из вывода видно, что последний релиз под номером release/2.12.6 . Переключаемся на него:

(linotp) [All Servers -  LinOTP]# git checkout release/2.12.6

Можно приступить к сборке. Собирать будем два пакета - LinOTP, LinOTPAdmiCLI. Что бы собрать пакет совместимый с python2, сборку реализуем при помощи pip.

Собираем основную прилу, для этого переходим в исходники - linotpd (/srv/linotp/LinOTP/linotpd/src). И собираем wheel-пакет:

(linotp) [All Servers - LinOTP]# cd linotpd/src/
(linotp) [All Servers - src]# python2 setup.py bdist_wheel

Полученный пакет устанавливаем через pip:

(linotp) [All Servers - src]# pip install dist/LinOTP-2.12.6-py2-none-any.whl

Если на этом этапе никаких ошибок не было поймано, собираем пакет c admin cli. Переходим в исходники - /srv/linotp/LinOTP/adminclient/LinOTPAdminClientCLI/src, и аналогично собираем wheel-пакет:

(linotp) [All Servers - src]# cd ../../adminclient/LinOTPAdminClientCLI/src/
(linotp) [All Servers - src]# python2 setup.py bdist_wheel

И теперь через pip устанавливаем пакет:

(linotp) [All Servers - src]# pip install dist/LinOTPAdminClientCLI-2.12.6-py2-none-any.whl

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

(linotp) [All Servers - src]# useradd -d /srv/linotp -U -r -s /sbin/nologin linotp

Создаем каталог под конфигурационные файлы сервиса:

(linotp) [All Servers - src]# mkdir -pv /etc/linotp2/data
(linotp) [All Servers - src]# mkdir -pv /var/log/linotp

Копируем семпл файла конфигурации в каталог /etc:

(linotp) [All Servers - src]# cp /srv/linotp/etc/linotp2/linotp.ini.example /etc/linotp2/linotp.ini

Генерим файл-секрет, которым будем шифровать токены в базе данных:

(linotp) [All Servers - src]# linotp-create-enckey -f /etc/linotp2/linotp.ini

Созданный файл (/etc/linotp2/encKey), после установки сервиса, нужно разместить на втором сервере. Иначе вторая нода не сможет прочитать токены, и нечего не будет работать.

Ну и меняем овнера на созданных каталогах:

(linotp) [All Servers - src]# chown -R linotp:linotp /etc/linotp2/
(linotp) [All Servers - src]# chown -R linotp:linotp /var/log/linotp/
(linotp) [All Servers - src]# chown -R linotp /srv/linotp/

Настройка LinOTP Сервиса

Для подключения к базе Postgresql отредактируем конфиг, пропишем настройки подключения к базе:

[root@lotp01 bin]# vim /etc/linotp2/linotp.ini
---
sqlalchemy.url = postgresql+psycopg2://linotp_user:<PASS>@127.0.0.1:5432/linotp_db

Создаем схемы в базе данных:

(linotp) [root@lotp01 LinOTP]# su linotp -s /bin/bash -c 'paster setup-app /etc/linotp2/linotp.ini'
Running setup_app() from linotp.websetup

Настройка Apache2

В качестве вебсервера будем использовать apache, для начала установим его и дополнительные модули (Логичным образом, установка выполняется на всех нодах):

[All Servers]# yum install -y httpd httpd-devel mod_ssl

Для взаимодействия апача с нашим приложением, нужно установить wsgi. Переключаемся в окружение и ставим через pip:

(linotp) [All Servers]# pip install mod_wsgi

Установленный модуль линкуем в каталог с модулями апача:

(linotp) [All Servers]# ln -s /srv/linotp/lib/python2.7/site-packages/mod_wsgi/server/mod_wsgi-py27.so /etc/httpd/modules/

Теперь нужно заставить апач, подгружать этот модуль. Создамим отдельный конфигурационный файл:

(linotp) [[All Servers]# echo "LoadModule wsgi_module modules/mod_wsgi-py27.so" >> /etc/httpd/conf.modules.d/99-wsgi.conf

Для загрузки нашего приложения апач будет использовать python-скрипт. Этот скрипт уже есть в семплах, поэтому просто копируем его:

[All Servers]# cp /srv/linotp/etc/linotp2/linotpapp.wsgi /etc/linotp2/
[All Servers]# chown -R linotp:apache /etc/linotp2/linotpapp.wsgi

Ну и копируем конфиг для самого апача:

[All Servers]# cp /srv/linotp/LinOTP/linotpd/src/config/apache2.4-example/linotp2.conf /etc/httpd/conf.d/

Для корректной работы веб-сервера, нужно внести некоторые изменения в скопированный конфиг.

[All Servers]# vi /etc/httpd/conf.d/linotp2.conf
---
## Закомментируем или удаляем инклуд
#Include /etc/linotp2/apache-servername.conf

## Прописываем хостнеймы, запросы на которые будет обрабатывать этот вебсервер
ServerName lotp.office.local
ServerName lotp01.office.local

## Указываем путь к установочному каталогу
WSGIDaemonProcess linotp processes=1 threads=15 display-name=%{GROUP} user=linotp python-home=/srv/linotp

## Меняем пути до логов:
ErrorLog /var/log/httpd/error.log
CustomLog /var/log/httpd/access.log LinOTP2

## Меняем пути к сертификатам:
SSLCertificateFile    /etc/linotp2/linotpserver.pem
SSLCertificateKeyFile /etc/linotp2/linotpserver.key

Для доступа к админку сервиса используется basic auth, в семпле конфига он уже настроен. Нам остается только забиндить пароль для пользователя admin:

[All Servers]# htdigest -c /etc/linotp2/admins 'LinOTP2 admin area' admin
[All Servers]# chown -R linotp:apache /etc/linotp2/admins

Далее просто указываем пароль для пользователя.

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

[All Servers]# openssl req -subj '/CN=192.168.110.11' -new -newkey rsa:2048 -sha256 -days 3650 -nodes -x509 -keyout /etc/linotp2/linotpserver.key -out /etc/linotp2/linotpserver.pem

Что бы у апача был доступ на чтение linotpapp.wsgi, добавил пользователя - apache в группу - linotp:

(linotp) [All Servers]# usermod -aG linotp apache

И сново рекурсивно сделал владельцем пользователя - linotp в каталоге - /srv/linotp/, так как модуль wsgi был установлен из под рута.:

(linotp) [All Servers]# chown -R linotp /srv/linotp/

Запускаем сервис:

[All Servers]# systemctl enable --now httpd

Ну и не забываем открыть порт на фаерволе:

[All Servers]# firewall-cmd --add-port=443/tcp --permanent
[All Servers]# firewall-cmd --reload

Настройка Radius

Для работы радиус сервера, нужно установить пакеты самого радиус сервера и его перловые дополнения.

[All Servers]# yum install freeradius freeradius-perl perl-Config-IniFiles perl-Try-Tiny perl-LWP-Protocol-https

Добавляем клиента, который будет кидать запросы с авторизацией пользователей через радиус. В моем случаи клиентом будет выступать Cisco Identity Services:

[All Servers]# mv /etc/raddb/clients.conf /etc/raddb/clients.conf.df
[All Servers]# vi /etc/raddb/clients.conf
---
client ise0 {
        ipaddr  = 192.168.110.20 # Active ISE node
        netmask = 32
        secret  = 'PASSWORD' #shared secret
}

Отредактируем файл - /etc/raddb/users, в котором описывается конфигурация как авторизовать и аутентифицироваться каждый запрос пользователя. В моем случаи этот радиус сервер, будет использоваться только для LinOTP. Поэтому прописываем:

[All Servers]$ mv /etc/raddb/users /etc/raddb/users.df
[All Servers]# echo "DEFAULT Auth-Type := perl" >> /etc/raddb/users

Создаем конфиг с описанием нашего модуля.:

[All Servers]# mv /etc/raddb/mods-available/perl /etc/raddb/mods-available/perl.default
[All Servers]# vi /etc/raddb/mods-available/perl
---
perl {
        filename = /usr/lib/linotp/radius_linotp.pm
}

Далее идем поссылке, и качаем сам модуль тут. Скачанный модуль закидываем в каталог - /usr/lib/linotp/.

[All Servers]# ls -lah /usr/lib/linotp/radius_linotp.pm
-rw-r-----. 1 root root 12K Mar 27 15:29 /usr/lib/linotp/radius_linotp.pm

Также мы должны дать права на чтение файла для пользователя - radiusd.:

[All Servers]# chown -R radiusd /usr/lib/linotp/
[All Servers]# chmod 750 /usr/lib/linotp/radius_linotp.pm

Для работы этого модуля, нужно создать файл конфигурации realm.

[All Servers]# vi /etc/linotp2/rlm_perl.ini
---
[Default]
#IP of the linotp server
URL=https://lotp.office.local/validate/simplecheck

#optional: limits search for user to this realm
REALM=MyOffice

#optional: only use this UserIdResolver
#RESCONF=flat_file

#optional: comment out if everything seems to work fine
Debug=False

#optional: use this, if you have selfsigned certificates, otherwise comment out
SSL_CHECK=False

В этой конфиге указываем свои значения в переменных. Url-адрес linotp сервиса и опциональные директивы.

Ну и создаем виртуальный хост радиус-сервера:

[All Servers]# vi /etc/raddb/sites-available/linotp
--
server linotp {
        listen {
                ipaddr = *
                port = 0
                type = auth
        }

        authorize {
                preprocess
                IPASS
                suffix
                ntdomain
                files
                expiration
                logintime
                pap
                update control {
                      Auth-Type := Perl
                }
        }

        authenticate {
        Auth-Type Perl {
                perl
          }
        }
}

С конфигурацией на этом все, остается только включить конфиги создав симлинки, и запустить сервис.

Удаляю дефолтные файлы:

[All Servers]# rm -rf /etc/raddb/sites-enabled/{default,inner-tunnel}
[All Servers]# rm -rf /etc/raddb/mods-enabled/eap

И линкую новую конфигурацию:

[All Servers]# cd /etc/raddb/sites-enabled/
[All Servers - sites-enabled]# ln -s ../sites-available/linotp /etc/raddb/sites-enabled
[All Servers - sites-enabled]# cd /etc/raddb/mods-enabled
[All Servers - mods-enabled]# ln -s ../mods-available/perl /etc/raddb/mods-enabled

Открываем порты фаерлове,

[All Servers]# firewall-cmd --add-port={1812,1645}/udp --permanent
success
[All Servers]# firewall-cmd --reload

Ну и ранним сервис:

[All Servers]# systemctl enable --now radiusd.service

Настройка keepalived

В заключении устанавливаем keepalived:

[All Servers]# yum install keepalived

Переименовываем дефолтный конфиг, и создаем свою конфигу:

[root@lotp01 ~]# mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.df
[root@lotp01 ~]# vi /etc/keepalived/keepalived.conf
---
global_defs {
    enable_script_security
}

vrrp_script postgresql_check {
    script "/usr/bin/systemctl is-active postgresql >/dev/null && echo 1 || echo 0"
    interval 5
    user postgres
}

vrrp_script radius_check {
    script "/usr/bin/systemctl is-active radiusd >/dev/null && echo 1 || echo 0"
    interval 5
    user radiusd
}


vrrp_script httpd_check {
    script "/usr/bin/systemctl is-active httpd >/dev/null && echo 1 || echo 0"
    interval 5
    user apache
}

vrrp_instance linotp-cluster {
    state MASTER
    interface ens192
    virtual_router_id 254
    priority 100
    advert_int 2
    authentication {
        auth_type PASS
        auth_pass PASSWORD
    }
    virtual_ipaddress {
        192.168.110.10
    }
    track_script {
        postgresql_check
        radius_check
        httpd_check
    }
}

Наш конфиг можно разделить на контексты,

  • global_defs - задает глобальные настройки для keepalived. В нашей конфиге мы только разрешаем испольвание скриптов.
  • vrrp_script - в этом контексте указывается параметры проверки. У меня используется 3 проверки - postgresql_check, radius_check, httpd_check с одинаковыми параметрами. Если выполнения скрипта заканчивается с exit кодом не равному - 0, то значит у нас проблемы в системе и ip-адрес будет назначен на второй ноде. Ну и далее как понятно из названия, мы указываем интервал равный пяти секунд, и пользователя из под которого будет выполняться проверка.
  • vrrp_instance - этот контекст описывает параметры, самого инстанса.
    • state - указывает на первоначальный режим работы ноды. У нас первая нода - Master, вторая в роли Backup;
    • interface - тут в значении указывается название интерфейса, на котором будет висеть кластерный ip.
    • virtual_router_id - виртуальный VRRP идентификатор, на всех участниках кластера должен быть одинаковым.
    • priority - приоритет ноды, у мастера должен быть самый наивысший приоритер
    • advert_int - здесь указывается интервал в секундах, в течении которого мастер должен сообшить о себе другим нодам кластера. Если в течении этого времени, мастером не будет отправлен запрос, в кластере будет переизбран новый мастер.
    • preempt_delay - также указывается интервал, после которого нода с самым высоким приоритетом опять назначит себе адрес.
    • authentication - в этом контексте описываются настройки авторизации.
    • virtual_ipaddress - тут описываем настройки для кластерного ip-адреса.
    • track_script - ну и здесь указываются проверки работоспособности наших сервисов.

Конфиг для второй ноды, аналогичен конфигурации на первом серваке, за исключением паре параметров. Изменения коснулись только,

  • state - тут в значении -> Backup
  • priority - здесь в значении указываем более низкий приоритет (50, например)

Перед запуском, нужно открыть порт на фаерволе.

[All Servers]# firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept'
[All Servers]# firewall-cmd --reload

Ранним сервис:

[All Servers]# systemctl enable --now keepalived

Для проверки корректности работы сервиса, может поотрубать сервисы, что-бы убедится как отработает вторая нода. Например, на первой ноде я выключаю апач и бегом иду смотреть логи.

Mar 27 17:49:04 lotp01.office.local Keepalived_vrrp[15493]: Script `httpd_check` now returning 3
Mar 27 17:49:04 lotp01.office.local Keepalived_vrrp[15493]: VRRP_Script(httpd_check) failed (exited with status 3)
Mar 27 17:49:04 lotp01.office.local Keepalived_vrrp[15493]: (linotp-cluster) Entering FAULT STATE
Mar 27 17:49:04 lotp01.office.local Keepalived_vrrp[15493]: (linotp-cluster) sent 0 priority
Mar 27 17:49:04 lotp01.office.local Keepalived_vrrp[15493]: (linotp-cluster) removing VIPs.

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

Отлично, настройка “backend-ов” реализована. Все последующие настройки уже будет выполнятся непосредственно в UI-менеджменте сервиса. Также предстоит причесать настройки, прикрутить общую авторизацию к админке и к порталу самообслуживания, настроить ssl-сертификаты. И все это будет основой для написания следующей заметки.


Полезные ссылки: