mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-12-24 07:37:04 +00:00
First steps in migrating to dkimpy-milter
This commit is contained in:
parent
21fd62ef4f
commit
ded1b55ebd
@ -33,6 +33,8 @@ Functionality changes and additions
|
|||||||
Using check-dnsbl.py from https://github.com/gsauthof/utility
|
Using check-dnsbl.py from https://github.com/gsauthof/utility
|
||||||
* Updated ssl security for web and email
|
* Updated ssl security for web and email
|
||||||
Removed older cryptos following internet.nl recommendations
|
Removed older cryptos following internet.nl recommendations
|
||||||
|
* Replace opendkim with dkimpy (https://launchpad.net/dkimpy-milter)
|
||||||
|
Added support for Ed25519 signing
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
* Munin error report fixed [see github issue](https://github.com/mail-in-a-box/mailinabox/issues/1555)
|
* Munin error report fixed [see github issue](https://github.com/mail-in-a-box/mailinabox/issues/1555)
|
||||||
|
@ -1262,7 +1262,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/MailUserAddResponse'
|
$ref: '#/components/schemas/MailUserAddResponse'
|
||||||
example: |
|
example: |
|
||||||
mail user added
|
mail user added
|
||||||
updated DNS: OpenDKIM configuration
|
updated DNS: DKIM configuration
|
||||||
400:
|
400:
|
||||||
description: Bad request
|
description: Bad request
|
||||||
content:
|
content:
|
||||||
@ -1863,7 +1863,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
example: |
|
example: |
|
||||||
mail user added
|
mail user added
|
||||||
updated DNS: OpenDKIM configuration
|
updated DNS: DKIM configuration
|
||||||
description: |
|
description: |
|
||||||
Mail user add response.
|
Mail user add response.
|
||||||
|
|
||||||
|
@ -105,21 +105,22 @@ def do_dns_update(env, force=False):
|
|||||||
if len(updated_domains) > 0:
|
if len(updated_domains) > 0:
|
||||||
shell('check_call', ["/usr/sbin/service", "nsd", "restart"])
|
shell('check_call', ["/usr/sbin/service", "nsd", "restart"])
|
||||||
|
|
||||||
# Write the OpenDKIM configuration tables for all of the mail domains.
|
# Write the DKIM configuration tables for all of the mail domains.
|
||||||
from mailconfig import get_mail_domains
|
from mailconfig import get_mail_domains
|
||||||
if write_opendkim_tables(get_mail_domains(env), env):
|
|
||||||
# Settings changed. Kick opendkim.
|
if write_dkim_tables(get_mail_domains(env), env):
|
||||||
shell('check_call', ["/usr/sbin/service", "opendkim", "restart"])
|
# Settings changed. Kick dkimpy.
|
||||||
|
shell('check_call', ["/usr/sbin/service", "dkimpy-milter", "restart"])
|
||||||
if len(updated_domains) == 0:
|
if len(updated_domains) == 0:
|
||||||
# If this is the only thing that changed?
|
# If this is the only thing that changed?
|
||||||
updated_domains.append("OpenDKIM configuration")
|
updated_domains.append("DKIM configuration")
|
||||||
|
|
||||||
# Clear bind9's DNS cache so our own DNS resolver is up to date.
|
# Clear bind9's DNS cache so our own DNS resolver is up to date.
|
||||||
# (ignore errors with trap=True)
|
# (ignore errors with trap=True)
|
||||||
shell('check_call', ["/usr/sbin/rndc", "flush"], trap=True)
|
shell('check_call', ["/usr/sbin/rndc", "flush"], trap=True)
|
||||||
|
|
||||||
if len(updated_domains) == 0:
|
if len(updated_domains) == 0:
|
||||||
# if nothing was updated (except maybe OpenDKIM's files), don't show any output
|
# if nothing was updated (except maybe DKIM's files), don't show any output
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
return "updated DNS: " + ",".join(updated_domains) + "\n"
|
return "updated DNS: " + ",".join(updated_domains) + "\n"
|
||||||
@ -303,10 +304,18 @@ def build_zone(domain, domain_properties, additional_records, env, is_zone=True)
|
|||||||
if not has_rec(None, "TXT", prefix="v=spf1 "):
|
if not has_rec(None, "TXT", prefix="v=spf1 "):
|
||||||
records.append((None, "TXT", 'v=spf1 mx -all', "Recommended. Specifies that only the box is permitted to send @%s mail." % domain))
|
records.append((None, "TXT", 'v=spf1 mx -all', "Recommended. Specifies that only the box is permitted to send @%s mail." % domain))
|
||||||
|
|
||||||
# Append the DKIM TXT record to the zone as generated by OpenDKIM.
|
# Append the DKIM TXT record to the zone as generated by DKIMpy.
|
||||||
# Skip if the user has set a DKIM record already.
|
# Skip if the user has set a DKIM record already.
|
||||||
opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt')
|
dkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim2/box-rsa.dns')
|
||||||
with open(opendkim_record_file) as orf:
|
with open(dkim_record_file) as orf:
|
||||||
|
m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S)
|
||||||
|
val = "".join(re.findall(r'"([^"]+)"', m.group(2)))
|
||||||
|
if not has_rec(m.group(1), "TXT", prefix="v=DKIM1; "):
|
||||||
|
records.append((m.group(1), "TXT", val, "Recommended. Provides a way for recipients to verify that this machine sent @%s mail." % domain))
|
||||||
|
|
||||||
|
# Also add a ed25519 DKIM record
|
||||||
|
dkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim2/box-ed25519.dns')
|
||||||
|
with open(dkim_record_file) as orf:
|
||||||
m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S)
|
m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S)
|
||||||
val = "".join(re.findall(r'"([^"]+)"', m.group(2)))
|
val = "".join(re.findall(r'"([^"]+)"', m.group(2)))
|
||||||
if not has_rec(m.group(1), "TXT", prefix="v=DKIM1; "):
|
if not has_rec(m.group(1), "TXT", prefix="v=DKIM1; "):
|
||||||
@ -817,14 +826,15 @@ def sign_zone(domain, zonefile, env):
|
|||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
def write_opendkim_tables(domains, env):
|
def write_dkim_tables(domains, env):
|
||||||
# Append a record to OpenDKIM's KeyTable and SigningTable for each domain
|
# Append a record to DKIMpy's KeyTable and SigningTable for each domain
|
||||||
# that we send mail from (zones and all subdomains).
|
# that we send mail from (zones and all subdomains).
|
||||||
|
|
||||||
opendkim_key_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.private')
|
dkim_rsa_key_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim2/box-rsa.key')
|
||||||
|
dkim_ed_key_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim2/box-ed25519.key')
|
||||||
|
|
||||||
if not os.path.exists(opendkim_key_file):
|
if not os.path.exists(dkim_rsa_key_file) || not os.path.exists(dkim_ed_key_file):
|
||||||
# Looks like OpenDKIM is not installed.
|
# Looks like DKIMpy is not installed.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
@ -846,7 +856,12 @@ def write_opendkim_tables(domains, env):
|
|||||||
# signing domain must match the sender's From: domain.
|
# signing domain must match the sender's From: domain.
|
||||||
"KeyTable":
|
"KeyTable":
|
||||||
"".join(
|
"".join(
|
||||||
"{domain} {domain}:mail:{key_file}\n".format(domain=domain, key_file=opendkim_key_file)
|
"{domain} {domain}:box-rsa:{key_file}\n".format(domain=domain, key_file=dkim_rsa_key_file)
|
||||||
|
for domain in domains
|
||||||
|
),
|
||||||
|
"KeyTableEd25519":
|
||||||
|
"".join(
|
||||||
|
"{domain} {domain}:box-ed25519:{key_file}\n".format(domain=domain, key_file=dkim_ed_key_file)
|
||||||
for domain in domains
|
for domain in domains
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -854,18 +869,18 @@ def write_opendkim_tables(domains, env):
|
|||||||
did_update = False
|
did_update = False
|
||||||
for filename, content in config.items():
|
for filename, content in config.items():
|
||||||
# Don't write the file if it doesn't need an update.
|
# Don't write the file if it doesn't need an update.
|
||||||
if os.path.exists("/etc/opendkim/" + filename):
|
if os.path.exists("/etc/dkim/" + filename):
|
||||||
with open("/etc/opendkim/" + filename) as f:
|
with open("/etc/dkim/" + filename) as f:
|
||||||
if f.read() == content:
|
if f.read() == content:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# The contents needs to change.
|
# The contents needs to change.
|
||||||
with open("/etc/opendkim/" + filename, "w") as f:
|
with open("/etc/dkim/" + filename, "w") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
did_update = True
|
did_update = True
|
||||||
|
|
||||||
# Return whether the files changed. If they didn't change, there's
|
# Return whether the files changed. If they didn't change, there's
|
||||||
# no need to kick the opendkim process.
|
# no need to kick the dkimpy process.
|
||||||
return did_update
|
return did_update
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
|
@ -376,7 +376,7 @@ def scan_mail_log_line(line, collector):
|
|||||||
if SCAN_BLOCKED:
|
if SCAN_BLOCKED:
|
||||||
scan_postfix_smtpd_line(date, log, collector)
|
scan_postfix_smtpd_line(date, log, collector)
|
||||||
elif service in ("postfix/qmgr", "postfix/pickup", "postfix/cleanup", "postfix/scache",
|
elif service in ("postfix/qmgr", "postfix/pickup", "postfix/cleanup", "postfix/scache",
|
||||||
"spampd", "postfix/anvil", "postfix/master", "opendkim", "postfix/lmtp",
|
"spampd", "postfix/anvil", "postfix/master", "dkimpy", "postfix/lmtp",
|
||||||
"postfix/tlsmgr", "anvil"):
|
"postfix/tlsmgr", "anvil"):
|
||||||
# nothing to look at
|
# nothing to look at
|
||||||
return True
|
return True
|
||||||
|
@ -28,7 +28,7 @@ def get_services():
|
|||||||
{ "name": "Dovecot LMTP LDA", "port": 10026, "public": False, },
|
{ "name": "Dovecot LMTP LDA", "port": 10026, "public": False, },
|
||||||
{ "name": "Postgrey", "port": 10023, "public": False, },
|
{ "name": "Postgrey", "port": 10023, "public": False, },
|
||||||
{ "name": "Spamassassin", "port": 10025, "public": False, },
|
{ "name": "Spamassassin", "port": 10025, "public": False, },
|
||||||
{ "name": "OpenDKIM", "port": 8891, "public": False, },
|
{ "name": "DKIMpy", "port": 8892, "public": False, },
|
||||||
{ "name": "OpenDMARC", "port": 8893, "public": False, },
|
{ "name": "OpenDMARC", "port": 8893, "public": False, },
|
||||||
{ "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, },
|
{ "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, },
|
||||||
{ "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, },
|
{ "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, },
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# OpenDKIM
|
# DKIM
|
||||||
# --------
|
# --------
|
||||||
#
|
#
|
||||||
# OpenDKIM provides a service that puts a DKIM signature on outbound mail.
|
# DKIMpy provides a service that puts a DKIM signature on outbound mail.
|
||||||
#
|
#
|
||||||
# The DNS configuration for DKIM is done in the management daemon.
|
# The DNS configuration for DKIM is done in the management daemon.
|
||||||
|
|
||||||
@ -10,34 +10,34 @@ source setup/functions.sh # load our functions
|
|||||||
source /etc/mailinabox.conf # load global vars
|
source /etc/mailinabox.conf # load global vars
|
||||||
|
|
||||||
# Install DKIM...
|
# Install DKIM...
|
||||||
echo Installing OpenDKIM/OpenDMARC...
|
echo Installing DKIMpy/OpenDMARC...
|
||||||
apt_install opendkim opendkim-tools opendmarc
|
apt_install dkimpy-milter opendmarc
|
||||||
|
|
||||||
# Make sure configuration directories exist.
|
# Make sure configuration directories exist.
|
||||||
mkdir -p /etc/opendkim;
|
mkdir -p /etc/dkim;
|
||||||
mkdir -p $STORAGE_ROOT/mail/dkim
|
mkdir -p $STORAGE_ROOT/mail/dkim2
|
||||||
|
|
||||||
# Used in InternalHosts and ExternalIgnoreList configuration directives.
|
# Used in InternalHosts and ExternalIgnoreList configuration directives.
|
||||||
# Not quite sure why.
|
# Not quite sure why.
|
||||||
echo "127.0.0.1" > /etc/opendkim/TrustedHosts
|
echo "127.0.0.1" > /etc/dkim/TrustedHosts
|
||||||
|
|
||||||
# We need to at least create these files, since we reference them later.
|
# We need to at least create these files, since we reference them later.
|
||||||
# Otherwise, opendkim startup will fail
|
touch /etc/dkim/KeyTable
|
||||||
touch /etc/opendkim/KeyTable
|
touch /etc/dkim/SigningTable
|
||||||
touch /etc/opendkim/SigningTable
|
|
||||||
|
|
||||||
if grep -q "ExternalIgnoreList" /etc/opendkim.conf; then
|
if grep -q "ExternalIgnoreList" /etc/dkimpy-milter/dkimpy-milter.conf; then
|
||||||
true # already done #NODOC
|
true # already done #NODOC
|
||||||
else
|
else
|
||||||
# Add various configuration options to the end of `opendkim.conf`.
|
# Add various configuration options to the end of `dkimpy-milter.conf`.
|
||||||
cat >> /etc/opendkim.conf << EOF;
|
cat >> /etc/dkimpy-milter/dkimpy-milter.conf << EOF;
|
||||||
Canonicalization relaxed/simple
|
Canonicalization relaxed/simple
|
||||||
MinimumKeyBits 1024
|
MinimumKeyBits 1024
|
||||||
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
|
ExternalIgnoreList refile:/etc/dkim/TrustedHosts
|
||||||
InternalHosts refile:/etc/opendkim/TrustedHosts
|
InternalHosts refile:/etc/dkim/TrustedHosts
|
||||||
KeyTable refile:/etc/opendkim/KeyTable
|
KeyTable refile:/etc/dkim/KeyTable
|
||||||
SigningTable refile:/etc/opendkim/SigningTable
|
KeyTableEd25519 refile:/etc/dkim/KeyTableEd25519
|
||||||
Socket inet:8891@127.0.0.1
|
SigningTable refile:/etc/dkim/SigningTable
|
||||||
|
Socket inet:8892@127.0.0.1
|
||||||
RequireSafeKeys false
|
RequireSafeKeys false
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
@ -48,17 +48,21 @@ fi
|
|||||||
# in our DNS setup. Note that the files are named after the
|
# in our DNS setup. Note that the files are named after the
|
||||||
# 'selector' of the key, which we can change later on to support
|
# 'selector' of the key, which we can change later on to support
|
||||||
# key rotation.
|
# key rotation.
|
||||||
#
|
if [ ! -f "$STORAGE_ROOT/mail/dkim2/box-rsa.key" ]; then
|
||||||
# A 1024-bit key is seen as a minimum standard by several providers
|
# All defaults are supposed to be ok, default key for rsa is 2048 bit
|
||||||
# such as Google. But they and others use a 2048 bit key, so we'll
|
dknewkey --ktype rsa $STORAGE_ROOT/mail/dkim2/box-rsa
|
||||||
# do the same. Keys beyond 2048 bits may exceed DNS record limits.
|
dknewkey --ktype ed25519 $STORAGE_ROOT/mail/dkim2/box-ed25519
|
||||||
if [ ! -f "$STORAGE_ROOT/mail/dkim/mail.private" ]; then
|
|
||||||
opendkim-genkey -b 2048 -r -s mail -D $STORAGE_ROOT/mail/dkim
|
# Force them into the format dns_update.py expects
|
||||||
|
sed -i 's/v=DKIM1;/box-rsa._domainkey IN TXT ( "v=DKIM1;/' $STORAGE_ROOT/mail/dkim2/box-rsa.dns
|
||||||
|
echo '" )' >> box-rsa.dns
|
||||||
|
sed -i 's/v=DKIM1;/box-ed25519._domainkey IN TXT ( "v=DKIM1;/' $STORAGE_ROOT/mail/dkim2/box-ed25519.dns
|
||||||
|
echo '" )' >> box-ed25519.dns
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure files are owned by the opendkim user and are private otherwise.
|
# Ensure files are owned by the dkimpy-milter user and are private otherwise.
|
||||||
chown -R opendkim:opendkim $STORAGE_ROOT/mail/dkim
|
chown -R dkimpy-milter:dkimpy-milter $STORAGE_ROOT/mail/dkim2
|
||||||
chmod go-rwx $STORAGE_ROOT/mail/dkim
|
chmod go-rwx $STORAGE_ROOT/mail/dkim2
|
||||||
|
|
||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
tools/editconf.py /etc/opendmarc.conf -s \
|
||||||
"Syslog=true" \
|
"Syslog=true" \
|
||||||
@ -94,23 +98,23 @@ tools/editconf.py /etc/opendmarc.conf -s \
|
|||||||
# domains does not cause the results header field to be added. This added header
|
# domains does not cause the results header field to be added. This added header
|
||||||
# is used by spamassassin to evaluate the mail for spamminess.
|
# is used by spamassassin to evaluate the mail for spamminess.
|
||||||
|
|
||||||
tools/editconf.py /etc/opendkim.conf -s \
|
tools/editconf.py /etc/dkimpy-milter/dkimpy-milter.conf -s \
|
||||||
"AlwaysAddARHeader=true"
|
"AlwaysAddARHeader=true"
|
||||||
|
|
||||||
# Add OpenDKIM and OpenDMARC as milters to postfix, which is how OpenDKIM
|
# Add DKIMpy and OpenDMARC as milters to postfix, which is how DKIMpy
|
||||||
# intercepts outgoing mail to perform the signing (by adding a mail header)
|
# intercepts outgoing mail to perform the signing (by adding a mail header)
|
||||||
# and how they both intercept incoming mail to add Authentication-Results
|
# and how they both intercept incoming mail to add Authentication-Results
|
||||||
# headers. The order possibly/probably matters: OpenDMARC relies on the
|
# headers. The order possibly/probably matters: OpenDMARC relies on the
|
||||||
# OpenDKIM Authentication-Results header already being present.
|
# DKIM Authentication-Results header already being present.
|
||||||
#
|
#
|
||||||
# Be careful. If we add other milters later, this needs to be concatenated
|
# Be careful. If we add other milters later, this needs to be concatenated
|
||||||
# on the smtpd_milters line.
|
# on the smtpd_milters line.
|
||||||
#
|
#
|
||||||
# The OpenDMARC milter is skipped in the SMTP submission listener by
|
# The OpenDMARC milter is skipped in the SMTP submission listener by
|
||||||
# configuring smtpd_milters there to only list the OpenDKIM milter
|
# configuring smtpd_milters there to only list the DKIMpy milter
|
||||||
# (see mail-postfix.sh).
|
# (see mail-postfix.sh).
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
tools/editconf.py /etc/postfix/main.cf \
|
||||||
"smtpd_milters=inet:127.0.0.1:8891 inet:127.0.0.1:8893"\
|
"smtpd_milters=inet:127.0.0.1:8892 inet:127.0.0.1:8893"\
|
||||||
non_smtpd_milters=\$smtpd_milters \
|
non_smtpd_milters=\$smtpd_milters \
|
||||||
milter_default_action=accept
|
milter_default_action=accept
|
||||||
|
|
||||||
@ -118,7 +122,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
hide_output systemctl enable opendmarc
|
hide_output systemctl enable opendmarc
|
||||||
|
|
||||||
# Restart services.
|
# Restart services.
|
||||||
restart_service opendkim
|
restart_service dkimpy-milter
|
||||||
restart_service opendmarc
|
restart_service opendmarc
|
||||||
restart_service postfix
|
restart_service postfix
|
||||||
|
|
||||||
|
@ -91,12 +91,14 @@ tools/editconf.py /etc/postfix/master.cf -s -w \
|
|||||||
-o smtpd_tls_wrappermode=yes
|
-o smtpd_tls_wrappermode=yes
|
||||||
-o smtpd_sasl_auth_enable=yes
|
-o smtpd_sasl_auth_enable=yes
|
||||||
-o syslog_name=postfix/submission
|
-o syslog_name=postfix/submission
|
||||||
-o smtpd_milters=inet:127.0.0.1:8891
|
-o smtpd_milters=inet:127.0.0.1:8892
|
||||||
|
-o milter_macro_daemon_name=VERIFYING
|
||||||
-o cleanup_service_name=authclean" \
|
-o cleanup_service_name=authclean" \
|
||||||
"submission=inet n - - - - smtpd
|
"submission=inet n - - - - smtpd
|
||||||
-o smtpd_sasl_auth_enable=yes
|
-o smtpd_sasl_auth_enable=yes
|
||||||
-o syslog_name=postfix/submission
|
-o syslog_name=postfix/submission
|
||||||
-o smtpd_milters=inet:127.0.0.1:8891
|
-o smtpd_milters=inet:127.0.0.1:8892
|
||||||
|
-o milter_macro_daemon_name=ORIGINATING
|
||||||
-o smtpd_tls_security_level=encrypt
|
-o smtpd_tls_security_level=encrypt
|
||||||
-o cleanup_service_name=authclean" \
|
-o cleanup_service_name=authclean" \
|
||||||
"authclean=unix n - - - 0 cleanup
|
"authclean=unix n - - - 0 cleanup
|
||||||
|
Loading…
Reference in New Issue
Block a user