Debian + Postfix + Dovecot + Multidomain + SSL + IPv6 + OpenVPN + Multi-interfaces + SpamAssassin-learn + Bind

    Данная статья о том как настроить современный почтовый сервер.
    Postfix + Dovecot. SPF + DKIM + rDNS. С IPv6.
    С шифрованием TLS. С поддержкой нескольких доменов — часть с настоящим SSL сертификатом.
    С антиспам-защитой и высоким антиспам-рейтингом у других почтовых серверов.
    С поддержкой нескольких физических интерфейсов.
    С OpenVPN, подключение к которому через IPv4, и которое даёт IPv6.

    Если вы не хотите изучать эти все технологии, но хотите настроить такой сервер — тогда эта статья для вас.

    В статье отсутствуют попытки пояснить каждую деталь. Пояснение идёт к тому, что настроено не стандартно или важно с точки зрения потребителя.

    Мотивация настроить почтовый сервер — моя давняя мечта. Может это звучит глупо, но ИМХО, это гораздо лучше, чем мечтать о новой машине любимой марки.

    Мотивация настроить IPv6 — две. ИТ специалисту необходимо изучать новые технологии постоянно, чтобы выжить. Хочется внести свой скромный вклад в борьбу с цензурой.

    Мотивация настройки OpenVPN — только для того, чтобы IPv6 работал на локальной машине.
    Мотивация настройки нескольких физических интерфейсов — у меня на сервере один интерфейс «медленный, но безлимитный», а другой «быстрый, но с тарифом».

    Мотивация настройки настройки Bind — мой провайдер предоставляет нестабильный DNS сервер, а google бывает тоже даёт сбои. Хочу стабильный DNS сервер для личного использования.

    Мотивация написать статью — черновик был написал 10 месяцев назад, и я в него уже два раза заглядывал. Если даже автору это регулярно надо — то большая вероятность, что и другим понадобится.

    Универсального решения для почтового сервера нет. Но я постараюсь написать типа «сделайте вот так и потом, когда всё будет работать как надо — выкиньте лишнее».

    Имеется сервер Colocation у компании tech.ru. Есть возможность сравнить с OVH, Hetzner, AWS. Для решения данной задачи гораздо эффективнее будет сотрудничество именно с tech.ru.

    На сервере установлен Debian 9.

    На сервере 2 интерфейса `eno1` и `eno2`. Первый безлимитный, а второй быстрый соответственно.

    Имеется 3 статических IP адреса, XX.XX.XX.X0 и XX.XX.XX.X1 и XX.XX.XX.X2 на интерфейсе `eno1` и XX.XX.XX.X5 на интерфейсе `eno2`.

    Имеется XXXX:XXXX:XXXX:XXXX::/64 пул IPv6 адресов, которые назначены на интерфейс `eno1` и из него XXXX:XXXX:XXXX:XXXX:1:2::/96 по моей просьбе назначили на `eno2`.

    Имеется 3 домена `domain1.com`, `domain2.com`, `domain3.com`. Для `domain1.com` и `domain3.com` есть SSL сертификат.

    Имеется google аккаунт, на который хочется привязать почтовый ящик `vasya.pupkin@domain1.com` (получение почты и отправка почты прямо из gmail интерфейса).
    Должен быть почтовый ящик `support@domain2.com`, копию почты с которого я хочу видеть у себя в gmail. И в редко иметь возможность отправить чего-то от имени `support@domain2.com` через web-интерфейс.

    Должен быть почтовый ящик `ivanov@domain3.com`, которым будет пользоваться Иванов со своего iPhone.

    Отправляемые письма должны соответствовать всем современным требованиям к антиспаму.
    Должен быть наивысший уровень шифрования предусмотренный в публичных сетях.
    Должна быть поддержка IPv6 и для отправки и для получения писем.
    Должен быть SpamAssassin, который никогда не будет удалять письма. А будет или bounce делать или пропускать или отправлять в IMAP папку «Спам».
    Должно быть настроено авто-обучение SpamAssassin: если я перемещаю письмо в папку «Спам» — обучится на этом; если я перемещаю письмо из папки «Спам» — обучится на этом. Результаты обучения SpamAssassin — должны влиять на попадаемость письма в папку «Спам».
    Скрипты php должны уметь отправлять почту от имени любого домена на данном сервере.
    Должен быть openvpn сервис, с возможностью использовать IPv6 на клиенте, у которого нет IPv6.

    Сначала надо настроить интерфейсы и маршрутизацию, включая IPv6.
    Потом надо будет настроить OpenVPN, который будет соединяться по IPv4 и предоставлять клиенту статический-реальный IPv6 адрес. У этого клиента будет доступ ко всем IPv6 сервисам на сервере и доступ к любым ресурсам IPv6 в интернете.
    Потом надо будет настроить Postfix на отправку писем + SPF + DKIM + rDNS и прочие тому подобные мелочи.
    Потом надо будет настроить Dovecot и настроить Multidomain.
    Потом надо будет настроить SpamAssassin и настроить обучение.
    В завершение установить Bind.

    ============= Multi-interfaces =============


    Для настройки интерфейсов надо прописать вот такое в "/etc/network/interfaces".

    # The loopback network interface
    auto lo
    iface lo inet loopback
    
    # The primary network interface
    allow-hotplug eno1
    iface eno1 inet static
            address XX.XX.XX.X0/24
            gateway XX.XX.XX.1
            dns-nameservers 127.0.0.1 213.248.1.6
            post-up ip route add XX.XX.XX.0/24 dev eno1 src XX.XX.XX.X0 table eno1t
            post-up ip route add default via XX.XX.XX.1 table eno1t
            post-up ip rule add table eno1t from XX.XX.XX.X0
            post-up ip rule add table eno1t to XX.XX.XX.X0
    
    auto eno1:1
    iface eno1:1 inet static
    address XX.XX.XX.X1
    netmask 255.255.255.0
            post-up ip rule add table eno1t from XX.XX.XX.X1
            post-up ip rule add table eno1t to XX.XX.XX.X1
            post-up   ip route add 10.8.0.0/24 dev tun0 src XX.XX.XX.X1 table eno1t
            post-down ip route del 10.8.0.0/24 dev tun0 src XX.XX.XX.X1 table eno1t
    
    auto eno1:2
    iface eno1:2 inet static
    address XX.XX.XX.X2
    netmask 255.255.255.0
            post-up ip rule add table eno1t from XX.XX.XX.X2
            post-up ip rule add table eno1t to XX.XX.XX.X2
    
    iface eno1 inet6 static
            address XXXX:XXXX:XXXX:XXXX:1:1::/64
            gateway XXXX:XXXX:XXXX:XXXX::1
            up   ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:1:1:1/64 dev $IFACE
            up   ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:1:1:2/64 dev $IFACE
            down ip -6 addr del XXXX:XXXX:XXXX:XXXX:1:1:1:1/64 dev $IFACE
            down ip -6 addr del XXXX:XXXX:XXXX:XXXX:1:1:1:2/64 dev $IFACE
    
    # The secondary network interface
    allow-hotplug eno2
    iface eno2 inet static
            address XX.XX.XX.X5
            netmask 255.255.255.0
            post-up   ip route add XX.XX.XX.0/24 dev eno2 src XX.XX.XX.X5 table eno2t
            post-up   ip route add default via XX.XX.XX.1 table eno2t
            post-up   ip rule add table eno2t from XX.XX.XX.X5
            post-up   ip rule add table eno2t to XX.XX.XX.X5
            post-up   ip route add 10.8.0.0/24 dev tun0 src XX.XX.XX.X5 table eno2t
            post-down ip route del 10.8.0.0/24 dev tun0 src XX.XX.XX.X5 table eno2t
    
    iface eno2 inet6 static
            address XXXX:XXXX:XXXX:XXXX:1:2::/96
            up   ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:2:1:1/64 dev $IFACE
            up   ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:2:1:2/64 dev $IFACE
            down ip -6 addr del XXXX:XXXX:XXXX:XXXX:1:2:1:1/64 dev $IFACE
            down ip -6 addr del XXXX:XXXX:XXXX:XXXX:1:2:1:2/64 dev $IFACE
    
    # OpenVPN network
    iface tun0 inet6 static
            address XXXX:XXXX:XXXX:XXXX:1:3::/80

    Данные настройки можно применять на любом сервере в tech.ru (с небольшим согласованием с поддержкой) и оно сразу заработает как надо.

    Если опыт настройки аналогичных вещей для Hetzner, OVH — там подругому. Сложнее.

    eno1 — это название сетевой карты #1 (медленный, но безлимитный).
    eno2 — это название сетевой карты #2 (быстрый, но с тарифом).
    tun0 — это название виртуальной сетевой карты от OpenVPN.
    XX.XX.XX.X0 — IPv4 #1 на eno1.
    XX.XX.XX.X1 — IPv4 #2 на eno1.
    XX.XX.XX.X2 — IPv4 #3 на eno1.
    XX.XX.XX.X5 — IPv4 #1 на eno2.
    XX.XX.XX.1 — IPv4 gateway.
    XXXX:XXXX:XXXX:XXXX::/64 — IPv6 на весь сервер.
    XXXX:XXXX:XXXX:XXXX:1:2::/96 — IPv6 для eno2, всё остальное извне заходит в eno1.
    XXXX:XXXX:XXXX:XXXX::1 — IPv6 gateway (стоит отметить, что тут можно/нужно сделать подругому. Указать IPv6 свича).
    dns-nameservers — указаны 127.0.0.1 (потому, что установлен bind локально) и 213.248.1.6 (это от tech.ru ).

    «table eno1t» и «table eno2t» — cмысл этих route-rule в том, чтобы трафик вошедший через eno1 -> ушёл бы через него же, а трафик вошедший через eno2 -> ушёл бы через него же. А также соединения по инициативе сервера уходили бы через eno1.

    ip route add default via XX.XX.XX.1 table eno1t

    Этой командой мы задаём, что любой непонятный трафик, который попал под любое rule у которого отмечено «table eno1t» -> направить в интерфейс eno1.

    ip route add XX.XX.XX.0/24 dev eno1 src XX.XX.XX.X0 table eno1t

    Этой командой мы задаём, что любой трафик по инициативе сервера направить в интерфейс eno1.

    ip rule add table eno1t from XX.XX.XX.X0
    ip rule add table eno1t to XX.XX.XX.X0

    Этой командой мы задаём сами правила маркировки трафика.

    auto eno1:2
    iface eno1:2 inet static
    address XX.XX.XX.X2
    netmask 255.255.255.0
            post-up ip rule add table eno1t from XX.XX.XX.X2
            post-up ip rule add table eno1t to XX.XX.XX.X2

    Этот блок задаёт второй IPv4 для интерфейса eno1.

    ip route add 10.8.0.0/24 dev tun0 src XX.XX.XX.X1 table eno1t

    Этой командой мы задаём route от клиентов OpenVPN до локальных IPv4 кроме XX.XX.XX.X0.
    Почему этой команды достаточно для всех IPv4 — я до сих пор не понимаю.

    iface eno1 inet6 static
            address XXXX:XXXX:XXXX:XXXX:1:1::/64
            gateway XXXX:XXXX:XXXX:XXXX::1

    Это мы задаём адрес для самого интерфейса. Сервер его будет использовать как «исходящий» адрес. Больше никак использоваться не будет.

    Почему указано ":1:1::" так сложно? Чтобы OpenVPN работало правильно и только для этого. Об этом подробнее позже.

    На тему gateway — так работает и ладно. Но по правильному — сюда надо указать IPv6 свича к которому подсоединён сервер.

    Однако почему-то IPv6 перестаёт работать, если я так делаю. Наверное это заморочки tech.ru какие-то.

    ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:1:1:1/64 dev $IFACE

    Это добавление IPv6 адреса на интерфейс. Если надо сотню адресов — значит сотню строк в этом файле.

    iface eno1 inet6 static
            address XXXX:XXXX:XXXX:XXXX:1:1::/64
    ...
    iface eno2 inet6 static
            address XXXX:XXXX:XXXX:XXXX:1:2::/96
    ...
    iface tun0 inet6 static
            address XXXX:XXXX:XXXX:XXXX:1:3::/80

    Отметил адреса и подсети всех интерфейсов, чтобы было наглядно.
    eno1 — обязательно должно быть "/64" — потому что это весь наш pool адресов.
    tun0 — подсеть должны быть обязательно больше eno1. Иначе нельзя будет настроить IPv6 gateway для клиентов OpenVPN.
    eno2 — подсеть должны быть обязательно больше tun0. Иначе клиенты OpenVPN не смогут попасть на IPv6 адреса локальные.
    Для наглядности я выбрал шаг подсети 16, но при желании можно даже «1» шаг делать.
    Соответственно 64+16 = 80, а 80+16 = 96.

    Для ещё большей наглядности:
    XXXX:XXXX:XXXX:XXXX:1:1:YYYY:YYYY — это адреса, которые должны быть назначены конкретным сайтам или сервисам на интерфейсе eno1.
    XXXX:XXXX:XXXX:XXXX:1:2:YYYY:YYYY — это адреса, которые должны быть назначены конкретным сайтам или сервисам на интерфейсе eno2.
    XXXX:XXXX:XXXX:XXXX:1:3:YYYY:YYYY — это адреса, которые должны быть назначены клиентам OpenVPN или использоваться как служебные адреса OpenVPN.


    Для настройки сети — должна быть возможность перезагружать сервер.
    IPv4 изменения подхватываются при выполнении (обязательно завернуть в screen — иначе эта команда просто уронит сеть на сервере):

    /etc/init.d/networking restart

    В файл "/etc/iproute2/rt_tables" добавить в конец:

    100 eno1t
    101 eno2t

    Без этого нельзя использовать кастомные table в файле "/etc/network/interfaces".
    Цифры должны быть уникальные и менее 65535.

    IPv6 изменения изменяются легко без перезагрузки, но для этого нужно научиться как минимум трём командам:

    ip -6 addr ...
    ip -6 route ...
    ip -6 neigh ...

    Настройка "/etc/sysctl.conf"

    # Uncomment the next line to enable packet forwarding for IPv4
    net.ipv4.ip_forward = 1
    
    # Do not accept ICMP redirects (prevent MITM attacks)
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv6.conf.all.accept_redirects = 0
    
    # Do not send ICMP redirects (we are not a router)
    net.ipv4.conf.all.send_redirects = 0
    
    # For receiving ARP replies
    net.ipv4.conf.all.arp_filter = 0
    net.ipv4.conf.default.arp_filter = 0
    
    # For sending ARP
    net.ipv4.conf.all.arp_announce = 0
    net.ipv4.conf.default.arp_announce = 0
    
    # Enable IPv6
    net.ipv6.conf.all.disable_ipv6 = 0
    net.ipv6.conf.default.disable_ipv6 = 0
    net.ipv6.conf.lo.disable_ipv6 = 0
    
    # IPv6 configuration
    net.ipv6.conf.all.autoconf = 1
    net.ipv6.conf.all.accept_ra = 0
    
    # For OpenVPN
    net.ipv6.conf.all.forwarding = 1
    net.ipv6.conf.all.proxy_ndp = 1
    
    # For nginx on boot
    net.ipv6.ip_nonlocal_bind = 1

    Это настройки «sysctl» моего сервера. Отмечу важное.

    net.ipv4.ip_forward = 1

    Без этого OpenVPN не будет работать никак.

    net.ipv6.ip_nonlocal_bind = 1

    Любой, кто попытается сделать bind IPv6 (например nginx) сразу после того, как интерфейс подялся — получит ошибку. Что такой адрес недоступен.

    Чтобы избежать такой ситуации и делается такая настройка.

    net.ipv6.conf.all.forwarding = 1
    net.ipv6.conf.all.proxy_ndp = 1

    Без этих настроек IPv6 трафик от клиента OpenVPN не выходит в мир.

    Другие настройки или не относятся к делу или я не помню зачем они.
    Но на всякий случай оставляю «как есть».

    Для того, чтобы изменения этого файла подхватились без перезагрузки сервера — надо выполнить команду:

    sysctl -p

    Более детально про «table» правила: habr.com/post/108690

    ============= OpenVPN =============


    OpenVPN IPv4 не работает без iptables.

    У меня iptables вот такие для VPN:

    iptables -A INPUT -p udp -s YY.YY.YY.YY --dport 1194 -j ACCEPT
    iptables -A FORWARD -i tun0 -o eno1 -j ACCEPT
    iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j SNAT --to-source XX.XX.XX.X0
    ##iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j MASQUERADE
    iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
    iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
    iptables -A INPUT -p udp --dport 1194 -j DROP
    iptables -A FORWARD -p udp --dport 1194 -j DROP

    YY.YY.YY.YY — это мой статический IPv4 адрес локальной машины.
    10.8.0.0/24 — IPv4 сеть openvpn. IPv4 адреса для клиентов openvpn.
    Последовательность правил важна.

    iptables -A INPUT -p udp -s YY.YY.YY.YY --dport 1194 -j ACCEPT
    iptables -A FORWARD -i tun0 -o eno1 -j ACCEPT
    ...
    iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
    iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
    iptables -A INPUT -p udp --dport 1194 -j DROP
    iptables -A FORWARD -p udp --dport 1194 -j DROP

    Это ограничение, чтобы только я со своего статического IP мог бы воспользоваться OpenVPN.

    iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j SNAT --to-source XX.XX.XX.X0
      -- или --
    iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j MASQUERADE

    Для пробрасывания IPv4 пакетов между клиентами OpenVPN и интернетом — нужно прописать одну из этих команд.

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

    Чтобы все настройки iptables подхватывались после reboot — надо сохранить их куда-то.

    iptables-save > /etc/iptables/rules.v4
    ip6tables-save > /etc/iptables/rules.v6

    Такие имена выбраны не случайно. Их использует пакет «iptables-persistent».

    apt-get install iptables-persistent

    Установка основного пакета OpenVPN:

    apt-get install openvpn easy-rsa

    Настроим шаблон для сертификатов (подставить свои значения):

    make-cadir ~/openvpn-ca
    cd ~/openvpn-ca
    ln -s openssl-1.0.0.cnf openssl.cnf

    Отредактируем настройки шаблона сертификатов:

    mcedit vars

    ...
    # These are the default values for fields
    # which will be placed in the certificate.
    # Don't leave any of these fields blank.
    export KEY_COUNTRY="RU"
    export KEY_PROVINCE="Krasnodar"
    export KEY_CITY="Dinskaya"
    export KEY_ORG="Own"
    export KEY_EMAIL="admin@domain1.com"
    export KEY_OU="VPN"
    
    # X509 Subject Field
    export KEY_NAME="server"
    ...

    Создаём серверный сертификат:

    cd ~/openvpn-ca
    source vars
    ./clean-all
    ./build-ca
    ./build-key-server server
    ./build-dh
    openvpn --genkey --secret keys/ta.key

    Приготовим возможность создавать итоговые «client-name.opvn» файлы:

    mkdir -p ~/client-configs/files
    chmod 700 ~/client-configs/files
    cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf ~/client-configs/base.conf
    mcedit ~/client-configs/base.conf

    # Client mode
    client
    
    # Interface tunnel type
    dev tun
    
    # TCP protocol
    proto tcp-client
    
    # Address/Port of VPN server
    remote XX.XX.XX.X0 1194
    
    # Don't bind to local port/address
    nobind
    
    # Don't need to re-read keys and re-create tun at restart
    persist-key
    persist-tun
    
    # Remote peer must have a signed certificate
    remote-cert-tls server
    ns-cert-type server
    
    # Enable compression
    comp-lzo
    
    # Custom
    ns-cert-type server
    tls-auth ta.key 1
    cipher DES-EDE3-CBC

    Приготовим скрипт, который будет сшивать все файлы в единый opvn файл.

    mcedit ~/client-configs/make_config.sh
    chmod 700 ~/client-configs/make_config.sh

    #!/bin/bash
    
    # First argument: Client identifier
    
    KEY_DIR=~/openvpn-ca/keys
    OUTPUT_DIR=~/client-configs/files
    BASE_CONFIG=~/client-configs/base.conf
    
    cat ${BASE_CONFIG} \
        <(echo -e '<ca>') \
        ${KEY_DIR}/ca.crt \
        <(echo -e '</ca>\n<cert>') \
        ${KEY_DIR}/${1}.crt \
        <(echo -e '</cert>\n<key>') \
        ${KEY_DIR}/${1}.key \
        <(echo -e '</key>\n<tls-auth>') \
        ${KEY_DIR}/ta.key \
        <(echo -e '</tls-auth>') \
        > ${OUTPUT_DIR}/${1}.ovpn

    Создаём первого клиента OpenVPN:

    cd ~/openvpn-ca
    source vars
    ./build-key client-name
    cd ~/client-configs
    ./make_config.sh client-name

    Файл "~/client-configs/files/client-name.ovpn" отправляем на утройство клиенту.

    Для iOS клиентов надо будет сделать трюк:
    Содержимое тэга «tls-auth» должно быть без комментариев.
    А также поставить «key-direction 1» сразу перед тэгом «tls-auth».

    Настроим конфиг OpenVPN сервера:

    cd ~/openvpn-ca/keys
    cp ca.crt ca.key server.crt server.key ta.key dh2048.pem /etc/openvpn
    gunzip -c /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz | tee /etc/openvpn/server.conf
    mcedit /etc/openvpn/server.conf

    # Listen port
    port 1194
    
    # Protocol
    proto tcp-server
    
    # IP tunnel
    dev tun0
    tun-ipv6
    push tun-ipv6
    
    # Master certificate
    ca ca.crt
    
    # Server certificate
    cert server.crt
    
    # Server private key
    key server.key
    
    # Diffie-Hellman parameters
    dh dh2048.pem
    
    # Allow clients to communicate with each other
    client-to-client
    
    # Client config dir
    client-config-dir /etc/openvpn/ccd
    
    # Run client-specific script on connection and disconnection
    script-security 2
    client-connect "/usr/bin/sudo -u root /etc/openvpn/server-clientconnect.sh"
    client-disconnect "/usr/bin/sudo -u root /etc/openvpn/server-clientdisconnect.sh"
    
    # Server mode and client subnets
    server 10.8.0.0 255.255.255.0
    server-ipv6 XXXX:XXXX:XXXX:XXXX:1:3::/80
    topology subnet
    
    # IPv6 routes
    push "route-ipv6 XXXX:XXXX:XXXX:XXXX::/64"
    push "route-ipv6 2000::/3"
    
    # DNS (for Windows)
    # These are OpenDNS
    push "dhcp-option DNS 208.67.222.222"
    push "dhcp-option DNS 208.67.220.220"
    
    # Configure all clients to redirect their default network gateway through the VPN
    push "redirect-gateway def1 bypass-dhcp"
    push "redirect-gateway ipv6" #For iOS
    
    # Don't need to re-read keys and re-create tun at restart
    persist-key
    persist-tun
    
    # Ping every 10s. Timeout of 120s.
    keepalive 10 120
    
    # Enable compression
    comp-lzo
    
    # User and group
    user vpn
    group vpn
    
    # Log a short status
    status openvpn-status.log
    
    # Logging verbosity
    ##verb 4
    
    # Custom config
    tls-auth ta.key 0
    cipher DES-EDE3-CBC

    Это нужно для того, чтобы задать статический адрес каждому клиенту (не обязательно, но я использую):

    # Client config dir
    client-config-dir /etc/openvpn/ccd

    Самая сложная и ключевая деталь.

    К сожалению OpenVPN ещё не умеет самостоятельно настраивать IPv6 gateway для клиентов.
    Приходится «вручную» пробрасывать это для каждого клиента.

    # Run client-specific script on connection and disconnection
    script-security 2
    client-connect "/usr/bin/sudo -u root /etc/openvpn/server-clientconnect.sh"
    client-disconnect "/usr/bin/sudo -u root /etc/openvpn/server-clientdisconnect.sh"

    Файл "/etc/openvpn/server-clientconnect.sh":

    #!/bin/sh
    
    # Check client variables
    if [ -z "$ifconfig_pool_remote_ip" ] || [ -z "$common_name" ]; then
            echo "Missing environment variable."
            exit 1
    fi
    
    # Load server variables
    . /etc/openvpn/variables
    
    ipv6=""
    
    # Find out if there is a specific config with fixed IPv6 for this client
    if [ -f "/etc/openvpn/ccd/$common_name" ]; then
            # Get fixed IPv6 from client config file
            ipv6=$(sed -nr 's/^.*ifconfig-ipv6-push[ \t]+([0-9a-fA-F\\:]+).*$/\1/p' "/etc/openvpn/ccd/$common_name")
            echo $ipv6
    fi
    
    # Get IPv6 from IPv4
    if [ -z "$ipv6" ]; then
            ipp=$(echo "$ifconfig_pool_remote_ip" | cut -d. -f4)
            if ! [ "$ipp" -ge 2 -a "$ipp" -le 254 ] 2>/dev/null; then
                    echo "Invalid IPv4 part."
                    exit 1
            fi
            hexipp=$(printf '%x' $ipp)
            ipv6="$prefix$hexipp"
    fi
    
    # Create proxy rule
    /sbin/ip -6 neigh add proxy $ipv6 dev eno1


    Файл "/etc/openvpn/server-clientdisconnect.sh":
    #!/bin/sh
    
    # Check client variables
    if [ -z "$ifconfig_pool_remote_ip" ] || [ -z "$common_name" ]; then
            echo "Missing environment variable."
            exit 1
    fi
    
    # Load server variables
    . /etc/openvpn/variables
    
    ipv6=""
    
    # Find out if there is a specific config with fixed IPv6 for this client
    if [ -f "/etc/openvpn/ccd/$common_name" ]; then
            # Get fixed IPv6 from client config file
            ipv6=$(sed -nr 's/^.*ifconfig-ipv6-push[ \t]+([0-9a-fA-F\\:]+).*$/\1/p' "/etc/openvpn/ccd/$common_name")
    fi
    
    # Get IPv6 from IPv4
    if [ -z "$ipv6" ]; then
            ipp=$(echo "$ifconfig_pool_remote_ip" | cut -d. -f4)
            if ! [ "$ipp" -ge 2 -a "$ipp" -le 254 ] 2>/dev/null; then
                    echo "Invalid IPv4 part."
                    exit 1
            fi
            hexipp=$(printf '%x' $ipp)
            ipv6="$prefix$hexipp"
    fi
    
    # Delete proxy rule
    /sbin/ip -6 neigh del proxy $ipv6 dev eno1

    Оба скрипта используют файл "/etc/openvpn/variables":

    # Subnet
    prefix=XXXX:XXXX:XXXX:XXXX:2:
    # netmask
    prefixlen=112

    Почему тут так написано — затрудняюсь вспомнить.

    Сейчас выглядит странным netmask = 112 (тут же 96 должен быть).
    И prefix странный, не соответствует сети tun0.
    Но ладно, оставляю «как есть».

    cipher DES-EDE3-CBC

    Это на любителя — я выбрал такой способ шифрования соединения.

    Более детально про настройку OpenVPN IPv4.

    Более детально про настройку OpenVPN IPv6.

    ============= Postfix =============


    Установка основного пакета:

    apt-get install postfix

    При установке выбрать «internet-site».

    Мой "/etc/postfix/main.cf" выглядит так:

    smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
    biff = no
    
    # appending .domain is the MUA's job.
    append_dot_mydomain = no
    
    readme_directory = no
    
    # See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
    # fresh installs.
    compatibility_level = 2
    
    # TLS parameters
    smtpd_tls_cert_file=/etc/ssl/domain1.com.2018.chained.crt
    smtpd_tls_key_file=/etc/ssl/domain1.com.2018.key
    smtpd_use_tls=yes
    smtpd_tls_auth_only = yes
    smtp_bind_address = XX.XX.XX.X0
    smtp_bind_address6 = XXXX:XXXX:XXXX:XXXX:1:1:1:1
    
    smtp_tls_security_level = may
    smtp_tls_ciphers = export
    smtp_tls_protocols = !SSLv2, !SSLv3
    smtp_tls_loglevel = 1
    
    smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
    myhostname = domain1.com
    alias_maps = hash:/etc/aliases
    alias_database = hash:/etc/aliases
    myorigin = domain1.com
    mydestination = localhost
    relayhost =
    mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
    mailbox_size_limit = 0
    recipient_delimiter = +
    inet_interfaces = all
    inet_protocols = ipv4
    
    internal_mail_filter_classes = bounce
    
    # Storage type
    virtual_transport = lmtp:unix:private/dovecot-lmtp
    virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
    virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
    virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
    
    # SMTP-Auth settings
    smtpd_sasl_type = dovecot
    smtpd_sasl_path = private/auth
    smtpd_sasl_auth_enable = yes
    smtpd_recipient_restrictions =
            permit_sasl_authenticated,
            permit_mynetworks,
            #reject_invalid_hostname,
            #reject_unknown_recipient_domain,
            reject_unauth_destination,
            reject_rbl_client sbl.spamhaus.org,
            check_policy_service unix:private/policyd-spf
    
    smtpd_helo_restrictions =
            #reject_invalid_helo_hostname,
            #reject_non_fqdn_helo_hostname,
            reject_unknown_helo_hostname
    
    smtpd_client_restrictions =
            permit_mynetworks,
            permit_sasl_authenticated,
            reject_non_fqdn_helo_hostname,
            permit
    
    # SPF
    policyd-spf_time_limit = 3600
    
    # OpenDKIM
    milter_default_action = accept
    milter_protocol = 6
    smtpd_milters = unix:var/run/opendkim/opendkim.sock
    non_smtpd_milters = unix:var/run/opendkim/opendkim.sock
    
    # IP address per domain
    sender_dependent_default_transport_maps = pcre:/etc/postfix/sdd_transport.pcre

    Рассмотрим детали этого конфига.

    smtpd_tls_cert_file=/etc/ssl/domain1.com.2018.chained.crt
    smtpd_tls_key_file=/etc/ssl/domain1.com.2018.key



    По мнению хабровчан данный блок содержит `дезинформацию и неверные тезисы`.
    Только спустя 8 лет после начала моей карьеры я стал понимать как работает SSL.

    Поэтому я возьму на себя смелость описать как пользоваться SSL (не отвечая на вопросы «Как это работает?» и «Почему это работает?»).

    Основа современного шифрования — это создание пары ключей (две очень длинные строки символов).

    Один «ключ» приватный, другой ключ «публичный». Приватный ключ храним очень старательно в секрете. Публичный ключ раздаём всем желающим.

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

    Шаг №1 — https сайты.
    Браузер при обращении к сайту узнаёт от веб сервера, что сайт https и поэтому запрашивает публичный ключ.
    Веб сервер отдаёт публичный ключ. Браузер используя публичный ключ зашифровывает http-request и отправляет его.
    Контент http-request может прочитать только тот у кого есть приватный ключ, то есть только сервер к которому выполняется обращение.
    Http-request содержит в себе как минимум URI. Поэтому если в стране пытыются ограничить доступ не ко всему сайту, а к конкретной странице — то для https сайтов это сделать невозможно.

    Шаг №2 — зашифрованный ответ.
    Веб сервер даёт ответ, который легко могут прочитать по дороге.
    Решение предельно простое — браузер у себя локально формирует такую-же пару приватный-публичный ключ для каждого https сайта.
    И вместе с запросом публичного ключа сайта отправляет свой локальный публичный ключ.
    Веб сервер запоминает его и при отправке http-response шифрует этим вот публичным ключём конкретного клиента.
    Теперь http-response может расшифровать только обладатель приватного ключа браузера клиента (то есть сам клиент).

    Шаг №3 — установка защищённого соединения по публичному каналу.
    В примере №2 есть уязвимость — ничего не мешает доброжелателям перехватить http-request и подредактировать информацию о публичном ключе.
    Таким образом посредник будет пракрасно видеть весь контент отправляемых-получаемых сообщений пока не сменится канал связи.
    Бороться с этим предельно просто — достаточно отправить публичный ключ браузера как сообщение зашифрованное публичным ключём веб сервера.
    Веб сервер тогда первым делом отправляет ответ типа «твой публичный ключ вот такой вот» и шифрует это сообщение этим же публичным ключём.
    Браузер смотрит ответ — если пришло сообщение «твой публичный ключ вот такой вот» — то это 100% гарантия, что данный канал связи безопасен.
    Насколько безопасен?
    Само создание такого безопасного канала связи происходит со скоростью ping*2. Например 20мс.
    Злоумышленник должен или заранее иметь приватный ключ одной из сторон. Или подобрать приватный ключ за пару милисекунд.
    Взлом одного современного приватнога ключа займт десятилетия на суперкомпьютере.

    Шаг №4 — публичная БД публичных ключей.
    Очевидно, что во всей этой истории существует возможность для злоумышленика сидящего на канале связи между клиентом и сервером.
    Возможность клиенту предствится сервером, а серверу представться клиентом. И сэмулировать пару ключей в обе стороны.
    Тогда злоумышленик будет видеть весь трафик и будет иметь возможность «подредактировать» трафик.
    Например изменить адрес куда отправлять деньги или скопировать пароль от онлайн-банка или заблокировать «неугодный» контент.
    Для борьбы с такими злоумышленниками придумали публичную БД с публичными ключами для каждого https сайта.
    Каждый браузер «знает» о существовании около 200 таких БД. Это предустановлено в каждый браузер.
    «Знание» подкреплено публичным ключём от каждого сертификата. То есть соединение с каждым конкретным центром сертификации подделать невозможно.

    Теперь есть простое понимание как пользоваться SSL для https.
    Если пошевелить мозгами — то станет понятно, как спец-службы могут в этой конструкции чего-то взломать. Но это им будет стоить чудовищных усилий.
    А организациям меньше АНБ или ЦРУ — практически невозможно взломать существующий уровень защиты даже для vip.

    Ещё добавлю про ssh соединения. Там никаких публичных ключей нет, как же быть. Вопрос решается двумя способами.
    Вариант ssh-по-паролю:
    При первом соединении ssh-клиент должен предупредить, что тут у нас новый публичный ключ от ssh-сервера.
    И при дальнейших соединениях если появилось предупреждение «новый публичный ключ от ssh-сервера» — будет означать, что вас пытаются прослушать.
    Или при первом соединении вас прослушивали, а теперь вы общаетесь с сервером без посредников.
    Собственно из-за того, что факт прослушки легко, быстро и без усилий вскрывается — этой атакой пользуются только в особых случаях под конкретного клиента.

    Вариант ssh-по-ключу:
    Берём флэшку, записываем на неё приватный ключ для ssh-сервера (для этого есть термины и куча нюансов существенных, но я пишу ликбез, а не инструкцию по применению).
    Публичный ключ оставляем на машине где будет ssh-клиент и его тоже держим в секрете.
    Приносим флэшку к серверу, вставляем, копируем приватный ключ, а флэшку сжигаем и развеиваем прах по ветру (или хотябы форматируем с заполнением нулями).
    Вот и всё — после такой операции будет невозможно взломать такое ssh соединение. Разумеется лет за 10 на суперкомпьютере можно будет посмотреть трафик — но это отдельная история.

    Прошу прощения за оффтоп.
    Итак, теперь когда известна теория. Расскажу про flow создания ssl сертификата.

    При помощи «openssl genrsa» мы создаём приватный ключ и «заготовок» для публичного ключа.
    «заготовок» отправляем сторонней компании, которой мы платим примерно $9 за самый простой сертификат.

    Через пару часов мы получаем от этой сторонней компании наш «публичный» ключ и ещё набор нескольких публичных ключей.

    Зачем сторонней компании платить за оформление моего публичного ключа — вопрос отдельный, тут рассматривать не будем.

    Теперь понятно в чём смысл надписи:

    smtpd_tls_key_file=/etc/ssl/domain1.com.2018.key

    В папке "/etc/ssl" сложены все файлы для ssl вопросов.
    domain1.com — название домена.
    2018 — год создания ключей.
    «key» — обозначение, что файл приватный-ключ.

    И смысл этого файла:

    smtpd_tls_cert_file=/etc/ssl/domain1.com.2018.chained.crt
    domain1.com — название домена.
    2018 — год создания ключей.
    chained — обозначение, что тут цепочка публичных ключей (первый — наш публичный и остальные — что пришло от компании оформившей публичный ключ).
    crt — обозначение, что тут готовый сертификат (публичный ключ с пояснениями техническими).

    smtp_bind_address = XX.XX.XX.X0
    smtp_bind_address6 = XXXX:XXXX:XXXX:XXXX:1:1:1:1

    Это установка в данном случае не используется, но написано для примера.

    Потому что ошибка в данном параметре приведёт к отправке от вашего сервера спама (без вашей воли).

    Потом доказывайте всем, что вы не виноваты.

    recipient_delimiter = +

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

    Например если у вас есть почтовый ящик «username@gmail.com» попробуйте отправть на «username+spam@gmail.com» — посмотрите что из этого получится.

    inet_protocols = ipv4

    Возможно это будет сбивать с толку.

    Но это не просто так. Каждый новый домен — по умолчанию только IPv4, потом включаю IPv6 для каждого в отдельности.

    virtual_transport = lmtp:unix:private/dovecot-lmtp
    virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
    virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
    virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf

    Тут мы задаём, что вся входящая почта уходит в dovecot.
    А правила для domain, mailbox, alias — смотреть в БД.

    /etc/postfix/mysql-virtual-mailbox-domains.cf

    user = usermail
    password = mailpassword
    hosts = 127.0.0.1
    dbname = servermail
    query = SELECT 1 FROM virtual_domains WHERE name='%s'

    /etc/postfix/mysql-virtual-mailbox-maps.cf

    user = usermail
    password = mailpassword
    hosts = 127.0.0.1
    dbname = servermail
    query = SELECT 1 FROM virtual_users WHERE email='%s'

    /etc/postfix/mysql-virtual-alias-maps.cf

    user = usermail
    password = mailpassword
    hosts = 127.0.0.1
    dbname = servermail
    query = SELECT destination FROM virtual_aliases WHERE source='%s'

    # SMTP-Auth settings
    smtpd_sasl_type = dovecot
    smtpd_sasl_path = private/auth
    smtpd_sasl_auth_enable = yes

    Теперь postfix знает, что принимать почту для дальнейшей отправки можно только по авторизации с dovecot.

    Мне правда не очень понятно, зачем это тут дублировать. Мы же уже указали в «virtual_transport» всё что надо.

    Но postfix система очень старая — наверное это кастыли от старых времён.

    smtpd_recipient_restrictions =
            ...
    
    smtpd_helo_restrictions =
            ...
    
    smtpd_client_restrictions =
            ...

    Это настраивать для каждого почтового сервера по своему.

    В моём распоряжении есть 3 почтовых сервера и эти настройки очень разные из-за разных требований к использованию.

    Настраивать надо внимательно — иначе спам хлынет к вам или ещё хуже — спам хлынет от вас.

    # SPF
    policyd-spf_time_limit = 3600

    Настройка для какого-то плагина связанного с проверкой SPF входящих писем.

    # OpenDKIM
    milter_default_action = accept
    milter_protocol = 6
    smtpd_milters = unix:var/run/opendkim/opendkim.sock
    non_smtpd_milters = unix:var/run/opendkim/opendkim.sock

    Настройка, что все исходящие письма мы должны снабжать DKIM подписью.

    # IP address per domain
    sender_dependent_default_transport_maps = pcre:/etc/postfix/sdd_transport.pcre

    Это ключевая деталь в маршрутизации писем при оотправки писем от php скриптов.

    Файл "/etc/postfix/sdd_transport.pcre":

    /^www-domain1@domain1\.com$/ domain1:
    /^www-domain2@domain1\.com$/ domain2:
    /^www-domain3@domain1\.com$/ domain3:
    /@domain1\.com$/             domain1:
    /@domain2\.com$/             domain2:
    /@domain3\.com$/             domain3:

    Слева — регулярные выражения. Справа — метка, которой отмечается письмо.
    Postfix в соответствии с меткой — учтёт ещё несколько строк конфигурации для конкретного письма.

    Как именно будет переконфигурирован postfix для конкретного письма — будет указано в «master.cf».

    Строки 4, 5, 6 — они главные. От имени какого домена отправляем письмо — такую метку и ставим.
    Но не всегда в php скриптах в старом коде указывается поле «from». Тогда на помощь приходит имя пользователя.

    Статья и так обширная — не хотелось бы отвлекаться на настройку nginx+fpm.

    Кратко — мы для каждого сайта задаём своего linux-user владельца. И соответственно свой fpm-pool.

    Fpm-pool использует любую версию php (это прекрасно когда на одном сервере без проблем для соседних сайтов можно использовать разную версию php и даже разный php.ini).

    Так вот у конкретного linux-user «www-domain2» есть сайт domain2.com. На этом сайте есть код отправки писем без указания поля from.

    Так вот даже в таком случае письма будут уходить корректно и никогда не попадут в спам.
    Мой "/etc/postfix/master.cf" выглядит так:

    ...
    smtp      inet  n       -       y       -       -       smtpd
      -o content_filter=spamassassin
    ...
    submission inet n       -       y       -       -       smtpd
      -o syslog_name=postfix/submission
      -o smtpd_tls_security_level=encrypt
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_client_restrictions=permit_sasl_authenticated,reject
    ...
    policyd-spf  unix  -       n       n       -       0       spawn
        user=policyd-spf argv=/usr/bin/policyd-spf
    
    spamassassin unix -     n       n       -       -       pipe
        user=spamd argv=/usr/bin/spamc -f -e
        /usr/sbin/sendmail -oi -f ${sender} ${recipient}
    ...
    domain1  unix -       -       n       -       -       smtp
       -o smtp_bind_address=XX.XX.XX.X1
       -o smtp_helo_name=domain1.com
       -o inet_protocols=all
       -o smtp_bind_address6=XXXX:XXXX:XXXX:XXXX:1:1:1:1
       -o syslog_name=postfix-domain1
    
    domain2  unix -       -       n       -       -       smtp
       -o smtp_bind_address=XX.XX.XX.X5
       -o smtp_helo_name=domain2.com
       -o inet_protocols=all
       -o smtp_bind_address6=XXXX:XXXX:XXXX:XXXX:1:2:1:1
       -o syslog_name=postfix-domain2
    
    domain3  unix -       -       n       -       -       smtp
       -o smtp_bind_address=XX.XX.XX.X2
       -o smtp_helo_name=domain3
       -o inet_protocols=all
       -o smtp_bind_address6=XXXX:XXXX:XXXX:XXXX:1:1:5:1
       -o syslog_name=postfix-domain3

    Файл приведён не полностью — он и так очень большой.
    Отметил только то, что изменено.

    smtp      inet  n       -       y       -       -       smtpd
      -o content_filter=spamassassin
    ...
    spamassassin unix -     n       n       -       -       pipe
        user=spamd argv=/usr/bin/spamc -f -e
        /usr/sbin/sendmail -oi -f ${sender} ${recipient}

    Это настройки связанные со spamassasin, о нём позже.

    submission inet n       -       y       -       -       smtpd
      -o syslog_name=postfix/submission
      -o smtpd_tls_security_level=encrypt
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_client_restrictions=permit_sasl_authenticated,reject

    Разрешаем присоединяться к почтовому серверу через 587 порт.
    Для этого обязательно авторизоваться.

    policyd-spf  unix  -       n       n       -       0       spawn
        user=policyd-spf argv=/usr/bin/policyd-spf

    Включаем проверку SPF.

    apt-get install postfix-policyd-spf-python

    Установим пакет для SPF проверок выше.

    domain1  unix -       -       n       -       -       smtp
       -o smtp_bind_address=XX.XX.XX.X1
       -o smtp_helo_name=domain1.com
       -o inet_protocols=all
       -o smtp_bind_address6=XXXX:XXXX:XXXX:XXXX:1:1:1:1
       -o syslog_name=postfix-domain1

    А это самое интересное. Это возможность отправлять письма для конкретного домена с конкретного IPv4/IPv6 адреса.

    Делается это ради rDNS. rDNS — это получение какой-то строки по IP адресу.
    И для почты эта возможность используется для подтверждения того, что helo точно соответствует rDNS того адреса, с которого отправили email.

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

    Helo не соответствует rDNS — начисляется много спам очков.
    Соответственно для каждого домена должен быть свой IP адрес.
    Для OVH — в консольке есть возможность указывать rDNS.
    Для tech.ru — через саппорт вопрос решается.
    Для AWS — через саппорт вопрос решается.
    «inet_protocols» и «smtp_bind_address6» — это мы включаем поддержку IPv6.
    Для IPv6 тоже надо rDNS прописывать.
    «syslog_name» — а это для удобства чтения логов.
    Покупать сертификаты рекомендую тут.

    Настройка связки postfix+dovecot тут.

    Настройка SPF.

    ============= Dovecot =============


    apt-get install dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql dovecot-antispam

    Настройка mysql, устанавливаем сами пакеты.

    Файл "/etc/dovecot/conf.d/10-auth.conf"

    disable_plaintext_auth = yes
    auth_mechanisms = plain login

    Авторизация только в зашифрованном виде.

    Файл "/etc/dovecot/conf.d/10-mail.conf"

    mail_location = maildir:/var/mail/vhosts/%d/%n

    Тут укажем место хранения писем.

    Я хочу, чтобы они хранились в файлах и были сгруппированы по доменам.

    Файл "/etc/dovecot/conf.d/10-master.conf"

    service imap-login {
      inet_listener imap {
        port = 0
      }
      inet_listener imaps {
        address = XX.XX.XX.X1, XX.XX.XX.X2, XX.XX.XX.X5, [XXXX:XXXX:XXXX:XXXX:1:1:1:1], [XXXX:XXXX:XXXX:XXXX:1:2:1:1], [XXXX:XXXX:XXXX:XXXX:1:1:5:1]
        port = 993
        ssl = yes
      }
    }
    service pop3-login {
      inet_listener pop3 {
        port = 0
      }
      inet_listener pop3s {
        address = XX.XX.XX.X1, XX.XX.XX.X2, XX.XX.XX.X5, [XXXX:XXXX:XXXX:XXXX:1:1:1:1], [XXXX:XXXX:XXXX:XXXX:1:2:1:1], [XXXX:XXXX:XXXX:XXXX:1:1:5:1]
        port = 995
        ssl = yes
      }
    }
    service lmtp {
      unix_listener /var/spool/postfix/private/dovecot-lmtp {
        mode = 0600
        user = postfix
        group = postfix
      }
    }
    service imap {
    }
    service pop3 {
    }
    service auth {
      unix_listener auth-userdb {
        mode = 0600
        user = vmail
      }
    
      unix_listener /var/spool/postfix/private/auth {
        mode = 0666
        user = postfix
        group = postfix
      }
      user = dovecot
    }
    service auth-worker {
      user = vmail
    }
    service dict {
      unix_listener dict {
      }
    }

    Это главный файл настроек dovecot.
    Тут мы отключаем не защищённые соединения.
    И включаем защищённые соединения.

    Файл "/etc/dovecot/conf.d/10-ssl.conf"

    ssl = required
    ssl_cert = </etc/nginx/ssl/domain1.com.2018.chained.crt
    ssl_key = </etc/nginx/ssl/domain1.com.2018.key
    local XX.XX.XX.X5 {
      ssl_cert = </etc/nginx/ssl/domain2.com.2018.chained.crt
      ssl_key =  </etc/nginx/ssl/domain2.com.2018.key
    }

    Настраиваем ssl. Указываем, что ssl — обязательно.
    И сам сертификат. И важная деталь — директива «local». Указывает, при соединении к какому локальному IPv4 — какой ssl сертификат использовать.

    Кстати IPv6 тут не настроен, исправлю это упущение как-нить потом.
    XX.XX.XX.X5 (domain2) — сертификата нет. Для соединения клиентов нужно указывать domain1.com.
    XX.XX.XX.X2 (domain3) — сертификат есть, для соединения клиентов можно указывать domain1.com или domain3.com .
    Файл "/etc/dovecot/conf.d/15-lda.conf"

    protocol lda {
      mail_plugins = $mail_plugins sieve
    }

    Это в дальнейшем нужно будет для spamassassin.

    Файл "/etc/dovecot/conf.d/20-imap.conf"

    protocol imap {
      mail_plugins = $mail_plugins antispam
    }

    Это antispam плагин. Нужен для обучения spamassasin в момент переноса в/из папки «Spam».

    Файл "/etc/dovecot/conf.d/20-pop3.conf"

    protocol pop3 {
    }

    Просто такой файл есть.

    Файл "/etc/dovecot/conf.d/20-lmtp.conf"

    protocol lmtp {
      mail_plugins = $mail_plugins sieve
      postmaster_address = admin@domain1.com
    }

    Настройка lmtp.

    Файл "/etc/dovecot/conf.d/90-antispam.conf"

    plugin {
      antispam_backend = pipe
      antispam_trash = Trash;trash
      antispam_spam = Junk;Spam;SPAM
      antispam_pipe_program_spam_arg = --spam
      antispam_pipe_program_notspam_arg = --ham
      antispam_pipe_program = /usr/bin/sa-learn
      antispam_pipe_program_args = --username=%Lu
    }

    Настройки обучения spamassasin в момент переноса в/из папки «Spam».

    Файл "/etc/dovecot/conf.d/90-sieve.conf"

    plugin {
      sieve = ~/.dovecot.sieve
      sieve_dir = ~/sieve
      sieve_after = /var/lib/dovecot/sieve/default.sieve
    }

    Файл в котором указано что делать со входящими письмами.

    Файл "/var/lib/dovecot/sieve/default.sieve"

    require ["fileinto", "mailbox"];
    
    if header :contains "X-Spam-Flag" "YES" {
            fileinto :create "Spam";
    }

    Надо скомпилировать файл: «sievec default.sieve».

    Файл "/etc/dovecot/conf.d/auth-sql.conf.ext"

    passdb {
      driver = sql
      args = /etc/dovecot/dovecot-sql.conf.ext
    }
    userdb {
      driver = static
      args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
    }

    Указание sql файлов для авторизации.
    А сам файл — как способ авторизации.

    Файл "/etc/dovecot/dovecot-sql.conf.ext"

    driver = mysql
    connect = host=127.0.0.1 dbname=servermail user=usermail password=password
    default_pass_scheme = SHA512-CRYPT
    password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

    Это соответствует аналогичным настройкам для postfix.

    Файл "/etc/dovecot/dovecot.conf"
    protocols = imap lmtp pop3
    listen = *, ::
    dict {
    }
    !include conf.d/*.conf
    !include_try local.conf

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

    ============= SpamAssassin =============


    apt-get install spamassassin spamc

    Установим пакеты.

    adduser spamd --disabled-login

    Добавим пользователя от имени которого.

    systemctl enable spamassassin.service

    Включаем авто-загрузку spamassassin сервис при загрузке.

    Файл "/etc/default/spamassassin":

    CRON=1

    Включаев автоматическое обновление правил «по умолчанию».

    Файл "/etc/spamassassin/local.cf":

    report_safe 0
    
    use_bayes          1
    bayes_auto_learn   1
    bayes_auto_expire  1
    bayes_store_module Mail::SpamAssassin::BayesStore::MySQL
    bayes_sql_dsn      DBI:mysql:sa:localhost:3306
    bayes_sql_username sa
    bayes_sql_password password

    Нужно сделать в mysql БД «sa» с пользователем «sa» с паролем «password» (заменить на что-то адекватное).

    report_safe — это вместо письма будет присылаться отчёт о письме-спаме.
    use_bayes — это настройки машинного обучения spamassassin.

    Остальные настройки spamassassin применялись ранее по статье.

    Общая настройка «spamassassin».
    Про перемещение новых Спам-писем в IMAP папку «Spam».
    Про простую связку Dovecot + SpamAssassin.
    Рекомендую к прочтению теория обучения spamassasin при движении писем в imap папках (и не рекомендую к применению).

    ============= Обращение к сообществу =============


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

    Чтобы пользователь мог бы у себя на клиенте (outlook, thunderbird, browser-plugin, ...) создать пару ключей. Публичный и приватный. Публичный — отправить в DNS. Приватный — сохранять на клиенте. Почтовые сервера бы умели применять публичный ключ для отправки конкретному адресату.

    И для защиты от спама при таких письмах (да, почтовый сервер жи не сможет посмотреть контент) — надо будет ввести 3 правила:

    1. Обязательная настоящая подпись DKIM, обязательный SPF, обязательный rDNS.
    2. Нейронная сеть на тему обучения антиспама + БД к ней на стороне клиента.
    3. Алгоритм шифрования должен быть таким, что отправляющая сторона должна потратить на шифрования в 100 раз больше мощностей CPU, чем принимающая сторона.

    Кроме публичных писем — разработать стандарт письма-предложения «начать защищённую переписку». Один из пользователей (почтовый ящик) шлёт другому почтовому ящику письмо с аттачем. В письме текст-предложение начать защищённый канал связи для переписки и публичный ключ владельца почтового ящика (при этом приватный ключ на стороне клиента).

    Можно даже пару ключей делать специально для каждой переписки. Пользователь-получатель может принять это предложение и отправить свой публичный ключ (тоже сделанный специально для данной переписки). Далее первый пользователь отправляет служебное контрольное письмо (зашифрованное публичным ключом второго пользователя) — при получении которого второй пользователь может считать сформированный канал связи надёжным. Далее второй пользователь отправляет контрольное письмо — и тогда первый пользователь тоже может считать сформированный канал защищённым.

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

    И самое главное — чтобы это всё работало (вопрос «а кто за это заплатит?»):
    Ввести почтовые сертификаты стоимостью от 10$ за 3 года. Которые будут позволять отправителю указать в dns, что «мои публичные ключи находятся вон-там». И будут давать возможность начинать защищённое соединение. При этом — принимать такие соединения бесплатно.
    gmail наконец монетизирует своих пользователей. За 10$ в 3 года — право создавать защищённые каналы переписки.

    ============= Заключение =============


    Для тестирования всей статьи я собирался арендовать выделенный сервер на месяц и купить домен с ssl сертификатом.

    Но жизненные обстоятельства сложились так этот вопрос затянулся на 2 месяца.
    И вот когда появилось снова свободное время — решил публиковать статью как есть, а не рисковать тем, что публикация затянется ещё на год.

    Если будет достаточно много вопросов типа «а вот тут не достаточно подробно описано» — тогда наверное найдутся силы таки взять выделенный сервер с новым доменом и новым SSL сертификатом и ещё подробнее описать и главное — выявить все упущенные важные детали.

    Также хотелось бы получить отзывы на тему идеи про почтовые сертификаты. Если идея понравится — постараюсь найти силы написать черновик для rfc.

    При копировании больших кусков статьи — указывать ссылку на эту статью.
    При переводе на любой другой язык — указывать ссылку на эту статью.
    На английский язык я сам постараюсь перевести и оставлю перекрёстные ссылки.

    Комментарии 31

      +1
      Посмотрите mailcow.
        +6

        Почитайте RFC 5737, 3849. Не очень удобно читать howto, когда ip-адреса имеют вид xx.xx.xx.xx
        Зря вы добавили openvpn в статью. К почте он отношения не имеет. В итоге получалась какая-то каша из настройки маршрутизации, vpn-а и почты. Хотя бы пометьте какие настройки не нужны, если не нужен vpn.

        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Это конкретный случай, когда в системе есть несколько доменов и несколько физических интерфейсов. Поэтому про маршрутизацию было важно рассказать.

            Почему я решил включить openvpn? Потому что в РФ нет возможности получить IPv6 без проблем на конечную машину. А как тестировать IPv6 без openvpn в такой ситуации? Никак. Потому и включил его в статью.

            Спасибо за ссылки на RFC.
              +2

              Просто писать про vpn в статье про почту это жуткий оверкилл.
              В РФ нативно даёт Эртелеком и МТС (на мобильной сети), т.е. получить нативный ipv6 не так уж и сложно.
              Ну ладно, допустим у вашего isp его нет. Но есть же туннельные брокеры https://en.m.wikipedia.org/wiki/List_of_IPv6_tunnel_brokers (для тестов это более чем достаточно)
              Допустим, по какой-то причине и это не вариант. Тогда берёте самую дешёвую vps под это дело (рекомендую использовать lowendbox/lowendtalk для поиска) и тестирует с неё, а то у вас даже тест не совсем честный, по сути внутри хоста тестируете часть вещей.

            0
            Таки, если необходимо, выделенный сервер предоставить готов. И выделенный домен. Что касается сертификатов — как на счет летсенкриптов?
            И, если уж заморачиваться с собственным почтовиком, так это или здоровая параноя или корпоративные фишки, которые никто предоставить не может.
            Сможете запилить и я буду первым подписчиком вашего стартапа :)
              0
              Финансово легко сам потяну. Времени резко меньше стало свободного в последнее время.

              Собственный почтовый сервер имеет смысл ставить в корпоративных целях. Или когда вам катастрофически важна гарантия доставки писем и/или максимальный антиспам рейтинг.
                0

                Это всё до первого ддос и прочих атак, а потом вы начинаете прятаться за антиддос сервис (что вносит огромные коррективы ко всей схеме) или уходите на публичные провайдеры типа Microsoft (office365)

              –4
              Вот меня удивляет, что люди в 2019 году, по-прежнему, запускают сервисы не в docker/kuberntes
                +4
                Все люди разные. Некоторым нужны безопасность, стабильность, надёжность и производительность, которых Docker с его тысячами открытых issues предоставить не может. А ещё некторые понимают, что Docker — средство доставки приложений. Если ваш стартап выкатывает новую версию сервиса 2 раза в неделю, Docker незаменим. Если вы поднимаете почтовый сервак, который будет работать годами Docker вообще ни к чему.
                  +1

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


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

                  +2
                  А это самое интересное. Это возможность отправлять письма для конкретного домена с конкретного IPv4/IPv6 адреса.

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

                  Сколько можно распространять по интернету эту глупость?
                  Кто-то один раз где-то это написал, и теперь каждый натыкающийся на эту "кладезь мудрости" старается внедрить её у себя и рассказать миру об этом достижении.


                  HELO не обязано соответствовать домену адреса отправителя, и никакие известные мне почтовые антиспамы за это штрафные баллы не начисляют.


                  Соответственно для каждого домена должен быть свой IP адрес.

                  Такого требования также нет.

                    0
                    Почтовые сервера на outlook.com всегда делают bounce письмам отправленным с сервера, у которого неправильный HELO.
                    spamassassin, который установлен у многих почтовых серверов, по умолчанию даёт 1.6 очков спама на неправильный HELO.

                    Так-то да, если вам не важно попадёт письмо в спам или нет — то на HELO можно не обращать внимание.
                      0
                      Вы путаете «неправильный» адрес в HELO (который не соответствует прямому и обратному DNS) и то, что у автора статьи — «helo не соответствует домену почты, от имени кого отправили письмо». Последнее — выдумка, потому что тот же outlook.com шлёт почту от имени тысяч доменов, нет и не может быть такого ограничения. Соответствие записям SPF/DMARC домена-отправителя могут проверяться, а сравнивать домен одного из почтовых серверов в цепочке почему-то с адресом FROM в письме, я не знаю, кому в голову может прийти такая идея, видимо это urban legend.
                      • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    Вместо связки OpenDKIM + SpamAssasin не смотрели в сторону Rspamd? Шустрый, работает «из коробки» с минимальными настройками.
                      0
                      Также хотелось бы получить отзывы на тему идеи про почтовые сертификаты. Если идея понравится — постараюсь найти силы написать черновик для rfc.

                      Его уже написали, одобрили и чёрт знает сколько лет успешно используют: tools.ietf.org/html/rfc4880
                        –1
                        Я в курсе про PGP. Но пользоваться им неудобно, уж простите.

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

                          Это кроме параноиков или людей занимающихся не совсем законной деятельностью нафиг никому не надо. А кому надо тот и PGP освоит.

                            +2
                            image

                            PGP за почти 3 десятка лет существования не получил распространения среди всех существующих почтовых клиентов ( хотя на мой взгляд в Evolution или Thunderbird пользоваться им достаточно легко ), а вы надеетесь вот так просто взять и договориться с Microsoft, Google, IBM, Apple и кучей компаний поменьше? Да вам сказки надо писать, а не RFC.
                          +4
                          > Только спустя 8 лет после начала моей карьеры я стал понимать как работает SSL.
                          >…
                          > Веб сервер отдаёт публичный ключ. Браузер используя публичный ключ зашифровывает
                          > http-request и отправляет его.

                          8 лет оказалось недостаточно.
                            0
                            Можете ли пояснить, что не так?
                              +3
                              Всё не так. Эта ваша желтая секция про HTTPS содержит набор ереси. Там просто через сстрочку можно писать ответ «это не так, читайте вики.»

                              > Браузер используя публичный ключ зашифровывает http-request и отправляет его.
                              Это не так, читайте вики: TLS.

                              > если в стране пытаются ограничить доступ не ко всему сайту,
                              > а к конкретной странице — то для https сайтов это сделать невозможно.
                              Очень даже возможно. Для этого государству надо либо вступить в сговор с CA (wiki: StartCom), либо самому стать CA ( habr.com/ru/post/272207 ), либо заюзать что-то типа Carnivore или NarusInsight (но это не точно, агентство которого нет всё отрицает а вики врет). Хотя, если вы король какой-нибудь банановой республики или царек в Рога и Копыта Интернешнл, то вы можете так не заморачиваться а просто связаться с Symantec Solutions и они вам помогут с перехватом HTTPS трафика всех этих ваших холопов.

                              > браузер у себя локально формирует такую-же пару приватный-публичный ключ
                              > для каждого https сайта
                              Не фомирует браузер никакую такую пару ключей.

                              > И вместе с запросом публичного ключа сайта отправляет свой локальный публичный ключ.
                              wiki: TLS. Ну или вы неправильно поняли аутентификацию по сертификатам, которая к тому же на публичных сайтах всё равно не применяется.

                              > http-response шифрует этим вот публичным ключём конкретного клиента.
                              wiki: TLS, сеансовый ключ там используется на самом деле а не публичный.

                              Вместо ожидаемого упоминания Диффи-Хеллмана написана какая-то ерунда.

                              > Само создание такого безопасного канала связи происходит со скоростью ping*2
                              Два пинга будет только если не учитывать TCP handshake (+1 пинг), не смотреть в CRL (много пингов), не дергать никого по OSCP (много-много пингов). Ну то есть если тупо не проверять совсем ничего и всегда слепо доверять всем без разбору — тогда да, парой-тройкой пингов можно обойтись. Хотя OCSP stapling устраняет эти вот многочисленные лишние пинги. Но его используют только 30% хостов примерно, а у вас он так и вообще не упомянут.

                              > Для борьбы с такими злоумышленниками придумали публичную БД
                              > с публичными ключами для каждого https сайта.
                              Вы, наверное, CA имели в виду. Там у них нет никакой публичной базы публичных ключей. Если CRL — публичные списки отозванных сертификатов — то есть таких, которые раньше можно было использовать а теперь нельзя. Есть OSCP, но это запрос-ответ а не скачивание базы чего-либо.

                              > Ещё добавлю про ssh соединения. Там никаких публичных ключей нет.
                              Да ну?

                              > При первом соединении ssh-клиент должен предупредить, что тут у нас новый публичный ключ.
                              Вы же только что написали, что никаких публичных ключей нет.

                              > Через пару часов мы получаем от этой сторонней компании наш «публичный» ключ и ещё набор нескольких публичных ключей.
                              Сертификат вы оттуда получаете подписанный а вовсе не ключ. Это разные вещи.

                              > chained — обозначение, что тут цепочка публичных ключей
                              Цепочка сертификатов. Не бывает никаких цепочек ключей в природе.

                                0
                                ну блин..., спасибо за информацию.
                                оказалось что всё еще на порядок сложнее и как правильно настроить это добро даже и приблизительно не понятно теперь, как же правильно то сделать?
                                  –3
                                  Спасибо за развёрнутый комментарий. Я хотел донести идеи, очень простым языком. Даже упуская важные детали.
                                    +1

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


                                    Я бы попросил Вас скрыть её в черновики до момента переработки содержимого.
                                    Вы уже получили много отзывов и Вам есть над чем подумать.

                              +4
                              А где ansible-плейбук? Или вы руками предлагаете все это натыкивать во второй раз. Сейчас 2k19 на минуточку, без плейбука зачет не приму!
                                +1
                                в 2к19 где контейнер, а не плейбук
                                0
                                del
                                  0

                                  Если уж включать spam паранойю, то я бы добавил еще:


                                  • поддержку DMARC и ARC
                                  • дополнительные и/или свои блеклисты (это отсылка к строке reject_rbl_client sbl.spamhaus.org,)
                                  • например fail2ban с реакцией на логи postfix о несовпадении hostname<->rDNS, невалидные сертификаты, оборванные входящие TLS/SSL сессии

                                  Ну и плюсую за OpenPGP aka RFC4880

                                  • НЛО прилетело и опубликовало эту надпись здесь

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое