At Sandstorm we use Mailcow. It comes with a very nice Web-UI both for administration and accessing mails, calendars and contacts. User can enable 2-factor-authentication which is great.
One details caught our attention though: users can access the Web-UI via an unencrypted HTTP-connection. Here I want to explain how we fixed that.
Desired behavior - what we want.
It is fine that the Web-UI is reachable via HTTP at port 80. However users should be redirected to the encrypted HTTPs connection on port 443. Nothing else than redirects should ever happen at port 80:
- http://mailserver/ -> 301 Moved Permanently https://mailserver/
- http://mailserver/sogo -> 301 Moved Permanently https://mailserver/sogo
- …
The patch - where to change what configuration.
As I did not find any built-in flag like enableHttpsRedirect I patched a configuration file. Hopefully it will survive updates, time will tell. Mailcow uses a nginx server which runs in an own Docker container. The nginx container handles connections incoming on the host server at ports 80 and 443.
$ docker-compose ps
…
mailcowdockerized_nginx-mailcow_1 … 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
…
A volume contains the nginx configuration for this container. You can find it on the host machine at data/conf/nginx/. We would have to adjust the sites.active file. However a template auto-generates this file so all changes will be lost as soon as we restart the nginx container. We have to adjust the template instead. You find it at data/conf/nginx/templates/sites.template.sh.
We have to adjust the server configuration a bit. Here you see the template file. All changes will go to the highlighted area.
echo '
server {
listen 127.0.0.1:65510;
include /etc/nginx/conf.d/listen_plain.active;
include /etc/nginx/conf.d/listen_ssl.active;
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
include /etc/nginx/conf.d/server_name.active;
include /etc/nginx/conf.d/includes/site-defaults.conf;
}
';
for cert_dir in /etc/ssl/mail/*/ ; do
if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then
continue
fi
# do not create vhost for default-certificate. the cert is already in the default server listen
domains="$(cat ${cert_dir}domains | sed -e 's/^[[:space:]]*//')"
case "${domains}" in
"") continue;;
"${MAILCOW_HOSTNAME}"*) continue;;
esac
echo -n '
server {
include /etc/nginx/conf.d/listen_ssl.active;
ssl_certificate '${cert_dir}'cert.pem;
ssl_certificate_key '${cert_dir}'key.pem;
';
echo -n '
server_name '${domains}';
include /etc/nginx/conf.d/includes/site-defaults.conf;
}
';
done
Now move the listen_plain.active include into an own server block, add the permanent redirect and we are good to go.
echo '
server {
include /etc/nginx/conf.d/listen_plain.active;
return 301 https://$host$request_uri;
}
server {
listen 127.0.0.1:65510;
include /etc/nginx/conf.d/listen_ssl.active;
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
include /etc/nginx/conf.d/server_name.active;
include /etc/nginx/conf.d/includes/site-defaults.conf;
}
';
for cert_dir in /etc/ssl/mail/*/ ; do
if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then
continue
fi
# do not create vhost for default-certificate. the cert is already in the default server listen
domains="$(cat ${cert_dir}domains | sed -e 's/^[[:space:]]*//')"
case "${domains}" in
"") continue;;
"${MAILCOW_HOSTNAME}"*) continue;;
esac
echo -n '
server {
include /etc/nginx/conf.d/listen_ssl.active;
ssl_certificate '${cert_dir}'cert.pem;
ssl_certificate_key '${cert_dir}'key.pem;
';
echo -n '
server_name '${domains}';
include /etc/nginx/conf.d/includes/site-defaults.conf;
}
';
done
As you see we added four lines and removed one line. After a restart of the nginx container all unencrypted HTTP requests are redirected to HTTPs.
$ docker-compose restart nginx-mailcow
A quick test in the end with curl confirms that the change works and is deployed.
$ curl -I http://mailserver
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Wed, 12 May 2021 06:38:51 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://mailserver/
Thanks for reading. I hope you found what you where looking for. If you have any comments or questions feel free to contact us.