Данная статья о том как настроить современный почтовый сервер.
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.
Для настройки интерфейсов надо прописать вот такое в "/etc/network/interfaces".
Данные настройки можно применять на любом сервере в 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.
Этой командой мы задаём, что любой непонятный трафик, который попал под любое rule у которого отмечено «table eno1t» -> направить в интерфейс eno1.
Этой командой мы задаём, что любой трафик по инициативе сервера направить в интерфейс eno1.
Этой командой мы задаём сами правила маркировки трафика.
Этот блок задаёт второй IPv4 для интерфейса eno1.
Этой командой мы задаём route от клиентов OpenVPN до локальных IPv4 кроме XX.XX.XX.X0.
Почему этой команды достаточно для всех IPv4 — я до сих пор не понимаю.
Это мы задаём адрес для самого интерфейса. Сервер его будет использовать как «исходящий» адрес. Больше никак использоваться не будет.
Почему указано ":1:1::" так сложно? Чтобы OpenVPN работало правильно и только для этого. Об этом подробнее позже.
На тему gateway — так работает и ладно. Но по правильному — сюда надо указать IPv6 свича к которому подсоединён сервер.
Однако почему-то IPv6 перестаёт работать, если я так делаю. Наверное это заморочки tech.ru какие-то.
Это добавление IPv6 адреса на интерфейс. Если надо сотню адресов — значит сотню строк в этом файле.
Для настройки сети — должна быть возможность перезагружать сервер.
IPv4 изменения подхватываются при выполнении (обязательно завернуть в screen — иначе эта команда просто уронит сеть на сервере):
В файл "/etc/iproute2/rt_tables" добавить в конец:
Без этого нельзя использовать кастомные table в файле "/etc/network/interfaces".
Цифры должны быть уникальные и менее 65535.
IPv6 изменения изменяются легко без перезагрузки, но для этого нужно научиться как минимум трём командам:
Настройка "/etc/sysctl.conf"
Это настройки «sysctl» моего сервера. Отмечу важное.
Без этого OpenVPN не будет работать никак.
Любой, кто попытается сделать bind IPv6 (например nginx) сразу после того, как интерфейс подялся — получит ошибку. Что такой адрес недоступен.
Чтобы избежать такой ситуации и делается такая настройка.
Без этих настроек IPv6 трафик от клиента OpenVPN не выходит в мир.
Другие настройки или не относятся к делу или я не помню зачем они.
Но на всякий случай оставляю «как есть».
Для того, чтобы изменения этого файла подхватились без перезагрузки сервера — надо выполнить команду:
Более детально про «table» правила: habr.com/post/108690
OpenVPN IPv4 не работает без iptables.
У меня iptables вот такие для VPN:
YY.YY.YY.YY — это мой статический IPv4 адрес локальной машины.
10.8.0.0/24 — IPv4 сеть openvpn. IPv4 адреса для клиентов openvpn.
Последовательность правил важна.
Это ограничение, чтобы только я со своего статического IP мог бы воспользоваться OpenVPN.
Для пробрасывания IPv4 пакетов между клиентами OpenVPN и интернетом — нужно прописать одну из этих команд.
Для разных случаев один из вариантов не подходит.
Для моего случая подходят обе команды.
Почитав документацию я выбрал первый вариант, потому что он кушает меньше CPU.
Чтобы все настройки iptables подхватывались после reboot — надо сохранить их куда-то.
Такие имена выбраны не случайно. Их использует пакет «iptables-persistent».
Установка основного пакета OpenVPN:
Настроим шаблон для сертификатов (подставить свои значения):
Отредактируем настройки шаблона сертификатов:
Создаём серверный сертификат:
Приготовим возможность создавать итоговые «client-name.opvn» файлы:
Приготовим скрипт, который будет сшивать все файлы в единый opvn файл.
Создаём первого клиента OpenVPN:
Файл "~/client-configs/files/client-name.ovpn" отправляем на утройство клиенту.
Для iOS клиентов надо будет сделать трюк:
Содержимое тэга «tls-auth» должно быть без комментариев.
А также поставить «key-direction 1» сразу перед тэгом «tls-auth».
Настроим конфиг OpenVPN сервера:
Это нужно для того, чтобы задать статический адрес каждому клиенту (не обязательно, но я использую):
Самая сложная и ключевая деталь.
К сожалению OpenVPN ещё не умеет самостоятельно настраивать IPv6 gateway для клиентов.
Приходится «вручную» пробрасывать это для каждого клиента.
Файл "/etc/openvpn/server-clientconnect.sh":
Файл "/etc/openvpn/server-clientdisconnect.sh":
Оба скрипта используют файл "/etc/openvpn/variables":
Почему тут так написано — затрудняюсь вспомнить.
Сейчас выглядит странным netmask = 112 (тут же 96 должен быть).
И prefix странный, не соответствует сети tun0.
Но ладно, оставляю «как есть».
Это на любителя — я выбрал такой способ шифрования соединения.
Более детально про настройку OpenVPN IPv4.
Более детально про настройку OpenVPN IPv6.
Установка основного пакета:
При установке выбрать «internet-site».
Мой "/etc/postfix/main.cf" выглядит так:
Рассмотрим детали этого конфига.
При помощи «openssl genrsa» мы создаём приватный ключ и «заготовок» для публичного ключа.
«заготовок» отправляем сторонней компании, которой мы платим примерно $9 за самый простой сертификат.
Через пару часов мы получаем от этой сторонней компании наш «публичный» ключ и ещё набор нескольких публичных ключей.
Зачем сторонней компании платить за оформление моего публичного ключа — вопрос отдельный, тут рассматривать не будем.
Теперь понятно в чём смысл надписи:
В папке "/etc/ssl" сложены все файлы для ssl вопросов.
domain1.com — название домена.
2018 — год создания ключей.
«key» — обозначение, что файл приватный-ключ.
И смысл этого файла:
smtpd_tls_cert_file=/etc/ssl/domain1.com.2018.chained.crt
domain1.com — название домена.
2018 — год создания ключей.
chained — обозначение, что тут цепочка публичных ключей (первый — наш публичный и остальные — что пришло от компании оформившей публичный ключ).
crt — обозначение, что тут готовый сертификат (публичный ключ с пояснениями техническими).
Это установка в данном случае не используется, но написано для примера.
Потому что ошибка в данном параметре приведёт к отправке от вашего сервера спама (без вашей воли).
Потом доказывайте всем, что вы не виноваты.
Возможно многие не знают, так вот это стандартный символ для ранжирования писем, и это поддерживается большинством современных почтовых серверов.
Например если у вас есть почтовый ящик «username@gmail.com» попробуйте отправть на «username+spam@gmail.com» — посмотрите что из этого получится.
Возможно это будет сбивать с толку.
Но это не просто так. Каждый новый домен — по умолчанию только IPv4, потом включаю IPv6 для каждого в отдельности.
Тут мы задаём, что вся входящая почта уходит в dovecot.
А правила для domain, mailbox, alias — смотреть в БД.
/etc/postfix/mysql-virtual-mailbox-domains.cf
/etc/postfix/mysql-virtual-mailbox-maps.cf
/etc/postfix/mysql-virtual-alias-maps.cf
Теперь postfix знает, что принимать почту для дальнейшей отправки можно только по авторизации с dovecot.
Мне правда не очень понятно, зачем это тут дублировать. Мы же уже указали в «virtual_transport» всё что надо.
Но postfix система очень старая — наверное это костыли от старых времён.
Это настраивать для каждого почтового сервера по своему.
В моём распоряжении есть 3 почтовых сервера и эти настройки очень разные из-за разных требований к использованию.
Настраивать надо внимательно — иначе спам хлынет к вам или ещё хуже — спам хлынет от вас.
Настройка для какого-то плагина связанного с проверкой SPF входящих писем.
Настройка, что все исходящие письма мы должны снабжать DKIM подписью.
Это ключевая деталь в маршрутизации писем при оотправки писем от php скриптов.
Файл "/etc/postfix/sdd_transport.pcre":
Файл приведён не полностью — он и так очень большой.
Отметил только то, что изменено.
Это настройки связанные со spamassasin, о нём позже.
Разрешаем присоединяться к почтовому серверу через 587 порт.
Для этого обязательно авторизоваться.
Включаем проверку SPF.
Установим пакет для SPF проверок выше.
Настройка связки postfix+dovecot тут.
Настройка SPF.
Настройка mysql, устанавливаем сами пакеты.
Файл "/etc/dovecot/conf.d/10-auth.conf"
Авторизация только в зашифрованном виде.
Файл "/etc/dovecot/conf.d/10-mail.conf"
Тут укажем место хранения писем.
Я хочу, чтобы они хранились в файлах и были сгруппированы по доменам.
Файл "/etc/dovecot/conf.d/10-master.conf"
Это главный файл настроек dovecot.
Тут мы отключаем не защищённые соединения.
И включаем защищённые соединения.
Файл "/etc/dovecot/conf.d/10-ssl.conf"
Это в дальнейшем нужно будет для spamassassin.
Файл "/etc/dovecot/conf.d/20-imap.conf"
Это antispam плагин. Нужен для обучения spamassasin в момент переноса в/из папки «Spam».
Файл "/etc/dovecot/conf.d/20-pop3.conf"
Просто такой файл есть.
Файл "/etc/dovecot/conf.d/20-lmtp.conf"
Настройка lmtp.
Файл "/etc/dovecot/conf.d/90-antispam.conf"
Настройки обучения spamassasin в момент переноса в/из папки «Spam».
Файл "/etc/dovecot/conf.d/90-sieve.conf"
Файл в котором указано что делать со входящими письмами.
Файл "/var/lib/dovecot/sieve/default.sieve"
Надо скомпилировать файл: «sievec default.sieve».
Файл "/etc/dovecot/conf.d/auth-sql.conf.ext"
Указание sql файлов для авторизации.
А сам файл — как способ авторизации.
Файл "/etc/dovecot/dovecot-sql.conf.ext"
Это соответствует аналогичным настройкам для postfix.
Файл "/etc/dovecot/dovecot.conf"
Основной файл конфигурации.
Важно то, что мы тут указываем-добавляем протоколы.
Установим пакеты.
Добавим пользователя от имени которого.
Включаем авто-загрузку spamassassin сервис при загрузке.
Файл "/etc/default/spamassassin":
Включаев автоматическое обновление правил «по умолчанию».
Файл "/etc/spamassassin/local.cf":
Нужно сделать в mysql БД «sa» с пользователем «sa» с паролем «password» (заменить на что-то адекватное).
report_safe — это вместо письма будет присылаться отчёт о письме-спаме.
use_bayes — это настройки машинного обучения spamassassin.
Остальные настройки spamassassin применялись ранее по статье.
Общая настройка «spamassassin».
Про перемещение новых Спам-писем в IMAP папку «Spam».
Про простую связку Dovecot + SpamAssassin.
Рекомендую к прочтению теория обучения spamassasin при движении писем в imap папках (и не рекомендую к применению).
Ещё хотелось бы закинуть идею в сообщество про то, как повысить уровень защищённости пересылаемых писем. Раз уж я так глубоко погрузился в тему почты.
Чтобы пользователь мог бы у себя на клиенте (outlook, thunderbird, browser-plugin, ...) создать пару ключей. Публичный и приватный. Публичный — отправить в DNS. Приватный — сохранять на клиенте. Почтовые сервера бы умели применять публичный ключ для отправки конкретному адресату.
И для защиты от спама при таких письмах (да, почтовый сервер жи не сможет посмотреть контент) — надо будет ввести 3 правила:
Кроме публичных писем — разработать стандарт письма-предложения «начать защищённую переписку». Один из пользователей (почтовый ящик) шлёт другому почтовому ящику письмо с аттачем. В письме текст-предложение начать защищённый канал связи для переписки и публичный ключ владельца почтового ящика (при этом приватный ключ на стороне клиента).
Можно даже пару ключей делать специально для каждой переписки. Пользователь-получатель может принять это предложение и отправить свой публичный ключ (тоже сделанный специально для данной переписки). Далее первый пользователь отправляет служебное контрольное письмо (зашифрованное публичным ключом второго пользователя) — при получении которого второй пользователь может считать сформированный канал связи надёжным. Далее второй пользователь отправляет контрольное письмо — и тогда первый пользователь тоже может считать сформированный канал защищённым.
Для борьбы с перехватом ключей по дороге — надо в протоколе предусмотреть возможность передачи хотя-бы одного публичного ключа при помощи флэшки.
И самое главное — чтобы это всё работало (вопрос «а кто за это заплатит?»):
Ввести почтовые сертификаты стоимостью от 10$ за 3 года. Которые будут позволять отправителю указать в dns, что «мои публичные ключи находятся вон-там». И будут давать возможность начинать защищённое соединение. При этом — принимать такие соединения бесплатно.
gmail наконец монетизирует своих пользователей. За 10$ в 3 года — право создавать защищённые каналы переписки.
Для тестирования всей статьи я собирался арендовать выделенный сервер на месяц и купить домен с ssl сертификатом.
Но жизненные обстоятельства сложились так этот вопрос затянулся на 2 месяца.
И вот когда появилось снова свободное время — решил публиковать статью как есть, а не рисковать тем, что публикация затянется ещё на год.
Если будет достаточно много вопросов типа «а вот тут не достаточно подробно описано» — тогда наверное найдутся силы таки взять выделенный сервер с новым доменом и новым SSL сертификатом и ещё подробнее описать и главное — выявить все упущенные важные детали.
Также хотелось бы получить отзывы на тему идеи про почтовые сертификаты. Если идея понравится — постараюсь найти силы написать черновик для rfc.
При копировании больших кусков статьи — указывать ссылку на эту статью.
При переводе на любой другой язык — указывать ссылку на эту статью.
На английский язык я сам постараюсь перевести и оставлю перекрёстные ссылки.
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
Итак, теперь когда известна теория. Расскажу про flow создания ssl сертификата.По мнению хабровчан данный блок содержит `дезинформацию и неверные тезисы`.Только спустя 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 на суперкомпьютере можно будет посмотреть трафик — но это отдельная история.
Прошу прощения за оффтоп.
При помощи «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:
Слева — регулярные выражения. Справа — метка, которой отмечается письмо.Мой "/etc/postfix/master.cf" выглядит так:
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.
Так вот даже в таком случае письма будут уходить корректно и никогда не попадут в спам.
...
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 — обязательно.Файл "/etc/dovecot/conf.d/15-lda.conf"
И сам сертификат. И важная деталь — директива «local». Указывает, при соединении к какому локальному IPv4 — какой ssl сертификат использовать.
Кстати IPv6 тут не настроен, исправлю это упущение как-нить потом.
XX.XX.XX.X5 (domain2) — сертификата нет. Для соединения клиентов нужно указывать domain1.com.
XX.XX.XX.X2 (domain3) — сертификат есть, для соединения клиентов можно указывать domain1.com или domain3.com .
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 правила:
- Обязательная настоящая подпись DKIM, обязательный SPF, обязательный rDNS.
- Нейронная сеть на тему обучения антиспама + БД к ней на стороне клиента.
- Алгоритм шифрования должен быть таким, что отправляющая сторона должна потратить на шифрования в 100 раз больше мощностей CPU, чем принимающая сторона.
Кроме публичных писем — разработать стандарт письма-предложения «начать защищённую переписку». Один из пользователей (почтовый ящик) шлёт другому почтовому ящику письмо с аттачем. В письме текст-предложение начать защищённый канал связи для переписки и публичный ключ владельца почтового ящика (при этом приватный ключ на стороне клиента).
Можно даже пару ключей делать специально для каждой переписки. Пользователь-получатель может принять это предложение и отправить свой публичный ключ (тоже сделанный специально для данной переписки). Далее первый пользователь отправляет служебное контрольное письмо (зашифрованное публичным ключом второго пользователя) — при получении которого второй пользователь может считать сформированный канал связи надёжным. Далее второй пользователь отправляет контрольное письмо — и тогда первый пользователь тоже может считать сформированный канал защищённым.
Для борьбы с перехватом ключей по дороге — надо в протоколе предусмотреть возможность передачи хотя-бы одного публичного ключа при помощи флэшки.
И самое главное — чтобы это всё работало (вопрос «а кто за это заплатит?»):
Ввести почтовые сертификаты стоимостью от 10$ за 3 года. Которые будут позволять отправителю указать в dns, что «мои публичные ключи находятся вон-там». И будут давать возможность начинать защищённое соединение. При этом — принимать такие соединения бесплатно.
gmail наконец монетизирует своих пользователей. За 10$ в 3 года — право создавать защищённые каналы переписки.
============= Заключение =============
Для тестирования всей статьи я собирался арендовать выделенный сервер на месяц и купить домен с ssl сертификатом.
Но жизненные обстоятельства сложились так этот вопрос затянулся на 2 месяца.
И вот когда появилось снова свободное время — решил публиковать статью как есть, а не рисковать тем, что публикация затянется ещё на год.
Если будет достаточно много вопросов типа «а вот тут не достаточно подробно описано» — тогда наверное найдутся силы таки взять выделенный сервер с новым доменом и новым SSL сертификатом и ещё подробнее описать и главное — выявить все упущенные важные детали.
Также хотелось бы получить отзывы на тему идеи про почтовые сертификаты. Если идея понравится — постараюсь найти силы написать черновик для rfc.
При копировании больших кусков статьи — указывать ссылку на эту статью.
При переводе на любой другой язык — указывать ссылку на эту статью.
На английский язык я сам постараюсь перевести и оставлю перекрёстные ссылки.