Begge forbindelsestilstande bærer den samme meddelelse til den samme relækonto, og forskellen er, hvor TLS begynder, ikke hvad der sendes. Den ene åbner i klartekst og opgraderes i båndet; den anden er krypteret fra den første byte. Den ene forskel afgør, hvad en streng relæpolitik accepterer, hvad en mellemboks kan observere, og hvilken fejlmeddelelse du kommer til at læse en hel eftermiddag. Her er, hvordan jeg vælger mellem dem, når en applikation på et privat netværk afleverer en meddelelse til en intern afsendelsestjeneste.

Hvor de to tilstande faktisk skilles

Opgaven er identisk i begge tilfælde: godkend som en godkendt konto, angiv en tilladte afsender, list modtagere, og vent på, at relæet påtager sig ansvaret. Det, der ændrer sig, er rækkefølgen af operationer. Port 587 starter som en SMTP-session i klartekst, annoncerer STARTTLS i sit EHLO-svar og opgraderes kun, hvis klienten beder om det. Port 465 forventer et TLS-håndtryk, før nogen SMTP-kommando siges.

Afsendelse og relæ er forskellige opgaver, og derfor er portnumrene ikke udskiftelige vaner. RFC 6409 beskriver afsendelse af meddelelser som sin egen tjeneste, og RFC 8314 dækker implicit-TLS-varianten, som de fleste moderne relæer udstiller på 465. Hvis dit relæ dokumenterer en af de to, går den dokumentation forud for enhver præference, du har fra en tidligere arbejdsgiver.

Hvad STARTTLS på 587 giver dig

Opgraderingen sker inde i SMTP-samtalen, så relæet kan annoncere den, og klienten kan nægte at fortsætte uden den. I Python kaster starttls() en undtagelse, når serveren ikke tilbyder udvidelsen, hvilket gør et nedgraderingsforsøg til en synlig fejl i stedet for et lydløst klartekst-login. Den adfærd er hovedargumentet for tilstanden, og det er værd at stole på den med vilje frem for ved et tilfælde.

Eksponeringsvinduet er lille men reelt: den indledende EHLO-udveksling er læsbar, og intet før opgraderingen bærer legitimationsoplysninger. På en privat vej mellem et applikationssubnet og et internt relæ er det vindue normalt acceptabelt. På en vej, du ikke kontrollerer, er det grunden til, at nogle teams insisterer på den anden tilstand.

En aflytningsenhed, der fjerner STARTTLS-evnen, giver en mislykket afsendelse, ikke et tilbageskridt. Det er det resultat, du ønsker, og det er også den hurtigste måde at opdage, at noget omskriver SMTP på vejen.

Hvad implicit TLS på 465 ændrer

Der er slet ingen klartekstfase. Klienten udfører et TLS-håndtryk, så snart soklen åbnes, validerer certifikatet mod sit tillidslager og udveksler først derefter EHLO, AUTH og meddelelsen. Intet kan nedgradere sessionen, fordi der ikke findes en klarteksttilstand at forhandle ud fra, og det fjerner en hel klasse af aflytningsfejl.

Handlen er diagnosticerbarhed. Et relæ, der svarer på 465 med klartekst-SMTP, eller en lastbalancer, der afslutter TLS med det forkerte certifikatnavn, fejler ved håndtrykket og giver dig en SSL-fejl i stedet for en mailfejl. Når det sker, ligger problemet under applikationen, og ingen mængde meddelelsesopbygning retter det.

Én afsendelsesfunktion, to transporter

Koden adskiller sig næsten ikke mellem tilstandene, og derfor bør valget træffes ud fra relæpolitik frem for biblioteksbekvemmelighed. Eksportér først indstillingerne, og kør derefter et enkelt script, der vælger transporten ud fra den konfigurerede port.

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() verificerer kæden og værtsnavnet, så en privat CA skal installeres i tillidslageret i stedet for at blive omgået med en uverificeret kontekst. send_message() returnerer en ordbog over modtagere, som relæet afviste; en tom ordbog betyder, at det accepterede dem alle. Timeoutet på femten sekunder er et udgangspunkt, ikke en finjusteret værdi for dit netværk.

Hold adgangskoden ude af imaget og ude af logfilerne. En hemmelighed, der monteres ved kørsel og læses ind i miljøet, er fin; at ekko miljøet ud i en loglinje er det ikke, og en fejlretningsspor af AUTH-udvekslingen er det heller ikke.

Vælg efter relæpolitik og vej, ikke efter vane

Tre betingelser afgør det. For det første, hvad relæet faktisk annoncerer: en tjeneste, der kun lytter på 587 med STARTTLS, kan ikke nås på 465, uanset hvor meget du foretrækker implicit TLS. For det andet, om vejen mellem applikationen og relæet er betroet nok til at acceptere en læsbar EHLO-udveksling. For det tredje, om noget i midten omskriver eller proxyer SMTP, fordi det afgør, om nedgraderingsrisikoen er teoretisk eller levende.

Når relæet tilbyder begge, tager jeg 465 til en tjeneste, der kører uden opsyn, og 587 til en, hvor en person ser den første udrulning, udelukkende fordi fejlmeddelelserne peger på forskellige lag. Den præference er min, og den er ikke en politik.

Uanset hvad du vælger, så notér porten, tillidsmaterialet og afsenderdomænet sammen med udrulningen. Send derefter én autoriseret testmeddelelse og bekræft, at relæet accepterede den, og opbevar afsendelsesresultatet og enhver senere leveringsrapport som særskilte poster. En vellykket aflevering til relæet er slutningen på applikationens ansvar, ikke bevis for, at meddelelsen nåede en indbakke.

Relaterede artikler