การส่ง SMTP ด้วย Python: STARTTLS บน 587 หรือ TLS บน 465?
STARTTLS บน 587 และ TLS แบบโดยนัยบน 465 ทั้งคู่ส่งต่อการส่งของ Python แต่มีเพียงตัวเดียวที่ตรงกับรีเลย์ของคุณ เปรียบเทียบแฮนด์เชกและรูปแบบความล้มเหลว
ทั้งสองโหมดเชื่อมต่อนำส่งข้อความเดียวกันไปยังบัญชีรีเลย์เดียวกัน และความแตกต่างอยู่ที่ TLS เริ่มต้นที่ใด ไม่ใช่สิ่งที่ถูกส่ง อันหนึ่งเปิดด้วยข้อความธรรมดาแล้วยกระดับภายในช่องสัญญาณ อีกอันถูกเข้ารหัสตั้งแต่ไบต์แรก ความแตกต่างเดียวนี้ตัดสินว่า นโยบายรีเลย์ที่เข้มงวดจะยอมรับอะไร อุปกรณ์กลางทางจะสังเกตเห็นอะไร และคุณจะใช้เวลาช่วงบ่ายอ่านข้อความแสดงข้อผิดพลาดใด นี่คือวิธีที่ผมเลือกระหว่างสองโหมดเมื่อแอปพลิเคชันในเครือข่ายส่วนตัวส่งมอบข้อความให้บริการส่งภายใน
จุดที่สองโหมดแตกต่างกันจริง
งานเหมือนกันในทั้งสองกรณี: ยืนยันตัวตนเป็นบัญชีที่ได้รับอนุมัติ ระบุผู้ส่งที่ได้รับอนุญาต แสดงรายการผู้รับ และรอให้รีเลย์รับผิดชอบ สิ่งที่เปลี่ยนคือลำดับการทำงาน พอร์ต 587 เริ่มต้นเป็นเซสชัน SMTP แบบข้อความธรรมดา ประกาศ STARTTLS ในคำตอบ EHLO และยกระดับก็ต่อเมื่อไคลเอนต์ร้องขอ พอร์ต 465 คาดหวังแฮนด์เชก TLS ก่อนที่จะมีการกล่าวคำสั่ง SMTP ใดๆ
การส่งและการรีเลย์เป็นงานคนละอย่าง จึงเป็นเหตุให้หมายเลขพอร์ตไม่ใช่ความเคยชินที่สลับกันได้ RFC 6409 อธิบายการส่งข้อความว่าเป็นบริการของตัวเอง และ RFC 8314 ครอบคลุมรูปแบบ TLS แบบโดยนัยที่รีเลย์สมัยใหม่ส่วนใหญ่ให้บริการบน 465 หากรีเลย์ของคุณมีเอกสารระบุหนึ่งในสองอย่าง เอกสารนั้นย่อมเหนือกว่าความชอบใดๆ ที่คุณได้มาจากนายจ้างเดิม
STARTTLS บน 587 ให้อะไรกับคุณ
การยกระดับเกิดขึ้นภายในบทสนทนา SMTP รีเลย์จึงประกาศได้ และไคลเอนต์สามารถปฏิเสธที่จะดำเนินการต่อหากไม่มี ใน Python นั้น starttls() โยนข้อยกเว้นเมื่อเซิร์ฟเวอร์ไม่เสนอส่วนขยายนั้น ซึ่งเปลี่ยนความพยายามลดระดับให้เป็นความล้มเหลวที่มองเห็นได้แทนการเข้าสู่ระบบแบบข้อความธรรมดาอย่างเงียบๆ พฤติกรรมนั้นคือข้อโต้แย้งหลักของโหมดนี้ และควรค่าแก่การพึ่งพาโดยเจตนาไม่ใช่โดยบังเอิญ
หน้าต่างการเปิดเผยนั้นเล็กแต่มีจริง: การแลกเปลี่ยน EHLO เริ่มต้นอ่านได้ และไม่มีสิ่งใดก่อนการยกระดับที่พาข้อมูลรับรอง ช่องทางส่วนตัวระหว่างซับเน็ตของแอปพลิเคชันกับรีเลย์ภายใน หน้าต่างนั้นมักยอมรับได้ บนเส้นทางที่คุณไม่ได้ควบคุม นั่นคือเหตุผลที่บางทีมยืนยันในอีกโหมดหนึ่ง
อุปกรณ์ดักจับที่ถอดความสามารถ STARTTLS ออกทำให้การส่งล้มเหลว ไม่ใช่การถอยกลับ นั่นคือผลลัพธ์ที่คุณต้องการ และยังเป็นวิธีเร็วที่สุดในการค้นพบว่ามีบางสิ่งกำลังเขียน SMTP ใหม่บนเส้นทาง
TLS แบบโดยนัยบน 465 เปลี่ยนแปลงอะไร
ไม่มีขั้นตอนข้อความธรรมดาเลย ไคลเอนต์ทำแฮนด์เชก TLS ทันทีที่ซ็อกเก็ตเปิด ตรวจสอบใบรับรองกับที่เก็บความเชื่อถือของตน และจากนั้นจึงแลกเปลี่ยน EHLO, AUTH และข้อความ ไม่มีสิ่งใดลดระดับเซสชันได้เพราะไม่มีสถานะข้อความธรรมดาให้เจรจา และสิ่งนี้กำจัดความล้มเหลวจากการดักจับทั้งประเภท
สิ่งที่แลกมาคือความสามารถในการวินิจฉัย รีเลย์ที่ตอบบน 465 ด้วย SMTP แบบข้อความธรรมดา หรือโหลดบาลานเซอร์ที่ สิ้นสุด TLS ด้วยชื่อใบรับรองที่ผิด จะล้มเหลวที่แฮนด์เชกและให้ข้อผิดพลาด SSL แทนข้อผิดพลาดของอีเมล เมื่อสิ่งนั้นเกิดขึ้น ปัญหาอยู่ใต้แอปพลิเคชัน และการสร้างข้อความไม่ว่าจะมากเพียงใดก็ไม่แก้ไข
ฟังก์ชันส่งหนึ่งฟังก์ชัน การขนส่งสองแบบ
โค้ดแทบไม่ต่างกันระหว่างสองโหมด จึงควรเลือกจากนโยบายของรีเลย์ไม่ใช่จากความสะดวกของไลบรารี ส่งออกการตั้งค่าก่อน จากนั้นรันสคริปต์เดียวที่เลือกการขนส่งตามพอร์ตที่กำหนด
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() ตรวจสอบห่วงโซ่และชื่อโฮสต์ CA ส่วนตัวจึงต้องติดตั้งในที่เก็บความเชื่อถือแทนที่จะข้ามด้วยบริบทที่ไม่ตรวจสอบ send_message() ส่งคืนพจนานุกรมของผู้รับที่รีเลย์ปฏิเสธ พจนานุกรมว่างหมายความว่ารีเลย์ยอมรับทั้งหมด การหมดเวลาสิบห้าวินาทีเป็นจุดเริ่มต้น ไม่ใช่ค่าที่ปรับแต่งสำหรับเครือข่ายของคุณ
เก็บรหัสผ่านไว้นอกอิมเมจและนอกบันทึก ความลับที่เมานต์ตอนรันไทม์และอ่านเข้าสภาพแวดล้อมนั้นใช้ได้ การสะท้อนสภาพแวดล้อมลงในบรรทัดบันทึกไม่ใช้ได้ และร่องรอยการดีบักของการแลกเปลี่ยน AUTH ก็ไม่ใช้ได้เช่นกัน
เลือกตามนโยบายรีเลย์และเส้นทาง ไม่ใช่ตามความเคยชิน
เงื่อนไขสามข้อตัดสิน สิ่งแรก สิ่งที่รีเลย์ประกาศจริง: บริการที่ฟังเฉพาะบน 587 ด้วย STARTTLS ไม่สามารถเข้าถึงได้บน 465 ไม่ว่าคุณจะชอบ TLS แบบโดยนัยเพียงใด สิ่งที่สอง เส้นทางระหว่างแอปพลิเคชันกับรีเลย์เชื่อถือได้พอที่จะยอมรับการแลกเปลี่ยน EHLO ที่อ่านได้หรือไม่ สิ่งที่สาม มีบางสิ่งตรงกลางเขียน SMTP ใหม่หรือพร็อกซีหรือไม่ เพราะสิ่งนั้นตัดสินว่า ความเสี่ยงจากการลดระดับเป็นทฤษฎีหรือเกิดขึ้นจริง
เมื่อรีเลย์เสนอทั้งสอง ผมเลือก 465 สำหรับบริการที่ทำงานโดยไม่มีผู้ดูแล และ 587 สำหรับบริการที่มีคนเฝ้าดูการติดตั้งครั้งแรก เพียงเพราะข้อความความล้มเหลวชี้ไปที่ชั้นที่ต่างกัน ความชอบนั้นเป็นของผมและไม่ใช่นโยบาย
ไม่ว่าคุณจะเลือกสิ่งใด บันทึกพอร์ต วัสดุความเชื่อถือ และโดเมนผู้ส่งไว้พร้อมกับการติดตั้ง จากนั้นส่งข้อความทดสอบที่ได้รับอนุญาตหนึ่งฉบับและยืนยันว่ารีเลย์ยอมรับ โดยเก็บผลการส่งและรายงานการนำส่งในภายหลังเป็นบันทึกแยกกัน การส่งมอบให้รีเลย์สำเร็จคือจุดสิ้นสุดความรับผิดชอบของแอปพลิเคชัน ไม่ใช่หลักฐานว่าข้อความถึงกล่องจดหมาย