Benedek Nagy 69a18565ea
email: Fix DKIM signing by setting correct ownership on private keys
See the spam score for an email sent with these settings:
https://www.mail-tester.com/test-jy6unbdzu

Tests done:

1. Install the email app with version 4, then check out the files:

```
ls -la /var/lib/rspamd/dkim/ ; cat /etc/rspamd/local.d/dkim_signing.conf
total 4
dr-x------ 1 _rspamd _rspamd    50 Jan  1 19:14 .
drwxr-x--- 1 _rspamd _rspamd 16936 Jan  1 20:21 ..
-r-------- 1 root    root     1704 Jan  1 19:14 freedombox.local.dkim.key
cat: /etc/rspamd/local.d/dkim_signing.conf: No such file or directory
```

2. Apply the patches and update the app:

```
ls -la /var/lib/rspamd/dkim/ ; cat /etc/rspamd/local.d/dkim_signing.conf
total 4
dr-x------ 1 _rspamd _rspamd    50 Jan  1 19:14 .
drwxr-x--- 1 _rspamd _rspamd 16936 Jan  1 20:22 ..
-r-------- 1 _rspamd _rspamd  1704 Jan  1 19:14 freedombox.local.dkim.key
allow_username_mismatch = true;
sign_authenticated = true;
use_domain = "header";
domain {
    freedombox.local {
        path = "/var/lib/rspamd/dkim/freedombox.local.dkim.key";
        selector = "dkim";
    }
}
```

3. Configure example.com as a domain under Name Services, then also change the primary domain in the email app and confirm it completes without errors.
Also see:

```
ls -la /var/lib/rspamd/dkim/ ; cat /etc/rspamd/local.d/dkim_signing.conf
total 8
dr-x------ 1 _rspamd _rspamd    90 Jan  1 21:15 .
drwxr-x--- 1 _rspamd _rspamd 16936 Jan  1 21:17 ..
-r-------- 1 _rspamd _rspamd  1704 Jan  1 21:15 example.com.dkim.key
-r-------- 1 _rspamd _rspamd  1704 Jan  1 19:14 freedombox.local.dkim.key
allow_username_mismatch = true;
sign_authenticated = true;
use_domain = "header";
domain {
    example.com {
        path = "/var/lib/rspamd/dkim/example.com.dkim.key";
        selector = "dkim";
    }
}
```

4. Uninstall the app and perform a fresh install, confirm it completes succesfully.

The configurations in /etc/rspamd/local.d/dkim_signing.conf have been
verified to work on a throw-away VPS setup.
Once merged, this should be mentioned here: https://discuss.freedombox.org/t/solved-email-messages-not-signed-with-dkim/2387

Signed-off-by: Benedek Nagy <contact@nbenedek.me>
[sunil: Add comment explaining allow_username_mismatch option]
[sunil: Drop an unused variable, added docstrings]
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
2025-01-02 15:40:14 -08:00

71 lines
2.1 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""Generate DKIM keys for signing outgoing messages.
See: https://rspamd.com/doc/modules/dkim_signing.html
"""
import pathlib
import re
import shutil
import subprocess
from plinth.actions import privileged
from plinth.privileged import service as service_privileged
_keys_dir = pathlib.Path('/var/lib/rspamd/dkim/')
rspamd_user = '_rspamd'
DOMAIN_PART_REGEX = r'^[a-zA-Z0-9]([-a-zA-Z0-9]{,61}[a-zA-Z0-9])?$'
def _validate_domain_name(domain):
for part in domain.split('.'):
if not re.match(DOMAIN_PART_REGEX, part):
raise ValueError('Invalid domain name')
@privileged
def get_dkim_public_key(domain: str) -> str:
"""Privileged action to get the public key from DKIM key."""
_validate_domain_name(domain)
key_file = _keys_dir / f'{domain}.dkim.key'
output = subprocess.check_output(
['openssl', 'rsa', '-in',
str(key_file), '-pubout'], stderr=subprocess.DEVNULL)
return ''.join(output.decode().splitlines()[1:-1])
@privileged
def setup_dkim(domain: str):
"""Create DKIM key for a given domain."""
_validate_domain_name(domain)
_keys_dir.mkdir(exist_ok=True)
_keys_dir.chmod(0o500)
shutil.chown(_keys_dir, rspamd_user, rspamd_user)
# Default path is /var/lib/dkim/$domain.$selector.key. Default selector is
# "dkim". Use these to simplify key management until we have a need to
# implement creating new or multiple keys.
key_file = _keys_dir / f'{domain}.dkim.key'
if key_file.exists():
return
# Ed25519 is widely *not* accepted as of 2022-01. See:
# https://serverfault.com/questions/1023674
subprocess.run([
'rspamadm', 'dkim_keygen', '-t', 'rsa', '-b', '2048', '-s', 'dkim',
'-d', domain, '-k', (str(key_file))
], check=True)
shutil.chown(key_file, rspamd_user, rspamd_user)
key_file.chmod(0o400)
service_privileged.try_restart('rspamd')
@privileged
def fix_incorrect_key_ownership():
"""Set the ownership on DKIM private keys."""
for key in _keys_dir.glob('*.dkim.key'):
shutil.chown(key, rspamd_user, rspamd_user)