ส่งจดหมายจาก Docker container พร้อมกับ Postfix ของโฮสต์


18

ฉันใช้เซิร์ฟเวอร์ Ubuntu 14.04 (Linux) ฉันได้ติดตั้งและกำหนดค่าPostfixและOpenDKIMอย่างดีบนเซิร์ฟเวอร์ ฉันสามารถส่งอีเมลไปยังตัวเองด้วยคำสั่งเช่นecho hi | sendmail rootและ postfix / opendkim จะเพิ่มส่วนหัวเช่นMessage-Id, DateและDKIM-Signatureส่งต่ออีเมลไปยังที่อยู่อีเมลส่วนตัวของฉันและทุกอย่างใช้งานได้ดี

ตอนนี้ฉันต้องการสร้างแอปพลิเคชันที่ทำงานในDocker container และสามารถส่งอีเมลได้อย่างง่ายดาย โดยเฉพาะอย่างยิ่งฉันไม่ต้องการกังวลเกี่ยวกับการเพิ่มส่วนหัวเช่นMessage-Idกันและฉันไม่ต้องการทำการกำหนดค่ามากหรือติดตั้งซอฟต์แวร์ภายในคอนเทนเนอร์เอง

วิธีที่ดีที่สุดในการทำเช่นนี้คืออะไร?

มีวิธีใดที่จะให้คอนเทนเนอร์รันsendmailexectuable บนโฮสต์หรือไม่?

ฉันพยายามทำการเชื่อมต่อกับ Postfix จากคอนเทนเนอร์โดยใช้โปรโตคอล SMTP บนพอร์ต 25 แต่ Postfix ดูเหมือนว่าจะรักษาข้อความที่ได้รับแตกต่างกัน ฉันคิดว่ามันไม่ได้เพิ่มส่วนหัวใด ๆ ดังนั้นข้อความที่ถูกปฏิเสธทันทีว่าเป็นจดหมายขยะโดย gmail (มันยังไม่ดีพอที่จะวางไว้ในโฟลเดอร์สแปมของฉัน)

นี่คือเนื้อหาของ maillog

Sep 28 23:35:52 dantooine postfix/smtpd[4306]: connect from unknown[172.17.0.95]
Sep 28 23:35:52 dantooine postfix/smtpd[4306]: DD457889B: client=unknown[172.17.0.95]
Sep 28 23:35:52 dantooine postfix/cleanup[4309]: DD457889B: message-id=<>
Sep 28 23:35:52 dantooine spamd[3175]: spamd: connection from localhost [::1]:59471 to port 783, fd 6
Sep 28 23:35:52 dantooine spamd[3175]: spamd: handle_user (getpwnam) unable to find user: 'someone'
Sep 28 23:35:52 dantooine spamd[3175]: spamd: still running as root: user not specified with -u, not found, or set to root, falling back to nobody
Sep 28 23:35:52 dantooine spamd[3175]: spamd: processing message (unknown) for someone:65534
Sep 28 23:35:52 dantooine spamd[3175]: spamd: clean message (2.5/5.0) for someone:65534 in 0.0 seconds, 331 bytes.
Sep 28 23:35:52 dantooine spamd[3175]: spamd: result: . 2 - MISSING_DATE,MISSING_FROM,MISSING_MID,UNPARSEABLE_RELAY scantime=0.0,size=331,user=someone,uid=65534,required_score=5.0,rhost=localhost,raddr=::1,rport=59471,mid=(unknown),autolearn=no autolearn_force=no
Sep 28 23:35:52 dantooine opendkim[3179]: DD457889B: can't determine message sender; accepting
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: DD457889B: from=<whoever@example.com>, size=275, nrcpt=1 (queue active)
Sep 28 23:35:53 dantooine postfix/smtpd[4306]: disconnect from unknown[172.17.0.95]
Sep 28 23:35:53 dantooine postfix/smtp[4311]: DD457889B: to=<someone@gmail.com>, relay=gmail-smtp-in.l.google.com[2607:f8b0:4003:c05::1b]:25, delay=0.25, delays=0.12/0.01/0.03/0.09, dsn=5.7.1, status=bounced (host gmail-smtp-in.l.google.com[2607:f8b0:4003:c05::1b] said: 550-5.7.1 [fd17:8b70:893a:44bf:fe73:6c21] Our system has detected that 550-5.7.1 this message is likely unsolicited mail. To reduce the amount of spam 550-5.7.1 sent to Gmail, this message has been blocked. Please visit 550-5.7.1 http://support.google.com/mail/bin/answer.py?hl=en&answer=188131 for 550 5.7.1 more information. su20si7357528oeb.94 - gsmtp (in reply to end of DATA command))
Sep 28 23:35:53 dantooine postfix/cleanup[4309]: 254E688A0: message-id=<20140928233553.254E688A0@myserver.example.com>
Sep 28 23:35:53 dantooine postfix/bounce[4330]: DD457889B: sender non-delivery notification: 254E688A0
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: 254E688A0: from=<>, size=3374, nrcpt=1 (queue active)
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: DD457889B: removed
Sep 28 23:35:53 dantooine postfix/virtual[4331]: 254E688A0: to=<whoever@example.com>, relay=virtual, delay=0.01, delays=0/0/0/0, dsn=2.0.0, status=sent (delivered to maildir)
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: 254E688A0: removed

โปรดโพสต์หัวข้อของอีเมลของคุณ (อันที่ถูกระบุว่าเป็นสแปมโดย GMAIL)
masegaloeh

อีเมลที่ฉันพยายามส่งมีเพียงToส่วนSubjectหัวส่วนหัวและเนื้อหาหนึ่งบรรทัด ฉันไม่แน่ใจว่าจะบอกได้อย่างไรว่าส่วนหัวของมันมีอะไรบ้างหลังจาก Postfix วิ่งผ่านกลุ่มเป้าหมายคุณรู้ได้อย่างไร นี่คือผลลัพธ์ใน / var / log / syslog ที่แสดงวิธีการประมวลผลโดย Postfix และปฏิเสธโดย Gmail: gist.github.com/DavidEGrayson/fbf65c8290c049a1f262
David Grayson

คำตอบ:


8

เนื่องจากคุณมีวิธีแก้ปัญหาการทำงานที่นี่ฉันจะพยายามอธิบายพฤติกรรมที่แตกต่างเมื่อคุณ telnet เพื่อ postfix (SMTP) และเมื่อคุณใช้ sendmail (ไม่ใช่ SMTP)

FYI, OpenDKIM จะเรียกโดย postfix กับกลไกปลาตัวผู้ คุณสามารถรับข้อมูลบางอย่างเกี่ยวกับการใช้งาน milter ใน postfix ผ่านเอกสารอย่างเป็นทางการนี้ นี่คือแผนภาพของ milter hook ใน postfix

             SMTP-only       non-SMTP
             filters         filters
                ^ |            ^ |
                | v            | |
Network ->  smtpd(8)           | |
                       \       | V
Network ->  qmqpd(8)    ->  cleanup(8)  ->  incoming
                       /
            pickup(8)
               :
Local   ->  sendmail(1)

คุณจะเห็นว่า sendmail-way (ไม่ใช่ SMTP) และ telnet-way (SMTP) มีลำดับการประมวลผลที่แตกต่างกัน

  • อีเมลที่ไม่ใช่ SMTP จะถูกประมวลผลโดยการล้างข้อมูลก่อนที่จะถูกส่งไปยัง milter ภูต Cleanupเป็นผู้รับผิดชอบสำหรับการเพิ่มส่วนหัวหายไป: (Resent-) จาก :, การ :, ID ข้อความ :,และวันที่: ดังนั้นอีเมลของคุณจะมีส่วนหัวที่สมบูรณ์เมื่อถูกส่งไปยัง OpenDKIM milter แม้อีเมลต้นฉบับจะมีส่วนหัวที่ไม่สมบูรณ์

  • อีเมล SMTP จะถูกส่งไปยัง OpenDKIM milter ก่อนที่จะมีการประมวลผลการล้างข้อมูล ดังนั้นหากอีเมลต้นฉบับของคุณมีส่วนหัวที่ไม่สมบูรณ์ opendkim อาจปฏิเสธที่จะลงนามอีเมล จาก:ส่วนหัวได้รับคำสั่ง (ดูRFC 6376 ) และถ้าอีเมลไม่ได้มัน OpenDKIM จะปฏิเสธที่จะลงนามในอีเมลและให้คำเตือน

    can't determine message sender; accepting
    

เนื่องจากฉันไม่เคยใช้นักเทียบท่ามากกว่าที่ฉันไม่รู้ว่าข้อ จำกัด ของ sendmail / pickup ภายในคอนเทนเนอร์ ฉันคิดว่าวิธีแก้ปัญหาของ David Grayson นั้นปลอดภัยเพียงพอที่จะรับรองว่า OpenDKIM ได้ลงนามในข้อความ


นั่นคือความกระจ่าง ขอขอบคุณ. น่าเสียดายที่ฉันยังไม่เห็นโซลูชันใด ๆ ดีกว่าโซลูชันปัจจุบันของฉัน (อธิบายไว้ในคำตอบของฉัน)
David Grayson

เหตุผลที่ชัดเจนคือแก้ไข app ที่จะเพิ่มFrom:ส่วนหัวในอีเมลของคุณ :)
masegaloeh

แต่ฉันก็ต้องเพิ่มสิ่งต่าง ๆMessage-Idที่ฉันไม่รู้มากและฉันอาจทำผิด ... มันดูเหมือนง่ายกว่าที่จะปล่อยให้ cleanup daemon ดูแลสิ่งนั้น
David Grayson

ที่จริง ID ข้อความไม่ได้บังคับเป็นRFC 6376กล่าวว่า โดยค่าเริ่มต้นส่วนหัวบังคับเป็นFromส่วนหัวเท่านั้น แต่ถ้าคุณต้องการสร้าง Message-ID ของคุณเองคุณสามารถใช้คำแนะนำเช่นร่าง IETF นี้
masegaloeh

6

คุณต้องชี้inet_interfacesไปที่นักเทียบท่าบริดจ์ ( docker0) ในการกำหนดค่า postfix ที่ตั้งไว้/etc/postfix/main.cf

inet_interfaces = <docker0_ip>

รายละเอียดการทำงานเพิ่มเติมภายในที่ การส่งอีเมลจากนักเทียบท่าถึง postfix ติดตั้งบนโฮสต์


ขอบคุณสำหรับลิงค์! ส่วนที่เกี่ยวข้องสำหรับฉันคือการเพิ่มสิ่งที่ต้องการ172.17.0.0/16ไปmynetworksในและ/etc/postfix/main.cf service postfix restart
JSchirrmacher

5

นี่คือคำตอบครึ่งหนึ่งหรืออย่างน้อยแบบทดสอบครึ่งเนื่องจากฉันกำลังทำงานผ่านปัญหาเดียวกัน ฉันหวังว่าใครบางคนสามารถช่วยออกสิ่งที่ฉันพลาด

คำตอบจาก OP (David Grayson) ฟังดูเป็นเหมือนสิ่งประดิษฐ์ใหม่ของ post-spool แต่การใช้ mail spool นั้นดูเหมือนเป็นแนวทางที่ดีดังนั้นนี่คือสิ่งที่ฉันได้รับ

อินเตอร์เฟสความเข้ากันได้ของ / usr / bin / sendmail ที่จัดทำโดย postfix จะส่งเมลไปยัง postdrop ซึ่งเป็น sgid postdrop ทำให้สามารถเก็บอีเมลไว้ในคิว maildrop ได้ที่ / var / spool / postfix / maildrop สิ่งนี้ควรเกิดขึ้นในคอนเทนเนอร์นักเทียบท่า ส่วนที่เหลือของ postfix ควรหวังว่าไม่จำเป็นต้องเรียกใช้ในคอนเทนเนอร์

ดังนั้นฉันกำลังติดตั้งโฮสต์ / var / spool / postfix / maildrop และ / var / spool / postfix / สาธารณะ ฉันสามารถรับเมลที่ส่งไปยัง / var / spool / postfix / maildrop ในสภาพแวดล้อมโฮสต์เนื่องจากฉันได้ติดตั้งไดเรกทอรีคิว maildrop เนื่องจากฉันติดตั้ง/var/spool/postfix/publicแล้วmaildropสามารถส่งสัญญาณpickupเพื่อรวบรวมเมลจากคิว น่าเสียดายที่ uids และ gids เกี่ยวข้องเว้นแต่ฉันจะดูแลสิ่งนั้นซึ่งหมายความว่าการรับในไดเรกทอรีโฮสต์ไม่สามารถอ่านไฟล์สปูลและยิ่งแย่กว่านั้นการติดตั้ง postfix ทำให้การอนุญาตในไดเรกทอรี maildrop ในสภาพแวดล้อมโฮสต์แย่ลง

ถึงกระนั้นก็ดูเหมือนว่าจะทำงาน:

$ cat Dockerfile 
FROM debian:jessie
# Ids from parent environment

    RUN groupadd -g 124 postfix && \
        groupadd -g 125 postdrop && \
    useradd -u 116 -g 124 postfix

    RUN apt-get update && \
      DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
        postfix \
        bsd-mailx

    CMD echo test mail | mail myemail@example.com

$ sudo docker build   .
...
Successfully built 16316fcd44b6

$ sudo docker run   -v /var/spool/postfix/maildrop:/var/spool/postfix/maildrop \
  -v /var/spool/postfix/public:/var/spool/postfix/public 16316fcd44b6

ในขณะที่ใช้งานได้ฉันไม่พอใจอย่างมากกับการเขียนโค้ด uids และ gids อย่างหนัก ซึ่งหมายความว่าไม่สามารถนับคอนเทนเนอร์เดียวกันเพื่อเรียกใช้ภาชนะเดียวกันได้ทุกที่ ฉันคิดว่าถ้าแทนที่จะติดตั้งไดรฟ์ข้อมูลจากโฮสต์ฉันติดมันจากภาชนะที่ใช้ postfix แล้วมันจะไม่ขัดแย้งกันและฉันต้องติดตั้ง postfix เพียงครั้งเดียวเพื่อรับจดหมายจากตู้คอนเทนเนอร์จำนวนมาก ฉันจะตั้งค่า uids และ gids เหล่านั้นในอิมเมจพื้นฐานที่คอนเทนเนอร์ของฉันทั้งหมดสืบทอดมา

ฉันสงสัยว่านี่เป็นวิธีการที่ดีหรือไม่ ด้วยการกำหนดค่าเมลแบบง่ายและไม่มี daemon ที่ใช้งานบนคอนเทนเนอร์สำหรับการพยายามส่งอีกครั้ง MTA แบบโลคัลที่เรียบง่ายเช่น msmtp อาจเหมาะสมกว่า มันจะส่งผ่าน TCP ไปยังรีเลย์บนโฮสต์เดียวกันซึ่งจะเกิดการสพูล

ความกังวลกับแนวทาง msmtp รวมถึง:

  • ความเป็นไปได้ที่จะสูญเสียเมลมากขึ้นหาก smtp relay ที่ส่งไปนั้นไม่พร้อมใช้งาน หากนั่นคือการถ่ายทอดในโฮสต์เดียวกันโอกาสของปัญหาเครือข่ายอยู่ในระดับต่ำ แต่ฉันต้องระวังเกี่ยวกับวิธีที่ฉันรีสตาร์ทคอนเทนเนอร์รีเลย์
  • ประสิทธิภาพ?
  • ถ้ามีจดหมายจำนวนมากผ่านเข้ามาเมลจะเริ่มตกหรือไม่

โดยทั่วไปวิธีการสปูล postfix แบบแชร์นั้นมีแนวโน้มที่จะเป็นการตั้งค่าที่เปราะบางในการตั้งค่า แต่มีโอกาสน้อยที่จะล้มเหลวในเวลาทำงาน (รีเลย์ไม่พร้อมใช้งาน


4

ฉันตัดสินใจว่าวิธีที่คอนเทนเนอร์จะส่งเมลคือการเขียนไปยังไฟล์ในไดเรกทอรีเฉพาะซึ่งจะสามารถเข้าถึงได้จากทั้งคอนเทนเนอร์และโฮสต์ในฐานะ "ปริมาณ" นักเทียบท่า

ฉันสร้างเชลล์สคริปต์ชื่อ mailsender.sh ที่อ่านเมลจากไดเรกทอรีที่ระบุส่งไปยัง sendmail แล้วลบออก:

#!/bin/bash
# Runs on the host system, reading mails files from a directory
# and piping them to sendmail -t and then deleting them.

DIR=$1

if [ \! \( -d "$DIR" -a -w "$DIR" \) ]
then
  echo "Invalid directory given: $DIR"
  exit 1
fi

echo "`date`: Starting mailsender on directory $DIR"

cd $DIR

while :
do
  for file in `find . -maxdepth 1 -type f`
  do
    echo "`date`: Sending $file"
    sendmail -t < $file
    rm $file
  done
  sleep 1
done

Ubuntu ใช้ upstart ดังนั้นฉันจึงสร้างไฟล์ที่ชื่อ/etc/init/mailsender.confเพื่อเปลี่ยนสคริปต์นี้เป็น daemon:

description "sends mails from directory"
start on stopped rc RUNLEVEL=[2345]
stop on runlevel[!2345]
respawn
exec start-stop-daemon --start --make-pidfile --pidfile /var/run/mailsender.pid --exec
/path/to/mailsender.sh /var/mailsend

ฉันจะเริ่มให้บริการด้วยและหยุดมันด้วยstart mailsender stop mailsenderฉันสามารถดูการเข้าสู่ระบบ/var/log/upstart/mailsender.logและแน่นอนฉันสามารถตรวจสอบได้โดยใช้ไฟล์ PID

คุณต้องสร้าง/var/mailsendไดเรกทอรีและทำให้สามารถเข้าถึงได้จากคอนเทนเนอร์ Docker โดยการเพิ่มอาร์กิวเมนต์-v /var/mailsend:/var/mailsendในdocker runคำสั่งของคุณ


บางทีบางอย่างเช่น mini_sendmail อาจเป็นประโยชน์หรือไม่ มันถูกใช้ในภาชนะบรรจุเช่นบริดจ์ระหว่างแอปแยกต่างหากและ daemon เซิร์ฟเวอร์ sendmail บนระบบโฮสต์คอนเทนเนอร์ cyberciti.biz/tips/… acme.com/software/mini_sendmail
Mikl

หากกำลังส่งอีเมลถึง Postfix ผ่าน SMTP ฉันไม่คิดว่า Postfix จะล้างอีเมล บางทีถ้าคุณมี MTA ที่สามารถกำหนดค่าได้มากกว่า (หรือเราหาวิธีกำหนดค่า Postfix ให้ดีขึ้น) มันจะใช้ได้
David Grayson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.