NGINX Mail Proxy: на пути к INBOX

от автора

Перед нами загруженный почтовый сервер с заполненными почтовыми ящиками, большим почтовым трафиком и задача сделать с этим что-нибудь, так как письма «не ходят», а ещё Sieve еле шевелится. Предположим, что докинуть ядер/дисков не получится, а сделать что-то нужно.

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

Для Nginx существует модуль mail, который позволяет проксировать и шифровать почтовые подключения, но это ещё не всё. Также имеется возможность авторизовать пользователя и перенаправить соединение. Осталось только завести базу пользователей и перенаправить трафик через прокси — выглядит отлично.

Подключения клиентов imap на сервер вида example.com чаще всего устанавливаются через считывание AutoDiscover/Autoconfig, либо SRV записи с imap сервером указанным в соответствии с RFC 6186. В противном случае домен imap указывается вручную, обычно как imap.example.com.

Мы рассмотрим простейший пример, когда клиент будет подключаться напрямую к прокси, и, в зависимости от записи в таблице (user; mail[n].example.com; port), будет происходить маршрутизация клиента на его почтовый сервер.

Техническое решение выбрано, опишем задачу:

  1. Развернуть nginx-proxy с модулем mail

  2. Написать скрипт авторизации пользователей

  3. Развернуть почтовый сервер

ПРИМЕЧАНИЕ: Это не законченное корпоративное решение, я не буду детально рассматривать в статье вопросы безопасности, просто 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 для проверки работы нашего «маршрутизатора».

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


ссылка на оригинал статьи https://habr.com/ru/articles/840564/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *