かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + Spring Integration でいろいろ試してみる ( その30 )( Docker Compose でサーバを構築する、SMTP over SSL+POP over SSLサーバ編 )

概要

記事一覧はこちらです。

Spring Integration のアプリケーションで使用するサーバを Docker Compose で構築します。

また動作確認のために Webmail クライアントの hardware/rainloop を利用します。Rainloop を利用するためには IMAP が必要なので、mail-server コンテナの IMAP の設定も利用可能に設定します(認証あり、SSLあり、ポート番号は993番を使用)。

参照したサイト・書籍

  1. tomav/docker-mailserver - Configure SSL
    https://github.com/tomav/docker-mailserver/wiki/Configure-SSL

  2. Using SSL/TLS with Postfix SMTP and Courier POP3/IMAP
    https://www.mad-hacking.net/documentation/linux/applications/mail/using-ssl-tls-postfix-courier.xml

  3. byeCloud: Building a mailserver with modern webmail
    https://www.davd.eu/byecloud-building-a-mailserver-with-modern-webmail/

  4. hardware/rainloop
    https://github.com/hardware/rainloop

  5. Rainloop initial configuration
    https://github.com/hardware/mailserver/wiki/Rainloop-initial-configuration

  6. Using Gmail as SMTP server from Java, Spring Boot apps
    https://sanaulla.info/2017/09/15/using-gmail-as-smtp-server-from-java-spring-boot-apps/

  7. Fixing PKIX Path Building Issues When Using JavaMail and SMTP
    http://springinpractice.com/2012/04/29/fixing-pkix-path-building-issues-when-using-javamail-and-smtp

  8. JavaSSL証明書を追加する
    https://qiita.com/nenokido2000/items/b36b6e5f0854d7d63ba6

  9. keytool を使用して証明書を削除する
    https://docs.oracle.com/cd/E19226-01/821-1299/ghleq/index.html

  10. how to ignore server cert error in javamail
    https://stackoverflow.com/questions/4894954/how-to-ignore-server-cert-error-in-javamail

  11. STARTTLSについて解説
    https://go-journey.club/archives/2326

  12. JavaMail API - SMTP Servers
    https://www.tutorialspoint.com/javamail_api/javamail_api_smtp_servers.htm

  13. JavaMail API - POP3 Servers
    https://www.tutorialspoint.com/javamail_api/javamail_api_pop3_servers.htm

  14. Networking features in Docker for Windows
    https://docs.docker.com/docker-for-windows/networking/

目次

  1. SMTP over SSL+POP over SSL サーバを構築する
    1. docker-compose.yml の mail-server コンテナの設定を変更し、rainloop コンテナの設定を追加する
    2. サーバを起動する
  2. Webmail クライアントの Rainloop で動作確認する
  3. SMTP over SSL でメールを送信するサンプルを作成する。。。が送信できませんでした
  4. 証明書の問題を解決する
    1. 【解決法その1】keytool -import コマンドでメールサーバの証明書をキーストアに追加する
    2. 【解決法その2】spring.mail.properties.mail.smtp.ssl.trust=* の設定で全ての証明書を信頼する
  5. POP over SSL でメールを送信するサンプルを作成する

手順

SMTP over SSL+POP over SSL サーバを構築する

docker-compose.yml の mail-server コンテナの設定を変更し、rainloop コンテナの設定を追加する

Rainloop のデータを保存するディレクトリ docker/rainloop/data を作成します。

docker/mail-server/config/dovecot.cf の以下の点を変更します。

# SSL を使用しない場合には以下のコメントアウトを解除し、ssl = yes をコメントアウトする
# disable_plaintext_auth = no
# ssl = no
ssl = yes

# debug したい場合には以下の行のコメントアウトを解除する
# auth_verbose = yes
# auth_debug = yes
  • disable_plaintext_auth = nossl = no の2行をコメントアウトします。
  • ssl = yes を追加します。

docker-compose.yml の以下の点を変更します。

  mail-server:
    image: tvial/docker-mailserver:latest
    container_name: mail-server
    hostname: mail
    domainname: example.com
    ports:
      - "25:25"
      - "110:110"
      - "143:143"
      - "465:465"
      - "587:587"
      - "993:993"
      - "995:995"
    volumes:
      - ./docker/mail-server/config/:/tmp/docker-mailserver/
    environment:
      # debug したい場合には以下の行のコメントアウトを解除する
      # - DMS_DEBUG=1
      - ENABLE_SPAMASSASSIN=0
      - ENABLE_CLAMAV=0
      - ENABLE_FETCHMAIL=0
      - ENABLE_FAIL2BAN=0
      - ENABLE_POSTGREY=0
      - ENABLE_POP3=1
    cap_add:
      - NET_ADMIN
      - SYS_PTRACE
    restart: always

  # Webmail クライアント
  rainloop:
    image: hardware/rainloop
    container_name: rainloop
    ports:
      - "8888:8888"
    volumes:
      - ./docker/rainloop/data/:/rainloop/data
  • mail-server コンテナの ports に以下の行を追加します。
    • - "143:143"
    • - "465:465"
    • - "587:587"
    • - "993:993"
    • - "995:995"
  • rainloop コンテナの設定を追加します。

サーバを起動する

docker-compose up -d コマンドを実行します。

f:id:ksby:20181212012711p:plain

Webmail クライアントの Rainloop で動作確認する

まずは管理画面にアクセスして mail-server コンテナに設定している mail.example.com ドメインを追加します。http://localhost:8888/?admin にアクセスしてログイン画面を表示した後、admin / 12345 でログインします。

f:id:ksby:20181212210158p:plain

ログイン後、「Language」「Language(admin)」で「日本語」を選択します。

f:id:ksby:20181212210605p:plain f:id:ksby:20181212211316p:plain f:id:ksby:20181212211527p:plain

画面左側のメニューから「ドメイン」を選択した後、画面右側に表示されるドメイン一覧の中の「gmail.com」のチェックを外してから、

f:id:ksby:20181212211829p:plain

ドメインを追加」ボタンをクリックします。

f:id:ksby:20181212212008p:plain

ドメインを追加」ダイアログが表示されるので、以下の値を入力します。

f:id:ksby:20181212212551p:plain

  • 「名前」には docker-compoyse.yml の mail-server コンテナの hostname + "." + domainname の文字列(mail.example.com)を入力します。
  • IMAP」「SMTP」どちらも、
    • 「サーバー」には mail-server を入力します。docker-compose で起動しており、ユーザ定義の bridge ネットワークにコンテナが接続されているので、rainloop コンテナから mail-server コンテナはコンテナ名でアクセスできます。
    • 「セキュリティ」には「SSL/TLS」を選択します。選択すると IMAP のポートは「993」に、SMTP のポートは「465」に自動で設定されます。

左下の「接続テスト」ボタンをクリックして「IMAP」「SMTP」の文字が緑色で表示されることを確認したら、右下の「追加」ボタンをクリックします。

f:id:ksby:20181212213402p:plain

ドメイン一覧に "mail.example.com" がチェックされた状態で表示されます。

f:id:ksby:20181212213611p:plain

今度はユーザ画面にログインしてメールを送受信してみます。http://localhost:8888/ にアクセスしてログイン画面を表示した後、tanaka@mail.example.com / xxxxxxxx でログインします。

f:id:ksby:20181212214236p:plain

ログインしたら左上の「新規作成」ボタンをクリックします。

f:id:ksby:20181212214458p:plain

メール送信用の画面が開いたら件名とメール本文を入力した後、「送信」ボタンをクリックして送信します。

f:id:ksby:20181212223531p:plain f:id:ksby:20181212223659p:plain

画面右上の人のアイコンのボタンをクリックして、メニューから「ログアウト」を選択します。

f:id:ksby:20181212223804p:plain

ログイン画面に戻ったら、suzuki@mail.example.com / yyyyyyyy でログインします。

f:id:ksby:20181212224112p:plain

受信トレイに tanaka@mail.example.com からのメールが届いています。

f:id:ksby:20181212224159p:plain

mail-server コンテナのログを見ると postfix/smtps/smtpdsmtps になっており、Anonymous TLS connection established とも出力されているので、SMTP によるメール送信は SMTP over SSL で送信できているようです。

f:id:ksby:20181212224722p:plain

Rainloop は Docker で簡単に起動できて設定も分かりやすく、画面のデザインもシンプルで見やすくていいですね。

SMTP over SSL でメールを送信するサンプルを作成する。。。が送信できませんでした

src/main/java/ksbysample/eipapp/dockerserver/flow/MailFlowConfig.java は修正する必要はありません。今は POP over SSL の受信処理はしないので、mailRecvByPop3Flow メソッドに付与した @Bean アノテーションコメントアウトしておきます。

//    @Bean
    public IntegrationFlow mailRecvByPop3Flow() {
        ..........

SMTPSSL 化及びポート番号の変更は application.properties を以下のように変更します。

spring.mail.host=localhost
#spring.mail.port=25
spring.mail.port=465
spring.mail.username=tanaka@mail.example.com
spring.mail.password=xxxxxxxx
spring.mail.properties.mail.smtp.ssl.enable=true
# メール送信処理を debug したい場合には以下の行のコメントアウトを解除する
# spring.mail.properties.mail.debug=true
  • spring.mail.port=25コメントアウトして spring.mail.port=465 を追加します。
  • SMTP 送信時に認証が必要になるので、以下の2行を追加します。
    • spring.mail.username=tanaka@mail.example.com
    • spring.mail.password=xxxxxxxx
  • SSL/TLS で通信をするために以下の1行を追加します。
    • spring.mail.properties.mail.smtp.ssl.enable=true

Tomcat を起動して送信してみますが、javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target という例外が発生して送信できません。。。

f:id:ksby:20181213215418p:plain f:id:ksby:20181213215600p:plain f:id:ksby:20181213215804p:plain

証明書の問題を解決する

原因を調べたところ、Fixing PKIX Path Building Issues When Using JavaMail and SMTP という記事を見つけました。Java がメールサーバに設定されている自己証明書を信頼できないので例外が発生しているようです。

解決方法は以下の2つ。

  • メールサーバの自己証明書をローカル PC の Java のキーストアに追加して、証明書を信頼できるようにする。
  • Java が証明書のチェックをしないようにする。

【解決法その1】keytool -import コマンドでメールサーバの証明書をキーストアに追加する

まずはメールサーバの自己証明書をファイルに保存します。https://github.com/tomav/docker-mailserver/wiki/Configure-SSL#testing-certificate に記載されている docker exec mail-server openssl s_client -connect 0.0.0.0:25 -starttls smtp -CApath /etc/ssl/certs/ コマンドを実行します。

f:id:ksby:20181213225946p:plain f:id:ksby:20181213225644p:plain

赤枠の -----BEGIN CERTIFICATE----------END CERTIFICATE----- の部分(この2行も含む)をコピーして D:\project-springboot\ksbysample-boot-integration\ksbysample-eipapp-dockerserver\mail-example-com.cer というファイルに保存します。

次にファイルに保存した自己証明書をキーストアに追加します。現在使用している Java SE が 8u192 でインストール先が D:\Java\jdk1.8.0_192 の場合、Java のキーストアは D:\Java\jdk1.8.0_192\jre\lib\security\cacerts になります。

keytool -import -alias mail.example.com -file D:\project-springboot\ksbysample-boot-integration\ksbysample-eipapp-dockerserver\mail-example-com.cer -keystore D:\Java\jdk1.8.0_192\jre\lib\security\cacerts コマンドを実行します。

f:id:ksby:20181213230937p:plain

  • 「キーストアのパスワードを入力してください:」の部分で入力待ちになりますので、changeit と入力します(これがデフォルトのパスワードです)。
  • 「この証明書を信頼しますか。 [いいえ]:」の部分で再度入力待ちになりますので、はい と入力します。

keytool -list -keystore D:\Java\jdk1.8.0_192\jre\lib\security\cacerts で登録済の証明書一覧を表示し、mail.example.com が登録されていることを確認します。

f:id:ksby:20181213233058p:plain (.....省略.....) f:id:ksby:20181213233248p:plain

これでメールサーバの証明書を信頼するようになったので、メールが送信できるようになったはずです。Tomcat を起動すると、例外は throw されずにメールが送信できています。

f:id:ksby:20181213233804p:plain

Rainloop で suzuki@mail.example.com の受信トレイを見ると、メールが届いていました。受信したメールは削除します。

f:id:ksby:20181213233943p:plain

最後に keytool -delete -noprompt -alias mail.example.com -keystore D:\Java\jdk1.8.0_192\jre\lib\security\cacerts コマンドでキーストアから登録した証明書を削除します。

f:id:ksby:20181213234932p:plain

【解決法その2】spring.mail.properties.mail.smtp.ssl.trust=* の設定で全ての証明書を信頼する

src/main/resources/application.properties に spring.mail.properties.mail.smtp.ssl.trust=* を追加します。

spring.mail.host=localhost
#spring.mail.port=25
spring.mail.port=465
spring.mail.username=tanaka@mail.example.com
spring.mail.password=xxxxxxxx
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.ssl.trust=*
# メール送信処理を debug したい場合には以下の行のコメントアウトを解除する
# spring.mail.properties.mail.debug=true

これで全ての証明書を信頼してチェックしないようになったので、メールが送信できるようになったはずです。Tomcat を起動すると、例外は throw されずにメールが送信できています。

f:id:ksby:20181213235926p:plain

Rainloop で suzuki@mail.example.com の受信トレイを見ると、メールが届いていました。受信したメールは削除します。

f:id:ksby:20181214000023p:plain

POP over SSL でメールを送信するサンプルを作成する

src/main/java/ksbysample/eipapp/dockerserver/flow/MailFlowConfig.java の以下の点を変更します。

    @Bean
    public Pop3MailReceiver pop3MailReceiver() {
        Pop3MailReceiver pop3MailReceiver
                // テキストベースの POP3 を使用する場合には上の、POP over SSL を使用する場合には下の行のコメントアウトを解除する
                // = new Pop3MailReceiver("localhost", "tanaka@mail.example.com", "xxxxxxxx");
                = new Pop3MailReceiver("localhost", 995, "tanaka@mail.example.com", "xxxxxxxx");
        pop3MailReceiver.setShouldDeleteMessages(true);
        Properties javaMailProperties = new Properties();

        // テキストベースの POP3 を使用する場合には下の4行をコメントアウトする
        javaMailProperties.put("mail.pop3.connectiontimeout", "5000");
        javaMailProperties.put("mail.pop3.timeout", "5000");
        javaMailProperties.put("mail.pop3.ssl.enable", "true");
        javaMailProperties.put("mail.pop3.ssl.trust", "*");

        // debug したい場合には以下のコメントアウトを解除する
        // javaMailProperties.put("mail.debug", "true");
        pop3MailReceiver.setJavaMailProperties(javaMailProperties);

        return pop3MailReceiver;
    }
  • ポート番号 995番を使用するので、= new Pop3MailReceiver("localhost", "tanaka@mail.example.com", "xxxxxxxx");= new Pop3MailReceiver("localhost", 995, "tanaka@mail.example.com", "xxxxxxxx"); に変更します。
  • POP over SSL とは関係ありませんが、タイムアウトを設定したいので以下の2行を追加します。
    • javaMailProperties.put("mail.pop3.connectiontimeout", "5000");
    • javaMailProperties.put("mail.pop3.timeout", "5000");
  • POP over SSL を使用するために以下の2行を追加します。今回は全ての証明書を信頼するよう設定します。
    • javaMailProperties.put("mail.pop3.ssl.enable", "true");
    • javaMailProperties.put("mail.pop3.ssl.trust", "*");

これで POP over SSL の設定は完了です。ただし Tomcat を起動して試してみたのですが、以下の画像の結果になりました。

f:id:ksby:20181215213941p:plain

  • 何件かは正常に受信できるが、少し経つと org.springframework.messaging.MessagingException: failure occurred while polling for mail; nested exception is javax.mail.MessagingException: Connect failed; というエラーメッセージが表示されて全く受信できなくなる。自動で復旧する様子はなし。
  • POP over SSL での受信で上のエラーが出ると SMTP over SSL での送信も失敗する。

原因を調査してみましたが、

  • docker-compose downdocker-compose up -d でコンテナを再起動しても復旧しない。
  • mail-server コンテナへの接続だけの問題かと思ったが、Rainloop(http://localhost:8888)や ftp、sftp サーバへの接続も全て失敗する。ホストからコンテナへ接続できなくなっている模様。
  • docker exec -it mail-server /bin/sh は成功する。コマンド実行後に ls コマンド等は動作する。
  • docker ps コマンドを実行すると e1594ff0dcc5 のコンテナの PORTS には 0.0.0.0:995->995/tcp が設定されている。 f:id:ksby:20181216092754p:plain docker inspect --format '{{ .NetworkSettings }}' mail-server を実行すると 995/tcp:[{0.0.0.0 995}] が出力されている。 f:id:ksby:20181216093315p:plain
  • docker 本体を再起動すると復旧する。 f:id:ksby:20181216093820p:plain
  • SMTP over SSL でメールを送信するだけではこの現象は発生しない。 f:id:ksby:20181216094542p:plain
  • docker/mail-server/config/dovecot.cf の設定を disable_plaintext_auth = nossl = no に戻して docker-compose コマンドでコンテナを再起動した後、ksbysample.eipapp.dockerserver.flow.MailFlowConfig の pop3MailReceiver メソッドの実装を 110番ポートの SSL ではない POP3 に戻した時はこの現象は発生しない。 f:id:ksby:20181216095252p:plain
  • 110番ポートで mail.pop3.starttls.enable=truemail.pop3.starttls.required=true の設定をして STARTTLS で接続した場合も、この現象は発生する。POP over SSL で通信した時だけ発生する模様。

結論。全然原因が分かりません! ネットワークインターフェース周りとかポートフォワードあたりのように思えますが Windows でどう調べればよいのか。。。

全てのコンテナへの通信ができなくなるなら今回実装した Java の問題ではなく Docker の問題のようなので、今回は SMTP over SSL、POP over SSL の実装方法が書けたということで終わりにします。POP の実装は実際に動くテキストベースの POP3 の方にしておきます(POP over SSL の実装はコメントアウトします)。

時間が出来たら tvial/docker-mailserver 以外の Image で SMTP, POP サーバを立てて試しみることにします。

履歴

2018/12/16
初版発行。