Nginx как SMTP proxy-сервер

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

nginx-logo

Зачем нужен proxy для SMTP и почему именно Nginx

Если в вашем приложении нужно реализовать кастомный SMTP-сервер, (например, наш Debug Mail или сервисы вроде Mailjet, Mailgun и тд.), то для вас SMTP proxy-сервер будет полезен для следующих целей:

Nginx из коробки предоставляет полнофункциональный smtp proxy с свойственной для него отличной производительностью. В моем случае в проекте уже использовался Nginx для проксирования запросов к Node.js приложению и для отдачи статики, по-этому выбор в его пользу был очевиден.

Далее мы рассмотрим процесс настройки proxy-сервера и способы его взаимодействия с приложением.

Настройка

Пример конфигурации SMTP proxy-сервера (должен быть определен или подключен в корне nginx.conf):

mail {
    auth_http  127.0.0.1:8000/auth;

    starttls        on;
    ssl_certificate /etc/nginx/cert.pem;
    ssl_certificate_key /etc/nginx/key.pem;
    auth_http_header X-Auth-Key wow_so_secret;

    # Generic port
    server {
        server_name     debugmail;
        listen          25;
        protocol        smtp;
        proxy           on;
        proxy_pass_error_message on;
        smtp_auth       login plain;
        xclient         on;
    }
}

Рассмотрим некоторые параметры:

Остальные параметры рассмотрим в следующих разделах.

Вы наверное заметили, что нигде не задается адрес проксируемого сервера. Чтобы узнать откуда Nginx узнает его адрес см. следующий раздел.

Аутентификация

Nginx поддерживает 3 метода аутентификации SMTP: login, plain и cram-md5. Для задания активных методов необходимо использовать параметр smtp_auth значением которого является список методов через пробел. Также можно полностью отключить аутентификацию задав значение none.

HTTP Auth сервер

Для проверки аутентификации необходимо реализовать сервер, который будет принимать по HTTP запросы от proxy сервера и возвращать результат проверки. Адрес такого сервера задается при помощи параметра auth_http.

Логин и пароль для проверки отправляются на сервер аутентификации через заголовки Auth-User и Auth-Pass соответственно. В заголовке Auth-Login-Attempt proxy сервер передает номер попытки аутентификации. После проверки в зависимости от результата сервер должен вернуть в ответе следующие заголовки:

Независимо от результата proxy сервер ждет ответ со статусом HTTP/1.0 200 OK.

Как вы видите адрес проксируемого сервера возвращает сервер аутентификации, что дает широкие возможности в плане балансировки нагрузки/логгирования/фильтрации и тд. Например, в зависимости от ip-адреса клиента (который передается в заголовке Client-IP), можно направить письмо на сервер, который географически ближе к данному клиенту (GeoIP). Или можно использовать другой способ балансировки, например, в зависимости от домена пользователя, даты его регистрации или любых других данных которые предаставляет proxy сервер или которые хранятся в БД.

auth_http_header

Я не рекомендую делать ваш сервер аутентификации доступным из вне, но если у вас есть веские причины для этого, то Nginx предоставляет возможность простой способ проверки запроса при помощи shared secret. Для настройки используется параметр auth_http_header <header-name> <shared-secret-key>. При отправки запроса на аутентификацию proxy также будет отправлять заголовок с именем <header-name> и значением <shared-secret-key>. На сервере аутентификации соответственно нужно выполнять проверку данного значения.

Как альтернативный или дополнительный вариант, можно выполнять проверку по IP-адресу proxy сервера, который вероятно является фиксированным.

XCLIENT

Nginx поддерживает расширение XCLIENT. Для активации данной возможности используется параметр xclient: on.

После активации xclient на бекенд (SMTP-сервер) будет приходить команда примерно следующего вида: XCLIENT <attr1=value1> <attr2=value2>. Среди атрибутов команды можно выделить LOGIN, который (как вы наверное догадались) передает логин при помощи которого пользователь аутентифицировался.

В Debug Mail у нас пароль является идентификатором проекта, к которому нужно в итоге прикрепить письмо. Соответственно одного логина нам было недостаточно, для того, чтобы понять куда сохранять email. К счастью Nginx позволяет переопределить значение атрибута LOGIN передаваемого на бекенд. Для этого необходимо (в случае успешной проверки) возвращать дополнительный заголовок Auth-User от сервера аутентификации, например так:

res.set "Auth-Status", "OK"
res.set "Auth-Server", settings.smtp_host
res.set "Auth-Port", settings.smtp_port
res.set "Auth-User", "#{login}##{password}"
res.send 200

SSL / TLS

Если вам нужно добавить поддержку ssl или tls, то в Nginx есть всё для этого. Для включения ssl необходимо задать параметр ssl: on. Если же вы предпочитаете TLS, то необходимо использовать параметр starttls: on, при этом использование TLS будет опциональным и будет зависеть от клиента. Чтобы сделать использование TLS обязательным нужно вместо on задать значение only.

Для дальнейшей настройки нам потребуется сертификат и секретный ключ в формате PEM. Для примера сгенерируем self-signed сертификат (для реального проекта вам вероятно потребуется купить сертификат выданный сертификационным центром):

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 50 -nodes

Далее задаем пути к сертификату и ключу в настройках Nginx (параметры ssl_certificate и ssl_certificate_key соответственно).

P.S.

Полная документация по всем параметрам на официальном сайте Nginx.