re-add bookworm and add deprecation notice on every page
This commit is contained in:
parent
7bb8bc74b7
commit
8f4bef9908
52 changed files with 3925 additions and 0 deletions
337
src/content/docs/ispmail-bookworm/80-tls-certificates.mdx
Normal file
337
src/content/docs/ispmail-bookworm/80-tls-certificates.mdx
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
---
|
||||
title: TLS certificate
|
||||
lastUpdated: 2023-10-03
|
||||
slug: ispmail-bookworm/creating-a-tls-encryption-key-and-certificate
|
||||
sidebar:
|
||||
order: 80
|
||||
---
|
||||
|
||||
import { Aside } from "@astrojs/starlight/components";
|
||||
|
||||
<Aside type="tip" title="TLS is SSL">
|
||||
If you are unfamiliar with the abbreviation “TLS“: it is the name successor to SSL but works one the same principle.
|
||||
SSL 3.0 was just renamed to TLS 1.0 in 1999. Every new version of TLS deprecates unsafe encryption methods (ciphers)
|
||||
of previous versions. You will still find people calling it _SSL_.
|
||||
</Aside>
|
||||
|
||||
The internet is not a friendly place where you can trust people. If you send data over the internet there is a pretty
|
||||
good chance someone intercepts it. You don’t want that. Our best weapon against that is transport encryption. All you
|
||||
need is to create an encryption _key_ and a _certificate_ for Postfix (SMTP), Dovecot (IMAP/POP3) and Apache (HTTPS). If
|
||||
you are confused why that requires a key and certificate then please consider reading the Wikipedia about
|
||||
[public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography). The single most important detail here:
|
||||
nobody but you must have access to the private key.
|
||||
|
||||
<Aside type="caution">
|
||||
Do not use different certificates for Postfix and Dovecot. At least the MacOS mail client would refuse your connection
|
||||
with confusing error messages. You will be safe If you follow this guide.
|
||||
</Aside>
|
||||
|
||||
## What makes mail clients trust a certificate?
|
||||
|
||||
Your client’s operating system contains a list of “trusted” certificate authorities. Sometimes that list is located in a
|
||||
central location for all applications. And sometimes a browser comes with its own list. A certificate signed by any of
|
||||
those organization is fully trusted. Yes, your browser trusts companies you probably have never heard of. And so does
|
||||
your mail client if you use software like Thunderbird instead of using web mail. The tricky part is not the technology.
|
||||
Everyone with some knowledge about certificates can create their own authority. Mathematically your certificates are
|
||||
just as good as anyone else’s. The tricky part is to convince browser manufacturers to trust them and add your
|
||||
certificates to their trust list.
|
||||
|
||||
Those organization claim to do checks (in exchange for money) whether you are the righteous owner of a certain domain.
|
||||
These checks have sometimes failed so badly that they popped up in the news. Ten years ago a nerd
|
||||
[requested](https://bugzilla.mozilla.org/show_bug.cgi?id=647959) to get trusted by Firefox with his “Honest Achmed’s
|
||||
Used Cars and Certificates” authority. It was fun to watch the developers struggle with arguments why Achmed would be
|
||||
any worse than authorities like
|
||||
[Türktrust](https://arstechnica.com/information-technology/2013/01/turkish-government-agency-spoofed-google-certificate-accidentally/)
|
||||
or [Verisign](https://en.wikipedia.org/wiki/Verisign#2001:_Code_signing_certificate_mistake) (who failed so hard one
|
||||
must wonder why they are still in business).
|
||||
|
||||
In 2012 [two Mozilla employees](https://en.wikipedia.org/wiki/Let%27s_Encrypt#History) decided to do something about
|
||||
that. They came up with the idea of a non-profit certificate authority. The world needed encryption everywhere and
|
||||
people should no longer shy away from it because getting certificates cost money. Two years later the service was ready
|
||||
and everyone could get a free certificate valid for 90 days. An automated process renewed the certificate in time so
|
||||
there was no hassle. (They surely deserve a [donation](https://letsencrypt.org/donate/) for the many years of service to
|
||||
the internet community.)
|
||||
|
||||
## Where is the certificate used?
|
||||
|
||||
You guessed it, we will need our own certificate. It will be used in three places:
|
||||
|
||||
1. the webmail interface (driven by the Apache web server)
|
||||
2. Postfix (to encrypt SMTP communication where possible)
|
||||
3. Dovecot (to encrypt IMAP/POP3 communication and deny any unencrypted traffic)
|
||||
|
||||
A single certificate (per domain) can and should be used by all three pieces of software.
|
||||
|
||||
## Choosing a hostname for your mail server
|
||||
|
||||
Your mail server will be able to serve emails in many domains. But a certificate is usually issued for a single
|
||||
[fully-qualified domain name](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) like “mail.example.org”. The
|
||||
_common name_ is an important part of the certificate. That attribute must correspond to the host name you use during
|
||||
communication or else the certificate is rejected. Hmmm, many domains but only one name is allowed on a certificate? How
|
||||
do we deal with that?
|
||||
|
||||
### Option 1: a generic name and a single certificate
|
||||
|
||||
The simple solution is to take a generic name. The German ISP Hetzner for example uses the name “mail.your-server.de”
|
||||
for all customers and their domains. And there are many examples of such an approach. Experience shows that most users
|
||||
do not care about the hostname as long as they get their emails. So I would generally recommend this approach due to its
|
||||
simplicity.
|
||||
|
||||
### Option 2: multiple names/certificates & SNI
|
||||
|
||||
Some people however want to give their mail server as many names as they have domains. Suppose that you have three
|
||||
domains: example.org, example.net and example.com. Users of example.org can use mail.example.org while users of
|
||||
example.net use mail.example.net. As you can imagine this would not work if you had a single certificate. After all
|
||||
which name would you use as a _common name_ there? If you used “mail.example.org” then talking to your server by the
|
||||
name “mail.example.net” would lead to a certificate error.
|
||||
|
||||
So your mail server needs multiple certificates – one for each host name. And depending on how a user connects to your
|
||||
server you need to send the matching certificate. If the user wants to speak to “mail.example.org” then your server
|
||||
needs to send the certificate for “mail.example.org”. Fortunately that problem has been addressed by a technique called
|
||||
[Server Name Indication (SNI)](https://en.wikipedia.org/wiki/Server_Name_Indication). A client talks to the server and
|
||||
even before the encrypted connection is established it tells the server that it expects a certificate for the intended
|
||||
host name. The server can then load the matching certificate and initiate the encryption process.
|
||||
|
||||
SNI has long been a problem for mail servers. The mail user agent (e.g. Thunderbird) needs to support it as well as
|
||||
Postfix and Dovecot. Postfix has finally added SNI in version 3.4 so that we can use it.
|
||||
|
||||
While adding multiple host names needs extra work, it also has a benefit. If you want to move domains to other servers
|
||||
(e.g. when upgrading your server) you can move one domain at a time.
|
||||
|
||||
<Aside type="tip" title="More security?">
|
||||
If you wish to further enhance security against [man-in-the-middle
|
||||
attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) you should get acquainted
|
||||
with [DNSSEC](https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions) and [DANE](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities).
|
||||
Also take a look at
|
||||
the [smtp\_dns\_support_level](http://www.postfix.org/postconf.5.html#smtp_dns_support_level) parameter in Postfix.
|
||||
</Aside>
|
||||
|
||||
## Preparing the Apache web server for HTTP
|
||||
|
||||
Let’s start with the web server. As an example I will assume that you want to offer a host name **webmail.example.org**
|
||||
to your users. Of course your server will have another name in a domain that you control. I will use that example
|
||||
throughout the tutorial though and keep that name printed in **bold** letters to remind you that you have to use your
|
||||
own host name.
|
||||
|
||||
Do you just want to play around with your new server and not use any real domain yet? No problem. Then use
|
||||
[nip.io](https://nip.io/). If your IP address is 1.2.3.4 then you can use 1.2.3.4.nip.io as a domain name that points to
|
||||
your server. (There used to be another domain xip.io for that purpose but it died in mid-2021.) Another service like
|
||||
that which also supports IPv6 is [sslip.io](https://sslip.io/).
|
||||
|
||||
If you have an actual domain then set up a DNS “A” and “AAAA” (if you use IPv6) record for that host name pointing to
|
||||
your server’s IP address.
|
||||
|
||||
First you need a web root directory for that host name:
|
||||
|
||||
```
|
||||
mkdir /var/www/webmail.example.org
|
||||
chown www-data:www-data /var/www/webmail.example.org
|
||||
```
|
||||
|
||||
Next you need to create a virtual host configuration file. Apache on Debian uses a neat system to manage virtual hosts:
|
||||
|
||||
- `/etc/apache2/sites-available/*.conf` contains the actual configuration files for each virtual host. Putting a file
|
||||
here does not enable that host though. That’s done in the next step. There are two configuration files by default.
|
||||
“000-default.conf” is a HTTP virtual host and “default-ssl.conf” is a HTTPS virtual host.
|
||||
- `/etc/apache2/sites-enabled/*.conf` contains symbolic links (“symlinks”) pointing to configuration files in the
|
||||
/etc/apache2/sites-available directory. Only \*.conf links in this directory will be loaded by Apache.
|
||||
|
||||
This technique allows you to enable and disable virtual hosts without having to destroy any configuration. Debian ships
|
||||
with the “a2ensite” (short for “apache2 enable site”) and “a2dissite” commands. In addition to some sanity checks those
|
||||
commands essentially create or remove symlinks between “sites-available” and “sites-enabled”.
|
||||
|
||||
<Aside type="tip">Apache configuration files must have a “.conf” suffix or else they will get ignored</Aside>
|
||||
|
||||
You may remove the default symlinks in `/etc/apache2/sites-enabled/*` unless you use them already.
|
||||
|
||||
Create a new virtual host configuration file /etc/apache2/sites-available/**webmail.example.org**-http.conf and make it
|
||||
contain:
|
||||
|
||||
<pre>
|
||||
<VirtualHost *:80> ServerName **webmail.example.org** DocumentRoot /var/www/**webmail.example.org**
|
||||
</VirtualHost>
|
||||
</pre>
|
||||
|
||||
The simple configuration makes Apache handle HTTP requests (on the standard TCP port 80) if a certain line in the
|
||||
request header from the browser reads “Host: webmail.example.org”. So the browser actually tells your Apache web server
|
||||
which server name it is looking for. That allows for multiple web sites on a single IP address. (Thanks to
|
||||
[Server Name Indication](https://en.wikipedia.org/wiki/Server_Name_Indication) as explained earlier this works well for
|
||||
HTTPS, too.)
|
||||
|
||||
Enable the site:
|
||||
|
||||
<pre>a2ensite **webmail.example.org**-http</pre>
|
||||
|
||||
You will be told:
|
||||
|
||||
```
|
||||
To activate the new configuration, you need to run:
|
||||
systemctl reload apache2
|
||||
```
|
||||
|
||||
Do that.
|
||||
|
||||
<Aside type="tip" title="Apache shows an error?">
|
||||
If after reloading/restarting the Apache web server you just get an error message like…
|
||||
|
||||
```
|
||||
Job for apache2.service failed.
|
||||
See “systemctl status apache2.service” and “journalctl -xeu apache2.service” for details.
|
||||
```
|
||||
|
||||
…then run “apache2ctl configtest” to find out why it is unhappy about your configuration."
|
||||
|
||||
</Aside>
|
||||
|
||||
Let’s check if the configuration works. Put a test file into your web root directory:
|
||||
|
||||
<pre>echo "Just a test" > /var/www/**webmail.example.org**/test</pre>
|
||||
|
||||
Now when you open the URL http://**webmail.example.org**/test in your browser you should see the text “Just a test”.
|
||||
|
||||
This is enough setup to make LetsEncrypt issue a certificate for you.
|
||||
|
||||
## Getting a LetsEncrypt certificate
|
||||
|
||||
Now you can use the _certbot_ tool to request an encryption certificate from LetsEncrypt. What will happen?
|
||||
|
||||
- certbot creates a _private key_ and a _certificate request_. It sends the _certificate request_ to the LetsEncrypt
|
||||
server.
|
||||
- the LetsEncrypt server replies with a _challenge_/_token_.
|
||||
- certbot puts that token into a file in the /var/www/**webmail.example.org**/.well-known/acme-challenge directory.
|
||||
- the LetsEncrypt server does an HTTP connection to `http://webmail.example.org/.well-known/acme-challenge/…` and
|
||||
expects to find that token. This verifies that you are in charge of the domain and the web server.
|
||||
- If all works well the LetsEncrypt server signs your _certificate request_ and thus creates the actual _certificate_.
|
||||
- certbot receives the certificate and puts it into /etc/letsencrypt/archive/**webmail.example.org**/
|
||||
|
||||
To get a certificate for your domain run:
|
||||
|
||||
<pre class="wrap">
|
||||
certbot certonly --webroot --webroot-path /var/www/**webmail.example.org** -d **webmail.example.org**
|
||||
</pre>
|
||||
|
||||
You can use multiple occurences of “-d” here to get a certificate valid for multiple domains. For example: “-d
|
||||
webmail.example.org -d something-else.example.org”. (See also:
|
||||
[https://eff-certbot.readthedocs.io/en/stable/using.html#webroot](https://eff-certbot.readthedocs.io/en/stable/using.html#webroot))
|
||||
|
||||
The first time you do that you will get asked for your email address so LetsEncrypt can send you reminders if your
|
||||
certificate would expire. You will also have to agree to their terms of service.
|
||||
|
||||
If everything worked well you should get output like:
|
||||
|
||||
```
|
||||
Requesting a certificate for webmail.example.org Successfully received certificate. Certificate is saved at:
|
||||
/etc/letsencrypt/live/webmail.example.org/fullchain.pem Key is saved at:
|
||||
/etc/letsencrypt/live/webmail.example.org/privkey.pem This certificate expires on 2024-01-02. These files will be
|
||||
updated when the certificate renews. Certbot has set up a scheduled task to automatically renew this certificate in
|
||||
the background.
|
||||
```
|
||||
|
||||
In /etc/letsencrypt/live/**webmail.example.org** you will find a couple of files now:
|
||||
|
||||
- cert.pem: the certificate file
|
||||
- chain.pem: the _chaining_ or _intermediate_ certificate. This certificate provides information how the LetsEncrypt
|
||||
certificates are linked to other known certificate authorities. It is generally a good idea to always send this
|
||||
certificate along with your own for clients who may not know LetsEncrypt properly yet.
|
||||
- fullchain.pem: this file contains a concatenation of the _cert.pem_ and the _chain.pem_. This is the preferred file to
|
||||
use when a piece of software asks where to find the _certificate_.
|
||||
- privkey.pem: the private key file. Keep it secret.
|
||||
|
||||
<Aside type="tip" title="Expires in 3 months?">
|
||||
LetsEncrypt issues certificates that are valid for 3 months. This is by design to get rid of old certificates quickly.
|
||||
The renewal feature of the certbot will automatically get a fresh certificate after 2 months. So you don’t have to
|
||||
worry about that. And if a renewal failed you will get an email from them reminding you to take a look.
|
||||
</Aside>
|
||||
|
||||
## Add HTTPS
|
||||
|
||||
Now that you have a valid certificate you can finally enable HTTPS for your web server. Create a new file
|
||||
/etc/apache2/sites-available/**webmail.example.org**-https.conf containing:
|
||||
|
||||
<pre>
|
||||
<VirtualHost *:443> ServerName **webmail.example.org** DocumentRoot /var/www/**webmail.example.org** SSLEngine
|
||||
on SSLCertificateFile /etc/letsencrypt/live/**webmail.example.org**/fullchain.pem SSLCertificateKeyFile
|
||||
/etc/letsencrypt/live/**webmail.example.org**/privkey.pem </VirtualHost>
|
||||
</pre>
|
||||
|
||||
This virtual host configuration looks suspiciously similar to the HTTP virtual host above. It just listens on port 443
|
||||
(standard port for HTTPS) instead of port 80. And it uses the “SSLEngine” that handles encryption and gets information
|
||||
about the certificate for your web server (that is shown to your users) and the private key (that the web servers uses
|
||||
to decrypt the user’s communication).
|
||||
|
||||
Enable the SSL module in Apache:
|
||||
|
||||
<pre>a2enmod ssl</pre>
|
||||
|
||||
Then enable the virtual host for HTTPS:
|
||||
|
||||
<pre>a2ensite **webmail.example.org**-https</pre>
|
||||
|
||||
And _restart_ the web server. A _reload_ is not sufficient this time because you added a module.
|
||||
|
||||
systemctl restart apache2
|
||||
|
||||
Now when you point your web browser to **webmail.example.org**, your browser should tell you that it trusts the web
|
||||
site’s certificate:
|
||||
|
||||

|
||||
|
||||
(Yes, sorry, this is not **webmail.example.org**. But I do not own the example.org domain and thus cannot get a valid
|
||||
certificate for it. This is my own site.)
|
||||
|
||||
So should you keep the HTTP virtual host? Yes. First for the HTTP->HTTPS redirection. And second to keep _certbot_
|
||||
working.
|
||||
|
||||
## Redirect HTTP to HTTPS
|
||||
|
||||
Sometimes users forget to enter https://… when accessing your webmail service. So they access the HTTP web site. We
|
||||
obviously don’t want them to send their password over HTTP. So we should redirect all HTTP connections to HTTPS.
|
||||
|
||||
One exception though. Let’s Encrypt will use HTTP to verify your challenge token. So we need to serve files at
|
||||
http://**webmail.example.org**/.well-known/acme-challenge/… directly while redirecting all other requests to HTTPS. You
|
||||
can accomplish that by putting these lines inside the <VirtualHost> section of your
|
||||
`/etc/apache2/sites-available/webmail.example.org-http.conf` file:
|
||||
|
||||
```
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_URI} !.well-known/acme-challenge
|
||||
RewriteRule ^(.*)$ https://%{SERVER_NAME}$1 \[R=301,L\]
|
||||
```
|
||||
|
||||
This requires the _rewrite_ module to be enabled in Apache. That is simple though:
|
||||
|
||||
```
|
||||
a2enmod rewrite
|
||||
systemctl restart apache2
|
||||
```
|
||||
|
||||
So now entering http://**webmail.example.org** will redirect you to https://**webmail.example.org**.
|
||||
|
||||
## Automatic certificate renewal
|
||||
|
||||
The *certbot* package automatically adds a timed job that runs twice a day at random times. The random part is important
|
||||
to avoid millions of server hammering the LetsEncrypt service at the same second.
|
||||
|
||||
<Aside type="tip" title="Systemd timer instead of a Cron job">
|
||||
This job is not a classic Cron job but instead latches into systemd. You can find the _timer_ definition in
|
||||
the `/lib/systemd/system/certbot.timer` file. That timer triggers the renewal service defined
|
||||
in `/lib/systemd/system/certbot.service`. To find out if the service has run and when the next occurence will be, run
|
||||
“systemctl status certbot.timer”. There is also a Cron file at /etc/cron.d/certbot. Don’t be confused. This job will
|
||||
not do anything due to the “`-d /run/systemd/system`” condition that checks if systemd is installed. And Debian
|
||||
nowadays uses systemd.
|
||||
</Aside>
|
||||
|
||||
So the renewal already happens automatically. Should it fail then LetsEncrypt start sending you reminder emails that
|
||||
your certificate should be renewed. That’s a clear sign that something went wrong with the automatic renewal.
|
||||
|
||||
There is one puzzle piece missing though. Even if the renewal worked it will only update the certificate files. But the
|
||||
software components – Postfix, Dovecot and Apache – will not notice the change. So we need to add a so called
|
||||
_post-hook_ to certbot that triggers a restart of all processes thereafter.
|
||||
|
||||
For that purpose edit the /etc/letsencrypt/cli.ini file and add:
|
||||
|
||||
```
|
||||
post-hook = systemctl restart postfix dovecot apache2
|
||||
```
|
||||
|
||||
Well done. You have implemented Let’s Encrypt for all your services now. Let’s go on.
|
||||
Loading…
Add table
Add a link
Reference in a new issue