Настройка Self-Hosted почтового сервера Postgresq/Postfix/Dovecot и т.п.

Пожалуй начнем

Настройка PostfixAdmin

PostfixAdmin это вообще какой-то привет из темного прошлого, но ничего лучше не завезли. Поэтому будем пользоваться тем, что дали и бесплатно.

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

  • За основу берем стабильный релиз (двух недельной давности в моем случае) из официального гита https://github.com/postfixadmin/postfixadmin
  • Создаем базу данных и пользователя (на хосте rw)
  • Генерируем Setup password
# php -r 'echo password_hash("xxxSecretPasswordxxx", PASSWORD_DEFAULT);'

Пример virtual-host для запуска:

server {
    listen 80;
    server_name postfixadmin.interlan.xyz;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name postfixadmin.interlan.xyz;

    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;
    add_header Referrer-Policy no-referrer always;

    # Path to the root of your installation
    root /var/www/vhosts/postfixadmin.interlan.xyz/public/;

    access_log  /var/log/nginx/postfixadmin.interlan.xyz-access.log;
    error_log  /var/log/nginx/postfixadmin.interlan.xyz-error.log warn;

    ssl_certificate /etc/letsencrypt/live/postfixadmin.interlan.xyz/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/postfixadmin.interlan.xyz/privkey.pem;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;

    client_max_body_size 1024M;
    fastcgi_buffers 64 4K;

    proxy_connect_timeout 6000;
    proxy_send_timeout 6000;
    proxy_read_timeout 6000;
    send_timeout 6000;

    gzip on;
    gzip_vary on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    if ($host ~ ^www\.(?<domain>.+)$) {
      return  301 $scheme://$domain$request_uri;
    }

    location = /robots.txt {
        allow all;
    }

    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php {
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        fastcgi_param front_controller_active true;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;
    }

    # Prevents caching of css/less/js/images, only use this in development
    location ~* \.(css|less|js|jpg|png|gif)$ {
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        add_header Pragma "no-cache";
        expires 0;
    }

}

Конфигурационный файл /var/www/vhosts/postfixadmin.interlan.xyz/config.local.php

<?php
$CONF['database_type'] = 'pgsql';
$CONF['database_host'] = '127.0.0.1';
$CONF['database_user'] = 'postfixadmin_user';
$CONF['database_password'] = 'xxxDBUSERPASSWORDxxx';
$CONF['database_name'] = 'postfixadmin_db';
$CONF['encrypt'] = 'md5crypt';
$CONF['configured'] = true;
$CONF['setup_password'] = 'xxxINSTALLxxxHASHxxx';
$CONF['default_aliases'] = array (
    'abuse' => 'chernousov@interlan.xyz',
    'hostmaster' => 'chernousov@interlan.xyz',
    'postmaster' => 'chernousov@interlan.xyz',
    'webmaster' => 'chernousov@interlan.xyz'
);
?>

Переходим по URL https://postfixadmin.interlan.xyz/setup.php (это пример конечно) и выполняем настройку (наполнение схемы базы производится автоматически) и создаем аккаунт администратора.

  • Поле Админ, это адрес электронной почты
  • Дополнительно устанавливаем расширение IMAP для php
    # aptitude install php8.1-imap
  • Симлинк на templates_c
    # ln -s ./templates/ ./templates_c

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

Установка и настройка Dovecot

Изначально хотел использовать DBmail, но из за больших задержек между датацентрами это будет полная боль и страдание. Посему хранилка будет файловая на GlusterFS для возможности масштабирования.

Пакеты для Dovecot и плагинов устанавливаем на пакете с репликой базы данных.

# aptitude install dovecot-antispam dovecot-lmtpd dovecot-managesieved dovecot-pgsql dovecot-sieve

Настройка Dovecot.

За основу берем базовые конфигурационные файл и вносим ряд правок.

conf.d/10-auth.conf b/conf.d/10-auth.conf
Отключаем  include auth-system.conf.ext (это механизмы системной авторизации) и добавляем:

!include auth-sql.conf.ext

/conf.d/10-mail.conf

Каталог где храним данные мэйлбоксов и UID/GID собственно Dovecot

mail_location = maildir:/opt/mail/%d/%u/
mail_uid = 118
mail_gid = 122
first_valid_uid = 118
last_valid_uid = 118
first_valid_gid = 122
last_valid_gid = 122

/conf.d/10-master.conf
Настраиваем сервисы LMTP и авторизации на работу как через сокет, так и по порту

inet_listener lmtp {
    address = 127.0.0.1
    port = 24
}
unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    mode = 0666
    user = postfix
    group = postfix
}

/conf.d/10-ssl.conf
Настройка Dovecot на использование Let’s encrypt

ssl_cert = </etc/letsencrypt/live/mail.bds.su/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.bds.su/privkey.pem

conf.d/15-lda.conf
Настройка lmtp сервиса

postmaster_address = chernousov@interlan.xyz
hostname = mail.bds.su
lda_mailbox_autocreate = yes
protocol lda {
  mail_plugins = $mail_plugins
}

conf.d/15-mailboxes.conf
Автосоздание каталогов в IMAP-папках

mailbox Drafts {
     special_use = \Drafts
    auto = subscribe
}
mailbox Junk {
     special_use = \Junk
    auto = subscribe
}
mailbox Trash {
     special_use = \Trash
    auto = subscribe
}

conf.d/20-lmtp.conf
LMTP-сервис переводим на работу по порту вместо сокета

protocol lmtp {
  mail_plugins = $mail_plugins
}
service lmtp {
   inet_listener lmtp {
      address = 127.0.0.1
      port = 24
}

dovecot-sql.conf.ext
Настройки авторизации на использование базы данных

driver = pgsql
connect = host=127.0.0.1 dbname=postfixadmin_db user=postfixadmin_user password=xxxPASSWORDxxx
default_pass_scheme = MD5-CRYPT
password_query = SELECT password FROM mailbox WHERE username = '%u'
user_query = select CONCAT('/opt/mail','/',LOWER(maildir)) as home,  118 AS uid, 122 AS gid FROM mailbox WHERE username = '%u';

dovecot.conf

listen = *

Разрешаем доступ к портам через UFW.

# ufw allow 143
# ufw allow 993

Создаем каталог для хранения почты и меняем владельца и группу.

# mkdir /mnt/gluster-storage/mail/
# chown dovecot:dovecot /mnt/gluster-storage/mail

Перезапускаем и активируем.

# systemctl restart dovecot            
# systemctl enable dovecot

Отладка авторизации.

Проверяем, что пользователь существует:

# doveadm user chernousov@interlan.xyz

Проверяем авторизацию:

# doveadm auth login chernousov@interlan.xyz xxxPASSWORDxxx
passdb: chernousov@interlan.xyz auth succeeded
extra fields:
 user=chernousov@interlan.xyz
userdb extra fields:
 chernousov@interlan.xyz
 home=/mnt/gluster-storage/mail/interlan.xyz/chernousov/
 uid=118
 gid=122
 auth_mech=PLAIN

Если все ок, то пробуем подключиться почтовым клиентом.

Настройка Postfix для приема и отправки электронной почты

Postfix это такая-же классика как и Dovecot, поэтому будем использовать его. Как мне видится можно даже сделать небольшую заметку как вообще вся эта связка работает, но в каком-то минимальном формате исключительно для лэндинга.

Устанавливаем по старой схеме необходимые пакеты.

# apt-get install postfix postfix-pgsql postfix-gld postfix-policyd-spf-python
# aptitude install opendkim opendkim-tools

Подготовка базы почтовых алиасов:

# newaliases
# postfix reload

Устанавливаем и настраиваем два дополнительных сервиса:

DKIM настроим по инструкции для mail-relay

Postgregrey подойдет в дефолтной поставке:

# apt-get install postgrey
# systemctl enable postgrey
# systemctl start postgrey

Дополнительные DNS-записи для доменов:

  • _dmarc TXT “v=DMARC1; p=reject; rua=mailto:chernousov@interlan.xyz”
  • @ TXT “v=spf1 +a +mx -all”

Настройка Firewall:

# ufw allow 25
# ufw allow 465
# ufw allow 587

Пример конфигурации:

myorigin = mail.bds.su
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
delay_warning_time = 15m
readme_directory = no
compatibility_level = 3.6

smtpd_tls_cert_file=/etc/letsencrypt/live/mail.bds.su/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.bds.su/privkey.pem
smtpd_tls_security_level=may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination

smtpd_recipient_restrictions =
        permit_mynetworks
        permit_sasl_authenticated
        reject_unauth_destination
        check_policy_service inet:localhost:10023

myhostname = mail.bds.su
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = localhost

relayhost =
mynetworks = 127.0.0.0/8
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4

virtual_alias_maps = proxy:pgsql:/etc/postfix/virtual_alias_maps.cf
virtual_mailbox_domains = proxy:pgsql:/etc/postfix/virtual_domains_maps.cf
virtual_mailbox_maps = proxy:pgsql:/etc/postfix/virtual_mailbox_maps.cf

virtual_transport = lmtp:127.0.0.1:24

smtpd_sasl_auth_enable = yes
smtpd_sasl_exceptions_networks = $mynetworks
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth

smtp_use_tls = yes
smtpd_use_tls = yes
smtpd_tls_auth_only = yes
smtpd_helo_required = yes

milter_default_action = accept
milter_protocol = 2
smtpd_milters = inet:localhost:10021
non_smtpd_milters = inet:localhost:10021

SQL-конфиги:

/etc/postfix/virtual_alias_maps.cf

user= postfixadmin_user
password= xxxPASSWORDxxx
hosts= localhost
dbname= postfixadmin_db
query= SELECT goto FROM alias WHERE address='%s' AND active='1'

/etc/postfix/virtual_domains_maps.cf

user= postfixadmin_user
password= xxxPASSWORDxxx
hosts= localhost
dbname= postfixadmin_db
query= SELECT domain FROM domain WHERE domain='%u'

/etc/postfix/virtual_mailbox_maps.cf

user= postfixadmin_user
password= xxxPASSWORDxxx
hosts= localhost
dbname= postfixadmin_db
query= SELECT goto FROM alias WHERE address='%s' AND active='1'

Отладка

Просмотр текущей очереди:

# mailq

Форсировать очередь:

# mailq -q

Подтверждение что мы не спамеры

 Публичные сервисы типа gmail стали очень злые и нервные и теперь для использования своего self-hosted почтового сервера придется немного поприседать и побить в бубен.

Итак, давайте разбираться, что мы можем сделать.

Google

У гугла как обычно все через одно место и в 90% случаев словим ошибку:

gmail-smtp-in.l.google.com[173.194.215.26] said: 550-5.7.1 [45.138.27.6]
    The IP youre using to send mail is not authorized to 550-5.7.1 send email
    directly to our servers. Please use the SMTP relay at your 550-5.7.1
    service provider instead. Learn more at 5505.7.1

И вот тут все не просто.

  • Идем добавлять домены https://postmaster.google.com/managedomains?pli=1
  • Добавляем DNS-запись подтверждения домена (TXT)
  • Они не быстрые и синхронизация даже после подтверждения займет много времени

Yandex

Здесь обычный GreyList и первое письмо будет уходить долго, а дальнейшие уходят на ура.

Mail.ru

Проверяет DKIM и SPF и все норм

Немного красоты

Веб-интерфейс для доступа к почте будем делать на базе RoundCube. Это обычное PHP-приложение, достаточно функциональное и удобное.

Скачиваем версию Complete на официальной странице загрузок проекта https://roundcube.net/download/

Виртуальный хост:

server {
    listen 80;
    server_name webmail.bds.su;

    # Enforce HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443      ssl http2;

    server_name webmail.bds.su;

    access_log  /var/log/nginx/webmail.bds.su-access.log;
    error_log  /var/log/nginx/webmail.bds.su-error.log warn;

    ssl_certificate /etc/letsencrypt/live/webmail.bds.su/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/webmail.bds.su/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;

    client_max_body_size 64M;
    fastcgi_buffers 64 4K;

    proxy_connect_timeout 600;
    proxy_send_timeout 600;
    proxy_read_timeout 600;
    send_timeout 600;

    gzip on;
    gzip_vary on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    add_header Referrer-Policy                      "no-referrer"   always;
    add_header X-Content-Type-Options               "nosniff"       always;
    add_header X-Download-Options                   "noopen"        always;
    add_header X-Frame-Options                      "SAMEORIGIN"    always;
    add_header X-Permitted-Cross-Domain-Policies    "none"          always;
    add_header X-Robots-Tag                         "none"          always;
    add_header X-XSS-Protection                     "1; mode=block" always;

    fastcgi_hide_header X-Powered-By;

    root /var/www/vhosts/webmail.bds.su/;

    location / {
       index index.php;
       try_files $uri $uri/ /index.php;
    }

    location ~* \.php$ {
         fastcgi_split_path_info ^(.+?\.php)(/.*)$;
         if (!-f $document_root$fastcgi_script_name) {return 404;}
         fastcgi_pass  unix:/run/php/php8.1-fpm.sock;
         fastcgi_index index.php;
         include fastcgi_params;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

}
  • Инициализируем схему
  • Конфигурацию
  • И плагинов по вкусу.

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

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