Перед нами загруженный почтовый сервер с заполненными почтовыми ящиками, большим почтовым трафиком и задача сделать с этим что-нибудь, так как письма "не ходят", а ещё Sieve еле шевелится. Предположим, что докинуть ядер/дисков не получится, а сделать что-то нужно.
Можно развернуть архивный почтовый сервер, перекинуть туда письма старше N лет, сделать скрипты перемещения на основном, но это не для нас. Мы будем делать более элегантное решение, которое позволит нам в будущем упростить расширение почтовой инфраструктуры.
Для Nginx существует модуль mail, который позволяет проксировать и шифровать почтовые подключения, но это ещё не всё. Также имеется возможность авторизовать пользователя и перенаправить соединение. Осталось только завести базу пользователей и перенаправить трафик через прокси - выглядит отлично.
Подключения клиентов imap на сервер вида example.com
чаще всего устанавливаются через считывание AutoDiscover/Autoconfig, либо SRV записи с imap сервером указанным в соответствии с RFC 6186. В противном случае домен imap указывается вручную, обычно как imap.example.com
.
Мы рассмотрим простейший пример, когда клиент будет подключаться напрямую к прокси, и, в зависимости от записи в таблице (user; mail[n].example.com; port), будет происходить маршрутизация клиента на его почтовый сервер.
Техническое решение выбрано, опишем задачу:
Развернуть nginx-proxy с модулем mail
Написать скрипт авторизации пользователей
Развернуть почтовый сервер
ПРИМЕЧАНИЕ: Это не законченное корпоративное решение, я не буду детально рассматривать в статье вопросы безопасности, просто proof-of-concept.
Итак, начнём.
Собираем NGINX из исходников с модулем mail
Дальнейшие действия проводятся на Debian 12.
По умолчанию, NGINX не поставляется с модулем mail (за это нужно доплатить), так что мы будем собирать его руками.
Для начала поставим зависимости:
apt install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev
Скачаем исходники последней версии:
NGINXLATEST=$(wget -qO- https://nginx.org/en/download.html | grep -oP 'nginx-\K[\d.]+(?=\.tar\.gz)' | head -1)
wget https://nginx.org/download/nginx-${NGINXLATEST}.tar.gz
tar -zxvf nginx-${NGINXLATEST}.tar.gz
cd nginx-${NGINXLATEST}
Добавим модуль mail:
./configure --with-mail --with-mail_ssl_module
Соберем NGINX (на 1 ядре 1 гиге заняло <30 секунд) и установим его в систему:
make
make install
Важно! NGINX устанавливается в папку /usr/local/nginx/
Сервис для NGINX заводим ручками:
nano /etc/systemd/system/nginx.service
[Unit]
Description=NGINX Web Server
After=network.target
[Service]
Type=forking
ExecStartPre=/usr/local/nginx/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/local/nginx/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/local/nginx/sbin/nginx -s reload -g 'daemon on; master_process on;'
ExecStop=/usr/local/nginx/sbin/nginx -s stop
PIDFile=/usr/local/nginx/logs/nginx.pid
Restart=on-failure
KillMode=mixed
[Install]
WantedBy=multi-user.target
Переходим к конфигурации:
nano /usr/local/nginx/conf/nginx.conf
worker_processes auto;
mail {
server_name example.com;
# Путь до нашего скрипта с авторизацией
auth_http 127.0.0.1:9000/auth;
proxy_pass_error_message on;
server {
listen 143;
protocol imap;
}
}
Запускаем сервис:
systemctl start nginx.service
Переходим к идентификации
Теперь займемся скриптом идентификации, нам нужно соответствовать спецификации NGINX Mail Auth. В качестве бекенда будем использовать flask.
nano mail_auth.py
from flask import Flask, request, jsonify, abort, make_response
import logging
app = Flask(__name__)
# настроим логгирование
logging.basicConfig(level=logging.INFO)
# Define your mapping table with ports
user_server_map = {
"user1": ("mail1.example.com", 143),
"user2": ("mail1.example.com", 143),
"user3": ("192.0.2.1", 143)
}
# Ограничим количество попыток авторизации
MAX_LOGIN_ATTEMPTS = 10
@app.before_request
def log_request_info():
logging.info('Request Path: %s', request.path)
logging.info('Request Method: %s', request.method)
logging.info('Request Headers: %s', request.headers)
logging.info('Request Remote Address: %s', request.remote_addr)
@app.route('/auth', methods=['GET'])
def auth():
# Читаем заголовки
auth_method = request.headers.get('Auth-Method')
auth_user = request.headers.get('Auth-User')
auth_pass = request.headers.get('Auth-Pass')
auth_protocol = request.headers.get('Auth-Protocol')
auth_login_attempt = int(request.headers.get('Auth-Login-Attempt', '1'))
# Проверим валидность клиента
if auth_user not in user_server_map or not auth_user or not auth_pass:
if auth_login_attempt > MAX_LOGIN_ATTEMPTS:
# Fail2ban
logging.info(f"User {auth_user} exceeded max login attempts.")
response = make_response()
response.headers['Auth-Status'] = 'Invalid login or password'
return response
# Ответ в случае некорректных данных авторизации
response = make_response()
response.headers['Auth-Status'] = 'Invalid login or password'
response.headers['Auth-Wait'] = '3' # ждем 3 секунды до следующей попытки
return response
# Ищем сервер
server, port = user_server_map.get(auth_user, ("127.0.0.1", 143))
logging.info(f"Authenticating user {auth_user} with server {server} on port {port}")
# Возвращаем сервер и порт для клиента
response = make_response()
response.headers['Auth-Status'] = 'OK'
response.headers['Auth-Server'] = server
response.headers['Auth-Port'] = str(port)
return response
if __name__ == '__main__':
app.run(host='127.0.0.1', port=9000, debug=True)
Переходим к конфигурации Dovecot
Установим пакет из репозитория:
apt install dovecot-core dovecot-imapd
Создадим пользователя и группу:
groupadd -g 5000 vmail
useradd -g vmail -u 5000 vmail -d /opt/demomail -m
chown vmail:vmail /opt/demomail
Сделаем минимальную конфигурацию:
mv /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.bak
nano /etc/dovecot/dovecot.conf
protocols = imap
listen = *
mail_location = maildir:/opt/demomail/%u
ssl = no
auth_mechanisms = plain
disable_plaintext_auth = no
mail_privileged_group = vmail
passdb {
driver = passwd-file
args = /etc/dovecot/conf.d/dovecot.passwd
}
userdb {
driver = static
args = uid=vmail gid=vmail home=/opt/demomail/%u
}
log_path = /var/log/dovecot.log
info_log_path = /var/log/dovecot-info.log
Добавим пользователя на наш почтовый сервер:
nano /etc/dovecot/conf.d/dovecot.passwd
user1:{PLAIN}password123 - для 1го сервера
user2:{PLAIN}password123 - для 2го сервера
Перезапускаем сервис:
systemctl restart dovecot.service
Проверяем работу сервиса
Запускаем наш скрипт на сервере указанном в nginx.conf:
python3 mail_auth.py
И пробуем подключиться:
telnet example.com 143
Нас приветствует NGINX Mail Proxy
Escape character is '^]'.
* OK IMAP4 ready
Вводим данные:
1 LOGIN user1 password123
Супер, мы попали на нужный нам почтовый сервер
1 OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SNIPPET=FUZZY PREVIEW=FUZZY PREVIEW STATUS=SIZE SAVEDATE LITERAL+ NOTIFY] Logged in
В логах /var/log/dovecot-info.log
отобразилось
Aug 30 10:19:36 imap-login: Info: Login: user=<user1>, method=PLAIN, rip=10.1.1.176, lip=10.1.1.81, mpid=8199, session=<4Ome7eMg9LQKAQGw>
Для отключения можно ввести Ctrl + ]
и нажать Enter
Резюмируя, в статье мы рассмотрели базовую настройку NGINX Mail Proxy сервера, написали простой скрипт для идентификации пользователей и развернули простейший сервер imap с использованием Dovecot для проверки работы нашего "маршрутизатора".
Основное преимущество такого подхода к проксированию пользователей заключается в том, что он позволяет гибко добавлять новые почтовые серверы с минимальным воздействием на существующую инфраструктуру. Например, можно постепенно переводить пользователей на новый сервер, наращивать количество серверов, при этом сохраняя непрерывность работы.
WhiteApfel
Статья имеет место быть как описание фичи, но вопрос, как это решает озвученную проблему, что есть сервер, которому плохо и на котором "не ходят" письма? Трафика на сервере меньше не стало, появилось только ещё одно промежуточное звено. Проблему решают дополнительные сервера
denplab Автор
Спасибо за комментарий, действительно, стоило подробнее описать архитектуру решения. У меня в планах написать ещё одну статью по теме, чтобы раскрыть не только распределение пользователей по почтовым серверам, но и описать работу почтовых шлюзов в сочетании с mail proxy для доставки писем. В контексте данной статьи я показал именно то, что вы описываете, развёртывание дополнительного сервера без влияния на текущую инфраструктуру (Подразумевается, что у вас уже есть настроенная почтовая система, присутствует некий pipeline для обработки писем, но, например, закончилось место для ящиков или медленно работает поиск по письмам). Если вы хотите избавиться от единой точки отказа или распределить трафик по серверам, вы можете развернуть N прокси с nginx, сделать Round-robin DNS для них. Если попытаться развернуть ещё один почтовый сервер без использования решения для маршрутизации пользователей, как, например, описанного в этой статье, вы столкнётесь с синхронизацией содержимого почтовых ящиков, что не всегда может быть удобно. В случае же "умного" проксирования, каждый из почтовых серверов будет загружен только теми пользователями и задачами, которые непосредственно на нём присутствует, что в конечном счёте позволит использовать меньше ресурсов для достижения той же цели.
ildarz
Сильно понятнее не стало. :) Во-первых, любая вменяемая корпоративная почтовая система уже имеет то самое "решение для маршрутизации писем" (оно же MTA) в качестве составной части, и дополнительный сервер для хранения новых п/я обычно вполне себе прозрачно разворачивается штатно.
Во-вторых, задачу "медленно работает поиск по письмам" даже добавление доп. сервера само по себе не способно решить принципиально, потому что письма уже лежат там, где лежат, и от того, что новые вы начнете класть куда-то еще, с имеющимися ситуация не изменится. И как тогда в итоге ваше элегантное решение заменит "архивный почтовый сервер, перекинуть туда письма старше N лет, сделать скрипты перемещения на основном, но это не для нас" (с), о чем вы прямо с начала пишете? :)
В-третьих:
Нет. Роль сервера п/я и так загружена "только пользователями, которые на нем присутствуют", ибо других п/я на нем просто нет. :) А роли МТА или клиентского доступа - вообще отдельный слой, могущий работать и на тех же серверах, и отдельно, и вопрос, выгоднее ли их комбинировать с мэйлбоксами или разносить отдельно, сильно зависит от конкретики, тут нет какого-то единственно верного решения, которое позволит "использовать меньше ресурсов".
По сути вы начинаете описывать сценарий "у нас был одинокий почтовик-комбайн, масштабирование никто не продумывал и не закладывал, что же теперь делать?"
denplab Автор
Да! Основной вопрос как раз к вменяемости корпоративной почтовой системы :)
В общем случае не во всех почтовых серверах имеются возможности масштабирования из коробки, и, просто развернуть ещё один сервер для почтовых ящиков не всегда возможно, отсюда и упоминание "синхронизации" в комментарии выше.
Не рассматривал в статье MTA, так как доставку писем на нужный сервер можно реализовать множеством способов и без Nginx. Речь идет именно о пользовательских почтовых ящиках и распределению пользователей по серверам, с минимальным вмешательством. Это как раз позволяет сделать imap прокси. При этом мы достигаем большой гибкости и не становимся зависимыми от платформенных почтовых решений, что может быть критично.
Также нужно уточнить, что всё равно потребуется перенос ящиков (если у нас уже перегруженный сервер) при добавлении нового почтового сервера, однако, можно будет отложить\избежать создания архивного сервера, оставив принцип "одного окна" для пользователей.
Надеюсь стало немного понятнее)