세 정책이 수신 서버에 요청하는 것

DMARC 레코드는 내 도메인 앞에 _dmarc.을 붙인 이름에 두는 TXT 레코드입니다.

$ dig +short TXT _dmarc.example.com
"v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com"

p= 태그는 내 도메인에서 왔다고 주장하지만 DMARC에 실패한 메일을 수신 메일 서버가 어떻게 처리하기를 원하는지 알려 줍니다.

  • p=none: 특별히 아무것도 하지 말라는 뜻입니다. 평소처럼 전달하거나 필터링하고, 보고서만 보내 달라는 것입니다. 모니터링 전용입니다.
  • p=quarantine: 의심스러운 메일로 다루라는 뜻입니다. 실제로는 대개 스팸함(정크 메일함)으로 갑니다.
  • p=reject: 거부하라는 뜻입니다. 수신 서버가 SMTP 대화 도중 메일을 거부하므로, 보낸 서버는 반송을 받습니다. Gmail은 도메인의 DMARC 정책을 언급하는 5.7.26 코드로, Outlook.com은 5.7.509 코드로 거부합니다.

많이들 놓치는 점이 두 가지 있습니다.

첫째, 정책은 실패한 메일에만 적용됩니다. DMARC를 통과한 메일은 p= 값이 무엇이든 영향을 받지 않습니다. 그러니 진짜 질문은 “어느 정책이 좋은가”가 아니라 “지금 우리 정상 메일 중 얼마나 실패하고 있는가”입니다. 하나도 없다면 reject로 가도 잃을 것이 없습니다. 조금이라도 있다면 reject는 그 메일을 반송시킵니다.

둘째, 이것은 요청입니다. RFC 7489는 수신 측이 자체 정책을 적용하는 것을 허용하고, 실제로 그렇게 합니다. 거부해 달라고 한 메일을 전달할 수도 있고, 통과한 메일을 스팸함에 넣을 수도 있습니다. DMARC는 내가 내보내는 강한 신호이지, 내가 직접 켜고 끄는 스위치가 아닙니다.

“DMARC 실패”의 실제 의미: 정렬(alignment)

헷갈리는 지점은 대부분 여기서 나옵니다. DMARC는 새로운 검사를 추가하지 않습니다. SPF와 DKIM 결과를 보고, 그 결과가 수신자가 From: 헤더에서 보는 바로 그 도메인에 대한 것인지를 따집니다. 이것을 정렬이라고 합니다.

다음 중 하나라도 맞으면 DMARC를 통과합니다.

  • SPF 통과 + 정렬: 봉투 발신자(Return-Path, MAIL FROM이라고도 함)의 도메인이 From: 도메인과 맞습니다.
  • DKIM 통과 + 정렬: 유효한 DKIM 서명의 d= 도메인이 From: 도메인과 맞습니다.

하나만 맞으면 됩니다. 전형적인 실패는 뉴스레터나 고객 지원 서비스가 news@example.com으로 보내면서 반송 주소는 자기 도메인을 쓰고, DKIM 서명도 자기 키로 하는 경우입니다. SPF와 DKIM은 둘 다 통과하지만 서비스 업체의 도메인으로 통과한 것이라, DMARC는 실패합니다. 수신 쪽의 Authentication-Results 헤더에서 보입니다(아래는 Gmail 형식).

Authentication-Results: mx.google.com;
       dkim=pass header.i=@esp-mailer.example header.s=k1;
       spf=pass (google.com: domain of bounces+8812@esp-mailer.example designates 198.51.100.23 as permitted sender) smtp.mailfrom=bounces+8812@esp-mailer.example;
       dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=example.com

해결은 서비스 업체 쪽 도메인 설정에서 합니다. 대부분의 업체가 내 도메인으로 DKIM 서명을 하도록 설정할 수 있게 해 주고(_domainkey.example.com 아래에 넣을 CNAME이나 TXT를 줍니다), bounce.example.com 같은 사용자 반송 도메인도 지원합니다. 꼭 맞춰야 하는 것은 DKIM 정렬입니다. SPF는 메일이 전달(forwarding)될 때마다 깨집니다. 전달하는 서버의 IP가 내 SPF에 없기 때문입니다. DKIM은 서명된 내용을 아무도 고치지 않는 한 전달을 거쳐도 살아남습니다. 제목에 태그를 붙이거나 꼬리말을 덧붙이는 메일링 리스트가 대표적인 예외입니다.

relaxed와 strict 정렬

adkim=과 aspf=는 도메인이 얼마나 정확히 맞아야 하는지 정합니다. 둘 다 기본값은 r(relaxed)입니다.

  • relaxed: 조직 도메인이 같으면 됩니다. bounce.example.com은 example.com과 정렬됩니다.
  • strict(s): 도메인이 완전히 같아야 합니다. bounce.example.com은 example.com과 정렬되지 않습니다.

분명한 이유가 없으면 relaxed를 유지하세요. strict는 서비스 업체가 내 서브도메인을 반송이나 서명에 쓰는 아주 흔한 구성을 깨뜨립니다. strict가 막아 주는 것은 내 서브도메인을 남에게 넘긴 경우(예를 들어 promo.example.com의 DNS를 외주 업체가 관리하는 경우)인데, 대부분의 조직은 그런 서브도메인이 없거나 그 서브도메인 자체의 DMARC 레코드로 대응할 수 있습니다.

그 밖에 중요한 태그

태그하는 일참고
rua=mailto:…집계 보고서를 받을 주소단계적 전환 전체에 필요한 데이터입니다. 첫날부터 넣으세요
ruf=mailto:…메일 한 통 단위의 실패 보고서를 받을 주소대형 메일 사업자 상당수가 개인정보 문제로 아예 보내지 않습니다. 선택 사항
pct=실패한 메일 중 정책을 적용할 비율기본값 100. 표본에서 빠진 메일에는 한 단계 낮은 정책이 적용됩니다. reject는 quarantine으로, quarantine은 none으로
sp=서브도메인 정책없으면 p=를 따릅니다. 자기 _dmarc 레코드가 있는 서브도메인은 그 레코드를 씁니다
adkim=, aspf=DKIM·SPF 정렬 방식r(기본) 또는 s
fo=실패 보고서를 만드는 조건ruf가 있을 때만 의미가 있습니다. fo=1은 SPF나 DKIM 중 하나만 실패해도 보고서를 요청합니다

자주 걸리는 규칙 몇 가지입니다.

  • 레코드는 v=DMARC1로 시작해야 합니다.
  • DMARC 레코드는 하나만 두세요. 수신 서버가 _dmarc.example.com에서 v=DMARC1로 시작하는 TXT 레코드를 두 개 찾으면 DMARC를 아예 적용하지 않습니다.
  • rua가 다른 도메인의 메일함을 가리키면, 그 도메인이 보고서를 받겠다고 동의해야 합니다. example.com._report._dmarc.reports.example.net 같은 이름에 v=DMARC1이 담긴 TXT 레코드가 필요합니다. 보고서 분석 서비스를 쓰면 업체가 알아서 해 둡니다. 내가 가진 다른 도메인으로 받는다면 직접 넣어야 하고, 넣지 않으면 수신 서버들이 그 주소를 건너뜁니다.

정책을 올리기 전에 집계 보고서 읽기

집계 보고서는 XML 파일입니다. 보통 zip이나 gzip으로 압축해 메일에 첨부되어 오고, 대개 수신 기관마다 하루에 한 통입니다. 각 record 행은 “이 IP에서 내 도메인을 주장하는 메일이 몇 통 왔고, SPF와 DKIM 결과는 이랬다”를 말합니다.

<record>
  <row>
    <source_ip>198.51.100.23</source_ip>
    <count>412</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>fail</dkim>
      <spf>fail</spf>
    </policy_evaluated>
  </row>
  <identifiers>
    <header_from>example.com</header_from>
  </identifiers>
  <auth_results>
    <dkim>
      <domain>esp-mailer.example</domain>
      <selector>k1</selector>
      <result>pass</result>
    </dkim>
    <spf>
      <domain>esp-mailer.example</domain>
      <result>pass</result>
    </spf>
  </auth_results>
</record>

두 층으로 읽으세요. auth_results는 SPF와 DKIM의 원래 결과이고, 둘 다 통과했습니다. policy_evaluated는 정렬된 결과만 치는 DMARC 관점이고, 둘 다 실패했습니다. 도메인이 example.com이 아니라 esp-mailer.example이었기 때문입니다. disposition은 수신 서버가 실제로 한 처리이고, 정책이 p=none이라 none입니다. reject였다면 이 412통은 반송됐을 것입니다. 앞의 헤더 예와 같은 서비스이고, 보고서는 바로 이런 것을 찾으라고 있습니다.

XML 원본은 금방 읽을 수 없는 분량이 됩니다. 보고서 분석 서비스나 parsedmarc 같은 오픈소스 파서로 모은 뒤, 모든 발송처를 세 무리로 나누세요.

  1. 우리 것, 정렬 통과. 보통 주력 메일 플랫폼입니다. 할 일이 없습니다.
  2. 우리 것, 실패. CRM, 청구 시스템, 고객 문의 시스템, 설문 도구, 스캔 문서를 메일로 보내는 복합기, 비밀번호 재설정 메일을 보내는 웹 서버 등입니다. 정책을 강제하기 전에 하나하나 DKIM 정렬(권장)이나 SPF 정렬을 맞춰야 합니다.
  3. 우리 것이 아님. 사칭이거나 전달입니다. 전달된 메일은 보통 SPF는 실패하지만 DKIM은 정렬된 채 통과하므로 DMARC를 통과합니다. 사칭은 둘 다 실패하고, quarantine과 reject로 막고 싶은 것이 바로 이 메일입니다.

2번 무리가 업무 주기 한 바퀴 동안 비어 있으면 올릴 준비가 된 것입니다. 한 달이나 분기에 한 번만 보내는 발송처까지 포함해서입니다. 전체 통과율로 판단하지 마세요. 통과율 99%라도 나머지 1%가 급여 명세 메일이라면 준비된 것이 아닙니다.

메일을 잃지 않는 단계적 전환

1. rua를 넣어 p=none으로 게시합니다. 이메일 점검으로 레코드가 제대로 읽히는지, SPF와 DKIM이 잡히는지 확인합니다.

v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com

2. 정상 발송처를 모두 고칩니다. 서비스마다 내 도메인 DKIM 서명을 켭니다. SPF에는 정말 필요한 서비스만 추가하고, 그러면서 조회 10회 제한을 넘지 않는지 봅니다(SPF PermError 가이드). DKIM 서명이 정렬 문제가 아니라 서명 자체로 실패한다면 DKIM 실패 가이드를 보세요.

3. p=quarantine으로 올립니다. 천천히 올리고 싶다면 pct를 씁니다. p=quarantine; pct=25로 시작해 50, 100으로 올리면서 단계마다 보고서와 고객 문의를 확인합니다. pct는 정확한 비율이 아니라 대략적인 조절 손잡이로 생각하세요. 표본을 얼마나 정확히 뽑는지는 수신 서버마다 다릅니다.

4. p=reject로 올립니다. rua는 계속 유지하세요. 다른 팀이 말없이 새 발송 서비스를 붙이는 일은 반드시 생기고, 그것을 알게 되는 경로가 보고서입니다.

전환하는 동안 _dmarc 레코드의 TTL은 적당히 짧게(1시간 정도) 두세요. 정상 메일이 반송되기 시작하면 quarantine이나 none으로 되돌린 것이 수신 서버들에 빨리 반영되어야 합니다. 그다음 그 발송처를 고치고 다시 올리면 됩니다.

서브도메인과 메일을 아예 보내지 않는 도메인

sp=로 서브도메인을 따로 잠글 수 있습니다. 서브도메인에서 보내는 메일이 없다면 p=가 아직 none이어도 sp=reject를 먼저 걸 수 있습니다. 메일을 보내는 서브도메인(예를 들어 뉴스레터 서비스용 news.example.com)이 있다면, _dmarc.news.example.com에 별도 레코드를 두어 그 서브도메인의 전환 일정이 본 도메인을 붙잡지 않게 하세요.

주차 도메인이나 방어용으로 등록해 둔 도메인처럼 메일을 아예 보내지 않는 도메인은 단계를 밟을 필요가 없습니다. 처음부터 reject로 게시하고, 아무것도 허용하지 않는 SPF와 메일을 받지 않는다고 알리는 null MX(RFC 7505)를 함께 둡니다.

parked-example.com.          IN  MX   0 .
parked-example.com.          IN  TXT  "v=spf1 -all"
_dmarc.parked-example.com.   IN  TXT  "v=DMARC1; p=reject"

점검 도구가 p=none을 지적하는 이유

보안 점검 도구나 협력사 보안 설문에 나오는 “DMARC quarantine/reject policy not enabled” 같은 항목은 모두 같은 뜻입니다. 레코드는 있지만 p=none이거나, 레코드가 없다는 것입니다. p=none으로는 누가 내 도메인을 사칭해 메일을 보내는 것을 막지 못한다는 지적은 옳습니다.

그래도 p=none은 한동안 머물러도 되는 정당한 단계입니다. 2024년 2월부터 시행된 Gmail과 Yahoo의 대량 발송자 요건도 DMARC 레코드를 요구하면서 p=none을 인정합니다. 하지만 none은 거쳐 가는 단계이지 목적지가 아닙니다. 받은편지함에 로고를 보여 주는 표준인 BIMI는 강제 정책(quarantine이나 reject)을 요구하고, 계속 같은 질문을 할 점검 도구와 설문들도 마찬가지입니다.

전환 도중에 보안 설문에 답해야 한다면 그대로 적으세요. 현재 정책, 목표 정책, 언제쯤 도달할지. 지적을 없애려고 reject로 바꿨다가 어떤 시스템을 빠뜨렸는지 고객에게서 듣는 것보다 훨씬 나은 답입니다.

지금 상태 확인하기

dig +short TXT _dmarc.example.com
dig +short TXT k1._domainkey.example.com    # k1 자리에 실제 DKIM 셀렉터

이메일 점검은 한 도메인의 SPF, DKIM, DMARC를 현재 정책과 함께 한 번에 보여 줍니다. DNS 조회는 게시된 TXT 레코드를 있는 그대로 보여 주므로, 관리 화면에 보이는 값과 수신 서버가 보는 값이 다를 때 유용합니다. SPF, DKIM, DMARC가 서로 어떻게 맞물리는지는 이메일 전달률 가이드에 정리돼 있습니다.

내 도메인에서 바로 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.