Pengiriman SMTP Python: STARTTLS di 587 atau TLS di 465?
STARTTLS di 587 dan TLS implisit di 465 keduanya membawa pengiriman Python, tetapi hanya satu yang cocok dengan relay Anda. Bandingkan jabat tangan dan mode kegagalannya.
Kedua mode koneksi membawa pesan yang sama ke akun relay yang sama, dan perbedaannya terletak pada tempat TLS dimulai, bukan pada apa yang dikirim. Yang satu terbuka dalam teks polos lalu meningkat di dalam koneksi; yang lain terenkripsi sejak byte pertama. Perbedaan tunggal itu menentukan apa yang akan diterima kebijakan relay yang ketat, apa yang dapat diamati perangkat perantara, dan pesan kesalahan mana yang akan Anda baca sepanjang sore. Begini cara saya memilih di antara keduanya ketika aplikasi di jaringan privat menyerahkan pesan ke layanan pengiriman internal.
Di mana kedua mode sebenarnya menyimpang
Tugasnya identik dalam kedua kasus: mengautentikasi sebagai akun yang disetujui, menyebutkan pengirim yang diizinkan, mendaftar penerima, dan menunggu relay mengambil tanggung jawab. Yang berubah adalah urutan operasi. Port 587 dimulai sebagai sesi SMTP teks polos, mengiklankan STARTTLS dalam respons EHLO, dan hanya meningkat jika klien memintanya. Port 465 mengharapkan jabat tangan TLS sebelum perintah SMTP apa pun diucapkan.
Pengiriman dan relay adalah pekerjaan berbeda, sehingga nomor port bukan kebiasaan yang dapat dipertukarkan. RFC 6409 menjelaskan pengiriman pesan sebagai layanan tersendiri, dan RFC 8314 mencakup varian TLS implisit yang ditawarkan sebagian besar relay modern pada 465. Jika relay Anda mendokumentasikan salah satu dari keduanya, dokumentasi itu mengalahkan preferensi apa pun yang Anda peroleh dari pemberi kerja sebelumnya.
Apa yang diberikan STARTTLS di 587 kepada Anda
Peningkatan terjadi di dalam percakapan SMTP, sehingga relay dapat mengiklankannya dan klien dapat menolak melanjutkan tanpanya. Di Python, starttls() melempar pengecualian ketika server tidak menawarkan ekstensi itu, yang mengubah upaya penurunan menjadi kegagalan yang terlihat alih-alih login teks polos yang senyap. Perilaku itu adalah argumen utama mode ini, dan sebaiknya diandalkan dengan sengaja, bukan secara kebetulan.
Jendela paparan kecil tetapi nyata: pertukaran EHLO awal dapat dibaca, dan tidak ada yang membawa kredensial sebelum peningkatan. Pada jalur privat antara subnet aplikasi dan relay internal, jendela itu biasanya dapat diterima. Pada jalur yang tidak Anda kendalikan, itulah alasan sebagian tim bersikeras pada mode lainnya.
Perangkat penyadapan yang menghapus kemampuan STARTTLS menghasilkan pengiriman yang gagal, bukan kemunduran. Itulah hasil yang Anda inginkan, dan itu juga cara tercepat untuk menemukan bahwa ada sesuatu yang menulis ulang SMTP di jalur.
Apa yang diubah TLS implisit di 465
Tidak ada fase teks polos sama sekali. Klien melakukan jabat tangan TLS begitu soket terbuka, memvalidasi sertifikat terhadap penyimpanan kepercayaannya, dan baru setelah itu menukar EHLO, AUTH, dan pesan. Tidak ada yang dapat menurunkan sesi karena tidak ada keadaan teks polos untuk dinegosiasikan, dan itu menghapus seluruh kelas kegagalan penyadapan.
Pertukarannya adalah kemampuan diagnostik. Relay yang menjawab di 465 dengan SMTP teks polos, atau penyeimbang beban yang mengakhiri TLS dengan nama sertifikat yang salah, gagal pada jabat tangan dan memberi Anda kesalahan SSL alih-alih kesalahan email. Ketika itu terjadi, masalahnya berada di bawah aplikasi, dan sebanyak apa pun konstruksi pesan tidak akan memperbaikinya.
Satu fungsi pengiriman, dua transport
Kodenya hampir tidak berbeda antar mode, sehingga pilihan harus diambil dari kebijakan relay, bukan dari kenyamanan pustaka. Ekspor pengaturannya lebih dulu, lalu jalankan satu skrip yang memilih transport menurut port yang dikonfigurasi.
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() memverifikasi rantai dan nama host, jadi CA privat harus dipasang di penyimpanan kepercayaan, bukan dilewati dengan konteks yang tidak terverifikasi. send_message() mengembalikan kamus penerima yang ditolak relay; kamus kosong berarti relay menerima semuanya. Batas waktu lima belas detik adalah titik awal, bukan nilai yang sudah disetel untuk jaringan Anda.
Jauhkan kata sandi dari image dan dari log. Rahasia yang dipasang saat runtime dan dibaca ke lingkungan tidak masalah; menggemakan lingkungan ke baris log tidak boleh, begitu pula jejak debug pertukaran AUTH.
Pilih berdasarkan kebijakan relay dan jalur, bukan kebiasaan
Tiga kondisi memutuskannya. Pertama, apa yang benar-benar diiklankan relay: layanan yang hanya mendengarkan di 587 dengan STARTTLS tidak dapat dijangkau di 465, seberapa pun Anda menyukai TLS implisit. Kedua, apakah jalur antara aplikasi dan relay cukup tepercaya untuk menerima pertukaran EHLO yang dapat dibaca. Ketiga, apakah ada sesuatu di tengah yang menulis ulang atau memproksikan SMTP, karena itu menentukan apakah risiko penurunan bersifat teoretis atau nyata.
Ketika relay menawarkan keduanya, saya mengambil 465 untuk layanan yang berjalan tanpa pengawasan dan 587 untuk layanan yang seseorang mengawasi penerapan pertamanya, semata-mata karena pesan kegagalan menunjuk ke lapisan yang berbeda. Preferensi itu milik saya dan itu bukan kebijakan.
Apa pun yang Anda pilih, catat port, materi kepercayaan, dan domain pengirim bersama penerapannya. Lalu kirim satu pesan uji yang diotorisasi dan pastikan relay menerimanya, dengan menyimpan hasil pengiriman dan laporan pengiriman berikutnya sebagai catatan terpisah. Penyerahan yang berhasil ke relay adalah akhir tanggung jawab aplikasi, bukan bukti bahwa pesan mencapai kotak masuk.