Odesílání e-mailů v Pythonu přes SMTP: STARTTLS na 587, nebo TLS na 465?
STARTTLS na 587 a implicitní TLS na 465 oba přenesou odeslání z Pythonu, ale vašemu relé vyhovuje jen jeden. Porovnejte navázání spojení a způsoby selhání.
Oba režimy připojení přenášejí stejnou zprávu na stejný účet relé a rozdíl je v tom, kde začíná TLS, ne v tom, co se odesílá. Jeden se otevírá v otevřeném textu a upgraduje se uvnitř spojení; druhý je šifrovaný od prvního bajtu. Tento jediný rozdíl rozhoduje o tom, co přijme striktní politika relé, co může pozorovat mezilehlé zařízení a kterou chybovou zprávu strávíte odpoledne čtením. Takto mezi nimi volím, když aplikace v privátní síti předává zprávu interní službě pro odesílání.
Kde se oba režimy skutečně rozcházejí
Úkol je v obou případech stejný: autentizovat se jako schválený účet, uvést povoleného odesílatele, vyjmenovat příjemce a počkat, až relé převezme odpovědnost. Mění se pořadí operací. Port 587 začíná jako SMTP relace v otevřeném textu, inzeruje STARTTLS v odpovědi EHLO a upgraduje se jen tehdy, pokud o to klient požádá. Port 465 očekává TLS handshake dříve, než zazní jakýkoli příkaz SMTP.
Odesílání a relé jsou různé úlohy, a proto čísla portů nejsou zaměnitelné zvyky. RFC 6409 popisuje odesílání zpráv jako samostatnou službu a RFC 8314 pokrývá variantu implicitního TLS, kterou většina moderních relé vystavuje na 465. Pokud vaše relé dokumentuje jeden z těchto dvou, má tato dokumentace přednost před jakoukoli preferencí z předchozího zaměstnání.
Co vám STARTTLS na 587 dává
Upgrade probíhá uvnitř konverzace SMTP, takže jej relé může inzerovat a klient může odmítnout pokračovat bez něj. V Pythonu starttls() vyhodí výjimku, když server rozšíření nenabízí, což pokus o downgrade promění ve viditelné selhání místo tichého přihlášení v otevřeném textu. Toto chování je hlavním argumentem pro tento režim a stojí za to se na něj spoléhat záměrně, ne náhodou.
Okno expozice je malé, ale skutečné: počáteční výměna EHLO je čitelná a nic před upgradem nenese přihlašovací údaje. Na privátní cestě mezi podsítí aplikace a interním relé je toto okno obvykle přijatelné. Na cestě, kterou nekontrolujete, je to důvod, proč některé týmy trvají na druhém režimu.
Zařízení pro odposlech, které odstraní schopnost STARTTLS, vytvoří neúspěšné odeslání, ne návrat. To je výsledek, který chcete, a zároveň nejrychlejší způsob, jak zjistit, že něco na cestě přepisuje SMTP.
Co mění implicitní TLS na 465
Žádná fáze otevřeného textu neexistuje. Klient provede TLS handshake hned po otevření soketu, ověří certifikát proti svému úložišti důvěry a teprve potom vymění EHLO, AUTH a zprávu. Nic nemůže relaci downgradovat, protože neexistuje stav otevřeného textu, ze kterého by se dalo vyjednávat, a to odstraňuje celou třídu selhání odposlechu.
Kompromisem je diagnostikovatelnost. Relé, které na 465 odpovídá otevřeným SMTP, nebo load balancer, který ukončí TLS s nesprávným názvem certifikátu, selže při handshaku a dá vám chybu SSL místo chyby pošty. Když se to stane, problém je pod aplikací a žádné sestavování zprávy to nespraví.
Jedna funkce pro odesílání, dva transporty
Kód se mezi režimy sotva liší, a proto by se volba měla dělat podle politiky relé, ne podle pohodlí knihovny. Nejprve exportujte nastavení a poté spusťte jediný skript, který vybere transport podle nakonfigurovaného portu.
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() ověřuje řetězec a název hostitele, takže privátní CA se musí nainstalovat do úložiště důvěry, ne obejít neověřeným kontextem. send_message() vrací slovník příjemců, které relé odmítlo; prázdný slovník znamená, že přijalo všechny. Patnáctisekundový timeout je výchozí bod, ne vyladěná hodnota pro vaši síť.
Držte heslo mimo image a mimo logy. Tajemství připojené za běhu a načtené do prostředí je v pořádku; vypisovat prostředí do řádku logu není, stejně jako ladicí stopa výměny AUTH.
Vybírejte podle politiky relé a cesty, ne podle zvyku
Rozhodují tři podmínky. Za prvé, co relé skutečně inzeruje: služba, která poslouchá jen na 587 se STARTTLS, není na 465 dostupná, ať už implicitnímu TLS fandíte jakkoli. Za druhé, zda je cesta mezi aplikací a relé natolik důvěryhodná, aby přijala čitelnou výměnu EHLO. Za třetí, zda něco uprostřed přepisuje nebo proxyuje SMTP, protože to rozhoduje o tom, zda je riziko downgrade teoretické, nebo skutečné.
Když relé nabízí obojí, beru 465 pro službu, která běží bez dozoru, a 587 pro tu, u níž člověk sleduje první nasazení, čistě proto, že chybové zprávy ukazují na různé vrstvy. Ta preference je moje a není to politika.
Ať už zvolíte cokoli, zaznamenejte port, důvěryhodný materiál a doménu odesílatele spolu s nasazením. Pak pošlete jednu autorizovanou testovací zprávu a potvrďte, že ji relé přijalo, přičemž výsledek odeslání a případný pozdější report o doručení uchovejte jako samostatné záznamy. Úspěšné předání relé je konec odpovědnosti aplikace, ne důkaz, že zpráva dorazila do schránky.