Discourse SMTP가 도메인 대신 "EHLO localhost"를 보내 Google smtp-relay가 작동하지 않음

여기에 관련 컨텍스트가 있습니다: Emails have stopped sending - end of file reached

대략 일주일 전(2021년 1월 13일)부터 Google의 smtp-relay.gmail.com 서버를 통한 이메일 전송이 실패하기 시작했습니다(이것은 Google Apps 사용자에게 허용되고 의도된 사용 방식입니다).

Sidekiq는 EOFError로 실패를 보고했습니다:

Jobs::HandledExceptionWrapper: Wrapped EOFError: end of file reached

그리고 /logs에도 실패한 작업이 보고되었습니다:

Job exception: end of file reached

백트레이스는 다른 게시글에서 확인하실 수 있습니다.

===================

조사를 통해 최신 Discourse 설치 환경이 SMTP 릴레이에 'EHLO localhost’로 연결하고 있으며, Google이 대략 일주일 전부터 이러한 요청을 거부하기 시작했음을 발견했습니다.

프로덕션 인스턴스의 tcpdump 결과:

0x0030:  d10f f8e4 4548 4c4f 206c 6f63 616c 686f  ....EHLO.localho
	0x0040:  7374 0d0a                                st..
...
	0x0030:  de62 f0c3 3432 3120 342e 372e 3020 5472  .b..421.4.7.0.Tr
	0x0040:  7920 6167 6169 6e20 6c61 7465 722c 2063  y.again.later,.c
	0x0050:  6c6f 7369 6e67 2063 6f6e 6e65 6374 696f  losing.connectio
	0x0060:  6e2e 2028 4548 4c4f 2920 6a31 3673 6d34  n..(EHLO).j16sm4
	0x0070:  3831 3932 3976 736d 2e31 202d 2067 736d  81929vsm.1.-.gsm
	0x0080:  7470 0d0a                                tp..

telnet으로 재현해도 동일한 결과가 나타납니다:

root@conversation:~# telnet smtp-relay.gmail.com 587
Trying 74.125.137.28...
Connected to smtp-relay.gmail.com.
Escape character is '^]'.
220 smtp-relay.gmail.com ESMTP ls8sm507258pjb.6 - gsmtp
ehlo localhost.localdomain
421 4.7.0 Try again later, closing connection. (EHLO) ls8sm507258pjb.6 - gsmtp
Connection closed by foreign host.

그러나 도메인별 ehlo는 정상적으로 작동합니다:

root@conversation:~# telnet smtp-relay.gmail.com 587
Trying 74.125.137.28...
Connected to smtp-relay.gmail.com.
Escape character is '^]'.
220 smtp-relay.gmail.com ESMTP p10sm668563uaw.3 - gsmtp
ehlo conversation.sevarg.net
250-smtp-relay.gmail.com at your service, [64.227.96.27]
250-SIZE 157286400
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8

======

로그를 기반으로 수정 사항을 테스트하기 위해 변경해야 할 파일을 식별했습니다(도커 이미지 내):

/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/mail-2.7.1/lib/mail/network/delivery_methods/smtp.rb

다음과 같이

DEFAULTS = {
      :address              => 'localhost',
      :port                 => 25,
      :domain               => 'localhost.localdomain',

을

    DEFAULTS = {
      :address              => 'conversation.sevarg.net',
      :port                 => 25,
      :domain               => 'conversation.sevarg.net',

으로 변경한 후(인스턴스 재시작 후) 문제가 해결되었습니다. 이제 EHLO가 도메인 문자열과 함께 전송되며, 제 인스턴스에서 이메일이 정상적으로 전송됩니다.

================

기대하는 동작: 이메일을 전송할 때, 기본 Discourse 설치 환경은 SMTP 서버와의 초기 연결에 구성된 도메인 이름을 사용해야 합니다. 또는, 전송되는 도메인을 오버라이드할 수 있는 구성 옵션이 존재해야 합니다. 만약 이러한 옵션이 존재한다면, 검색을 통해 찾을 수 없었습니다.

5개의 좋아요

다른 사용자들(그들도 Google Domains를 사용했을 가능성이 있음)로부터 동일한 오류를 확인한 적이 있습니다.

장기적인 해결책은 app.yml에 해당 재작성을 자동으로 수행하는 설정을 추가하는 것입니다. 하지만 대신 근본적인 해결책이 나올 것으로 기대합니다.

app.yml로 수정할 수 있는 방법이 있다면 저는 분명히 관심이 있습니다. 이메일이 작동하도록 도메인을 코드에 하드코딩하는 것은 명백히 “적절한” 해결책이 아니지만, 문제를 보다 영구적으로 해결해야 할 위치를 보여 주는 데는 도움이 됩니다.

ehlo에 구성된 도메인을 단순히 사용하지 않는 이유가 있나요? localhost보다 그것이 "더 정확"할 것입니다.

훌륭한 조사였어요, @Syonyk!

app.yml 파일의 SMTP 설정을 공유해 주시겠어요?

2개의 좋아요

필수 설정 외에 다른 것은 없습니다.

  DISCOURSE_SMTP_ADDRESS: smtp-relay.gmail.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: [이메일 사용자 이름]
  DISCOURSE_SMTP_PASSWORD: [비밀번호]

새로운 줄을 추가해 보세요.

DISCOURSE_SMTP_DOMAIN: conversation.sevarg.net

그리고 다시 시도해 주시겠습니까?

3개의 좋아요

그 줄을 추가하고 앱을 다시 빌드했습니다(이 단계를 건너뛸 수 있는 방법이 있을까요?).

이제 이메일 설정에서 "domain"이 표시되고, smtp.rb 파일은 기본적으로 localhost를 사용하도록 되돌려졌으며, 이메일이 정상적으로 전송되는 것 같습니다 - 테스트 메시지를 보내면 제대로 전송됩니다.

제가 아는 한, 이로써 문제가 해결되었습니다. 이 내용을 문서나 설정 플로우 어딘가에 추가할 수 있을까요? 해당 설정을 꽤 오랫동안 찾아보았지만 이 옵션을 찾을 수 없었습니다 - 해당 설정 옵션을 알고 있더라도, 이에 대한 언급이 매우 적었습니다.

2개의 좋아요

기본 app.yml 샘플 파일의 이 블록에 추가할 수 있습니다:

이 정보가 도움이 될까요?

1개의 좋아요

어딘가에 문서화되어 있다면 괜찮습니다.

다만, 값이 설정되지 않은 경우, 코드가 DISCOURSE_HOSTNAME의 값을 사용할 수 있을까요? (거의 모든 간단한 경우에서 이것이 올바른 값일 것입니다.) EHLO에 'localhost’를 보내는 것은 일반적으로 잘못된 것입니다 (적어도 localhost에 있지 않은 서버와 통신하는 경우).

standalone.yml과 web_only.yml에 추가하는 건 좋은 아이디어라고 생각합니다.

기본값은 DISCOURSE_HOSTNAME으로 설정하는 것이 더 적절할 것 같습니다. localhost보다 확실히 나은 선택이죠. 최근 이 부분이 변경되었나요?

여기서 중요한 점은, 현재 이메일을 위해 Google에 의존하지 않는 수만 개의 Discourse 인스턴스에서 이 기능이 정상적으로 작동하고 있다는 것입니다. 이러한 기본값을 변경하면 Google Apps 사용자를 위한 비교적 작은 문제를 해결하는 대신, 모든 사용자에게 문제를 일으킬 수 있습니다.

예제에 추가할 것이며, 이 내용을 문서화하는 "발신 이메일에 Google Apps 사용하기"라는 이름의 howto 주제도 있어야 합니다. 이 작업을 맡을 수 있는 분이 있을까요?

4개의 좋아요

네. 신중함을 우선시하는 것이 합리적이라고 동의합니다. 다만 제 추측으로는 localhost를 허용하는 메일 서버라면 DISCOURSE_HOSTNAME도 함께 허용할 가능성이 높지만, 이를 뒷받침할 만한 데이터는 없습니다. :wink: 표준 템플릿에 포함시켜 두는 것이 충분히 좋다고 생각합니다.

1개의 좋아요

네, 하지만 RFC에 따르면 원격 호스트에 'localhost’를 보내는 것도 잘못된 것입니다.

강조한 부분은 제 것입니다.

구글이 그렇게 하고 있는 것으로 보이지만, 구식 RFC에는 서버가 EHLO 문자열을 기반으로 클라이언트를 거부하지 않아야 한다고 명시되어 있습니다. 그러나 5321에는 그런 표현을 찾을 수 없습니다.

RFC에서 요구하는 FQDN을 허용(그리고 선호)할 것으로 예상합니다. 문제를 일으키지 않으려는 마음을 이해하지만, 관련 RFC를 읽은 결과에 따르면 Discourse는 기본적으로 단순히 잘못된 것이며, 그것이 작동하는 이유는 원격 SMTP 서버가 지나치게 관대하기 때문입니다.

1개의 좋아요

우리가 권장하는 가장 일반적인 SMTP 서비스에서 무해함이 입증된 경우, DISCOURSE_HOSTNAME과 동일한 값으로 기본적으로 설정하도록 ./discourse-setup에 대한 PR을 병합하는 데 기꺼이 응하겠습니다.

2개의 좋아요

계정이 없어서 전체 end-to-end 메일 전달을 테스트할 수는 없지만:

Mailgun

% nc smtp.mailgun.com 587
220 Mailgun Influx ready
ehlo conversation.sevarg.net
250-smtp-out-n04.prod.us-west-2.postgun.com
250-AUTH PLAIN LOGIN
...

Sendgrid

% nc smtp.sendgrid.net 587
220 SG ESMTP service ready at ismtpd0021p1las1.sendgrid.net
ehlo conversation.sevarg.net
250-smtp.sendgrid.net
250-8BITMIME
...

Mailjet

 % nc smtp.mailjet.com 587
220 in.mailjet.com ESMTP Mailjet
ehlo conversation.sevarg.net
250-smtpin.mailjet.com
250-PIPELINING
...

ElasticMail

… 실제로 helo나 ehlo에 어떤 형태로도 응답하지 않습니다. o.O 로컬호스트나 실제 도메인을 사용해도 마찬가지입니다.

설치 과정에서 이를 설정하는 것이 정답이라고 생각합니다. 적어도 사람들이 이를 인지하고 필요 시 수정할 수 있기 때문입니다.

6개의 좋아요

또 다른 관련 문제가 있습니다: discourse-doctor가 도메인을 올바르게 설정하지 않는 것으로 보이며, 실제 설치 환경에서 메일을 정상적으로 보낼 수 있는 경우에도 여전히 실패합니다.

정상적으로 동작하는 설정을 사용하더라도 discourse-doctor는 여전히 end of file 오류를 보고합니다.

======================================== ERROR ========================================
                                    UNEXPECTED ERROR

end of file reached

====================================== SOLUTION =======================================
This is not a common error. No recommended solution exists!

Please report the exact error message above to https://meta.discourse.org/
(And a solution, if you find one!)
=======================================================================================

테스트 스크립트에는 SMTP_DOMAIN에 대한 언급이 없습니다.

root@conversation:/var/discourse# grep SMTP_DOMAIN discourse-doctor
root@conversation:/var/discourse#

그리고 tcpdump에 따르면 discourse-doctor를 실행해도 EHLO에 여전히 'localhost’가 전송됩니다. 이 부분도 수정이 필요합니다.

	0x0030:  cccd b12c 4548 4c4f 206c 6f63 616c 686f  ...,EHLO.localho
	0x0040:  7374 0d0a                                st..
...
	0x0030:  e247 1aa5 3432 3120 342e 372e 3020 5472  .G..421.4.7.0.Tr
	0x0040:  7920 6167 6169 6e20 6c61 7465 722c 2063  y.again.later,.c
	0x0050:  6c6f 7369 6e67 2063 6f6e 6e65 6374 696f  losing.connectio
	0x0060:  6e2e 2028 4548 4c4f 2920 6e6d 3773 6d31  n..(EHLO).nm7sm1
	0x0070:  3032 3832 3139 706a 622e 3620 2d20 6773  028219pjb.6.-.gs
	0x0080:  6d74 700d 0a                             mtp..

discourse-doctor가 아니라 emails.rake입니다:

아, 그리고 localhost를 사용하는 것 같습니다. #{ENV["DISCOURSE_SMTP_PORT"]}를 참조해야 할 것 같네요–아니면 지금은 DISCOURSE_SMTP_DOMAIN을 참조해야 할지도요.

@falco 이건 항상 그랬던 건지, 아니면 최근 발생한 회귀(regression)인지 확실합니까?

제가 기억하기로는 예전부터 ehlo {invalid domain} 전송과 관련된 문제가 있었던 것 같습니다. 그래서 ehlo localhost를 오랫동안 잘못 전송해 왔다면 매우 놀라울 것 같네요?

1개의 좋아요

죄송합니다. 제가 본 내용을 보고 있을 뿐입니다. 거기에 있는 'localhost’도 틀린 것 같습니다. 제 웹 개발 경험은 꽤 오래되었고, 전문적으로 일한 지 10년이 넘었습니다. 제가 이 정도까지 해낸 데에는 시간이 좀 걸렸는데, Docker, Ruby 등은 저에게 모두 비교적 새로운 것입니다. 반면 tcpdump는… 그 도구는 잘 알고 있습니다. :wink:

아직도 원격 서버로 'localhost’를 보내는 것은 어떤 상황에서도 잘못된 동작이라고 생각합니다.

1개의 좋아요

참고로, 1월 13일 기준으로 저희도 정확히 동일한 문제를 겪고 있습니다. 마지막으로 이메일이 성공적으로 전송된 시점은 1월 13일이었고, 저희 역시 smtp-relay.gmail.com을 사용하고 있습니다. 아직 이 문제를 우회하는 방법을 찾지 못했습니다(소스 코드를 수정하지 않는 한).