1
0
mirror of https://github.com/Mailu/Mailu.git synced 2025-01-26 03:52:50 +02:00
2479: Rework the anti-spoofing rule r=mergify[bot] a=nextgens

## What type of PR?

Feature

## What does this PR do?

We shouldn't assume that Mailu is the only MTA allowed to send emails on behalf of the domains it hosts.
We should also ensure that it's non-trivial for email-spoofing of hosted domains to happen

Previously we were preventing any spoofing of the envelope from; Now we are preventing spoofing of both the envelope from and the header from unless some form of authentication passes (is a RELAYHOST, SPF, DKIM, ARC)

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

## Prerequisites
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>
This commit is contained in:
bors[bot] 2022-11-09 15:16:36 +00:00 committed by GitHub
commit 0839490beb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 57 additions and 20 deletions

View File

@ -23,7 +23,7 @@ Main features include:
- **User features**, aliases, auto-reply, auto-forward, fetched accounts
- **Admin features**, global admins, announcements, per-domain delegation, quotas
- **Security**, enforced TLS, DANE, MTA-STS, Letsencrypt!, outgoing DKIM, anti-virus scanner
- **Antispam**, auto-learn, greylisting, DMARC and SPF
- **Antispam**, auto-learn, greylisting, DMARC and SPF, anti-spoofing
- **Freedom**, all FOSS components, no tracker included
![Domains](docs/assets/screenshots/domains.png)

View File

@ -158,21 +158,6 @@ def postfix_sender_rate(sender):
user = models.User.get(sender) or flask.abort(404)
return flask.abort(404) if user.sender_limiter.hit() else flask.jsonify("450 4.2.1 You are sending too many emails too fast.")
@internal.route("/postfix/sender/access/<path:sender>")
def postfix_sender_access(sender):
""" Simply reject any sender that pretends to be from a local domain
"""
if '@' in sender:
if sender.startswith('<') and sender.endswith('>'):
sender = sender[1:-1]
try:
localpart, domain_name = models.Email.resolve_domain(sender)
if models.Domain.query.get(domain_name):
return flask.jsonify("REJECT")
except sqlalchemy.exc.StatementError:
pass
return flask.abort(404)
# idna encode domain part of each address in list of addresses
def idna_encode(addresses):
return [

View File

@ -25,3 +25,7 @@ def rspamd_dkim_key(domain_name):
}
)
return flask.jsonify({'data': {'selectors': selectors}})
@internal.route("/rspamd/local_domains", methods=['GET'])
def rspamd_local_domains():
return '\n'.join(domain[0] for domain in models.Domain.query.with_entities(models.Domain.name).all() + models.Alternative.query.with_entities(models.Alternative.name).all())

View File

@ -110,7 +110,6 @@ check_ratelimit = check_sasl_access ${podop}senderrate
smtpd_client_restrictions =
permit_mynetworks,
check_sender_access ${podop}senderaccess,
reject_non_fqdn_sender,
reject_unknown_sender_domain,
reject_unknown_recipient_domain,

View File

@ -27,7 +27,6 @@ def start_podop():
("mailbox", "url", url + "mailbox/§"),
("recipientmap", "url", url + "recipient/map/§"),
("sendermap", "url", url + "sender/map/§"),
("senderaccess", "url", url + "sender/access/§"),
("senderlogin", "url", url + "sender/login/§"),
("senderrate", "url", url + "sender/rate/§")
])

View File

@ -0,0 +1,17 @@
rules {
ANTISPOOF_NOAUTH {
action = "reject";
expression = "!MAILLIST & ((IS_LOCAL_DOMAIN_E & MISSING_FROM) | (IS_LOCAL_DOMAIN_H & (R_DKIM_NA & R_SPF_NA & DMARC_NA & ARC_NA)))";
message = "Rejected (anti-spoofing: noauth). Please setup DMARC with DKIM or SPF if you want to send emails from your domain from other servers.";
}
ANTISPOOF_DMARC_ENFORCE_LOCAL {
action = "reject";
expression = "!MAILLIST & (IS_LOCAL_DOMAIN_H | IS_LOCAL_DOMAIN_E) & (DMARC_POLICY_SOFTFAIL | DMARC_POLICY_REJECT | DMARC_POLICY_QUARANTINE | DMARC_NA)";
message = "Rejected (anti-spoofing: DMARC compliance is enforced for local domains, regardless of the policy setting)";
}
ANTISPOOF_AUTH_FAILED {
action = "reject";
expression = "!MAILLIST & BLACKLIST_ANTISPOOF";
message = "Rejected (anti-spoofing: auth-failed)";
}
}

View File

@ -0,0 +1,11 @@
IS_LOCAL_DOMAIN_H {
type = "selector"
selector = "from('mime'):domain";
map = "http://{{ ADMIN_ADDRESS }}/internal/rspamd/local_domains";
}
IS_LOCAL_DOMAIN_E {
type = "selector"
selector = "from('smtp'):domain";
map = "http://{{ ADMIN_ADDRESS }}/internal/rspamd/local_domains";
}

View File

@ -0,0 +1,8 @@
rules {
BLACKLIST_ANTISPOOF = {
valid_dmarc = true;
blacklist = true;
domains = "http://{{ ADMIN_ADDRESS }}/internal/rspamd/local_domains";
score = 0.0;
}
}

View File

@ -3,7 +3,9 @@
import os
import glob
import logging as log
import requests
import sys
import time
from socrate import system, conf
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
@ -19,5 +21,16 @@ if os.environ.get("ANTIVIRUS") == 'clamav':
for rspamd_file in glob.glob("/conf/*"):
conf.jinja(rspamd_file, os.environ, os.path.join("/etc/rspamd/local.d", os.path.basename(rspamd_file)))
# Admin may not be up just yet
healthcheck = f'http://{os.environ["ADMIN_ADDRESS"]}/internal/rspamd/local_domains'
while True:
time.sleep(1)
try:
if requests.get(healthcheck,timeout=2).ok:
break
except:
pass
log.warning("Admin is not up just yet, retrying in 1 second")
# Run rspamd
os.execv("/usr/sbin/rspamd", ["rspamd", "-i", "-f"])

View File

@ -28,8 +28,8 @@ Main features include:
- **Web access**, multiple Webmails and administration interface
- **User features**, aliases, auto-reply, auto-forward, fetched accounts
- **Admin features**, global admins, announcements, per-domain delegation, quotas
- **Security**, enforced TLS, Letsencrypt!, outgoing DKIM, anti-virus scanner
- **Antispam**, auto-learn, greylisting, DMARC and SPF
- **Security**, enforced TLS, DANE, MTA-STS, Letsencrypt!, outgoing DKIM, anti-virus scanner
- **Antispam**, auto-learn, greylisting, DMARC and SPF, anti-spoofing
- **Freedom**, all FOSS components, no tracker included
.. image:: assets/screenshots/create.png

View File

@ -0,0 +1 @@
Upgrade the anti-spoofing rule. We shouldn't assume that Mailu is the only MTA allowed to send emails on behalf of the domains it hosts... but we should also ensure that both the envelope from and header from are checked.