.forward 파일은 Unix에서 가장 따분한 물건이다. 한 줄, 주소 하나 적어두면 나한테 온 메일이 그 주소로 다시 보내진다. 1980년대 초 sendmail에 딸려 나왔다. 30년 동안 그냥 잘 돌아갔다.
그러다 이메일이 애초에 그런 걸 가정한 적 없는 프로토콜 위에 인증 계층을 세 개나 쌓아 올렸고, 포워딩은 — 그 따분한 한 줄은 — 인터넷에서 가장 확실하게 고장 나는 기능이 됐다.
지금도 짜증 나는 부분은 이거다. 포워딩은 누가 뭘 잘못해서 깨지는 게 아니다. 교과서대로 다 맞게 설정해도 메일이 사라지는 걸 지켜볼 수 있다. 고장은 구조적이다. 메시지가 hop 하나를 더 거치는 순간 SPF·DKIM·DMARC가 맞물리는 방식에 그냥 박혀 있다.
SPF는 메일이 움직이는 순간 끝났다
SPF가 확인하는 건 딱 하나다. 이 메시지가 도메인이 발송을 허가한 IP 주소에서 도착했는가? 메일 서버 목록을 TXT 레코드로 공개하면, 수신 측이 접속해 온 IP를 그 목록과 대조한다.
이제 메일을 포워딩해 보자. example.com의 Alice가 bob@oldcompany.com으로 보내고, 이게 bob@gmail.com으로 포워딩된다. Gmail은 oldcompany.com의 forwarder에서 들어온 접속을 본다. 그리고 example.com의 SPF 레코드를 확인한다. forwarder의 IP는 거기 없다 — 당연히 없다. example.com은 oldcompany.com의 메일 서버를 들어본 적도 없으니까. SPF는 실패한다. 매번. 설계상 그렇다.
여기엔 고칠 설정 오류 같은 게 없다. 메일이 움직이는 세상에서 SPF는 엉뚱한 걸 인증한다. 마지막 hop을 검증하는데, 정작 우리가 신경 쓰는 도메인은 첫 hop에 있다. 누군가 메일을 relay하는 순간 둘은 어긋난다.
이걸 때우는 패치가 SRS, Sender Rewriting Scheme다. 2003년쯤 SPF 저자 중 한 명인 Meng Weng Wong이 만들어냈다. forwarder가 envelope sender를 자기 도메인을 가리키도록 다시 쓴다 — SRS0=...@oldcompany.com 같은 식으로 — 그러면 SPF 검사가 oldcompany.com을 대상으로 돌고, 그 서버는 실제로 허가돼 있으니 SPF가 통과한다.
SRS는 작동한다. 동시에 패배 선언이기도 하다. 메시지가 누구로부터 왔다고 주장하는지를 덮어써서 SPF를 통과시킨 거니까. SPF만 따로 보면 괜찮다. 그 다음에 나온 것한테는 조용히 치명적이다.
DKIM은 살아남는다 — 깨지기 전까지는
DKIM은 더 영리한 설계였다. 접속 IP를 보는 대신 메시지 자체에 서명한다. 발송 도메인이 헤더와 본문을 해시하고 개인 키로 서명한 뒤, 공개 키를 DNS에 올린다. 수신 측이 해시를 다시 계산해 서명을 검증한다. 핵심은 서명이 메시지와 함께 이동한다는 것이다. hop을 열 번 거쳐도 여전히 검증된다 — 서명된 바이트를 아무도 건드리지 않는 한.
바로 그 마지막 단서에서 무너진다.
단순 포워딩은 보통 DKIM을 멀쩡히 남긴다. 사실 상당수의 포워딩이 아직도 굴러가는 유일한 이유가 이거다. SPF는 도착하자마자 죽지만 DKIM은 살아남고, DMARC는 둘 중 하나만 통과하면 되니까. 그래서 메시지가 DKIM 하나로 절뚝거리며 통과한다.
메일링 리스트는 얘기가 다르다. 리스트는 제목에 [listname]을 붙인다. 본문에 구독 취소 푸터를 박는다. 첨부 파일을 떼어내기도 한다. 이 편집 하나하나가 DKIM이 서명한 바이트를 바꾸고, body hash가 안 맞으면 서명이 깨진다. 이제 SPF도 깨지고(리스트가 relay했으니) DKIM도 깨진다(리스트가 편집했으니). 두 다리가 동시에 나간다.
그리고 DMARC가 골칫거리를 장애로 바꿨다
오랫동안 포워딩된 메일의 깨진 SPF는 견딜 만한 성가심이었다. 대부분의 수신 측이 SPF를 참고용으로만 취급했으니까. DMARC가 판돈을 키웠다. DMARC는 말한다. SPF도 DKIM도 From: 도메인과 align되어 통과하지 못하면, 내 정책을 적용하라 — 그 정책은 reject일 수 있다. 반송하라는 뜻이다.
2014년 4월 8일, Yahoo가 p=reject를 공개했다. AOL이 며칠 뒤 뒤따랐다.
하룻밤 사이에 지구상의 모든 메일링 리스트가 깨졌다. Yahoo 사용자가 리스트에 글을 올리면, 리스트가 메시지를 편집해 재발송하고, 그 메시지는 이제 SPF와 DKIM을 둘 다 실패하고, DMARC를 존중하는 모든 수신 측이 그걸 반송한다 — Yahoo가 그러라고 했으니까. 이메일 인프라를 누구 못지않게 잘 아는 John Levine은 IETF 리스트에 건조하게 적었다. “Yahoo breaks every mailing list in the world including the IETF’s.” Mailman은 2주 안에 긴급 우회책을 냈다. 깔끔한 건 하나도 없었다. 흔히 쓰는 방식은 From: 헤더를 리스트 자신의 주소로 다시 쓰는 것이라, 이제 리스트에서 온 모든 메시지가 글쓴이가 아니라 리스트에서 온 것처럼 보인다. 작성자에게 답장하기가 수백만 명에게서 조용히 작동을 멈췄고, 대체로 지금도 그렇다.
이게 핵심 증거다. DMARC는 인증이 드디어 작동하는 것처럼 팔린다. 실제로 벌어진 일은, 한 회사가 DNS 레코드 하나를 바꿔서 인터넷이 10년간 메일을 relay하던 방식을 깨뜨렸고, 그 “해결책”이라는 게 From: 헤더를 뭉개서 아무도 밑바닥 인증을 마주하지 않게 만든 것이다.
ARC는 패치의 패치의 패치다
현재의 답은 ARC, Authenticated Received Chain, RFC 8617이다. 2019년에 나왔다 — Yahoo가 불을 지른 지 5년 뒤. ARC는 각 중간 서버가 메시지를 건드리기 전에 본 인증 결과를 기록하고, 그 기록을 자기 서명으로 봉인하게 한다. 최종 수신 측은 체인을 읽고 판단한다. 지금은 SPF와 DKIM이 실패하지만, 내가 신뢰하는 forwarder가 리스트가 손대기 전엔 통과했다고 증언하니, 그냥 배달하겠다.
다시 읽어보라. “내가 신뢰하는 forwarder.” ARC는 인증을 복원하지 않는다. 인증이 예전엔 성립했다는 중간 서버의 말을 믿어달라고 요청할 뿐이다. DMARC의 전제 자체가 검증 못 하는 hop을 더는 믿지 말자는 거였다. ARC의 답은 신뢰하는 hop을, 조심스럽게, 서명을 붙여 다시 들여오는 것이다. 한 바퀴를 빙 돌아 제자리다.
게다가 이건 수신 측이 당신의 봉인을 존중할 때만 작동하고, 그러려면 수신 측이 당신의 중간 서버를 신뢰할 만하다고 판단해야 한다 — 암호학적 판단이 아니라 평판 판단이다. Gmail과 Outlook은 이미 신뢰하는 forwarder의 ARC를 존중한다. 아무도 들어본 적 없는 작은 forwarder는 그런 호의를 못 받는다.
진짜 교훈
계층을 세어보자. 발신자를 허가하는 SPF. 포워딩에서 SPF를 안 깨지게 하는 SRS. IP 대신 메시지를 인증하는 DKIM. 앞의 둘을 강제하는 DMARC. 포워딩에서 DMARC를 안 깨지게 하는 ARC. 모든 계층이 바로 앞 계층이 벌려놓은 틈을 덮으려고 존재한다.
근본 원인은 한 번도 안 움직였다. SMTP는 1982년에, 어떤 호스트든 다른 호스트를 위해 메일을 relay하던, 신뢰에 기반하고 신원 개념이 없던 네트워크를 위해 설계됐다. 그 설계에서 포워딩은 예외 케이스가 아니었다 — 바로 그게 핵심이었다. 그 이후의 모든 인증 방식은 신원의 부재 위에 지어진 프로토콜에 신원을 볼트로 박아 넣으려는 시도였고, 포워딩은 늘 그 볼트가 부러지는 지점이다. 포워딩이야말로 SMTP가 하라고 만들어진 바로 그 일을 정확히 하는 유일한 동작이기 때문이다.
forwarder를 운영한다면 SRS와 ARC를 깔고 넘어가라. 대안이 더 나쁘니까. 하지만 이걸 해결책으로 착각하진 마라. 철거할 수 없는 건물 둘레에 세운 비계일 뿐이다. 지구 절반이 아직 그 안에 살고 있어서 철거를 못 하는 것이다.