SMTP релей за Python: STARTTLS на 587 или TLS на 465?
STARTTLS на 587 и неявният TLS на 465 и двата пренасят подаване от Python, но само единият пасва на вашия релей. Сравнете ръкостискането и режимите на отказ.
И двата режима на свързване носят същото съобщение до същия акаунт на релея, а разликата е къде започва TLS, а не какво се изпраща. Единият се отваря в открит текст и се надгражда в самия канал; другият е криптиран от първия байт. Тази единствена разлика решава какво ще приеме строга политика на релея, какво може да наблюдава междинно устройство и кое съобщение за грешка ще четете цял следобед. Ето как избирам между тях, когато приложение в частна мрежа предава съобщение на вътрешна услуга за подаване.
Къде всъщност се разминават двата режима
Задачата е еднаква и в двата случая: удостоверяване като одобрен акаунт, посочване на разрешен подател, изброяване на получателите и изчакване релеят да поеме отговорност. Променя се редът на операциите. Порт 587 започва като SMTP сесия с открит текст, обявява STARTTLS в отговора EHLO и се надгражда само ако клиентът поиска. Порт 465 очаква TLS ръкостискане, преди да бъде произнесена каквато и да е SMTP команда.
Подаването и релеят са различни задачи, затова номерата на портовете не са взаимозаменяеми навици. RFC 6409 описва подаването на съобщения като самостоятелна услуга, а RFC 8314 обхваща варианта с неявен TLS, който повечето съвременни релеи предоставят на 465. Ако вашият релей документира един от двата, тази документация има предимство пред всяко предпочитание, придобито от предишен работодател.
Какво ви дава STARTTLS на 587
Надграждането се случва вътре в SMTP разговора, така че релеят може да го обяви, а клиентът може да откаже да продължи без него. В Python starttls() хвърля изключение, когато сървърът не предлага разширението, което превръща опита за понижаване във видим отказ вместо тих вход с открит текст. Това поведение е основният аргумент за режима и си струва да се разчита на него умишлено, а не случайно.
Прозорецът на излагане е малък, но реален: първоначалният обмен EHLO е четим и нищо преди надграждането не носи удостоверения. На частен път между подмрежа на приложението и вътрешен релей този прозорец обикновено е приемлив. На път, който не контролирате, точно това кара някои екипи да настояват за другия режим.
Устройство за прихващане, което премахва възможността STARTTLS, произвежда неуспешно подаване, а не отстъпление. Това е резултатът, който искате, и е най-бързият начин да откриете, че нещо пренаписва SMTP по пътя.
Какво променя неявният TLS на 465
Изобщо няма фаза на открит текст. Клиентът извършва TLS ръкостискане веднага щом сокетът се отвори, проверява сертификата спрямо своето хранилище за доверие и само след това обменя EHLO, AUTH и съобщението. Нищо не може да понижи сесията, защото няма състояние на открит текст, от което да се преговаря, и това премахва цял клас откази при прихващане.
Разменната стойност е диагностицируемостта. Релей, който отговаря на 465 с открит SMTP, или балансиращ товара, който прекратява TLS с грешно име на сертификат, се проваля при ръкостискането и ви дава SSL грешка вместо пощенска грешка. Когато това се случи, проблемът е под приложението и никакво изграждане на съобщение няма да го поправи.
Една функция за подаване, два транспорта
Кодът едва се различава между режимите, затова изборът трябва да се прави от политиката на релея, а не от удобството на библиотеката. Първо експортирайте настройките, после стартирайте един скрипт, който избира транспорта от конфигурирания порт.
export SMTP_HOST=smtp.relay.internal export SMTP_PORT=587 export SMTP_USER=svc-notify export [email protected] export [email protected] export SMTP_PASSWORD="$(cat /run/secrets/smtp_password)"
import os, smtplib, ssl
from email.message import EmailMessage
ctx = ssl.create_default_context()
msg = EmailMessage()
msg["From"] = os.environ["MAIL_FROM"]
msg["To"] = os.environ["MAIL_TEST_TO"]
msg["Subject"] = "relay submission test"
msg.set_content("One authorized test message from the app.")
host = os.environ["SMTP_HOST"]
port = int(os.environ.get("SMTP_PORT", "587"))
if port == 465:
client = smtplib.SMTP_SSL(host, port, context=ctx, timeout=15)
else:
client = smtplib.SMTP(host, port, timeout=15)
client.ehlo()
client.starttls(context=ctx)
client.ehlo()
with client:
client.login(os.environ["SMTP_USER"], os.environ["SMTP_PASSWORD"])
refused = client.send_message(msg)
print("refused:", refused)
create_default_context() проверява веригата и името на хоста, така че частен CA трябва да се инсталира в хранилището за доверие, а не да се заобикаля с непроверен контекст. send_message() връща речник на получателите, които релеят е отказал; празният речник означава, че е приел всички. Петнадесетсекундният таймаут е отправна точка, а не настроена стойност за вашата мрежа.
Дръжте паролата извън образа и извън журналите. Тайна, монтирана по време на изпълнение и прочетена в средата, е добре; отразяването на средата в ред от журнала не е, нито отладочна следа от AUTH обмена.
Избирайте по политика на релея и път, а не по навик
Три условия решават въпроса. Първо, какво релеят всъщност обявява: услуга, която слуша само на 587 със STARTTLS, не може да бъде достигната на 465, колкото и да предпочитате неявния TLS. Второ, дали пътят между приложението и релея е достатъчно доверен, за да приеме четим обмен EHLO. Трето, дали нещо по средата пренаписва или проксира SMTP, защото това решава дали рискът от понижаване е теоретичен или реален.
Когато релеят предлага и двете, избирам 465 за услуга, която работи без надзор, и 587 за такава, при която човек наблюдава първото внедряване, просто защото съобщенията за отказ сочат към различни слоеве. Това предпочитание е мое и не е политика.
Каквото и да изберете, запишете порта, доверителния материал и домейна на подателя заедно с внедряването. След това изпратете едно разрешено тестово съобщение и потвърдете, че релеят го е приел, като пазите резултата от подаването и всеки по-късен отчет за доставка като отделни записи. Успешното предаване на релея е краят на отговорността на приложението, а не доказателство, че съобщението е стигнало до пощенска кутия.