From 1af9a6b11473aecea8945ad49fb1151afc63346c Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 15 Feb 2022 15:48:54 -0800 Subject: [PATCH] email: dkim: Implement setting up DKIM signing keys Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/email/__init__.py | 2 +- plinth/modules/email/audit/__init__.py | 4 +- plinth/modules/email/audit/dkim.py | 62 ++++++++++++++++++++++++++ plinth/modules/email/audit/domain.py | 1 + 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 plinth/modules/email/audit/dkim.py diff --git a/plinth/modules/email/__init__.py b/plinth/modules/email/__init__.py index 298046d54..85c82c92f 100644 --- a/plinth/modules/email/__init__.py +++ b/plinth/modules/email/__init__.py @@ -79,7 +79,7 @@ class EmailApp(plinth.app.App): 'packages-email', [ 'postfix', 'postfix-ldap', 'postfix-sqlite', 'dovecot-pop3d', 'dovecot-imapd', 'dovecot-ldap', 'dovecot-lmtpd', - 'dovecot-managesieved', 'rspamd', 'redis-server' + 'dovecot-managesieved', 'rspamd', 'redis-server', 'openssl' ], conflicts=['exim4-base', 'exim4-config', 'exim4-daemon-light'], conflicts_action=Packages.ConflictsAction.IGNORE) self.add(packages) diff --git a/plinth/modules/email/audit/__init__.py b/plinth/modules/email/audit/__init__.py index bdd68a8fc..b9896482e 100644 --- a/plinth/modules/email/audit/__init__.py +++ b/plinth/modules/email/audit/__init__.py @@ -3,6 +3,6 @@ Provides diagnosis and repair of email server configuration issues """ -from . import aliases, domain, home, ldap, spam, tls +from . import aliases, dkim, domain, home, ldap, spam, tls -__all__ = ['aliases', 'domain', 'home', 'ldap', 'spam', 'tls'] +__all__ = ['aliases', 'domain', 'dkim', 'home', 'ldap', 'spam', 'tls'] diff --git a/plinth/modules/email/audit/dkim.py b/plinth/modules/email/audit/dkim.py new file mode 100644 index 000000000..711882f91 --- /dev/null +++ b/plinth/modules/email/audit/dkim.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Generate DKIM keys for signing outgoing messages. +""" + +import pathlib +import re +import shutil +import subprocess + +from plinth import actions + +_keys_dir = pathlib.Path('/var/lib/rspamd/dkim/') + +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') + + +def get_public_key(domain): + """Return the DKIM public key for the given domain.""" + output = actions.superuser_run('email', + ['dkim', 'get_dkim_public_key', domain]) + return output.strip() + + +def action_get_dkim_public_key(domain): + """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) + print(''.join(output.decode().splitlines()[1:-1])) + + +def action_setup_dkim(domain): + """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', '_rspamd') + + # 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) + key_file.chmod(0o400) diff --git a/plinth/modules/email/audit/domain.py b/plinth/modules/email/audit/domain.py index 94ae99bb1..51d189e8f 100644 --- a/plinth/modules/email/audit/domain.py +++ b/plinth/modules/email/audit/domain.py @@ -33,6 +33,7 @@ def set_domains(primary_domain=None): superuser_run( 'email', ['domain', 'set_domains', primary_domain, ','.join(all_domains)]) + superuser_run('email', ['dkim', 'setup_dkim', primary_domain]) def action_set_domains(primary_domain, all_domains):