Båda anslutningssätten bär samma meddelande till samma reläkonto, och skillnaden är var TLS börjar, inte vad som skickas. Det ena öppnar i klartext och uppgraderas inom anslutningen; det andra är krypterat från första byten. Den enda skillnaden avgör vad en strikt reläpolicy accepterar, vad en mellanliggande enhet kan observera och vilket felmeddelande du kommer att ägna en eftermiddag åt att läsa. Så här väljer jag mellan dem när en applikation i ett privat nätverk lämnar ett meddelande till en intern utskickstjänst.

Var de två sätten faktiskt skiljer sig

Uppgiften är identisk i båda fallen: autentisera som ett godkänt konto, ange en tillåten avsändare, lista mottagare och vänta på att reläet tar ansvar. Det som ändras är ordningen på operationerna. Port 587 börjar som en SMTP-session i klartext, annonserar STARTTLS i EHLO-svaret och uppgraderas bara om klienten begär det. Port 465 förväntar sig en TLS-handskakning innan något SMTP-kommando sägs.

Utskick och relä är olika jobb, därför är portnumren inte utbytbara vanor. RFC 6409 beskriver meddelandeutskick som en egen tjänst, och RFC 8314 täcker den implicita TLS-varianten som de flesta moderna reläer erbjuder på 465. Om ditt relä dokumenterar en av de två gäller den dokumentationen före varje preferens du fått från en tidigare arbetsgivare.

Vad STARTTLS på 587 ger dig

Uppgraderingen sker inne i SMTP-samtalet, så reläet kan annonsera den och klienten kan vägra att fortsätta utan den. I Python kastar starttls() ett undantag när servern inte erbjuder tillägget, vilket gör ett nedgraderingsförsök till ett synligt fel i stället för en tyst klartextinloggning. Det beteendet är huvudargumentet för sättet, och det är värt att förlita sig på det medvetet och inte av misstag.

Exponeringsfönstret är litet men verkligt: det inledande EHLO-utbytet är läsbart, och ingenting före uppgraderingen bär inloggningsuppgifter. På en privat väg mellan ett applikationssubnät och ett internt relä är det fönstret vanligtvis acceptabelt. På en väg du inte kontrollerar är det just därför vissa team insisterar på det andra sättet.

En avlyssningsenhet som tar bort STARTTLS-förmågan ger ett misslyckat utskick, inte en återgång. Det är det resultat du vill ha, och det är också det snabbaste sättet att upptäcka att något skriver om SMTP på vägen.

Vad implicit TLS på 465 ändrar

Det finns ingen klartextfas alls. Klienten utför en TLS-handskakning så snart socketen öppnas, validerar certifikatet mot sitt trust store och utbyter först därefter EHLO, AUTH och meddelandet. Ingenting kan nedgradera sessionen eftersom det inte finns något klartexttillstånd att förhandla från, och det tar bort en hel klass av avlyssningsfel.

Bytet är diagnostiserbarhet. Ett relä som svarar på 465 med klartext-SMTP, eller en lastbalanserare som avslutar TLS med fel certifikatnamn, misslyckas vid handskakningen och ger dig ett SSL-fel i stället för ett mailfel. När det händer ligger problemet under applikationen, och ingen mängd meddelandekonstruktion åtgärdar det.

En utskicksfunktion, två transporter

Koden skiljer sig knappt mellan sätten, därför bör valet göras utifrån reläpolicyn och inte utifrån bibliotekets bekvämlighet. Exportera först inställningarna och kör sedan ett enda skript som väljer transport utifrån den konfigurerade porten.

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() verifierar kedjan och värdnamnet, så en privat CA måste installeras i trust store i stället för att kringgås med en overifierad kontext. send_message() returnerar en ordbok över mottagare som reläet avvisade; en tom ordbok betyder att det accepterade alla. Timeouten på femton sekunder är en utgångspunkt, inte ett värde inställt för ditt nätverk.

Håll lösenordet utanför imagen och utanför loggarna. En hemlighet som monteras vid körning och läses in i miljön är okej; att eka miljön i en logglinje är det inte, och inte heller ett felsökningsspår av AUTH-utbytet.

Välj efter reläpolicy och väg, inte efter vana

Tre villkor avgör. För det första vad reläet faktiskt annonserar: en tjänst som bara lyssnar på 587 med STARTTLS går inte att nå på 465, hur mycket du än föredrar implicit TLS. För det andra om vägen mellan applikationen och reläet är betrodd nog att acceptera ett läsbart EHLO-utbyte. För det tredje om något i mitten skriver om eller proxar SMTP, eftersom det avgör om nedgraderingsrisken är teoretisk eller verklig.

När reläet erbjuder båda tar jag 465 för en tjänst som körs utan övervakning och 587 för en där en person följer den första driftsättningen, bara för att felmeddelandena pekar på olika lager. Den preferensen är min och den är inte en policy.

Vad du än väljer, anteckna porten, förtroendematerialet och avsändardomänen tillsammans med driftsättningen. Skicka sedan ett auktoriserat testmeddelande och bekräfta att reläet accepterade det, och spara utskicksresultatet och varje senare leveransrapport som separata poster. En lyckad överlämning till reläet är slutet på applikationens ansvar, inte bevis för att meddelandet nådde en inkorg.

Relaterade artiklar