For hosting environments--where TLS certs can change hundreds of times in a 
matter of minutes--it would be a boon for Dovecot to load those certificates 
dynamically rather than all at once.

Pure-FTPd implements a nice solution to this: a standalone service that fetches 
TLS certificates & keys. Documented here:

https://github.com/jedisct1/pure-ftpd/blob/9d25440e5b5283fbeca94dd0595aa6672c3f8428/README.TLS#L161

-FG


> On Sep 2, 2022, at 08:44, Bartosz Kwitniewski <zerg-dove...@uid0.pl> wrote:
> 
> Hello,
> 
> I'm running a dovecot 2.3.19.1 server that has around 6000 SSL certificates 
> in separate config files, each containing:
> local_name "domain" {
>    ssl_cert = ...
>    ssl_key = ...
> }
> When new certificate is added, dovecot is reloaded (around 20 times a day). 
> When dovecot is being reloaded, users are unable to log in for around 30 
> seconds.
> 
> The main problem here seems to be that during reload, new config process is 
> immediately designated as the one serving config requests and then it starts 
> parsing config files, which takes around 20-30 seconds. If it would parse 
> config files first, and only then would become a new process for serving 
> config requests, then it would probably solve the problem. Or perhaps there 
> is a better way to load new certificates or a way to optimize?
> 
> There is another problem with config process and shutdown_clients=no. We do 
> not want to disconnect users during reload, because e.g. Thunderbird displays 
> a popup that server is shutting down. When there are long lasting IMAP 
> connections from Google and other services that aggregate e-mail, old config 
> process is not being killed. Because config process with ~6000 certificates 
> is using ~1 GB of RAM, it can quickly rise to 20 GB of memory used. This is 
> not a big issue however, because we have created a task that kills old 
> processes, but there could be a built-in mechanism to solve that problem.
> 
> I have created minimal configuration and scripts to recreate problem. 
> Reproduction steps below.
> 
> Configuration (doveadm config -n):
> ==========
> # 2.3.19.1 (9b53102964): /etc/dovecot/dovecot.conf
> # OS: Linux 4.18.0-372.9.1.1.lve.el8.x86_64 x86_64 CloudLinux release 8.6 
> (Leonid Kadenyuk)
> # Hostname: -
> auth_mechanisms = plain login
> default_client_limit = 12288
> default_process_limit = 2048
> default_vsz_limit = 1 G
> first_valid_uid = 1000
> mail_location = maildir:~/mail
> mbox_write_locks = fcntl
> namespace inbox {
>  inbox = yes
>  location =
>  mailbox Drafts {
>    special_use = \Drafts
>  }
>  mailbox Sent {
>    special_use = \Sent
>  }
>  mailbox "Sent Messages" {
>    special_use = \Sent
>  }
>  mailbox Trash {
>    special_use = \Trash
>  }
>  prefix =
> }
> passdb {
>  args = scheme=CRYPT username_format=%u /etc/dovecot/users
>  driver = passwd-file
> }
> service auth-worker {
>  user = $default_internal_user
> }
> service auth {
>  user = $default_internal_user
> }
> service config {
>  vsz_limit = 4 G
> }
> service imap-login {
>  process_min_avail = 20
>  service_count = 1
> }
> service imap {
>  process_limit = 4096
> }
> service pop3-login {
>  process_min_avail = 20
>  service_count = 1
> }
> service pop3 {
>  process_limit = 512
> }
> shutdown_clients = no
> ssl_cert = </etc/dovecot/certs/example.org.crt
> ssl_cipher_list = 
> ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:DHE-DSS-AES128-GCM-SHA256:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:ECDH-ECDSA-AES128-GCM-SHA256:ECDH-ECDSA-AES256-GCM-SHA384:ECDH-RSA-AES128-GCM-SHA256:ECDH-RSA-AES256-GCM-SHA384:ECDH-ECDSA-AES128-SHA256:ECDH-ECDSA-AES256-SHA384:ECDH-ECDSA-AES128-SHA:ECDH-RSA-AES128-SHA256:ECDH-RSA-AES256-SHA384:ECDH-RSA-AES128-SHA:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305
> ssl_dh = </etc/dovecot/dh.pem
> ssl_key = </etc/dovecot/certs/example.org.key
> ssl_options = no_compression
> ssl_prefer_server_ciphers = yes
> userdb {
>  args = username_format=%u /etc/dovecot/users
>  driver = passwd-file
> }
> verbose_proctitle = yes
> protocol imap {
>  mail_max_userip_connections = 2048
> }
> protocol pop3 {
>  mail_max_userip_connections = 2048
> }
> local_name domain4587.example.org mail.domain4587.example.org {
>  ssl_cert = </etc/dovecot/certs/domain4587.example.org.crt
>  ssl_key = </etc/dovecot/certs/domain4587.example.org.key
> }
> local_name domain4588.example.org mail.domain4588.example.org {
>  ssl_cert = </etc/dovecot/certs/domain4588.example.org.crt
>  ssl_key = </etc/dovecot/certs/domain4588.example.org.key
> }
> (...) 5000 certificates goes here
> ==========
> 
> /etc/dovecot/users:
> ==========
> u...@domain1.example.org:{SHA512-CRYPT}$6$...:1000:1000::/home/test/mail/domain1.example.org/user::
> u...@domain2.example.org:{SHA512-CRYPT}$6$...:1000:1000::/home/test/mail/domain2.example.org/user::
> (...) 5000 users goes here, but one should be enough for testing.
> ==========
> 
> Log file from dovecot reload:
> ==========
> Sep 02 12:37:24 dovecot[264040]: imap-login: Login: 
> user=<u...@domain1.example.org>, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, 
> mpid=264964, TLS, session=<eQXGUK/ngJJ/AAAB>
> Sep 02 12:37:24 dovecot[264040]: 
> imap(u...@domain1.example.org)<264964><eQXGUK/ngJJ/AAAB>: Disconnected: 
> Logged out in=25 out=782 deleted=0 expunged=0 trashed=0 hdr_count=0 
> hdr_bytes=0 body_count=0 body_bytes=0
> Sep 02 12:37:25 systemd[1]: Reloading Dovecot IMAP/POP3 email server.
> Sep 02 12:37:26 dovecot[263234]: master: Warning: SIGHUP received - reloading 
> configuration
> Sep 02 12:37:36 dovecot[263234]: master: Error: Error reading configuration: 
> read(/usr/local/var/run/dovecot/config) failed: read(size=8192) failed: 
> Interrupted system call - Also failed to read config by executing doveconf: 
> /usr/local/var/run/dovecot/config is a UNIX socket
> Sep 02 12:37:36 systemd[1]: Reloaded Dovecot IMAP/POP3 email server.
> Sep 02 12:37:36 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: stat(/usr/local/var/run/dovecot/config) failed: No 
> such file or directory (path is from CONFIG_FILE environment)
> Sep 02 12:37:36 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: stat(/usr/local/var/run/dovecot/config) failed: No 
> such file or directory (path is from CONFIG_FILE environment)
> Sep 02 12:37:42 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: stat(/usr/local/var/run/dovecot/config) failed: No 
> such file or directory (path is from CONFIG_FILE environment)
> Sep 02 12:37:46 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: /usr/local/var/run/dovecot/config is a UNIX socket 
> (path is from CONFIG_FILE environment)
> Sep 02 12:37:46 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: /usr/local/var/run/dovecot/config is a UNIX socket 
> (path is from CONFIG_FILE environment)
> Sep 02 12:37:46 dovecot[263234]: master: Error: service(imap-login): command 
> startup failed, throttling for 2.000 secs
> Sep 02 12:37:46 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: /usr/local/var/run/dovecot/config is a UNIX socket 
> (path is from CONFIG_FILE environment)
> Sep 02 12:37:47 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: stat(/usr/local/var/run/dovecot/config) failed: No 
> such file or directory (path is from CONFIG_FILE environment)
> Sep 02 12:37:47 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: /usr/local/var/run/dovecot/config is a UNIX socket 
> (path is from CONFIG_FILE environment)
> Sep 02 12:37:48 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: stat(/usr/local/var/run/dovecot/config) failed: No 
> such file or directory (path is from CONFIG_FILE environment)
> Sep 02 12:37:48 dovecot[264040]: imap-login: Fatal: Error reading 
> configuration: read(/usr/local/var/run/dovecot/config) failed: 
> read(size=8192) failed: Interrupted system call - Also failed to read config 
> by executing doveconf: /usr/local/var/run/dovecot/config is a UNIX socket 
> (path is from CONFIG_FILE environment)
> Sep 02 12:37:48 dovecot[263234]: master: Error: service(imap-login): command 
> startup failed, throttling for 4.000 secs
> Sep 02 12:37:49 dovecot[264040]: imap-login: Disconnected: Connection closed: 
> read(size=737) failed: Connection reset by peer (no auth attempts in 5 secs): 
> user=<>, rip=127.0.0.1, lip=127.0.0.1, TLS handshaking: read(size=737) 
> failed: Connection reset by peer, session=<eNlHUq/njJJ/AAAB>
> Sep 02 12:37:49 dovecot[264040]: imap-login: Login: 
> user=<u...@domain1.example.org>, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, 
> mpid=265059, TLS, session=<duBHUq/njpJ/AAAB>
> Sep 02 12:37:49 dovecot[264040]: 
> imap(u...@domain1.example.org)<265059><duBHUq/njpJ/AAAB>: Disconnected: 
> Logged out in=25 out=790 deleted=0 expunged=0 trashed=0 hdr_count=0 
> hdr_bytes=0 body_count=0 body_bytes=0
> ==========
> 
> Script for testing login:
> ========== test-imap.expect ==========
> #!/usr/bin/expect -f
> 
> set timeout 5
> expect_before {
>    timeout { puts "Error: Timeout"; exit 1 }
>    eof     { puts "Error: EOF";     exit 1 }
> }
> log_user 0
> 
> spawn openssl s_client -connect 127.0.0.1:993
> expect "* OK"
> send -- "C1 LOGIN u...@domain1.example.org PASSWORD\n"
> expect "C1 OK"
> send -- "C2 SELECT Inbox\n"
> expect "C2 OK"
> send -- "C3 LOGOUT\n"
> expect "C3 OK"
> expect eof
> ==========
> 
> Script for testing long lasting login:
> ========== test-imap-long.expect ==========
> #!/usr/bin/expect -f
> 
> set timeout -1
> expect_before {
>    timeout { puts "Error: Timeout"; exit 1 }
>    eof     { puts "Error: EOF";     exit 1 }
> }
> log_user 0
> 
> spawn openssl s_client -connect 127.0.0.1:993
> expect "* OK"
> send -- "C1 LOGIN u...@domain1.example.org PASSWORD\n"
> expect "C1 OK"
> send -- "C2 SELECT Inbox\n"
> expect "C2 OK"
> ==========
> 
> Reproduction steps (I can attach full package for testing with 5000 
> self-signed certificates):
> 1. Start dovecot 2.3.19.1 with 5000 certificates in configuration files using 
> config above.
> 2. Start test-imap.expect on another console, like that:
> while true; do date_now=$(date "+%Y-%m-%d %H:%M:%S"); echo -n "[$date_now] 
> Test: "; ./imap-test.expect && echo "OK"; sleep 1; done
> 3. Reload dovecot.
> 4. Check logs and test-imap.expect output.
> 
> Reproduction steps for config process not being terminated:
> 1. Start dovecot 2.3.19.1 using config above.
> 2. Start test-imap-long.expect (the second script) on another console.
> 3. Reload dovecot.
> 4. Start another test-imap-long.expect on another console.
> 5. Reload dovecot.
> 6. Check process list for dovecot config.
> 
> Best regards,
> -- 
> Bartosz Kwitniewski
> 


Reply via email to