blog/_posts/2024-11-24-mta-sts-for-protonmail-custom-domain-the-zero-maintenance-way.md

169 lines
7.7 KiB
Markdown

---
title: "MTA-STS for ProtonMail custom domain, the \"zero maintenance\" way"
date: 2024-11-24 20:01
url: mta-sts-for-protonmail-custom-domain-the-zero-maintenance-way
layout: post
category: Security
image: /img/blog/mta-sts-for-protonmail-custom-domain-the-zero-maintenance-way.png
description: "MTA-STS and TLS-RPT transparent setup for ProtonMail custom domain"
---
[![A missing blog post image](/img/blog/mta-sts-for-protonmail-custom-domain-the-zero-maintenance-way.png)](/img/blog/mta-sts-for-protonmail-custom-domain-the-zero-maintenance-way.png)
## Introduction
This blog post isn't about explaining [MTA-STS](https://datatracker.ietf.org/doc/html/rfc8461) (there are already plenty of resources on the Internet to read about this), but more about its integration with [custom domains](https://proton.me/support/custom-domain) when e-mail is handled by ProtonMail.
@Wonderfall wrote some years ago a very interesting [blog post](https://wonderfall.dev/mta-sts/) detailing how ProtonMail lack of MTA-STS support could be circumvented using two DNS entries and a statically served Web page.
However, one month and half ago, someone on Reddit has come up with an [elegant solution](https://old.reddit.com/r/ProtonMail/comments/y6q6g8/mtasts_for_custom_domains/#lq1d1hs), presented as "zero maintenance", leveraging a CloudFlare worker ([source](https://pastebin.com/95fY5KrM), inspired by CloudFlare own [documentation](https://developers.cloudflare.com/email-routing/setup/mta-sts/)) :
{% highlight javascript %}
export default {
async fetch(request, env, ctx) {
return fetch('https://mta-sts.protonmail.ch/.well-known/mta-sts.txt');
},
};
{% endhighlight %}
So we will properly detail how to implement this solution, but more importantly how we can self-host a CloudFlare worker alternative, so as not to depend on (another) third-party service provider.
## How to setup MTA-STS
> Below configuration and code snippets use fake `example.org` domain name.
### Create a new `mta-sts` subdomain
Before anything else, we have to create a `mta-sts.example.org` subdomain. So you can create an `A` or `AAAA` record which targets a Web server you administrate. `A` record type is currently safer, just in case the [MTA](https://en.wikipedia.org/wiki/Message_transfer_agent) on the verge of connecting to ProtonMail servers lack of IPv6 support.
### Create a new Apache site : TLS, reverse proxy and cache
Once it's done and DNS propagation took place, you have to setup TLS for this domain on the Web server, as you would do for any other site.
As MTA-STS policies aren't really known to change over time, we enable caching to prevent systematic connections to ProtonMail servers.
For instance, your Apache VHOST configurations could look like (`/etc/apache2/sites-available/mta-sts.example.org.conf`) :
{% highlight apache %}
<VirtualHost *:443>
ServerName mta-sts.example.org
ServerAdmin webmaster@example.org
# Allow proxy engine to connect to an HTTPS server
SSLProxyEngine On
# Setup caching to make it honor ACL and cache the response for 1 day
CacheQuickHandler Off
CacheSocache shmcb
CacheSocacheMaxTime 86400
# Ignore ProtonMail "Cache-Control" and "Expires" headers
Header unset Cache-Control
Header unset Expires
# Ignore lack of "Last Modified" header in ProtonMail response
CacheIgnoreNoLastMod On
# Transparently serve Proton's MTA-STS policy
<Location /.well-known/mta-sts.txt>
CacheEnable socache
# Result will be cached, always close TCP session afterwards
ProxyPass https://mta-sts.protonmail.ch/.well-known/mta-sts.txt disablereuse=on
ProxyPassReverse https://mta-sts.protonmail.ch/.well-known/mta-sts.txt
</Location>
ErrorLog ${APACHE_LOG_DIR}/mta-sts.example.org_error.log
CustomLog ${APACHE_LOG_DIR}/mta-sts.example.org_access.log combined
SSLEngine On
SSLCertificateFile /path/to/mta-sts.example.org/fullchain.pem
SSLCertificateKeyFile /path/to/mta-sts.example.org/privkey.pem
Header always set Strict-Transport-Security: "max-age=63072000"
</VirtualHost>
<VirtualHost *:80>
ServerName mta-sts.example.org
ServerAdmin webmaster@example.org
RewriteEngine On
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
{% endhighlight %}
You now only have to enable some Apache modules as well as above new site, and reload Apache configuration (Debian procedure) :
{% highlight bash %}
a2enmod cache_socache proxy ssl
a2ensite mta-sts.example.org.conf
systemctl reload apache2.service
# If you opt for disk-based caching (mod_cache_disk), don't forget to enable Apache2 cleaning systemd service
systemctl enable --now apache-htclean.service
{% endhighlight %}
You can now check MTA-STS policy is properly enforced for your domain by downloading it :
{% highlight bash %}
curl https://mta-sts.example.org/.well-known/mta-sts.txt
version: STSv1
mode: enforce
mx: mail.protonmail.ch
mx: mailsec.protonmail.ch
max_age: 604800
{% endhighlight %}
### Setup MTA-STS discovery through `_mta-sts` subdomain
Now your MTA-STS policy is available to the world, you have to allow its "discovery", through an additional `TXT` DNS record.
So the idea here, instead of copying ProtonMail own `TXT` record value (`dig +noall +answer TXT _mta-sts.protonmail.ch`) and adding it ourselves as `_mta-sts.example.org`, is rather to simply create a `CNAME` for `_mta-sts.example.org`, pointing to `_mta-sts.protonmail.ch`.
This way, when MTA will try to probe SMTP server for MTA-STS support, they will hit ProtonMail own beacon :
{% highlight bash %}
dig +noall +answer TXT _mta-sts.example.org
_mta-sts.example.org. 1799 IN CNAME _mta-sts.protonmail.ch.
_mta-sts.protonmail.ch. 1200 IN TXT "v=STSv1; id=190906205100Z;"
{% endhighlight %}
That's it ! MTA-STS policy should be enforced for your domain :innocent:
## How to setup TLS-RPT
Wait ! Don't close this tab yet ! Let's take the opportunity to have access to our DNS administration console to also setup [TLS-RPT](https://datatracker.ietf.org/doc/html/rfc8460).
TLS-RPT ("TLS Reporting") allows you to receive reports about e-mail delivering issues, which are related to TLS connection. As we just have enforced a strict TLS policy, this is important to at least be notified in case of errors.
So first, let's query ProtonMail's TLS-RPT configuration :
{% highlight bash %}
dig +noall +answer TXT _smtp._tls.protonmail.ch
_smtp._tls.protonmail.ch. 1200 IN TXT "v=TLSRPTv1; rua=https://reports.proton.me/reports/smtptls"
{% endhighlight %}
So ProtonMail asks MTA to `POST` TLS-RPT reports to their own API endpoint.
However, as TLS-RPT allows us to specify [more than one RUA](https://datatracker.ietf.org/doc/html/rfc8460#section-3) (which stands for "Reporting URI for Aggregated reports") :
> The record supports the ability to declare more than one rua, and if there exists more than one, the reporter MAY attempt to deliver to each of the supported rua destinations.
So let's leverage this specification detail to send report to ourselves, as well as ProtonMail (hoping they actually do something about them).
You only have to create a new `TXT` record for `_smtp._tls.example.org` which contains the value `v=TLSRPTv1; rua=mailto:security@example.org,https://reports.proton.me/reports/smtptls`. Don't forget to adapt the e-mail address which should receive those reports !
## Check your security policy
If you're happy with your setup, you can then test it using (for instance, and I've absolutely nothing to do with them) [EasyDMARC](https://easydmarc.com/tools/mta-sts-check). A [TLS-RPT checker](https://easydmarc.com/tools/tls-rpt-check) is also available.
## Conclusion
I hope this post has helped you ! As always, credits go to their respective owners and comments are appreciated :pray:
---
_> Post header image was generated using [ChatGPT](https://chatgpt.com/) (please, never again)_