Trimitere SMTP cu Python: STARTTLS pe 587 sau TLS pe 465?
STARTTLS pe 587 și TLS implicit pe 465 transportă ambele o trimitere Python, dar numai unul se potrivește cu relay-ul tău. Compară handshake-ul și modurile de eșec.
Ambele moduri de conexiune transportă același mesaj către același cont de relay, iar diferența stă în locul unde începe TLS, nu în ce se trimite. Unul se deschide în text clar și este actualizat în interiorul conexiunii; celălalt este criptat de la primul octet. Această singură diferență decide ce va accepta o politică strictă de relay, ce poate observa un dispozitiv intermediar și ce mesaj de eroare vei petrece o după-amiază citindu-l. Iată cum aleg între ele când o aplicație dintr-o rețea privată predă un mesaj unui serviciu intern de trimitere.
Unde cele două moduri chiar diverg
Sarcina este identică în ambele cazuri: autentificare ca un cont aprobat, numirea unui expeditor permis, listarea destinatarilor și așteptarea ca relay-ul să își asume responsabilitatea. Ceea ce se schimbă este ordinea operațiilor. Portul 587 începe ca o sesiune SMTP în text clar, anunță STARTTLS în răspunsul EHLO și este actualizat doar dacă clientul cere asta. Portul 465 așteaptă un handshake TLS înainte ca vreo comandă SMTP să fie rostită.
Trimiterea și relay-ul sunt lucruri diferite, de aceea numerele de port nu sunt obiceiuri interschimbabile. RFC 6409 descrie trimiterea mesajelor ca serviciu propriu, iar RFC 8314 acoperă varianta TLS implicit pe care majoritatea relay-urilor moderne o expun pe 465. Dacă relay-ul tău documentează una dintre cele două, acea documentație are prioritate față de orice preferință dobândită la un angajator anterior.
Ce îți oferă STARTTLS pe 587
Actualizarea are loc în interiorul conversației SMTP, așa că relay-ul o poate anunța, iar clientul poate refuza să continue fără ea. În Python, starttls() aruncă o excepție când serverul nu oferă extensia, ceea ce transformă o încercare de retrogradare într-un eșec vizibil în locul unei autentificări silențioase în text clar. Acest comportament este argumentul principal al modului și merită să te bazezi pe el în mod deliberat, nu din întâmplare.
Fereastra de expunere este mică, dar reală: schimbul EHLO inițial este lizibil și nimic dinaintea actualizării nu transportă acreditări. Pe o cale privată între o subrețea a aplicației și un relay intern, acea fereastră este de obicei acceptabilă. Pe o cale pe care nu o controlezi, exact acesta este motivul pentru care unele echipe insistă pe celălalt mod.
Un dispozitiv de interceptare care elimină capacitatea STARTTLS produce o trimitere eșuată, nu o revenire. Acesta este rezultatul dorit și este totodată cea mai rapidă cale de a descoperi că ceva rescrie SMTP pe cale.
Ce schimbă TLS implicit pe 465
Nu există deloc o fază de text clar. Clientul efectuează un handshake TLS imediat ce socketul se deschide, validează certificatul împotriva magazinului său de încredere și abia apoi schimbă EHLO, AUTH și mesajul. Nimic nu poate retrograda sesiunea pentru că nu există o stare de text clar din care să negociezi, iar asta elimină o întreagă clasă de eșecuri de interceptare.
Schimbul este diagnosticabilitatea. Un relay care răspunde pe 465 cu SMTP în text clar, sau un balansor de sarcină care încheie TLS cu numele greșit al certificatului, eșuează la handshake și îți dă o eroare SSL în locul unei erori de poștă. Când se întâmplă asta, problema este sub aplicație, iar nicio construcție a mesajului nu o repară.
O singură funcție de trimitere, două transporturi
Codul aproape nu diferă între moduri, de aceea alegerea ar trebui făcută din politica relay-ului, nu din comoditatea bibliotecii. Exportă mai întâi setările, apoi rulează un singur script care selectează transportul în funcție de portul configurat.
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() verifică lanțul și numele gazdei, așa că o AC privată trebuie instalată în magazinul de încredere în loc să fie ocolită cu un context neverificat. send_message() returnează un dicționar al destinatarilor pe care relay-ul i-a refuzat; un dicționar gol înseamnă că i-a acceptat pe toți. Timeout-ul de cincisprezece secunde este un punct de plecare, nu o valoare reglată pentru rețeaua ta.
Ține parola în afara imaginii și în afara jurnalelor. Un secret montat la execuție și citit în mediu este în regulă; să reflectezi mediul într-o linie de jurnal nu este, la fel cum nu este nici o urmă de depanare a schimbului AUTH.
Alege după politica relay-ului și cale, nu după obicei
Trei condiții decid. În primul rând, ce anunță relay-ul de fapt: un serviciu care ascultă doar pe 587 cu STARTTLS nu poate fi atins pe 465, oricât ai prefera TLS implicit. În al doilea rând, dacă calea dintre aplicație și relay este suficient de demnă de încredere pentru a accepta un schimb EHLO lizibil. În al treilea rând, dacă ceva de la mijloc rescrie sau face proxy pentru SMTP, pentru că asta decide dacă riscul de retrogradare este teoretic sau real.
Când relay-ul oferă ambele, iau 465 pentru un serviciu care rulează nesupravegheat și 587 pentru unul în care o persoană urmărește prima implementare, doar pentru că mesajele de eșec indică straturi diferite. Acea preferință este a mea și nu este o politică.
Orice ai alege, notează portul, materialul de încredere și domeniul expeditorului împreună cu implementarea. Apoi trimite un mesaj de test autorizat și confirmă că relay-ul l-a acceptat, păstrând rezultatul trimiterii și orice raport de livrare ulterior ca înregistrări separate. O predare reușită către relay este sfârșitul responsabilității aplicației, nu dovada că mesajul a ajuns într-o cutie de intrare.