Skip to content

Instantly share code, notes, and snippets.

@gomin1d
Last active February 21, 2024 02:27
Show Gist options
  • Save gomin1d/92dffa0bb60a92f060d8cc0bfc7251b4 to your computer and use it in GitHub Desktop.
Save gomin1d/92dffa0bb60a92f060d8cc0bfc7251b4 to your computer and use it in GitHub Desktop.
Встановлення поштового сервера postfix на debian 10

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

DNS

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

Cloudflare

Почтовый сервер для своей работы использует домен. Этот домен должен находиться на Cloudflare, чтобы была возможность настроить автоматическое продление Let's Encrypt сертификата по api токену Cloudflare.

Чтобы получить токен Cloudflare, нужно перейти по ссылке https://dash.cloudflare.com/profile/api-tokens. Там можно получить глобальный ключ Global API Key или сделать отдельный токен с правами на редактирование конкретного домена.

После того, как api токен получен, нужно также получить id домена. Чтобы сделать это, нужно выполнить такой запрос:

# сюда подставьте свой токен
export TOKEN="h6N8z2L4HU4xvoJzKdPD8wTURW0SeSeWRLeI-NGk"
# сюда подставьте свой домен
export DOMAIN="example.com"

apt-get install jq -y
curl -X GET "https://api.cloudflare.com/client/v4/zones" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    | jq -r ".result[] | select(.name == \"$DOMAIN\") | .id"

Для последующих запросов сохраним полученный id домена в переменную среды такой командой:

# сюда подставьте свой id домена
export ZONE_ID="a2127914087be198d9eddc7a159d6d2c"

Создание a-записи mail

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

Сначала нужно создать a-запись mail, которая будет указывать на айпи адрес хоста, на котором будет установлен почтовый сервер. Чтобы сделать это, нужно выполнить такие запросы:

# сюда подставьте свой айпи адрес хоста
export HOST_IP="11.22.33.44"

# удаляем предыдущую a-запись mail, если таковая была
curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=mail.$DOMAIN" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     | jq -r '.result[].id' | xargs -i \
     curl -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/{}" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json"

# создаем a-запись mail
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     --data '{"type":"A","name":"mail.'$DOMAIN'","content":"'$HOST_IP'","ttl":120}'

Создание spf txt-записи

Теперь нужно создать spf txt-запись, которая будет указывать на созданную a-запись mail.

Чтобы сделать это, нужно выполнить такой запрос:

curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     --data '{"type":"TXT","name":"'$DOMAIN'","content":"v=spf1 a:mail.'$DOMAIN' -all","ttl":120}'

Создание dmarc txt-записи

Чтобы почтовые сервера не отправляли нашу почту в спам, нужно добавить dmarc txt-запись. Эта запись хранит в себе политику, которой придерживается наш почтовый сервер. По сути эта запись сообщаем всем "мы почтовый сервер, мы можем отправлять письма, много писем, не баньте нас, все так и задумано".

Чтобы создать эту запись, нужно выполнить такой запрос:

curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     --data '{"type":"TXT","name":"_dmarc.'$DOMAIN'","content":"v=DMARC1; p=none; aspf=r; sp=none","ttl":120}'

Установка dovecot

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

Чтобы можно было отправлять письма через почтовый сервер удаленно, нужно установить stmp сервер dovecot. Этот сервер выступает в качестве прокси между клиентами и почтовым сервером postfix, а также он берет на себя роль аутентификации.

Устанавливаем его такими командами:

apt-get install dovecot-imapd dovecot-pop3d -y
sed -i 's/^auth_mechanisms.\+/auth_mechanisms = plain login/g' /etc/dovecot/conf.d/10-auth.conf

После установки нужно настроить конфиг, открываем его:

nano /etc/dovecot/conf.d/10-master.conf

Находим в конфиге секцию # Postfix smtp-auth и приводим ее к такому виду:

  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }

Открываем еще один конфиг:

nano /etc/dovecot/conf.d/auth-system.conf.ext

Находим секцию passdb { и приводим ее к виду:

passdb {
  #driver = pam
  driver = passwd-file
  # [session=yes] [setcred=yes] [failure_show_msg=yes] [max_requests=<n>]
  # [cache_key=<key>] [<service name>]
  #args = dovecot
  args = scheme=SHA1 /etc/dovecot/passwd
}

Создание smtp юзера

Юзер представляет из себя строку в файле /etc/dovecot/passwd, которую можно создать такой командой:

doveadm pw -s sha1 | cut -d '}' -f2 | xargs -i echo "login@$DOMAIN:{}" > /etc/dovecot/passwd
# вводим пароль юзера

chown dovecot: /etc/dovecot/passwd
chmod 600 /etc/dovecot/passwd

Логином юзера будет login@example.com, только вместо example.com будет свой домен.

Удаление предыдущего почтового сервера

В linux может быть установлен почтовый сервер по умолчанию с установкой системы, его нужно удалить.

Чтобы проверить его наличие, можно проверить прослушивание порта 25 такой командой:

netstat -tnlp | grep ":25 "

В Debian скорее всего будет установлен exim, удалить его можно такой командой:

apt-get remove -y exim*

Установка почтового сервера postfix

postfix - это почтовый сервер, он может отправлять письма. Устанавливаем его такими командами:

apt-get -y install bsd-mailx postfix

В процессе установки вам будут задавать вопросы:

  • Вопрос General type of mail configuration: - надо выбрать Internet Site.
  • Вопрос System mail name: - надо ввести mail.example.com, только вместо example.com укажите свой домен.

После установки редактируем конфиг такими командами:

sed -i "s/^myhostname.\+/myhostname = mail.$DOMAIN/g" /etc/postfix/main.cf
sed -i "s/^smtpd_banner = \$myhostname/smtpd_banner = mail.$DOMAIN/g" /etc/postfix/main.cf
sed -i "s/^mydestination.\+/mydestination = \$myhostname, $DOMAIN, localhost/g" /etc/postfix/main.cf
sed -i 's/^inet_protocols.\+/inet_protocols = ipv4/g' /etc/postfix/main.cf

После чего открываем этот конфиг редактором:

nano /etc/postfix/main.cf

И добавляем такие параметры после комментария # TLS parameters:

# TLS parameters
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot

Если на хосте имеется несколько айпи адресов, тогда нужно добавить еще одну настройку, в которую надо указать тот адрес, который вы использовали при создании a-записи mail:

smtp_bind_address = 11.22.33.44

Настройка TLS сертификата

Чтобы соотвествовать требованиям безопасности, и наши письма не попадали в спам, надо настроить шифрование TLS.

Для этого подходят бесплатные сертификаты Lets Encrypt. Чтобы сертификат не приходилось вручную пересоздавать каждые 3 месяца, установим certbot, который будет это делать за нас с помощью Cloudflare api.

Команды для установки:

# install certbot and certbot-dns-cloudflare
apt install certbot python3-pip -y
pip3 install certbot-dns-cloudflare

# configure cloudflare
mkdir ~/.certbot
printf "# Cloudflare API token used by Certbot\ndns_cloudflare_api_token = $TOKEN" > ~/.certbot/cloudflare.ini
chmod 600 ~/.certbot/cloudflare.ini

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials ~/.certbot/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 60 \
  -d $DOMAIN

После последней команды certbot будет задавать нам вопросы:

  • Вопрос Enter email address - надо ввести почту администратора, именно вашу личную, чтобы получать уведомления от Lets Encrypt.
  • Вопрос Please read the Terms of Service at... - надо ввести A.
  • Вопрос Would you be willing... - надо ввести N.

Если все хорошо, сертификат будет сохранен в файлах:

  • /etc/letsencrypt/live/example.com/fullchain.pem
  • /etc/letsencrypt/live/example.com/privkey.pem

Можно проверить работает ли обновление сертификата командой certbot renew --dry-run. Если есть сертификат и нет красных ошибок - значит всё ок.

Чтобы сертификат пересоздавался автоматически, нужно добавить такую команду в crontab -e:

  0 3  *   *   *     certbot renew

Настройка сертификата в postfix

Редактируем конфиг postfix такими командами:

sed -i "s/^smtpd_tls_cert_file.\+/smtpd_tls_cert_file=\/etc\/letsencrypt\/live\/$DOMAIN\/fullchain.pem/g" /etc/postfix/main.cf
sed -i "s/^smtpd_tls_key_file.\+/smtpd_tls_key_file=\/etc\/letsencrypt\/live\/$DOMAIN\/privkey.pem/g" /etc/postfix/main.cf

После чего открываем этот конфиг редактором:

nano /etc/postfix/main.cf

И добавляем такие параметры после комментария # TLS parameters:

# TLS parameters
smtp_tls_security_level = may
smtp_tls_note_starttls_offer = yes
smtpd_tls_security_level = may

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

DKIM - это еще одна ступен безопасности, без которой письма будут попадать в спам.

Устанавливаем и настраиваем сервис opendkim такими командами:

apt-get install opendkim opendkim-tools -y
mkdir -m 750 /etc/opendkim && chown opendkim:opendkim /etc/opendkim
opendkim-genkey -D /etc/opendkim --domain=$(postconf -h mydomain) --selector=dkim
chown opendkim:opendkim /etc/opendkim/dkim.private
chmod 600 /etc/opendkim/dkim.private

sed -i "s/^Socket.\+/Socket                  inet:8892@localhost/g" /etc/opendkim.conf
sed -i "s/^#SubDomains.\+/SubDomains              Yes/g" /etc/opendkim.conf
sed -i "s/^#Canonicalization.\+/Canonicalization        relaxed\/relaxed/g" /etc/opendkim.conf

echo "dkim._domainkey.$DOMAIN $DOMAIN:dkim:/etc/opendkim/dkim.private" > /etc/opendkim/KeyTable
echo "*@$DOMAIN dkim._domainkey.$DOMAIN" > /etc/opendkim/SigningTable
echo -e "127.0.0.1\nlocalhost\n*.$DOMAIN" > /etc/opendkim/TrustedHosts

# удаляем предыдущую txt-запись dkim._domainkey, если таковая была
curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=TXT&name=dkim._domainkey.$DOMAIN" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     | jq -r '.result[].id' | xargs -i \
     curl -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/{}" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json"

# создаем txt-запись dkim._domainkey
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/import" \
     -H "Authorization: Bearer $TOKEN" \
     --form 'file=@/etc/opendkim/dkim.txt' \
     --form 'proxied=false'

После установки открываем конфиг редактором:

nano /etc/opendkim.conf

И добавляем такие параметры:

RemoveARAll             Yes
RemoveOldSignatures     Yes
SendReports             Yes
KeyTable                /etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts

Подключаем opendkim к postfix

Открываем конфиг postfix редактором:

nano /etc/postfix/main.cf

Добавляем эти параметры в конец файла после всех остальных параметров:

# DKIM
milter_default_action = accept
smtpd_milters = inet:localhost:8892
non_smtpd_milters = inet:localhost:8892

Тестируем

Перезагружаем сервисы командами:

systemctl restart dovecot
systemctl enable dovecot
systemctl restart opendkim
systemctl enable opendkim
systemctl restart postfix
systemctl enable postfix

Ждем ~10 минут, пока обновит DNS и Lets Encrypt.

Пробуем отправить письмо через linux команду на gmail:

echo "This is message body" | mailx -s "This is Subject" -r "no-reply@$DOMAIN" admin@gmail.com

Вместо admin@gmail.com укажите вашу личную почту gmail.

Письмо не должно попасть в Spam и в нем должно быть так:

Далее нужно проверить оригинал письма, его можно открыть так:

В оригинале напротив 3 ступеней безопасности должно быть написано PASS:

Если все так, значит все хорошо.

Тестируем через smtp

Вот пример java кода, который отправляет письмо через smtp:

import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

public class Debug{
	public static void main(String[] args) throws Exception{
		Properties prop = new Properties();
		AuthenticatorMail auth = new AuthenticatorMail("login@example.com", "password");
		prop.put("mail.smtp.host", "mail.example.com"); //Хост
		prop.put("mail.smtp.ssl.enable", "false"); // SSL используем ?
		prop.put("mail.smtp.starttls.enable", "true"); // TLS используем ?
		prop.put("mail.smtp.auth", "true"); // Авторизация нужна ли ? Во всех случаях нужна, если это не твой личный-приватный SMTP сервер.
		prop.put("mail.smtp.port", 25); //Порт
		prop.put("mail.debug", "false");
		prop.put("mail.smtp.connectiontimeout", 30_000);
		prop.put("mail.smtp.timeout", 30_000);
		prop.put("mail.smtp.writetimeout", 30_000);
		Session session = Session.getInstance(prop, auth); //Создаем сессию, с указаными настройками, а также логинимся

		javax.mail.Message message = new MimeMessage(session); //Сессия уже создана. Определяем к какой сессии принадлежит сообщение.
		message.setFrom(new InternetAddress("no-reply@example.com")); //от кого идет сообщение.
		message.setRecipients(javax.mail.Message.RecipientType.TO, InternetAddress.parse("admin@gmail.com")); //Тут указываем кому идет сообщениею
		message.setSubject("privet3"); //Заголовок
		message.setText("privet4"); //текст
		Transport.send(message);
	}

	public static class AuthenticatorMail extends Authenticator{

		String login;
		String password;

		public AuthenticatorMail(String login, String password){
			this.login = login;
			this.password = password;
		}

		@Override
		protected PasswordAuthentication getPasswordAuthentication(){
			return new PasswordAuthentication(login, password);
		}
	}
}

Этот код для своей работы требует такую зависимость:

<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.5.6</version>
</dependency>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment