다른 웹사이트와의 Discourse 연동, SMTP 문제: End of file reached

(직원분들께: 제 계정에 연결된 호스티드 구독과는 무관합니다)

Run other websites on the same machine as Discourse 의 설명을 참고하여 VPS에 Discourse를 설치했지만, 다음과 같은 차이점이 있습니다. 첫 설치 전에 app.yml을 수정했습니다:

sudo mkdir /var/discourse
sudo git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
sudo cp samples/standalone.yml containers/app.yml
sudo nano containers/app.yml

YAML에서 포트 섹션을 주석 처리하고, - "templates/web.socketed.template.yml"을 추가했으며, 호스트네임과 SMTP를 설정했습니다:

  ## TODO: 이 Discourse 인스턴스가 응답할 도메인 이름
  ## 필수. Discourse는 IP 주소만으로는 작동하지 않습니다.
  DISCOURSE_HOSTNAME: 'discuss.mydomain.community'

  ## TODO: 초기 가입 시 관리자 및 개발자로 지정될 쉼표로 구분된 이메일 목록
  ## 예: 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: 'someuser@protonmail.com,anotheruser@otherdomain.io'

  ## TODO: 새 계정을 인증하고 알림을 보내는 데 사용되는 SMTP 메일 서버
  # SMTP 주소, 사용자 이름, 비밀번호는 필수입니다
  # 경고: SMTP 비밀번호에 '#' 문자가 있으면 문제가 발생할 수 있습니다!
  DISCOURSE_SMTP_ADDRESS: smtp.myprovider.email
  DISCOURSE_SMTP_PORT: 465
  DISCOURSE_SMTP_USER_NAME: mydomain-no-reply@otherdomain.io
  DISCOURSE_SMTP_PASSWORD: pa$$word
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (선택 사항, 기본값 true)

그 후 Discourse 앱을 부트스트랩하고 시작했습니다:

sudo ./launcher bootstrap app
sudo ./launcher start app

정상적으로 작동했습니다. Discourse를 중지하고 Outer nginx(/etc/nginx/conf.d/discourse.conf)를 제안된 http-only 설정으로 구성했습니다(테스트용). http://discuss.mydomain.community에 접근할 수 있었습니다.

다시 Discourse를 중지하고 certbot으로 discourse.conf를 업그레이드한 후 현재 설정은 다음과 같습니다:

server {
	server_name discuss.mydomain.community;  # <-- 이 부분을 변경하세요

	location / {
		proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
		proxy_set_header Host $http_host;
		proxy_http_version 1.1;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header X-Real-IP $remote_addr;
	}

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/discuss.mydomain.community/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/discuss.mydomain.community/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = discuss.mydomain.community) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


	listen 80; listen [::]:80;
	server_name discuss.mydomain.community;
    return 404; # managed by Certbot
}

지금까지 순조롭습니다. https를 통해 Discourse에 접근할 수 있습니다(작동하지 않을 때 나중에 ./launcher rebuild app을 실행했지만, 차이가 없었습니다).

가입 양식을 제출한 후 ‘설치 완료’ 화면에서 _“**someuser@protonmail.com**으로 활성화 메일을 보냈습니다”_라는 메시지를 받았습니다.

문제: 메일이 도착하지 않습니다.

관리자의 이메일 수신함에 메일이 도착하지 않습니다. mydomain-no-reply@otherdomain.io 메일함을 확인해 보았지만, 역시 아무것도 없습니다.

참고: 메일 사용자가 otherdomain.io에 있고 mydomain.community에 있어야 한다는 것이 문제의 원인일 수 있습니다. 하지만 제가 아는 한 이는 필수 요건이 아닙니다.

문제 해결을 시작하고, 다음과 같은 app.yml 변경 사항으로 테스트했습니다(그 사이 앱 재구성을 수행했습니다):

  DISCOURSE_SMTP_ENABLE_START_TLS: false

성공하지 않았습니다. 계속 진행했습니다:

## 첫 가입 시 'From' 이메일 주소를 설정하려면 주석을 해제하고 변경하세요:
  - exec: rails r "SiteSetting.notification_email='mydomain-no-reply@otherdomain.io'"

그 후 Troubleshoot email on a new Discourse install 를 따랐습니다:

  • otherdomain.io는 DNS에 DKIM과 SPF가 올바르게 설정되어 있습니다.
  • VPS에서 telnet으로 SMTP 제공자에게 연결하는 것은 작동합니다.
  • Docker 컨테이너에서(docker exec 및 telnet 설치 후) telnet을 사용하는 것도 작동합니다.

./discourse-doctor를 실행했고, 두 가지 부분이 눈에 띄었습니다(나머지 결과는 예상대로였습니다):

========================================
discuss.mydomain.community의 Discourse 버전: Discourse 2.6.0.beta2 
localhost의 Discourse 버전: NOT FOUND
==================== DNS PROBLEM ====================
이 서버는 NOT FOUND를 보고하지만, discuss.mydomain.community는 Discourse 2.6.0.beta2를 보고합니다.
이것은 DNS 문제 또는 중간 프록시 때문일 수 있음을 시사합니다.
Cloudflare 또는 CDN을 사용 중이라면, 잘못 구성되어 있을 수 있습니다.
==================== MAIL TEST ====================
견고한 테스트를 위해 http://www.mail-tester.com/에서 주소를 받으세요.
REDACTED 로 메일 전송 중 . . 
smtp.myprovider.email:465를 사용하여 test-k86jiyqb9@srv1.mail-tester.com으로 전송 테스트 중.
======================================== ERROR ========================================
                                    UNEXPECTED ERROR

end of file reached

====================================== SOLUTION =======================================
이것은 흔하지 않은 오류입니다. 권장되는 해결책이 없습니다!

위와 동일한 오류 메시지를 https://meta.discourse.org/에 보고해 주세요.
(해결책을 찾으면 그것도 함께!)

두 번 시도했습니다. 한 번은 설정된 이메일로, 한 번은 mail-tester.com에서 제공한 이메일로. 결과는 동일했습니다. 따라서 상황이 좋지 않아 보입니다.

참고: Docker 설치 절차가 베타 버전도 제공하는 것 같습니다. 알아둘 가치가 있는 점입니다.

프로덕션 로그를 확인한 결과 다음과 같은 내용이 있었습니다:

Started GET "/" for REDACTED-IP at 2020-09-03 06:21:57 +0000
Processing by FinishInstallationController#index as HTML
  Rendering finish_installation/index.html.erb within layouts/finish_installation
  Rendered finish_installation/index.html.erb within layouts/finish_installation (Duration: 3.2ms | Allocations: 356)
  Rendered layouts/_head.html.erb (Duration: 15.7ms | Allocations: 2969)
Completed 200 OK in 323ms (Views: 140.3ms | ActiveRecord: 0.0ms | Allocations: 32137)
Started GET "/finish-installation/register" for REDACTED-IP at 2020-09-03 06:22:01 +0000
Processing by FinishInstallationController#register as HTML
  Rendering finish_installation/register.html.erb within layouts/finish_installation
  Rendered finish_installation/register.html.erb within layouts/finish_installation (Duration: 7.1ms | Allocations: 1607)
  Rendered layouts/_head.html.erb (Duration: 22.3ms | Allocations: 3139)
Completed 200 OK in 107ms (Views: 41.3ms | ActiveRecord: 0.0ms | Allocations: 11760)
Started POST "/finish-installation/register" for REDACTED-IP at 2020-09-03 06:22:22 +0000
Processing by FinishInstallationController#register as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"l1hEEsK/gur5yplJdMIHttZAgYcuzLLkESaAI87IMb88nAFqNwi7l3yJ+3EJBw7leFypVGbH4C5hJl7VnJVYBQ==", "email"=>"someuser@protonmail.com", "username"=>"someusername", "password"=>"[FILTERED]", "commit"=>"Register"}
Redirected to https://discuss.mydomain.community/finish-installation/confirm-email
Completed 302 Found in 48ms (ActiveRecord: 0.0ms | Allocations: 4246)
  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.2ms | Allocations: 31)
Started GET "/finish-installation/confirm-email" for REDACTED-IP at 2020-09-03 06:22:22 +0000
Processing by FinishInstallationController#confirm_email as HTML
  Rendering finish_installation/confirm_email.html.erb within layouts/finish_installation
  Rendered finish_installation/confirm_email.html.erb within layouts/finish_installation (Duration: 2.3ms | Allocations: 418)
  Rendered layouts/_head.html.erb (Duration: 7.9ms | Allocations: 1612)
Completed 200 OK in 25ms (Views: 22.1ms | ActiveRecord: 0.0ms | Allocations: 4611)
Delivered mail ea8af868-4a2c-4312-85dd-f57061a3cd90@discuss.mydomain.community (60015.9ms)
Job exception: Net::ReadTimeout

잠시 기다린 후 브라우저에서 '알림 이메일 다시 보내기’를 요청했습니다. 프로덕션 로그에 다음 내용이 추가되었습니다:

Delivered mail 47ca6f15-cd9e-4c96-a670-6646e2bda585@discuss.mydomain.community (60007.9ms)
Job exception: end of file reached

  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.2ms | Allocations: 31)
Delivered mail 02367872-af3a-4df4-9a68-4e5e7c5eda60@discuss.mydomain.community (60007.4ms)
Job exception: end of file reached

  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.2ms | Allocations: 31)
Delivered mail ee9ee1fc-6fd1-4970-89fa-260efe2dd04c@discuss.mydomain.community (60014.1ms)
Job exception: end of file reached

  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.2ms | Allocations: 31)
Delivered mail 9030f87b-99df-4de2-9a60-2c57f7c752de@discuss.mydomain.community (60007.0ms)
Job exception: end of file reached

  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.2ms | Allocations: 31)
Started PUT "/finish-installation/resend-email" for REDACTED-IP at 2020-09-03 06:31:22 +0000
Processing by FinishInstallationController#resend_email as HTML
  Parameters: {"authenticity_token"=>"NE21scxxyZz3/DxDkoF8kwi9GXoNKvnstNJdKZjQs7afigDKWcbw4XK/XnvvRHTApqExqUUghybE1oPfyo3aDA=="}
  Rendering finish_installation/resend_email.html.erb within layouts/finish_installation
  Rendered finish_installation/resend_email.html.erb within layouts/finish_installation (Duration: 0.9ms | Allocations: 163)
  Rendered layouts/_head.html.erb (Duration: 2.5ms | Allocations: 269)
Completed 200 OK in 63ms (Views: 6.5ms | ActiveRecord: 0.0ms | Allocations: 6408)
  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.2ms | Allocations: 31)
Delivered mail 127cc350-3fb0-4ef1-8759-391fb407b1cb@discuss.mydomain.community (60008.0ms)
Job exception: Net::ReadTimeout

Delivered mail d9c70dfd-5a8b-4f4c-bafa-5a540fc8ed4f@discuss.mydomain.community (60010.0ms)
Job exception: end of file reached

  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.1ms | Allocations: 31)
Delivered mail e9e67ed1-d7b1-4e8a-ba11-30501c6fae89@discuss.mydomain.community (60012.4ms)
Job exception: end of file reached

  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.2ms | Allocations: 31)
  Rendering layouts/email_template.html.erb
  Rendered layouts/email_template.html.erb (Duration: 0.1ms | Allocations: 31)
Delivered mail 82d0361e-0349-4a1a-928e-dcb16dcffdbc@discuss.mydomain.community (60008.6ms)
Job exception: end of file reached

문제가 상대방 측에 있는지 확인하기 위해 호스팅 제공업체에 문의했습니다. 그들은 이 구성이 정상적으로 작동해야 한다고 했지만, VPS가 SMTP 서버에서 아무 활동도 일으키지 않았다고 말했습니다.

그래서 이것이 제 현재 상황입니다.. Meta에 게시합니다.

메일 서버로의 исходящий трафик이 차단되고 있는 건가요? telnet으로 접속해 볼 수 있나요? 컨테이너 내부에서요?

네, Docker 컨테이너 내부에서 telnet을 사용하여 SMTP 서버에 성공적으로 연결할 수 있습니다. 다만, 제 프로바이더가 telnet으로 테스트 이메일을 보내는 것을 허용하지 않아(추가 명령을 보내면 연결이 끊어짐) 테스트 이메일을 보내지는 못합니다.

해결책은 간단해 보였습니다. 제 제공업체가 위 포트(465)를 표시하고 있지만, 587 포트로 telnet을 시도해 보니 작동했습니다. 그래서 app.yml을 수정하고 다시 빌드했는데, 모든 것이 정상적으로 작동했습니다 :smiley:

이것을 보기 전에 이 변경 사항을 추천하려 했었습니다.

포트 465는 SMTP-over-SSL이며, 이는 비추천(deprecated) 상태입니다.
포트 587은 MSA(메일 제출 에이전트) 포트이며, 일반적으로 STARTTLS(평문에서 TLS 연결로 업그레이드)가 지원됩니다.

설정에서 465를 사용했을 때, Discourse는 SSL 포트에 평문으로 통신을 시도하고 있었습니다.

감사합니다, @supermathie. 답변을 해결책으로 채택했습니다. 설명이 명확합니다.

@supermathie 님 안녕하세요

RFC 8314에 따라 포트 465가 (또는 현재 진행 중인 것으로 보아) 다시 권장 사항으로 복원된 것 같습니다. 현재 사용 중인 이메일 제공업체(mailfence.com)도 이 권고를 따르는 것으로 보입니다.

포트 465를 사용하여 연결 시 TLS를 강제하는 방법이 있을까요? swaks로 확인해 본 결과, STARTTLS(–tls) 사용 시에는 실패하지만, 컨테이너 내에서 --tls-on-connect(–tlsc)를 사용하면 정상적으로 작동하므로, 이 방법으로 제 문제를 해결할 수 있을 것으로 기대합니다:

swaks --to user@example.com --from myuser@mailfence.com --server smtp.mailfence.com:465 --auth LOGIN --tlsc --auth-user myuser@mailfence.com

감사합니다.

아, 제출(submission)을 위해 TLS로 재사용되는군요. 합리적인 선택이네요.

현재는 불가능합니다. smtp_settings에서 :ssl = true를 설정하면 ActionMailer가 이를 지원합니다만, 현재 이를 활성화할 수 있는 코드 경로가 없습니다.

해당 기능을 추가하는 PR은 환영할 것입니다.