1
0
mirror of https://github.com/Mailu/Mailu.git synced 2024-12-12 10:45:38 +02:00
1922: Harden postfix's configuration r=mergify[bot] a=nextgens

## What type of PR?

enhancement

## What does this PR do?

It hardens the default configuration:
- disable AUTH commands on port 25 (nginx was not advertising the capability: normal clients wouldn't attempt it)
- fix Forward Secrecy by ensuring that we don't use session tickets and don't cache on forensically carveable mediums
- prevent clear-text credentials from being sent while authenticating to remote relays (this may break things if the relay doesn't support challenge-based authentication NOR STARTTLS - unlikely).
- switch to default RSA keysizes (2048 bits and they get rekeyed every 3 months -modern clients will do ECC)
- enable ECC certificates (much smaller than RSA keys, faster for better security margin)
- configure nginx so that it doesn't send the legacy/root CA (clients that require it are unlikely to do TLS1.2 any ways)

I don't think that any of those changes is impactful enough to warrant being documented.

### Related issue(s)
- close #1804

## Prerequistes
Before we can consider review and merge, please make sure the following list is done and checked.
If an entry in not applicable, you can check it or remove it from the list.

- [x] In case of feature or enhancement: documentation updated accordingly
- [x] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file.


Co-authored-by: Florent Daigniere <nextgens@freenetproject.org>
Co-authored-by: Jack Murray <github@c0rporation.com>
This commit is contained in:
bors[bot] 2021-08-18 14:25:20 +00:00 committed by GitHub
commit 34b35ca9b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 81 additions and 21 deletions

View File

@ -81,6 +81,13 @@ def handle_authentication(headers):
raw_password = urllib.parse.unquote(headers["Auth-Pass"])
password = raw_password.encode("iso8859-1").decode("utf8")
ip = urllib.parse.unquote(headers["Client-Ip"])
service_port = int(urllib.parse.unquote(headers["Auth-Port"]))
if service_port == 25:
return {
"Auth-Status": "AUTH not supported",
"Auth-Error-Code": "502 5.5.1",
"Auth-Wait": 0
}
user = models.User.query.get(user_email)
if check_credentials(user, password, ip, protocol):
return {

View File

@ -1,13 +1,11 @@
-----BEGIN DH PARAMETERS-----
MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3
7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32
nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e
8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx
iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K
zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=
MIIBiAKCAYEAtQlUSOKGjpdXJ154qmMEa1pEs+9CdSxWiZFkiXBJb0lTafOh8cfF
2IkcWSwzxWwjW4Ad26UQQFh1poGf2QBzVk2vuKCekYzPAs/WqH8VwiXBiWR5R9lh
v/+CkEBYuQOzAhXLN6ZGdPPa2sjdI49rlaIqyLJE4D0TI/VHYmC/vEwqkJUgaGrS
19LhHZimnmouvrnyBPyf00czXlMow0RnmYeHVZ7W5hu7t9TH9o3QAN/GKiFfxFj+
RkdLM7beQdS0He5YeTaElM5l1YT5d5gHFbOzEQyKHd10ux+bgVcgUeVbBnI1SAIC
w53yc1PkDAiRijSP5j5aWq1djtJPheS13o35HyIf0cHzkNYhKfX5JWPj/cbgdM+C
FL1bnRc8sL5oxmkDoGJhiNZIf4n2WtS8Zu28gUgat6S+vCm/4yavIc/T1g6UiNKE
X41HPbsma/QWUwOL6S+b2qr+7rKqjI5TzVek8vBMellEV4mBvfQU3NDSQ4WvxbTq
ZEOgLPA178nrAgEC
-----END DH PARAMETERS-----

View File

@ -250,6 +250,7 @@ mail {
listen 10025;
protocol smtp;
smtp_auth plain;
auth_http_header Auth-Port 10025;
}
# Default IMAP server for the webmail (no encryption, but authentication)
@ -257,6 +258,7 @@ mail {
listen 10143;
protocol imap;
smtp_auth plain;
auth_http_header Auth-Port 10043;
}
# SMTP is always enabled, to avoid losing emails when TLS is failing
@ -271,6 +273,7 @@ mail {
{% endif %}
protocol smtp;
smtp_auth none;
auth_http_header Auth-Port 25;
}
# All other protocols are disabled if TLS is failing
@ -283,6 +286,7 @@ mail {
{% endif %}
protocol imap;
imap_auth plain;
auth_http_header Auth-Port 143;
}
server {
@ -293,6 +297,7 @@ mail {
{% endif %}
protocol pop3;
pop3_auth plain;
auth_http_header Auth-Port 110;
}
server {
@ -303,6 +308,7 @@ mail {
{% endif %}
protocol smtp;
smtp_auth plain login;
auth_http_header Auth-Port 587;
}
{% if TLS %}
@ -311,6 +317,7 @@ mail {
listen [::]:465 ssl;
protocol smtp;
smtp_auth plain login;
auth_http_header Auth-Port 465;
}
server {
@ -318,6 +325,7 @@ mail {
listen [::]:993 ssl;
protocol imap;
imap_auth plain;
auth_http_header Auth-Port 993;
}
server {
@ -325,6 +333,7 @@ mail {
listen [::]:995 ssl;
protocol pop3;
pop3_auth plain;
auth_http_header Auth-Port 995;
}
{% endif %}
{% endif %}

View File

@ -1,5 +1,10 @@
ssl_certificate {{ TLS[0] }};
ssl_certificate_key {{ TLS[1] }};
{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %}
ssl_certificate {{ TLS[2] }};
ssl_certificate_key {{ TLS[3] }};
ssl_trusted_certificate /etc/ssl/certs/ca-cert-DST_Root_CA_X3.pem;
{% endif %}
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_dhparam /conf/dhparam.pem;

View File

@ -26,11 +26,11 @@ cert_name = os.getenv("TLS_CERT_FILENAME", default="cert.pem")
keypair_name = os.getenv("TLS_KEYPAIR_FILENAME", default="key.pem")
args["TLS"] = {
"cert": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name),
"letsencrypt": ("/certs/letsencrypt/live/mailu/fullchain.pem",
"/certs/letsencrypt/live/mailu/privkey.pem"),
"letsencrypt": ("/certs/letsencrypt/live/mailu/nginx-chain.pem",
"/certs/letsencrypt/live/mailu/privkey.pem", "/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem", "/certs/letsencrypt/live/mailu-ecdsa/privkey.pem"),
"mail": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name),
"mail-letsencrypt": ("/certs/letsencrypt/live/mailu/fullchain.pem",
"/certs/letsencrypt/live/mailu/privkey.pem"),
"mail-letsencrypt": ("/certs/letsencrypt/live/mailu/nginx-chain.pem",
"/certs/letsencrypt/live/mailu/privkey.pem", "/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem", "/certs/letsencrypt/live/mailu-ecdsa/privkey.pem"),
"notls": None
}[args["TLS_FLAVOR"]]

View File

@ -4,7 +4,6 @@ import os
import time
import subprocess
command = [
"certbot",
"-n", "--agree-tos", # non-interactive
@ -14,10 +13,37 @@ command = [
"--cert-name", "mailu",
"--preferred-challenges", "http", "--http-01-port", "8008",
"--keep-until-expiring",
"--rsa-key-size", "4096",
"--config-dir", "/certs/letsencrypt",
"--post-hook", "/config.py"
]
command2 = [
"certbot",
"-n", "--agree-tos", # non-interactive
"-d", os.environ["HOSTNAMES"],
"-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]),
"certonly", "--standalone",
"--cert-name", "mailu-ecdsa",
"--preferred-challenges", "http", "--http-01-port", "8008",
"--keep-until-expiring",
"--key-type", "ecdsa",
"--config-dir", "/certs/letsencrypt",
"--post-hook", "/config.py"
]
def format_for_nginx(fullchain, output):
""" We may want to strip ISRG Root X1 out
"""
certs = []
with open(fullchain, 'r') as pem:
cert = ''
for line in pem:
cert += line
if '-----END CERTIFICATE-----' in line:
certs += [cert]
cert = ''
with open(output, 'w') as pem:
for cert in certs[:-1] if len(certs)>2 and os.getenv('LETSENCRYPT_SHORTCHAIN', default="False") else certs:
pem.write(cert)
# Wait for nginx to start
time.sleep(5)
@ -25,5 +51,7 @@ time.sleep(5)
# Run certbot every day
while True:
subprocess.call(command)
format_for_nginx('/certs/letsencrypt/live/mailu/fullchain.pem', '/certs/letsencrypt/live/mailu/nginx-chain.pem')
subprocess.call(command2)
format_for_nginx('/certs/letsencrypt/live/mailu-ecdsa/fullchain.pem', '/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem')
time.sleep(86400)

View File

@ -33,7 +33,8 @@ relayhost = {{ RELAYHOST }}
{% if RELAYUSER %}
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_security_options = noanonymous, noplaintext
smtp_sasl_tls_security_options = noanonymous
{% endif %}
# Recipient delimiter for extended addresses
@ -50,7 +51,7 @@ smtpd_authorized_xclient_hosts={{ POD_ADDRESS_RANGE or SUBNET }}
# General TLS configuration
tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
tls_preempt_cipherlist = yes
tls_ssl_options = NO_COMPRESSION
tls_ssl_options = NO_COMPRESSION, NO_TICKET
# By default, outgoing TLS is more flexible because
# 1. not all receiving servers will support TLS,
@ -58,7 +59,8 @@ tls_ssl_options = NO_COMPRESSION
smtp_tls_security_level = {{ OUTBOUND_TLS_LEVEL|default('may') }}
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
smtp_tls_protocols =!SSLv2,!SSLv3
smtp_tls_session_cache_database = lmdb:${data_directory}/smtp_scache
smtp_tls_session_cache_database = lmdb:/dev/shm/postfix/smtp_scache
smtpd_tls_session_cache_database = lmdb:/dev/shm/postfix/smtpd_scache
###############
# Virtual

View File

@ -15,6 +15,7 @@ log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
def start_podop():
os.setuid(getpwnam('postfix').pw_uid)
os.mkdir('/dev/shm/postfix',mode=0o700)
url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/postfix/"
# TODO: Remove verbosity setting from Podop?
run_server(0, "postfix", "/tmp/podop.socket", [

View File

@ -163,6 +163,11 @@ See the `python docs`_ for more information.
.. _`python docs`: https://docs.python.org/3.6/library/logging.html#logging-levels
The ``LETSENCRYPT_SHORTCHAIN`` (default: False) setting controls whether we send the ISRG Root X1 certificate in TLS handshakes. This is required for `android handsets older than 7.1.1` but slows down the performance of modern devices.
.. _`android handsets older than 7.1.1`: https://community.letsencrypt.org/t/production-chain-changes/150739
Antivirus settings
------------------

View File

@ -0,0 +1,5 @@
Add support for ECDSA certificates when letsencrypt is used. This means dropping compatibility for android < 4.1.1
Add LETSENCRYPT_SHORTCHAIN to your configuration to avoid sending ISRG Root X1 (this will break compatibility with android < 7.1.1)
Disable AUTH command on port 25
Disable TLS tickets, reconfigure the cache to improve Forward Secrecy
Prevent clear-text credentials from being sent to relays