Когда я расположил приложение в Docker-контейнере и попробовал отправить email на почтовый сервер в другом Docker-контейнере, столкнулся с непредвиденной проблемой. Почтовый сервер postfix по умолчанию отправляет почту на произвольный домен получателя только от локального клиента. Все остальные домены нужно прописывать в параметре relay_domains, и если параметр mynetwors настроен правильно, то почта будет отправляться на перечисленные в параметре relay_domains домены с клиента из mynetwors.

В принципе, мне этого было достаточно, т.к. приложение теоретически должно было отправлять почту на ровно один корпоративный почтовый сервер. Но такое решение меня не очень устраивало, потому что задача может поменяться в любой момент. Поэтому, я попытался настроить sasl-авторизацию, которая позволяет отправлять почту авторизованным пользователям на произвольный домен.


Первым решением, которым я попробовал воспользоваться — это установить postfix вне контейнера Docker и оправлять на него прямо на порт 25 сообщения с клиента из контейнера Docker. Конечно, меня ожидало разочарование, т.к. localhhost внутри контейнера не имеет ничего общего с localhost вне контейнера.

Следовательно пришлось бы обращаться к postfix не через localhost. Да и изначально цель была такая, чтобы все нужное установить в контейнерах, и не зависеть от окружения на сервере. То есть postfix желательно было установить в контейнере.

Дополнительным плюсом установки postfix внутри контейнера является его полная закрытость от внешнего мира, если только явно не опубликовать его порт в docker-compose, что естественно легче контролировать, чем экзотические параметры в конфигурационных файлах самого postfix.

Для того чтобы можно было обращаться к postfix серверу, который работает внутри контейнера, нужно настроить авторизацию. Самым популярным решением является авторизация sasl. Я ее и решил настроить с использованием sasldb2. Как оказалось не так уж много подробных инструкций об установке sasl для postfix с использованием sasldb2. Некоторые инструкции содержали устаревшую информацию и не содержали нужных сведений. В результате, с первого раза авторизация упорно отказывалась работать. Как оказалось, две основные причины были: использование postfix сервером файловой системе в «песочнице» /var/spool/postfix/, о которой ничего не знает sasl, а также необходимость раздачи прав на файл базы данных /etc/sasldb2, который создает пользователь sasl — пользователю postfix.

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

FROM ubuntu:xenial

ARG UID

RUN   useradd -u $UID www-arc &&   apt-get update && apt-get install tzdata &&   echo Europe/Kiev | tee /etc/timezone &&   dpkg-reconfigure --frontend noninteractive tzdata &&   apt-get install -y postfix rsyslog sasl2-bin &&   postconf -e "mydestination = localhost" &&   postconf -e "myhostname = example.com" &&   postconf -e "always_bcc = www-arc@localhost" &&   postconf -e "smtpd_sasl_auth_enable=yes" &&   postconf -e "broken_sasl_auth_clients=yes" &&   postconf -e "smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination" &&   postconf -e "smtpd_client_restrictions = permit_sasl_authenticated,reject_unauth_destination" &&   postconf -e "smtpd_sasl_security_options = noanonymous" &&   echo 123456 | saslpasswd2 -c -p -u example.com postfix &&   ln  /etc/sasldb2 /var/spool/postfix/etc/sasldb2 &&   adduser postfix sasl &&   touch /var/log/mail.log

COPY ./smtpd.conf /etc/postfix/sasl/smtpd.conf

CMD service rsyslog start && service postfix start && tail -f /var/log/mail.log

ln /etc/sasldb2 /var/spool/postfix/etc/sasldb2 По умолчанию postfix работет с файловой системой в «песочнице» /var/spool/postfix/. Там он будет искать файл с логинами /etc/sasldb2. Поэтому, задаем ссылку на файл /etc/sasldb2 из песочницы на реальный файл. Ссылка должны быть только жесткой (без параметра -s).

adduser postfix sasl вполне понятное и очень важно действие позволяет работать postfix с файлом /var/spool/postfix/etc/sasldb2.

Правило postconf -e "smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination" как раз и позволяет отправлять почту на произвольный домен всем прошедшим авторизацию sasl клиентам.

Конфигурация авторизации sasl в файле ./smtpd.conf:

log_level: 3
pwcheck_method: auxprop
auxprop_plugin: sasldb
mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM

Параметр postconf -e "mydestination = localhost" говорит о том, что почту для этого домена не отправлять дальше а принимать на этом хосте. Как показывает опыт, почтовые сервера, которые должны принимать почту, очень часто настроены неверно, теряют почту. Иногда это критично т.к. может вредить бизнесу, если почтовое сообщение содержит заявку клиента на приобретение товара или услуги. Поэтому данная конфигурация настроена на отправку копии локальному получателю postconf -e "always_bcc = www-arc@localhost". Это получатель создается с идентификатором текущего пользователя useradd -u $UID www-arc.

Текущий пользователь определяется в конфигурации docker-compose:

  postfix:
    build:
      context: ./docker/postfix
      args:
       - UID
    volumes:
      - ./docker/postfix/mail:/var/mail

При билде текущий пользователь передается из окружения env UID=$UID docker-compose build.

Для подключения из другого контейнера docker к этому сервису, необходимо в качестве хоста указать postfix (название сервиса из docker-compose.yml), а имя с учетом домена (postfix@example.com) и пароль. Во всех случаях имя postfix не является обязательным инастраивается в конфигурации.

Дополнение.

Конечно при решении задачи я «подглядывал» как это делают делают другие разработчики в открытых репозитариях и очень много почерпнул из этих источников. Поэтому разберу, как некоторые из конфигураций решают аналогичные задачи.

Для обзора я воспользовался ключевой фразой в поиске Google «docker sasl postfix». Поэтому, если кто-нибудь посоветует еще какие-то репозитарии, я из включу в этот список.

1) github.com/MarvAmBass/docker-versatile-postfix

Автор воспользовался не плагином postfix auxprop_plugin: sasldb, а стандартным средством sasl pwcheck_method: saslauthd. Это еще один дополнительный процесс который запускается в контейнере, и который не очень удобно конфигурировать, т.к. по умолчанию он выключен. Поэтому автор применяет в своей конфигурации такие вот конструкции:
RUN sed -i 's/^START=.*/START=yes/g' /etc/default/saslauthd;   sed -i 's/^MECHANISMS=.*/MECHANISMS="shadow"/g' /etc/default/saslauthd
RUN sed -i 's/^OPTIONS=/#OPTIONS=/g' /etc/default/saslauthd;   echo 'OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"' >> /etc/default/saslauthd


2) github.com/cloudposse/postfix

Аналогично предыдущему используется pwcheck_method: saslauthd

3) github.com/floriandejonckheere/docker-postfix

Автор использовал sasldb2 с плагином postfix, но отключил механизм «песочницы»: smtp inet n - n - - smtpd, и для запуска процессов использовал supervisor, таким образом включив в контейнер несколько процессов.

4) github.com/catatnight/docker-postfix
Подобно предыдущему, автор откючил «песочницу»: postconf -F '*/*/chroot = n' и использовал supervisor.

5) github.com/juanluisbaptiste/docker-postfix

Использует плоский файл с паролями: echo "[$SMTP_SERVER]:587 $SMTP_USERNAME:$SMTP_PASSWORD" >> /etc/postfix/sasl_passwd

6) github.com/container-images/postfix

Отключает песочницу sed -i 's/^smtp\(\s*\)inet\(.*\)/docker_smtp\1inet\tn\ty\tn\t-\t-\tsmtpd -v/g' "$MASTER"

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

Буду благодарен за дополнение этого списка репозитариев, и обещаю оперативно дополнять этот список по Вашим комментариям.

apapacy@gmail.com
8 апреля 2018 года

Комментарии (12)


  1. antirek
    09.04.2018 11:55

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


    1. aol-nnov
      09.04.2018 12:10

      я больше скажу: в докерхабе образов, решающих данную задачу как за баней достаточно. :)


      1. apapacy Автор
        09.04.2018 15:19

        Привел наиболее популярные ссылки на репозитарии в дополнении к статье. Если Вы знаете еще буду рад дополнить список Вашими дополнениями.


    1. apapacy Автор
      09.04.2018 15:18

      Проблему и ключевые моменты дополнил. Опасений пока не имею когда начну пользоваться наверное появятся. Сделать репозитарий на githab и выложить контейнер в общий доступ думаю. Но нужно еще провести дополнительную работу для этого. Подумать немного чтобы было более универсально. Боюсь что например bcc нужно из общедоступного репозитария выбросить т.к. кто-нибудь включит эту фичу для дублирования почты там где это совсем не нужно или даже неуместно.


  1. akamensky
    09.04.2018 12:26

    Мне одно непонятно — зачем?

    Если надо отправить почту из приложения которое крутится внутри контейнера, можно:

    а) подключиться к локальному MTA на хосте из докера (локально оно слушает всегда на 127.0.0.1:25)
    или
    б) отправлять по API через предназначенные для этого сервисы (а не заморачиваться с настройкой своего mail-сервера, а то адрес отправителя и IP очень быстро улетит в замечательный spam-blacklist)


    1. apapacy Автор
      09.04.2018 15:29

      Дополнил в статье проблемы которые возникают при подключении к postfix из докера на localhost. За 25-й порт спасибо за дополнение в дальнейшем учту. По пункту б) сервер улетает в spam-лист если он действительно рассылает спам или если он не нестроен до конца. В частности, докеризация предполагает такую комплексную настойку, в частности совместно с dkim сервисом, который в этой статье не описан, но в тех репозитариях на которые даны ссылки в дополнении к статье этот вопрос решен комплексно.

      Другая часть этих настроек конечно должна быть проведена в настройках домена. Что к сожалению не всегда выполняется правильно. И поэтому почта часто попадает в серые и черные списки или отвергается некоторыми серверами.


      1. akamensky
        11.04.2018 08:54

        Другая часть этих настроек конечно должна быть проведена в настройках домена. Что к сожалению не всегда выполняется правильно. И поэтому почта часто попадает в серые и черные списки или отвергается некоторыми серверами.

        Так я вот именно поэтому и спрашиваю — зачем? Настройка полноценного мейл-сервера, которому другие сервера будут доверять, это дело очень сложное (DNS A/AAAA, DNS PTR, DNS SPF это как минимум, еще добавим TLS, DKMS и прочее), что все достоинства использования контейнеров сходят на ноль, совсем.

        При этом в интернете тысячи (ну или хотя бы сотни уж точно) сервисов, которые делают отправку почты через API банальным делом (например — AWS SES, не знаю какие сервисы для этого популярны в России).


        1. apapacy Автор
          11.04.2018 09:14

    1. powernic
      09.04.2018 15:29

      Может для бесплатной массовой рассылки в 100тыс клиентов?


      1. apapacy Автор
        09.04.2018 15:36

        Технически любой сервер может отправить спам и 100 тыс. и 1 млн. клиентов. Сложнее (практически невозможно) сделать так чтобы эта почта была принята принимающим сервером. Увы это так. У меня более простая задача отправить почту на корпоративный сервер админами которого я всегда могу договориться чтобы они включили мой адрес в белый список. Однако и там не без проблем это все. Т.к. всяческих фильтров иногда совершенно неожиданных для отброса спама больше чем можно себе вообразить. Например я отправляю письмо менеджеру продаж и копию на общий почтовый ящик. Почта не пропускается правилом которое гласит то два письма с одним контентом это уже спам.


  1. Darigaaz
    10.04.2018 23:24
    +1

    По-моему


    postconf -e "smtpd_recipient_restrictions = permit_sasl_authenticated,reject_unauth_destination" 
    postconf -e "smtpd_client_restrictions = permit_sasl_authenticated,reject_unauth_destination"

    лучше заменить на


    postconf -e "smtpd_relay_restrictions = permit_sasl_authenticated,reject_unauth_destination"
    postconf -e "smtpd_recipient_restrictions = reject" # (reject_unauth_destination - если хотите локальный bcc)

    smtpd_recipient_restrictions после версии 2.10 (я уверен, у вас старше) предназначен для ограничений на "локальную" доставку, вы же хотите настроить relay, поэтому лучше использовать соответствующий параметр.
    smtpd_client_restrictions — это generic ограничения
    Причем, правило reject_unauth_destination — это правило из раздела smtpd_recipient_restrictions, и в smtpd_client_restrictions оно будет иметь силу только если стоит smtpd_delay_reject = yes (значение по умолчанию).
    И получается что вы одно и то же написали 2 раза.


    Тут написано подробнее
    И последний момент


    myhostname = example.com

    советую все таки вписывать именно имя хоста docker-mx.mycompany.com (а не просто доменную часть) ведь именно так он будет представляться в HELO/EHLO при отправке почты. И на это имя вы будете заводить dkim/spf/dmark записи, а так же PTR запись в днс, а то рискуете попасть в спам.


    Если представляться надо как-то иначе, то можно воспользоваться параметром smtp_helo_name.


    1. apapacy Автор
      10.04.2018 23:38

      Спасибо, очень ценное дополнение. Когда проверю дополню статью и свои конфигурации. К сожалению параметров у postfix довольно много и не все сразу доходит до понимания.