mirror of
https://github.com/chatmail/relay.git
synced 2026-05-14 09:54:38 +00:00
Compare commits
2 Commits
fix/multip
...
j-g00da/dk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40c93ffe52 | ||
|
|
1726ee7c67 |
@@ -80,7 +80,7 @@ jobs:
|
|||||||
|
|
||||||
- name: set DNS entries
|
- name: set DNS entries
|
||||||
run: |
|
run: |
|
||||||
ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org chown opendkim:opendkim -R /etc/dkimkeys
|
ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org chown dkim-milter:dkim-milter -R /etc/dkimkeys
|
||||||
cmdeploy dns --zonefile staging-generated.zone
|
cmdeploy dns --zonefile staging-generated.zone
|
||||||
cat staging-generated.zone >> .github/workflows/staging-ipv4.testrun.org-default.zone
|
cat staging-generated.zone >> .github/workflows/staging-ipv4.testrun.org-default.zone
|
||||||
cat .github/workflows/staging-ipv4.testrun.org-default.zone
|
cat .github/workflows/staging-ipv4.testrun.org-default.zone
|
||||||
|
|||||||
2
.github/workflows/test-and-deploy.yaml
vendored
2
.github/workflows/test-and-deploy.yaml
vendored
@@ -82,7 +82,7 @@ jobs:
|
|||||||
|
|
||||||
- name: set DNS entries
|
- name: set DNS entries
|
||||||
run: |
|
run: |
|
||||||
ssh -o StrictHostKeyChecking=accept-new root@staging2.testrun.org chown opendkim:opendkim -R /etc/dkimkeys
|
ssh -o StrictHostKeyChecking=accept-new root@staging2.testrun.org chown dkim-milter:dkim-milter -R /etc/dkimkeys
|
||||||
cmdeploy dns --zonefile staging-generated.zone --verbose
|
cmdeploy dns --zonefile staging-generated.zone --verbose
|
||||||
cat staging-generated.zone >> .github/workflows/staging.testrun.org-default.zone
|
cat staging-generated.zone >> .github/workflows/staging.testrun.org-default.zone
|
||||||
cat .github/workflows/staging.testrun.org-default.zone
|
cat .github/workflows/staging.testrun.org-default.zone
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ from .basedeploy import (
|
|||||||
configure_remote_units,
|
configure_remote_units,
|
||||||
get_resource,
|
get_resource,
|
||||||
)
|
)
|
||||||
|
from .dkim_milter.deployer import DkimMilterDeployer
|
||||||
from .dovecot.deployer import DovecotDeployer
|
from .dovecot.deployer import DovecotDeployer
|
||||||
from .filtermail.deployer import FiltermailDeployer
|
from .filtermail.deployer import FiltermailDeployer
|
||||||
from .mtail.deployer import MtailDeployer
|
from .mtail.deployer import MtailDeployer
|
||||||
from .nginx.deployer import NginxDeployer
|
from .nginx.deployer import NginxDeployer
|
||||||
from .opendkim.deployer import OpendkimDeployer
|
|
||||||
from .postfix.deployer import PostfixDeployer
|
from .postfix.deployer import PostfixDeployer
|
||||||
from .www import build_webpages, find_merge_conflict, get_paths
|
from .www import build_webpages, find_merge_conflict, get_paths
|
||||||
|
|
||||||
@@ -572,7 +572,7 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
|
|||||||
WebsiteDeployer(config),
|
WebsiteDeployer(config),
|
||||||
ChatmailVenvDeployer(config),
|
ChatmailVenvDeployer(config),
|
||||||
MtastsDeployer(),
|
MtastsDeployer(),
|
||||||
OpendkimDeployer(mail_domain),
|
DkimMilterDeployer(mail_domain),
|
||||||
# Dovecot should be started before Postfix
|
# Dovecot should be started before Postfix
|
||||||
# because it creates authentication socket
|
# because it creates authentication socket
|
||||||
# required by Postfix.
|
# required by Postfix.
|
||||||
|
|||||||
169
cmdeploy/src/cmdeploy/dkim_milter/deployer.py
Normal file
169
cmdeploy/src/cmdeploy/dkim_milter/deployer.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
"""
|
||||||
|
Installs DKIM Milter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyinfra import facts, host
|
||||||
|
from pyinfra.facts.files import File, Sha256File
|
||||||
|
from pyinfra.operations import apt, files, server, systemd
|
||||||
|
|
||||||
|
from cmdeploy.basedeploy import Deployer, get_resource
|
||||||
|
|
||||||
|
|
||||||
|
class DkimMilterDeployer(Deployer):
|
||||||
|
required_users = [("dkim-milter", None, ["dkim-milter"])]
|
||||||
|
|
||||||
|
def __init__(self, mail_domain):
|
||||||
|
self.mail_domain = mail_domain
|
||||||
|
self.need_restart = False
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
"""Builds and installs dkim-milter"""
|
||||||
|
|
||||||
|
# openssl is required to generate the signing key
|
||||||
|
apt.packages(
|
||||||
|
name="Install openssl required by DKIM Milter",
|
||||||
|
packages=["openssl"],
|
||||||
|
)
|
||||||
|
|
||||||
|
(url, sha256sum) = {
|
||||||
|
"x86_64": (
|
||||||
|
"https://github.com/chatmail/dkim-milter/releases/download/0.1.0/dkim-milter-x86_64",
|
||||||
|
"e676837b362ebef461881079e3e1151ed2db2d942d98b7103974921ac69ce5de",
|
||||||
|
),
|
||||||
|
"aarch64": (
|
||||||
|
"https://github.com/chatmail/dkim-milter/releases/download/0.1.0/dkim-milter-aarch64",
|
||||||
|
"b853ab85a535b7e7e548ae0e4d85a61d4c0fd44f2912c3439662c56ca8a369e6",
|
||||||
|
),
|
||||||
|
}[host.get_fact(facts.server.Arch)]
|
||||||
|
|
||||||
|
existing_sha256sum = host.get_fact(Sha256File, "/usr/local/sbin/dkim-milter")
|
||||||
|
if existing_sha256sum != sha256sum:
|
||||||
|
server.shell(
|
||||||
|
name="Download DKIM Milter",
|
||||||
|
commands=[
|
||||||
|
f"(curl -L {url} >/usr/local/sbin/dkim-milter.new && (echo '{sha256sum} /usr/local/sbin/dkim-milter.new' | sha256sum -c) && mv /usr/local/sbin/dkim-milter.new /usr/local/sbin/dkim-milter)",
|
||||||
|
"chmod 755 /usr/local/sbin/dkim-milter",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.need_restart = True
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
"""Configures dkim-milter"""
|
||||||
|
|
||||||
|
domain = self.mail_domain
|
||||||
|
# note - we are using "opendkim" for backward compatibility
|
||||||
|
# for relays that were set up before we migrated from OpenDKIM
|
||||||
|
# to DKIM Milter.
|
||||||
|
selector = "opendkim"
|
||||||
|
signing_key_name = selector
|
||||||
|
# for backward compatibility with opendkim-genkey
|
||||||
|
signing_key_filename = f"{signing_key_name}.private"
|
||||||
|
config_common = {
|
||||||
|
"domain": domain,
|
||||||
|
"selector": selector,
|
||||||
|
"signing_key_name": signing_key_name,
|
||||||
|
"signing_key_filename": signing_key_filename,
|
||||||
|
}
|
||||||
|
config_verify = {
|
||||||
|
**config_common,
|
||||||
|
"mode": "verify",
|
||||||
|
"config_file": "/etc/dkim-milter/dkim-milter-verify.conf",
|
||||||
|
"socket_name": "dkim-milter-verify.sock",
|
||||||
|
}
|
||||||
|
config_sign = {
|
||||||
|
**config_common,
|
||||||
|
"mode": "sign",
|
||||||
|
"config_file": "/etc/dkim-milter/dkim-milter-sign.conf",
|
||||||
|
"socket_name": "dkim-milter-sign.sock",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.need_restart |= files.directory(
|
||||||
|
name="Create a directory for DKIM Milter configs",
|
||||||
|
path="/etc/dkim-milter",
|
||||||
|
user="dkim-milter",
|
||||||
|
group="dkim-milter",
|
||||||
|
mode="750",
|
||||||
|
present=True,
|
||||||
|
).changed
|
||||||
|
|
||||||
|
for config in [config_verify, config_sign]:
|
||||||
|
self.need_restart |= files.template(
|
||||||
|
src=get_resource("dkim_milter/dkim-milter.conf.j2"),
|
||||||
|
dest=config["config_file"],
|
||||||
|
user="dkim-milter",
|
||||||
|
group="dkim-milter",
|
||||||
|
mode="644",
|
||||||
|
config=config,
|
||||||
|
).changed
|
||||||
|
|
||||||
|
self.need_restart |= files.directory(
|
||||||
|
name="Create dkimkeys directory",
|
||||||
|
path="/etc/dkimkeys",
|
||||||
|
user="dkim-milter",
|
||||||
|
group="dkim-milter",
|
||||||
|
mode="750",
|
||||||
|
present=True,
|
||||||
|
).changed
|
||||||
|
|
||||||
|
self.need_restart |= files.template(
|
||||||
|
src=get_resource("dkim_milter/signing-keys"),
|
||||||
|
dest="/etc/dkim-milter/signing-keys",
|
||||||
|
user="dkim-milter",
|
||||||
|
group="dkim-milter",
|
||||||
|
mode="644",
|
||||||
|
config=config_common,
|
||||||
|
).changed
|
||||||
|
|
||||||
|
self.need_restart |= files.template(
|
||||||
|
src=get_resource("dkim_milter/signing-senders"),
|
||||||
|
dest="/etc/dkim-milter/signing-senders",
|
||||||
|
user="dkim-milter",
|
||||||
|
group="dkim-milter",
|
||||||
|
mode="644",
|
||||||
|
config=config_common,
|
||||||
|
).changed
|
||||||
|
|
||||||
|
self.need_restart |= files.directory(
|
||||||
|
name="Create DKIM Milter unix sockets directory",
|
||||||
|
path="/var/spool/postfix/dkim-milter",
|
||||||
|
user="dkim-milter",
|
||||||
|
group="dkim-milter",
|
||||||
|
mode="770",
|
||||||
|
).changed
|
||||||
|
|
||||||
|
if not host.get_fact(File, f"/etc/dkimkeys/{signing_key_filename}"):
|
||||||
|
server.shell(
|
||||||
|
name=f"Generate DKIM Milter signing key '{signing_key_name}'",
|
||||||
|
commands=[
|
||||||
|
f"openssl genpkey -algorithm RSA -out /etc/dkimkeys/{signing_key_filename}"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.need_restart = True
|
||||||
|
|
||||||
|
# enforce restrictive permissions for the signing key
|
||||||
|
self.need_restart |= files.file(
|
||||||
|
path=f"/etc/dkimkeys/{signing_key_filename}",
|
||||||
|
present=True,
|
||||||
|
user="dkim-milter",
|
||||||
|
group="dkim-milter",
|
||||||
|
mode="0400",
|
||||||
|
).changed
|
||||||
|
|
||||||
|
self.need_restart |= files.put(
|
||||||
|
name="Create dkim-milter service",
|
||||||
|
src=get_resource("dkim_milter/dkim-milter@.service"),
|
||||||
|
dest=f"/etc/systemd/system/dkim-milter@.service",
|
||||||
|
).changed
|
||||||
|
|
||||||
|
def activate(self):
|
||||||
|
"""Start and enable DKIM Milter"""
|
||||||
|
for mode in ["sign", "verify"]:
|
||||||
|
systemd.service(
|
||||||
|
name=f"Start and enable DKIM Milter in {mode} mode",
|
||||||
|
service=f"dkim-milter@{mode}",
|
||||||
|
running=True,
|
||||||
|
enabled=True,
|
||||||
|
daemon_reload=self.need_restart,
|
||||||
|
restarted=self.need_restart,
|
||||||
|
)
|
||||||
|
self.need_restart = False
|
||||||
30
cmdeploy/src/cmdeploy/dkim_milter/dkim-milter.conf.j2
Normal file
30
cmdeploy/src/cmdeploy/dkim_milter/dkim-milter.conf.j2
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
mode = {{ config.mode }}
|
||||||
|
|
||||||
|
{% if config.mode == "verify" %}
|
||||||
|
# DKIM milter will skip verification for trusted sources,
|
||||||
|
# which in our case is everything, since we run DKIM milter on a reinjection port,
|
||||||
|
# and all connections are local.
|
||||||
|
# We force verification for local connections by not trusting anyone.
|
||||||
|
trusted_networks =
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
log_destination = syslog
|
||||||
|
log_level = info
|
||||||
|
|
||||||
|
canonicalization = relaxed/simple
|
||||||
|
|
||||||
|
lookup_timeout = 60s
|
||||||
|
|
||||||
|
signing_keys = /etc/dkim-milter/signing-keys
|
||||||
|
signing_senders = /etc/dkim-milter/signing-senders
|
||||||
|
|
||||||
|
# Signing
|
||||||
|
sign_headers = default; autocrypt:content-type
|
||||||
|
oversign_headers = signed-extended
|
||||||
|
|
||||||
|
# Verification
|
||||||
|
required_signed_headers = From*
|
||||||
|
forbid_unsigned_content = yes
|
||||||
|
reject_failures = missing, no-pass, author-mismatch
|
||||||
|
|
||||||
|
socket = unix:/var/spool/postfix/dkim-milter/{{ config.socket_name }}
|
||||||
15
cmdeploy/src/cmdeploy/dkim_milter/dkim-milter@.service
Normal file
15
cmdeploy/src/cmdeploy/dkim_milter/dkim-milter@.service
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=DKIM Milter %i
|
||||||
|
Documentation=man:dkim-milter(8) man:dkim-milter.conf(5)
|
||||||
|
After=network-online.target nss-lookup.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=dkim-milter
|
||||||
|
UMask=007
|
||||||
|
ExecStart=/usr/local/sbin/dkim-milter -c /etc/dkim-milter/dkim-milter-%i.conf
|
||||||
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
2
cmdeploy/src/cmdeploy/dkim_milter/signing-keys
Normal file
2
cmdeploy/src/cmdeploy/dkim_milter/signing-keys
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Key name Signing key
|
||||||
|
{{ config.signing_key_name }} </etc/dkimkeys/{{ config.signing_key_filename }}
|
||||||
2
cmdeploy/src/cmdeploy/dkim_milter/signing-senders
Normal file
2
cmdeploy/src/cmdeploy/dkim_milter/signing-senders
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Sender expression Domain Selector Key name
|
||||||
|
.{{ config.domain }} {{ config.domain }} {{ config.selector }} {{ config.signing_key_name }}
|
||||||
@@ -1 +0,0 @@
|
|||||||
{{ config.opendkim_selector }}._domainkey.{{ config.domain_name }} {{ config.domain_name }}:{{ config.opendkim_selector }}:/etc/dkimkeys/{{ config.opendkim_selector }}.private
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
*@{{ config.domain_name }} {{ config.opendkim_selector }}._domainkey.{{ config.domain_name }}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
"""
|
|
||||||
Installs OpenDKIM
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pyinfra import host
|
|
||||||
from pyinfra.facts.files import File
|
|
||||||
from pyinfra.operations import apt, files, server, systemd
|
|
||||||
|
|
||||||
from cmdeploy.basedeploy import Deployer, get_resource
|
|
||||||
|
|
||||||
|
|
||||||
class OpendkimDeployer(Deployer):
|
|
||||||
required_users = [("opendkim", None, ["opendkim"])]
|
|
||||||
|
|
||||||
def __init__(self, mail_domain):
|
|
||||||
self.mail_domain = mail_domain
|
|
||||||
|
|
||||||
def install(self):
|
|
||||||
apt.packages(
|
|
||||||
name="apt install opendkim opendkim-tools",
|
|
||||||
packages=["opendkim", "opendkim-tools"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def configure(self):
|
|
||||||
domain = self.mail_domain
|
|
||||||
dkim_selector = "opendkim"
|
|
||||||
"""Configures OpenDKIM"""
|
|
||||||
need_restart = False
|
|
||||||
|
|
||||||
main_config = files.template(
|
|
||||||
src=get_resource("opendkim/opendkim.conf"),
|
|
||||||
dest="/etc/opendkim.conf",
|
|
||||||
user="root",
|
|
||||||
group="root",
|
|
||||||
mode="644",
|
|
||||||
config={"domain_name": domain, "opendkim_selector": dkim_selector},
|
|
||||||
)
|
|
||||||
need_restart |= main_config.changed
|
|
||||||
|
|
||||||
screen_script = files.put(
|
|
||||||
src=get_resource("opendkim/screen.lua"),
|
|
||||||
dest="/etc/opendkim/screen.lua",
|
|
||||||
user="root",
|
|
||||||
group="root",
|
|
||||||
mode="644",
|
|
||||||
)
|
|
||||||
need_restart |= screen_script.changed
|
|
||||||
|
|
||||||
final_script = files.put(
|
|
||||||
src=get_resource("opendkim/final.lua"),
|
|
||||||
dest="/etc/opendkim/final.lua",
|
|
||||||
user="root",
|
|
||||||
group="root",
|
|
||||||
mode="644",
|
|
||||||
)
|
|
||||||
need_restart |= final_script.changed
|
|
||||||
|
|
||||||
files.directory(
|
|
||||||
name="Add opendkim directory to /etc",
|
|
||||||
path="/etc/opendkim",
|
|
||||||
user="opendkim",
|
|
||||||
group="opendkim",
|
|
||||||
mode="750",
|
|
||||||
present=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
keytable = files.template(
|
|
||||||
src=get_resource("opendkim/KeyTable"),
|
|
||||||
dest="/etc/dkimkeys/KeyTable",
|
|
||||||
user="opendkim",
|
|
||||||
group="opendkim",
|
|
||||||
mode="644",
|
|
||||||
config={"domain_name": domain, "opendkim_selector": dkim_selector},
|
|
||||||
)
|
|
||||||
need_restart |= keytable.changed
|
|
||||||
|
|
||||||
signing_table = files.template(
|
|
||||||
src=get_resource("opendkim/SigningTable"),
|
|
||||||
dest="/etc/dkimkeys/SigningTable",
|
|
||||||
user="opendkim",
|
|
||||||
group="opendkim",
|
|
||||||
mode="644",
|
|
||||||
config={"domain_name": domain, "opendkim_selector": dkim_selector},
|
|
||||||
)
|
|
||||||
need_restart |= signing_table.changed
|
|
||||||
files.directory(
|
|
||||||
name="Add opendkim socket directory to /var/spool/postfix",
|
|
||||||
path="/var/spool/postfix/opendkim",
|
|
||||||
user="opendkim",
|
|
||||||
group="opendkim",
|
|
||||||
mode="750",
|
|
||||||
present=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not host.get_fact(File, f"/etc/dkimkeys/{dkim_selector}.private"):
|
|
||||||
server.shell(
|
|
||||||
name="Generate OpenDKIM domain keys",
|
|
||||||
commands=[
|
|
||||||
f"/usr/sbin/opendkim-genkey -D /etc/dkimkeys -d {domain} -s {dkim_selector}"
|
|
||||||
],
|
|
||||||
_use_su_login=True,
|
|
||||||
_su_user="opendkim",
|
|
||||||
)
|
|
||||||
|
|
||||||
service_file = files.put(
|
|
||||||
name="Configure opendkim to restart once a day",
|
|
||||||
src=get_resource("opendkim/systemd.conf"),
|
|
||||||
dest="/etc/systemd/system/opendkim.service.d/10-prevent-memory-leak.conf",
|
|
||||||
)
|
|
||||||
need_restart |= service_file.changed
|
|
||||||
|
|
||||||
self.need_restart = need_restart
|
|
||||||
|
|
||||||
def activate(self):
|
|
||||||
systemd.service(
|
|
||||||
name="Start and enable OpenDKIM",
|
|
||||||
service="opendkim.service",
|
|
||||||
running=True,
|
|
||||||
enabled=True,
|
|
||||||
daemon_reload=self.need_restart,
|
|
||||||
restarted=self.need_restart,
|
|
||||||
)
|
|
||||||
self.need_restart = False
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
mtaname = odkim.get_mtasymbol(ctx, "{daemon_name}")
|
|
||||||
if mtaname == "ORIGINATING" then
|
|
||||||
-- Outgoing message will be signed,
|
|
||||||
-- no need to look for signatures.
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
nsigs = odkim.get_sigcount(ctx)
|
|
||||||
if nsigs == nil then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local valid = false
|
|
||||||
local error_msg = "No valid DKIM signature found."
|
|
||||||
for i = 1, nsigs do
|
|
||||||
sig = odkim.get_sighandle(ctx, i - 1)
|
|
||||||
sigres = odkim.sig_result(sig)
|
|
||||||
|
|
||||||
-- All signatures that do not correspond to From:
|
|
||||||
-- were ignored in screen.lua and return sigres -1.
|
|
||||||
--
|
|
||||||
-- Any valid signature that was not ignored like this
|
|
||||||
-- means the message is acceptable.
|
|
||||||
if sigres == 0 then
|
|
||||||
valid = true
|
|
||||||
else
|
|
||||||
error_msg = "DKIM signature is invalid, error code " .. tostring(sigres) .. ", search https://github.com/trusteddomainproject/OpenDKIM/blob/master/libopendkim/dkim.h#L108"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if valid then
|
|
||||||
-- Strip all DKIM-Signature headers after successful validation
|
|
||||||
-- Delete in reverse order to avoid index shifting.
|
|
||||||
for i = nsigs, 1, -1 do
|
|
||||||
odkim.del_header(ctx, "DKIM-Signature", i)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
odkim.set_reply(ctx, "554", "5.7.1", error_msg)
|
|
||||||
odkim.set_result(ctx, SMFIS_REJECT)
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# OpenDKIM configuration.
|
|
||||||
|
|
||||||
Syslog yes
|
|
||||||
SyslogSuccess yes
|
|
||||||
#LogWhy no
|
|
||||||
|
|
||||||
# Common signing and verification parameters. In Debian, the "From" header is
|
|
||||||
# oversigned, because it is often the identity key used by reputation systems
|
|
||||||
# and thus somewhat security sensitive.
|
|
||||||
Canonicalization relaxed/simple
|
|
||||||
OversignHeaders From
|
|
||||||
|
|
||||||
On-BadSignature reject
|
|
||||||
On-KeyNotFound reject
|
|
||||||
On-NoSignature reject
|
|
||||||
DNSTimeout 60
|
|
||||||
|
|
||||||
# Signing domain, selector, and key (required). For example, perform signing
|
|
||||||
# for domain "example.com" with selector "2020" (2020._domainkey.example.com),
|
|
||||||
# using the private key stored in /etc/dkimkeys/example.private. More granular
|
|
||||||
# setup options can be found in /usr/share/doc/opendkim/README.opendkim.
|
|
||||||
Domain {{ config.domain_name }}
|
|
||||||
Selector {{ config.opendkim_selector }}
|
|
||||||
KeyFile /etc/dkimkeys/{{ config.opendkim_selector }}.private
|
|
||||||
KeyTable /etc/dkimkeys/KeyTable
|
|
||||||
SigningTable refile:/etc/dkimkeys/SigningTable
|
|
||||||
|
|
||||||
# Sign Autocrypt header in addition to the default specified in RFC 6376.
|
|
||||||
#
|
|
||||||
# Default list is here:
|
|
||||||
# <https://github.com/trusteddomainproject/OpenDKIM/blob/5c539587561785a66c1f67f720f2fb741f320785/libopendkim/dkim.c#L221-L245>
|
|
||||||
SignHeaders *,+autocrypt,+content-type
|
|
||||||
|
|
||||||
# Prevent addition of second Content-Type header
|
|
||||||
# and other important headers that should not be added
|
|
||||||
# after signing the message.
|
|
||||||
# See
|
|
||||||
# <https://www.zone.eu/blog/2024/05/17/bimi-and-dmarc-cant-save-you/>
|
|
||||||
# and RFC 6376 (page 41) for reference.
|
|
||||||
#
|
|
||||||
# We don't use "l=" body length so the problem described in RFC 6376
|
|
||||||
# is not applicable, but adding e.g. a second "From" header
|
|
||||||
# or second "Autocrypt" header is better prevented in any case.
|
|
||||||
#
|
|
||||||
# Default is empty.
|
|
||||||
OversignHeaders from,reply-to,subject,date,to,cc,resent-date,resent-from,resent-sender,resent-to,resent-cc,in-reply-to,references,list-id,list-help,list-unsubscribe,list-subscribe,list-post,list-owner,list-archive,autocrypt
|
|
||||||
|
|
||||||
# Script to ignore signatures that do not correspond to the From: domain.
|
|
||||||
ScreenPolicyScript /etc/opendkim/screen.lua
|
|
||||||
|
|
||||||
# Script to reject mails without a valid DKIM signature.
|
|
||||||
FinalPolicyScript /etc/opendkim/final.lua
|
|
||||||
|
|
||||||
# In Debian, opendkim runs as user "opendkim". A umask of 007 is required when
|
|
||||||
# using a local socket with MTAs that access the socket as a non-privileged
|
|
||||||
# user (for example, Postfix). You may need to add user "postfix" to group
|
|
||||||
# "opendkim" in that case.
|
|
||||||
UserID opendkim
|
|
||||||
UMask 007
|
|
||||||
|
|
||||||
Socket local:/var/spool/postfix/opendkim/opendkim.sock
|
|
||||||
|
|
||||||
PidFile /run/opendkim/opendkim.pid
|
|
||||||
|
|
||||||
# The trust anchor enables DNSSEC. In Debian, the trust anchor file is provided
|
|
||||||
# by the package dns-root-data.
|
|
||||||
TrustAnchorFile /usr/share/dns/root.key
|
|
||||||
|
|
||||||
# Sign messages when `-o milter_macro_daemon_name=ORIGINATING` is set.
|
|
||||||
MTA ORIGINATING
|
|
||||||
|
|
||||||
# No hosts are treated as internal, ORIGINATING daemon name should be set explicitly.
|
|
||||||
InternalHosts -
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
-- Ignore signatures that do not correspond to the From: domain.
|
|
||||||
|
|
||||||
from_domain = odkim.get_fromdomain(ctx)
|
|
||||||
if from_domain == nil then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
n = odkim.get_sigcount(ctx)
|
|
||||||
if n == nil then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, n do
|
|
||||||
sig = odkim.get_sighandle(ctx, i - 1)
|
|
||||||
sig_domain = odkim.sig_getdomain(sig)
|
|
||||||
if from_domain ~= sig_domain then
|
|
||||||
odkim.sig_ignore(sig)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[Service]
|
|
||||||
Restart=always
|
|
||||||
RuntimeMaxSec=1d
|
|
||||||
@@ -4,7 +4,7 @@ from cmdeploy.basedeploy import Deployer, get_resource
|
|||||||
|
|
||||||
|
|
||||||
class PostfixDeployer(Deployer):
|
class PostfixDeployer(Deployer):
|
||||||
required_users = [("postfix", None, ["opendkim"])]
|
required_users = [("postfix", None, ["dkim-milter"])]
|
||||||
daemon_reload = False
|
daemon_reload = False
|
||||||
|
|
||||||
def __init__(self, config, disable_mail):
|
def __init__(self, config, disable_mail):
|
||||||
|
|||||||
@@ -80,13 +80,13 @@ filter unix - n n - - lmtp
|
|||||||
127.0.0.1:{{ config.postfix_reinject_port }} inet n - n - 100 smtpd
|
127.0.0.1:{{ config.postfix_reinject_port }} inet n - n - 100 smtpd
|
||||||
-o syslog_name=postfix/reinject
|
-o syslog_name=postfix/reinject
|
||||||
-o milter_macro_daemon_name=ORIGINATING
|
-o milter_macro_daemon_name=ORIGINATING
|
||||||
-o smtpd_milters=unix:opendkim/opendkim.sock
|
-o smtpd_milters=unix:dkim-milter/dkim-milter-sign.sock
|
||||||
-o cleanup_service_name=authclean
|
-o cleanup_service_name=authclean
|
||||||
|
|
||||||
# Local SMTP server for reinjecting incoming filtered mail
|
# Local SMTP server for reinjecting incoming filtered mail
|
||||||
127.0.0.1:{{ config.postfix_reinject_port_incoming }} inet n - n - 100 smtpd
|
127.0.0.1:{{ config.postfix_reinject_port_incoming }} inet n - n - 100 smtpd
|
||||||
-o syslog_name=postfix/reinject_incoming
|
-o syslog_name=postfix/reinject_incoming
|
||||||
-o smtpd_milters=unix:opendkim/opendkim.sock
|
-o smtpd_milters=unix:dkim-milter/dkim-milter-verify.sock
|
||||||
|
|
||||||
# Cleanup `Received` headers for authenticated mail
|
# Cleanup `Received` headers for authenticated mail
|
||||||
# to avoid leaking client IP.
|
# to avoid leaking client IP.
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import datetime
|
|
||||||
import smtplib
|
import smtplib
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -58,15 +57,6 @@ class TestSSHExecutor:
|
|||||||
else:
|
else:
|
||||||
pytest.fail("didn't raise exception")
|
pytest.fail("didn't raise exception")
|
||||||
|
|
||||||
def test_opendkim_restarted(self, sshexec):
|
|
||||||
"""check that opendkim is not running for longer than a day."""
|
|
||||||
cmd = "systemctl show opendkim --timestamp=utc --property=ActiveEnterTimestamp"
|
|
||||||
out = sshexec(call=remote.rshell.shell, kwargs=dict(command=cmd))
|
|
||||||
datestring = out.split("=")[1]
|
|
||||||
since_date = datetime.datetime.strptime(datestring, "%a %Y-%m-%d %H:%M:%S %Z")
|
|
||||||
now = datetime.datetime.now(since_date.tzinfo)
|
|
||||||
assert (now - since_date).total_seconds() < 60 * 60 * 51
|
|
||||||
|
|
||||||
|
|
||||||
def test_timezone_env(remote):
|
def test_timezone_env(remote):
|
||||||
for line in remote.iter_output("env"):
|
for line in remote.iter_output("env"):
|
||||||
@@ -146,7 +136,7 @@ def test_reject_missing_dkim(cmsetup, maildata, from_addr):
|
|||||||
conn.starttls()
|
conn.starttls()
|
||||||
|
|
||||||
with conn as s:
|
with conn as s:
|
||||||
with pytest.raises(smtplib.SMTPDataError, match="No valid DKIM signature"):
|
with pytest.raises(smtplib.SMTPDataError, match="No DKIM signature found"):
|
||||||
s.sendmail(from_addr=from_addr, to_addrs=recipient.addr, msg=msg)
|
s.sendmail(from_addr=from_addr, to_addrs=recipient.addr, msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def test_status_cmd(chatmail_config, capsys, request):
|
|||||||
"filtermail",
|
"filtermail",
|
||||||
"lastlogin",
|
"lastlogin",
|
||||||
"nginx",
|
"nginx",
|
||||||
"opendkim",
|
"dkim-milter",
|
||||||
"postfix@-",
|
"postfix@-",
|
||||||
"systemd-journald",
|
"systemd-journald",
|
||||||
"turnserver",
|
"turnserver",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ in this case, just run ``ssh-keygen -R "mail.example.org"`` as recommended.
|
|||||||
|
|
||||||
ssh root@$NEW_IP4
|
ssh root@$NEW_IP4
|
||||||
chown root: -R /var/lib/acme
|
chown root: -R /var/lib/acme
|
||||||
chown opendkim: -R /etc/dkimkeys
|
chown dkim-milter: -R /etc/dkimkeys
|
||||||
chown vmail: -R /home/vmail/mail
|
chown vmail: -R /home/vmail/mail
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ The deployed system components of a chatmail relay are:
|
|||||||
- `acmetool <https://hlandau.github.io/acmetool/>`_ manages TLS
|
- `acmetool <https://hlandau.github.io/acmetool/>`_ manages TLS
|
||||||
certificates for Dovecot, Postfix, and Nginx
|
certificates for Dovecot, Postfix, and Nginx
|
||||||
|
|
||||||
- `OpenDKIM <http://www.opendkim.org/>`_ for signing messages with
|
- `DKIM Milter <https://github.com/chatmail/dkim-milter>`_ for signing messages with
|
||||||
DKIM and rejecting inbound messages without DKIM
|
DKIM and rejecting inbound messages without DKIM
|
||||||
|
|
||||||
- `mtail <https://google.github.io/mtail/>`_ for collecting anonymized
|
- `mtail <https://google.github.io/mtail/>`_ for collecting anonymized
|
||||||
@@ -268,12 +268,10 @@ Chatmail relays enforce :rfc:`DKIM <6376>` to authenticate incoming emails.
|
|||||||
Incoming emails must have a valid DKIM signature with
|
Incoming emails must have a valid DKIM signature with
|
||||||
Signing Domain Identifier (SDID, ``d=`` parameter in the DKIM-Signature
|
Signing Domain Identifier (SDID, ``d=`` parameter in the DKIM-Signature
|
||||||
header) equal to the ``From:`` header domain. This property is checked
|
header) equal to the ``From:`` header domain. This property is checked
|
||||||
by OpenDKIM screen policy script before validating the signatures. This
|
by dkim-milter ``reject_failures = author-mismatch `` policy. This
|
||||||
corresponds to strict :rfc:`DMARC <7489>` alignment (``adkim=s``).
|
corresponds to strict :rfc:`DMARC <7489>` alignment (``adkim=s``).
|
||||||
If there is no valid DKIM signature on the incoming email, the
|
If there is no valid DKIM signature on the incoming email, the
|
||||||
sender receives a “5.7.1 No valid DKIM signature found” error.
|
sender receives a “5.7.1 No valid DKIM signature found” error.
|
||||||
After validating the DKIM signature,
|
|
||||||
the `final.lua` script strips all ``OpenDKIM:`` headers to reduce message size on disc.
|
|
||||||
|
|
||||||
Note that chatmail relays
|
Note that chatmail relays
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user