ابزارهای Self-Hostedمهندسانسطح متوسط

رفع dkim=none در Postfix با راه‌اندازی OpenDKIM

اگر ایمیل‌های سرور Postfix شما در Gmail به اسپم می‌روند و هدر پیام dkim=none نشان می‌دهد، یعنی هیچ امضای DKIM روی پیام‌ها درج نمی‌شود. در این مقاله OpenDKIM را نصب، پیکربندی و به Postfix وصل می‌کنیم و رکورد DNS را منتشر می‌کنیم.

تحریریه کلودیکپ ۶ دقیقه مطالعه
رفع dkim=none در Postfix با راه‌اندازی OpenDKIM

۱. نشانه‌ی مشکل: ایمیل می‌رود، اما در اسپم می‌نشیند

یک روز صبح تیکت می‌آید: «ایمیل بازیابی رمز عبور به Gmail نمی‌رسد» یا «همه‌ی ایمیل‌های اطلاع‌رسانی سایت رفتند داخل Spam». سریع به سرور می‌زنید؛ Postfix بالا است، صف خالی است (mailq چیزی نشان نمی‌دهد) و در /var/log/mail.log هم خطای واضحی ثبت نشده. یعنی از دید سرور شما همه‌چیز «موفق» بوده است.

حالا در Gmail روی پیام گیرافتاده در اسپم کلیک کنید و گزینه‌ی Show original را بزنید. چیزی شبیه این می‌بینید:

Authentication-Results: mx.google.com;
  spf=pass (google.com: domain of no-reply@example.com designates 203.0.113.10 as permitted sender);
  dkim=none;
  dmarc=pass (p=NONE sp=NONE) header.from=example.com

نکته‌ی کلیدی خط dkim=none است. یعنی هیچ امضای DKIM روی پیام وجود ندارد. DMARC فعلاً به‌خاطر SPF پاس می‌شود، ولی این پاس‌شدن شکننده است: کافی است پیام توسط یک mailing list فوروارد شود یا app شما از طریق یک relay بیرونی ارسال کند تا SPF از alignment خارج شود. آن‌وقت چون هیچ امضای DKIM هم وجود ندارد، DMARC شکست می‌خورد و پیام یا قرنطینه می‌شود یا رد.

از سمت سرور هم می‌توانید مطمئن شوید که milter امضاکننده اصلاً اجرا نمی‌شود:

grep -i dkim /var/log/mail.log
# خروجی خالی است؛ هیچ خطی درباره‌ی DKIM-Signature وجود ندارد.

در Gmail یا Outlook هم به‌جای dkim=pass عدد dkim=none (یا در سناریوهای بدتر dkim=fail) دیده می‌شود. نبود DKIM یکی از سه دلیل اصلی افتادن ایمیل‌های تراکنشی در اسپم است.

۲. علت ریشه‌ای: Postfix به‌صورت پیش‌فرض هیچ چیزی را امضا نمی‌کند

Postfix یک MTA است، نه یک امضاکننده‌ی رمزنگاری. DKIM به این شکل کار می‌کند که یک نرم‌افزار جداگانه (milter) پیام را قبل از رسیدن به صف ارسال می‌گیرد، هدرهای مشخصی را canonicalize می‌کند، با کلید خصوصی RSA هش را امضا می‌کند و هدر DKIM-Signature را به پیام اضافه می‌کند. گیرنده با خواندن رکورد TXT در DNS (که کلید عمومی در آن است) امضا را اعتبارسنجی می‌کند.

پس تا وقتی این سه ضلع کنار هم نباشند، امضایی وجود ندارد:

  1. یک جفت کلید DKIM تولید نشده است.

  2. رکورد TXT کلید عمومی در DNS منتشر نشده است.

  3. milter امضاکننده روی همان سروری که Postfix بیرون می‌دهد، متصل نشده است.

اشتباه رایج دوم این است که مهندس فقط smtpd_milters را ست می‌کند. این پارامتر فقط روی پیام‌هایی اعمال می‌شود که از طریق SMTP وارد می‌شوند (submission). اما ایمیل‌های cron، mail() در PHP و اسکریپت‌هایی که از دستور sendmail استفاده می‌کنند، مستقیماً به Postfix تزریق می‌شوند و از مسیر SMTP عبور نمی‌کنند؛ آن‌ها پارامتر جداگانه‌ی non_smtpd_milters را می‌خوانند. اگر فقط اولی را ست کنید، ایمیل‌های اپلیکیشن امضا نمی‌شوند و ساعت‌ها دنبال چرایی می‌گردید.

۳. راه‌حل گام‌به‌گام

گام ۱: نصب OpenDKIM

sudo apt update
sudo apt install opendkim opendkim-tools

# روی خانواده‌ی RHEL:
# sudo dnf install opendkim opendkim-tools

گام ۲: تولید جفت کلید

نام selector را mail می‌گذاریم؛ در واقع این «نسخه»ی کلید است و بعداً برای چرخش کلید لازم می‌شود.

sudo mkdir -p /etc/opendkim/keys/example.com
sudo opendkim-genkey -b 2048 -d example.com -s mail \
  -D /etc/opendkim/keys/example.com
sudo chown -R opendkim:opendkim /etc/opendkim/keys
sudo chmod 600 /etc/opendkim/keys/example.com/mail.private
ls -l /etc/opendkim/keys/example.com

دو فایل ساخته می‌شود: mail.private (کلید خصوصی، فقط با دسترسی ریشه و کاربر opendkim) و mail.txt (محتوای رکورد DNS).

گام ۳: ساخت جدول‌های کلید و امضا

# /etc/opendkim/key.table
mail._domainkey.example.com  example.com:mail:/etc/opendkim/keys/example.com/mail.private
# /etc/opendkim/signing.table
*@example.com  mail._domainkey.example.com
# /etc/opendkim/trusted.hosts
127.0.0.1
::1
localhost

جدول signing.table می‌گوید هر پیامی که From آن @example.com است با کدام selector امضا شود. برای چند دامنه، به‌ازای هر دامنه یک خط اضافه کنید.

گام ۴: پیکربندی OpenDKIM

# /etc/opendkim.conf
Syslog              yes
UMask               002
KeyTable            refile:/etc/opendkim/key.table
SigningTable        refile:/etc/opendkim/signing.table
ExternalIgnoreList  refile:/etc/opendkim/trusted.hosts
InternalHosts       refile:/etc/opendkim/trusted.hosts
Socket              inet:12301@localhost
Canonicalization    relaxed/relaxed
Mode                sv
OversignHeaders     From
PidFile             /run/opendkim/opendkim.pid
UserID              opendkim

اگر توزیع شما از systemd socket activation استفاده می‌کند (روی Debian و Ubuntu رایج است)، سوکت را در فایل جدا تنظیم کنید و تعریف تکراری در opendkim.conf را بردارید:

# /etc/default/opendkim
SOCKET="inet:12301@localhost"

گام ۵: وصل کردن milter به Postfix

sudo postconf -e 'milter_default_action = accept'
sudo postconf -e 'milter_protocol = 6'
sudo postconf -e 'smtpd_milters = inet:localhost:12301'
sudo postconf -e 'non_smtpd_milters = inet:localhost:12301'

sudo systemctl restart opendkim
sudo systemctl restart postfix
sudo systemctl status opendkim --no-pager

مقدار milter_default_action = accept یعنی اگر milter به‌هر دلیلی در دسترس نبود، Postfix ایمیل را دور نریزد و بدون امضا عبور دهد؛ این انتخاب هنگام راه‌اندازی امن‌تر است تا در صورت کرش OpenDKIM، صف شما قفل نشود.

گام ۶: انتشار رکورد DNS

sudo cat /etc/opendkim/keys/example.com/mail.txt

خروجی چیزی شبیه این است:

mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
    "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...QAB" ) ;

یک رکورد TXT بسازید با نام mail._domainkey.example.com و مقدار کل رشته‌ی v=DKIM1; h=sha256; k=rsa; p=... را بدون گیومه‌ها، بدون پرانتز و بدون شکستن خط داخلش بگذارید. اگر مقدار طولانی است، برخی کنترل‌پنل‌های DNS خودشان آن را به دو string تقسیم می‌کنند که اشکالی ندارد؛ مهم این است که نرم‌افزار DNS آن‌ها را دوباره به یکدیگر بچسباند.

۴. بررسی اینکه مشکل واقعاً حل شده است

اول صحت کلید و هم‌خوانی آن با DNS را چک کنید:

sudo opendkim-testkey -d example.com -s mail -vvv

اگر همه‌چیز درست باشد، در انتهای خروجی key OK می‌بینید. اگر خطای key not secure دیدید، یعنی رکورد DNS هنوز propagate نشده یا مقدار اشتباه کپی شده است.

سپس یک ایمیل آزمایشی بفرستید و لاگ را نگاه کنید:

echo "DKIM test" | mail -s "dkim check" you@gmail.com
sleep 5
grep -i dkim /var/log/mail.log

باید خطی شبیه این ببینید:

opendkim[1234]: 1A2B3C4D: DKIM-Signature field added (s=mail, d=example.com)

در سمت گیرنده هم دوباره Show original را باز کنید. حالا باید این را ببینید:

dkim=pass header.i=@example.com header.s=mail header.b=AbCdEfGh

برای تست دقیق‌تر می‌توانید پیام را به سرویس‌های بررسی احراز هویت ایمیل بفرستید و نتیجه‌ی SPF، DKIM و DMARC را یک‌جا ببینید. نکته‌ی مهم این است که dkim=pass باید همراه با header.i=@example.com باشد؛ اگر دامنه‌ی امضا با دامنه‌ی From یکی نباشد، DMARC همچنان شکست می‌خورد.

۵. پیشگیری و بهترین روش‌ها

  • SPF و DMARC را فراموش نکنید. DKIM جایگزین آن‌ها نیست، مکمل است. یک رکورد SPF معتبر و یک رکورد _dmarc با p=none بگذارید و بعد از چند هفته پایش، به quarantine و سپس reject بروید.

  • الگوی چرخش کلید داشته باشید. هر ۶ تا ۱۲ ماه یک selector جدید بسازید (مثلاً mail2)، رکورد DNS آن را منتشر کنید، حداقل ۴۸ ساعت صبر کنید تا propagate شود، بعد در signing.table امضا را به آن منتقل کنید و در پایان کلید قدیمی را حذف کنید. این‌طور هیچ‌وقت بدون امضا نمی‌مانید.

  • کلید خصوصی را امن نگه دارید. مالکیت opendkim:opendkim و مجوز 600 الزامی است. کلید را در بکاپ‌های رمزنگاری‌شده نگه دارید؛ گم‌شدن آن یعنی باید کل چرخه‌ی DNS را از نو طی کنید.

  • گزارش‌های DMARC را جمع کنید. با rua=mailto:dmarc@example.com می‌فهمید کدام سرویس‌های جانبی (خبرنامه، CRM، فاکتور) به‌جای سرور شما ایمیل می‌فرستند و هنوز DKIM ندارند.

  • طول کلید را جدی بگیرید. حداقل ۲۰۴۸ بیت. کلید ۱۰۲۴ بیتی هنوز «کار می‌کند» ولی اعتبار کمتری نزد گیرنده‌ها دارد.

  • پورت ۱۲۳۰۱ را فقط روی localhost باز کنید. سوکت inet نباید از بیرون قابل دسترس باشد؛ milter در همان سرور اجرا می‌شود.

اشتراک‌گذاری:

نویسنده

تحریریه کلودیکپ

تیم محتوای کلودیکپ

تیم فنی و محتوای کلودیکپ؛ مهندسانی که هر روز با DevOps، Kubernetes و زیرساخت ابری کار می‌کنند و تجربه‌هایشان را اینجا می‌نویسند.

سوالات متداول

سوالات متداول این مقاله

چون آن‌ها از مسیر SMTP سابمیشن وارد نمی‌شوند و مستقیماً با sendmail به Postfix تزریق می‌شوند. باید پارامتر non_smtpd_milters را هم روی همان سوکت milter تنظیم و Postfix را ری‌استارت کنید.

dkim=none یعنی هیچ امضایی روی پیام وجود ندارد و علت آن معمولاً نبود milter یا نبود رکورد DNS است. dkim=fail یعنی امضا وجود دارد ولی اعتبارسنجی شکست خورده؛ مثلاً رکورد TXT اشتباه کپی شده، کلید خصوصی با کلید عمومی هم‌خوان نیست یا سرویس فوروارد بدنه‌ی پیام را تغییر داده است.

نه. DKIM تمامیت و اصالت پیام را ثابت می‌کند، SPF مجازبودن IP فرستنده را. DMARC به گیرنده می‌گوید وقتی هیچ‌کدام از این دو با دامنه‌ی From هم‌راستا نبودند چه کند. هر سه باید کنار هم پیکربندی شوند تا سیاست p=reject بدون ریسک قابل اعمال باشد.

ادامه مطالعه

مقالات مرتبط

چرا همگام‌سازی JQL در Jira فقط ۵۰ ایشو برمی‌گرداند؟
CI/CD و اتوماسیونمتوسط

چرا همگام‌سازی JQL در Jira فقط ۵۰ ایشو برمی‌گرداند؟

اگر job همگام‌سازی Jira شما بدون خطا تمام می‌شود ولی فقط ۵۰ ایشو را می‌آورد یا در حلقه بی‌پایان گیر می‌کند، مشکل از JQL نیست؛ اندپوینت search به pagination مبتنی بر nextPageToken منتقل شده و startAt و total دیگر وجود ندارند.

تحریریه کلودیکپ ۸ دقیقه مطالعه
قفل state ترافورم گیر کرده؛ آزادسازی امن بعد از کیل شدن apply
زیرساخت ابری و IaCمتوسط

قفل state ترافورم گیر کرده؛ آزادسازی امن بعد از کیل شدن apply

وقتی پایپلاین CI وسط اجرای apply کشته می‌شود، قفل state ترافورم در جدول DynamoDB باقی می‌ماند و اجرای بعدی با خطای Error acquiring the state lock متوقف می‌شود. در این مقاله علت ریشه‌ای، روش آزادسازی امن و راه‌های پیشگیری را بررسی می‌کنیم.

تحریریه کلودیکپ ۶ دقیقه مطالعه
حلقه بی‌پایان Jira Automation: وقتی قانون خودش را صدا می‌زند
CI/CD و اتوماسیونمتوسط

حلقه بی‌پایان Jira Automation: وقتی قانون خودش را صدا می‌زند

اگر trigger و action یک قانون Jira Automation روی یک رویداد بیفتند، قانون خودش را دوباره اجرا می‌کند و issue با صدها کامنت تکراری پر می‌شود. این مقاله علت، پاک‌سازی و راه‌حل پایدار را نشان می‌دهد.

تحریریه کلودیکپ ۷ دقیقه مطالعه

در پیاده‌سازی به کمک نیاز دارید؟

تیم کلودیکپ همین کار را هر روز برای تیم‌های دیگر انجام می‌دهد. اگر جایی گیر کرده‌اید، با ما صحبت کنید.