Python SMTP relej: STARTTLS na 587 ili TLS na 465?
STARTTLS na 587 i implicitni TLS na 465 oba prenose Python slanje, ali samo jedan odgovara vašem releju. Uporedite rukovanje i načine neuspjeha.
Oba načina povezivanja nose istu poruku na isti relejni račun, a razlika je u tome gdje TLS počinje, a ne šta se šalje. Jedan se otvara u čistom tekstu i nadograđuje unutar kanala; drugi je šifrovan od prvog bajta. Ta jedna razlika odlučuje šta će stroga relejna politika prihvatiti, šta posredni uređaj može primijetiti i koju ćete poruku o grešci čitati cijelo poslijepodne. Evo kako biram između njih kada aplikacija na privatnoj mreži predaje poruku internoj usluzi za slanje.
Gdje se dva načina zapravo razilaze
Zadatak je isti u oba slučaja: autentifikovati se kao odobreni račun, navesti dozvoljenog pošiljaoca, navesti primaoce i čekati da relej preuzme odgovornost. Mijenja se redoslijed operacija. Port 587 počinje kao SMTP sesija u čistom tekstu, objavljuje STARTTLS u svom EHLO odgovoru i nadograđuje se samo ako klijent zatraži. Port 465 očekuje TLS rukovanje prije nego što se izgovori ijedna SMTP naredba.
Slanje i relej su različiti poslovi, zbog čega brojevi portova nisu zamjenjive navike. RFC 6409 opisuje slanje poruka kao sopstvenu uslugu, a RFC 8314 pokriva varijantu implicitnog TLS-a koju većina modernih releja izlaže na 465. Ako vaš relej dokumentuje jedan od dva, ta dokumentacija nadjačava svaku preferenciju koju ste stekli kod prethodnog poslodavca.
Šta vam STARTTLS na 587 daje
Nadogradnja se dešava unutar SMTP razgovora, pa relej može da je objavi, a klijent može odbiti da nastavi bez nje. U Pythonu starttls() podiže izuzetak kada server ne nudi ekstenziju, što pokušaj degradacije pretvara u vidljiv neuspjeh umjesto tihog prijavljivanja u čistom tekstu. To ponašanje je glavni argument za ovaj način i vrijedi se na njega namjerno osloniti, a ne slučajno.
Prozor izloženosti je mali ali stvaran: početna EHLO razmjena je čitljiva i ništa prije nadogradnje ne nosi akreditive. Na privatnom putu između podmreže aplikacije i internog releja taj prozor je obično prihvatljiv. Na putu koji ne kontrolišete, upravo je to razlog zašto neki timovi insistiraju na drugom načinu.
Uređaj za presretanje koji uklanja mogućnost STARTTLS proizvodi neuspjelo slanje, a ne povratak na staro. To je ishod koji želite, a ujedno i najbrži način da otkrijete da nešto prepisuje SMTP na putu.
Šta implicitni TLS na 465 mijenja
Faze čistog teksta uopšte nema. Klijent izvodi TLS rukovanje čim se socket otvori, provjerava certifikat prema svom spremištu povjerenja i tek onda razmjenjuje EHLO, AUTH i poruku. Ništa ne može degradirati sesiju jer ne postoji stanje čistog teksta iz kojeg bi se pregovaralo, što uklanja cijelu klasu neuspjeha presretanja.
Trgovina je mogućnost dijagnostike. Relej koji odgovara na 465 čistim SMTP-om, ili balanser opterećenja koji završava TLS s pogrešnim imenom certifikata, pada na rukovanju i daje vam SSL grešku umjesto poštanske. Kada se to dogodi, problem je ispod aplikacije i nikakvo sastavljanje poruke ga neće popraviti.
Jedna funkcija za slanje, dva transporta
Kod se jedva razlikuje između načina, zbog čega odluku treba donijeti na osnovu relejne politike, a ne pogodnosti biblioteke. Prvo izvezite postavke, a zatim pokrenite jednu skriptu koja bira transport prema konfigurisanom 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() provjerava lanac i ime hosta, pa se privatni CA mora instalirati u spremište povjerenja, a ne zaobići neprovjerenim kontekstom. send_message() vraća rječnik primalaca koje je relej odbio; prazan rječnik znači da je prihvatio sve. Petnaestosekundni timeout je početna tačka, a ne podešena vrijednost za vašu mrežu.
Držite lozinku izvan slike i izvan zapisa. Tajna montirana u vrijeme izvršavanja i pročitana u okruženje je u redu; preslikavanje okruženja u liniju zapisa nije, kao ni debug trag AUTH razmjene.
Birajte prema relejnoj politici i putu, a ne prema navici
Tri uslova odlučuju. Prvo, šta relej zapravo objavljuje: usluga koja sluša samo na 587 sa STARTTLS ne može se dosegnuti na 465, koliko god preferirali implicitni TLS. Drugo, da li je put između aplikacije i releja dovoljno pouzdan da prihvati čitljivu EHLO razmjenu. Treće, da li nešto u sredini prepisuje ili proksira SMTP, jer to odlučuje da li je rizik degradacije teoretski ili stvaran.
Kada relej nudi oba, uzimam 465 za uslugu koja radi bez nadzora i 587 za onu gdje čovjek gleda prvo postavljanje, samo zato što poruke o neuspjehu upućuju na različite slojeve. Ta preferencija je moja i nije politika.
Šta god odaberete, zabilježite port, materijal povjerenja i domen pošiljaoca uz postavljanje. Zatim pošaljite jednu ovlaštenu testnu poruku i potvrdite da ju je relej prihvatio, čuvajući rezultat slanja i svaki kasniji izvještaj o dostavi kao odvojene zapise. Uspješno predavanje releju je kraj odgovornosti aplikacije, a ne dokaz da je poruka stigla u inbox.