Merge branch 'dkim-key-management' into 'master'
Add support for DKIM key management Closes #341 See merge request simple-nixos-mailserver/nixos-mailserver!484
This commit is contained in:
@@ -46,6 +46,8 @@ SNM branch corresponding to your NixOS version.
|
||||
* [x] Via ClamAV
|
||||
* DKIM Signing
|
||||
* [x] Via Rspamd
|
||||
* [x] Automatic key generation
|
||||
* [x] Multiple selectors per Domain
|
||||
* User Management
|
||||
* [x] Declarative user management
|
||||
* [x] Declarative password management
|
||||
@@ -66,9 +68,6 @@ SNM branch corresponding to your NixOS version.
|
||||
* [ ] [Autoconfig](https://web.archive.org/web/20210624004729/https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration)
|
||||
* [ ] [Autodiscovery](https://learn.microsoft.com/en-us/exchange/architecture/client-access/autodiscover?view=exchserver-2019)
|
||||
* [ ] [Mobileconfig](https://support.apple.com/guide/profile-manager/distribute-profiles-manually-pmdbd71ebc9/mac)
|
||||
* DKIM Signing
|
||||
* [ ] Allow per domain selectors
|
||||
* [ ] Allow passing DKIM signing keys
|
||||
* Improve the Forwarding Experience
|
||||
* [ ] Support [ARC](https://en.wikipedia.org/wiki/Authenticated_Received_Chain) signing with [Rspamd](https://rspamd.com/doc/modules/arc.html)
|
||||
* User management
|
||||
|
||||
+142
-28
@@ -917,31 +917,33 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
dkimSigning = mkOption {
|
||||
type = types.bool;
|
||||
dkim = {
|
||||
enable = mkEnableOption "DKIM signing" // {
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to activate dkim signing.
|
||||
'';
|
||||
};
|
||||
|
||||
dkimSelector = mkOption {
|
||||
type = types.str;
|
||||
default = "mail";
|
||||
description = ''
|
||||
The DKIM selector.
|
||||
'';
|
||||
};
|
||||
|
||||
dkimKeyDirectory = mkOption {
|
||||
keyDirectory = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/dkim";
|
||||
description = ''
|
||||
The DKIM directory.
|
||||
The path where DKIM siging keys are stored.
|
||||
'';
|
||||
};
|
||||
|
||||
dkimKeyType = mkOption {
|
||||
defaults = {
|
||||
selector = mkOption {
|
||||
type = types.str;
|
||||
default = "mail";
|
||||
description = ''
|
||||
The default selector used to reference and lookup DKIM keys.
|
||||
|
||||
This value should most likely not be changed. Instead manage
|
||||
{option}`mailserver.dkim.domains.<name>.selectors` to sign with one
|
||||
or multiple DKIM key pairs and manage migrations.
|
||||
'';
|
||||
};
|
||||
|
||||
keyType = mkOption {
|
||||
type = types.enum [
|
||||
"rsa"
|
||||
"ed25519"
|
||||
@@ -952,29 +954,134 @@ in
|
||||
introduced in RFC6376 (2018).
|
||||
|
||||
:::{warning}
|
||||
Ed25519 DKIM keys are currently not recommended for primary use, as
|
||||
various DKIM validators out there lack support and consider the keypair invalid.
|
||||
Ed25519 DKIM keys are currently not recommended for sole use, as
|
||||
various DKIM validators out there lack support and consider the
|
||||
keypair invalid.
|
||||
:::
|
||||
|
||||
If you have already deployed a key with a different type than specified
|
||||
here, then you should use a different selector ({option}`mailserver.dkimSelector`). In order to get
|
||||
this package to generate a key with the new type, you will either have to
|
||||
change the selector or delete the old key file.
|
||||
This value should most likely not be changed. Once DKIM keys for
|
||||
domain and selector are generated changing this value will not
|
||||
regenerate the keypair. Instead create a new selector and configure
|
||||
{option}`mailserver.dkim.domains.<name>.selectors.<name>.keyType`.
|
||||
'';
|
||||
};
|
||||
|
||||
dkimKeyBits = mkOption {
|
||||
keyLength = mkOption {
|
||||
type = types.int;
|
||||
default = 2048;
|
||||
description = ''
|
||||
How many bits in generated DKIM keys. RFC8301 suggests a minimum RSA key length of 2048 bit.
|
||||
The default key length used for generating new DKIM keys.
|
||||
|
||||
If you have already deployed a key with a different number of bits than specified
|
||||
here, then you should use a different selector ({option}`mailserver.dkimSelector`). In order to get
|
||||
this package to generate a key with the new number of bits, you will either have to
|
||||
change the selector or delete the old key file.
|
||||
Only applies for RSA keys, Ed25519 keys use a fixed key length.
|
||||
|
||||
Per [RFC8301 3.2] the minimum RSA key length should be at least
|
||||
2048 bit.
|
||||
|
||||
This value should most likely not be changed. Once DKIM keys for
|
||||
domain and selector are generated changing this value will not
|
||||
regenerate the keypair. Instead create a new selector and configure
|
||||
{option}`mailserver.dkim.domains.<name>.selectors.<name>.keyLength`.
|
||||
|
||||
[RFC8301 3.2]: https://datatracker.ietf.org/doc/html/rfc8301#section-3.2
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
domains = mkOption {
|
||||
description = "DKIM configuration per domain.";
|
||||
type = types.attrsOf (
|
||||
types.submodule ({
|
||||
options = {
|
||||
selectors = mkOption {
|
||||
description = ''
|
||||
DKIM selectors used for signing outgoing mail for this domain.
|
||||
|
||||
When no selector is configured a default selector will be
|
||||
created with settings inherited from {option}`mailserver.defaults.dkim <mailserver.dkim.defaults.keyLength>`.
|
||||
'';
|
||||
type = types.attrsOf (
|
||||
types.submodule ({
|
||||
options = {
|
||||
keyType = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (enum [
|
||||
"rsa"
|
||||
"ed25519"
|
||||
]);
|
||||
default = null;
|
||||
example = "rsa";
|
||||
description = ''
|
||||
The key type used for generating this DKIM keypair.
|
||||
|
||||
:::{warning}
|
||||
Ed25519 DKIM keys are currently not recommended for sole use, as
|
||||
various DKIM validators out there lack support and consider the
|
||||
keypair invalid.
|
||||
:::
|
||||
|
||||
This option is mutually exclusive with `keyFile`.
|
||||
'';
|
||||
};
|
||||
|
||||
keyLength = mkOption {
|
||||
type = with types; nullOr int;
|
||||
default = null;
|
||||
example = 2048;
|
||||
description = ''
|
||||
The key length used for generating this DKIM key.
|
||||
|
||||
Only applies for RSA keys, Ed25519 keys use a fixed key size.
|
||||
|
||||
This option is mutually exclusive with `keyFile`.
|
||||
'';
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
type =
|
||||
with types;
|
||||
nullOr (pathWith {
|
||||
inStore = false;
|
||||
});
|
||||
default = null;
|
||||
example = "/run/keys/example.com-dkim-rsa-2026-03.key";
|
||||
description = ''
|
||||
Path to an existing DKIM private key file.
|
||||
|
||||
DKIM keys can be generated using `rspamadm dkim_keygen`.
|
||||
|
||||
This option is mutually exclusive with `keyType` and `keyLength`.
|
||||
'';
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
"mail" = {
|
||||
# inherit defaults from mailserver.dkim.defaults
|
||||
};
|
||||
"rsa-2026-03".keyFile = "/run/keys/example.com-dkim-rsa-2026-03.key";
|
||||
};
|
||||
'';
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
"example.com".selectors = {
|
||||
"mail" = {
|
||||
# inherit defaults from mailserver.dkim.defaults
|
||||
};
|
||||
"rsa-2026-03".keyFile = "/run/keys/example.com-dkim-rsa-2026-03.key";
|
||||
};
|
||||
};
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
dmarcReporting = {
|
||||
enable = mkOption {
|
||||
@@ -1468,6 +1575,13 @@ in
|
||||
(mkRemovedOptionModule [ "mailserver" "smtpdForbidBareNewline" ] ''
|
||||
The workaround for the SMTP Smuggling attack is default enabled in Postfix >3.9. Use `services.postfix.config.smtpd_forbid_bare_newline` if you need to deviate from its default.
|
||||
'')
|
||||
(mkRenamedOptionModule [ "mailserver" "dkimSigning" ] [ "mailserver" "dkim" "enable" ])
|
||||
(mkRenamedOptionModule [ "mailserver" "dkimKeyDirectory" ] [ "mailserver" "dkim" "keyDirectory" ])
|
||||
(mkRenamedOptionModule
|
||||
[ "mailserver" "dkimSelector" ]
|
||||
[ "mailserver" "dkim" "defaults" "selector" ]
|
||||
)
|
||||
(mkRenamedOptionModule [ "mailserver" "dkimKeyType" ] [ "mailserver" "dkim" "defaults" "keyType" ])
|
||||
(mkRenamedOptionModule [ "mailserver" "dmarcReporting" "domain" ] [ "mailserver" "systemDomain" ])
|
||||
(mkRenamedOptionModule
|
||||
[ "mailserver" "dmarcReporting" "organizationName" ]
|
||||
|
||||
@@ -21,7 +21,7 @@ Maildir.
|
||||
|
||||
To backup spam and ham training data, backup ``/var/lib/redis-rspamd``.
|
||||
|
||||
Finally you can (optionally) make a backup of ``/var/dkim`` (or whatever
|
||||
you specified as ``dkimKeyDirectory``). If you should lose those don’t
|
||||
worry, new ones will be created on the fly. But you will need to repeat
|
||||
step ``B)5`` and correct all the ``dkim`` keys.
|
||||
Finally you can (optionally) make a backup of ``/var/dkim`` (or whatever you
|
||||
specified as :option:`mailserver.dkim.keyDirectory`). If you should lose those
|
||||
don’t worry, new ones will be created on the fly. But you will need to update
|
||||
the DKIM TXT records to reflect the new key material.
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
DKIM Signing
|
||||
============
|
||||
|
||||
DKIM (DomainKeys Identified Mail) is an email authentication mechanism that
|
||||
allows a mailserver to digitally sign outgoing emails for a domain. Receiving
|
||||
mail servers can verify this signature using a public key published in DNS to
|
||||
confirm the message was authorized by the domain and was not modified during
|
||||
transit.
|
||||
|
||||
How DKIM works in practice
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. ``bob@bar.example`` sends an email to ``alice@foo.example``. The sending
|
||||
mail server for ``bar.example`` selects one or multiple DKIM keys using a
|
||||
**selector** (e.g., ``mail``) and creates one or multiple cryptographic
|
||||
signature for selected headers and the message body, adding a ``DKIM-Signature``
|
||||
header that references the selector.
|
||||
|
||||
2. The message arrives at ``foo.example``. The receiving mail server reads the
|
||||
``DKIM-Signature`` headers, looks up the public keys for ``bar.example`` for
|
||||
each of the specified selectors (e.g., ``mail._domainkey.bar.example``), and
|
||||
verifies that at least one signature matches the message content.
|
||||
|
||||
3. With a valid signature, the receiver knows the message was authorized by
|
||||
``bar.example`` and that the signed headers and body were not modified in
|
||||
transit. If the content or signed headers were changed, the DKIM verification
|
||||
fails. The use of selectors allows ``bar.example`` to rotate or migrate keys
|
||||
without disrupting verification for previously sent messages.
|
||||
|
||||
Enabling DKIM Signing
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because DKIM signing is crucial for reliable mail delivery it is enabled by
|
||||
default and without further configuration a DKIM keypair will be generated for
|
||||
each :option:`mailserver.domains` (including :option:`mailserver.srs.domain`,
|
||||
if set) based on :option:`mailserver.dkim.defaults
|
||||
<mailserver.dkim.defaults.keyLength>`.
|
||||
|
||||
.. code:: nix
|
||||
|
||||
{
|
||||
mailserver = {
|
||||
domains = [ "example.com" ];
|
||||
dkim.enable = true; # enabled by default
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
.. note::
|
||||
If you set up NixOS Mailserver before the `25.11 release`_ your DKIM keys were
|
||||
generated with 1024 bit RSA and we recommend replacing them with 2048 bit
|
||||
RSA key material per `RFC8301 3.2`_.
|
||||
|
||||
.. _25.11 release: release-notes.html#nixos-25-11
|
||||
.. _RFC8301 3.2: https://www.rfc-editor.org/rfc/rfc8301#section-3.2
|
||||
|
||||
DKIM Key Rotation
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
DKIM key rotation replaces a domain's signing keys to maintain
|
||||
strong email authentication and support algorithm upgrades. Rotation is
|
||||
essential for migrating away from weaker or deprecated algorithms.
|
||||
|
||||
Selectors allow multiple keys to coexist during the transition: a new key can
|
||||
be deployed under a different selector while the old key remains valid for a
|
||||
limited period to verify messages still in transit. Once all messages signed
|
||||
with the old key have been delivered, the key can be safely retired, ensuring a
|
||||
reliable migration without breaking verification.
|
||||
|
||||
|
||||
1. Make the automatically generated key explicit
|
||||
************************************************
|
||||
|
||||
First we need to make sure we keep the current DKIM key configured. If you were
|
||||
relying on automatically generated keys before, you now need to start explicitly
|
||||
defining that key, because explicit selector configuration takes precedence.
|
||||
|
||||
.. code:: nix
|
||||
|
||||
{
|
||||
mailserver = {
|
||||
domains = [ "example.com" ];
|
||||
dkim.domains = {
|
||||
"example.com".selectors = {
|
||||
"${config.mailserver.dkim.defaults.selector}" = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
2. Create the new DKIM keypair
|
||||
******************************
|
||||
|
||||
Next we need to create a new DKIM key with a unique selector, you can
|
||||
for example choose the current date. Without any settings passed a new
|
||||
key will be generated from the current :option:`mailserver.dkim.defaults
|
||||
<mailserver.dkim.defaults.keyLength>`, which should be sufficient.
|
||||
|
||||
.. code:: nix
|
||||
|
||||
{
|
||||
mailserver = {
|
||||
domains = [ "example.com" ];
|
||||
dkim = {
|
||||
enable = true;
|
||||
domains."example.com".selectors = {
|
||||
"${config.mailserver.dkim.defaults.selector}" = { };
|
||||
"rsa-2026-03" = {
|
||||
keyType = "rsa";
|
||||
keyLength = 2048;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
.. warning::
|
||||
While DKIM does support Ed25519 keys (`RFC8463`_), many validators still lack
|
||||
proper support and may treat Ed25519 key material as invalid. As a result,
|
||||
mail signed only with Ed25519 DKIM keys may fail verification at some
|
||||
receivers.
|
||||
|
||||
.. _RFC8463: https://datatracker.ietf.org/doc/html/rfc8463
|
||||
|
||||
Once this configuration is applied the new keypair will be generated below
|
||||
:option:`mailserver.dkim.keyDirectory`, which defaults to ``/var/dkim``. The
|
||||
mailserver then begins signing outgoing mail with this key, so that it is now
|
||||
signing with two DKIM keys simultaneously.
|
||||
|
||||
To allow receiving servers to verify the new DKIM signature its
|
||||
public key needs to be published into DNS. Look up the public key from
|
||||
``/var/dkim/example.com.rsa-2026-03.txt`` and create the following DNS record by
|
||||
substituting selector and public key.
|
||||
|
||||
.. csv-table::
|
||||
:header: "Name", "TTL", "Type", "Value"
|
||||
:widths: 30, 10, 10, 50
|
||||
|
||||
rsa-2026-03._domainkey.example.com., 86400, TXT, v=DKIM1; k=rsa; p=<public key>
|
||||
|
||||
.. note::
|
||||
If you created an Ed25519 key, make sure to set the correct key type: ``k=ed25519``
|
||||
|
||||
Now wait for a few minutes and then check DNS propagation to show the value specified.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ nix-shell -p dig --command "dig @ns1.example.org TXT rsa-2026-03._domainkey.example.com"
|
||||
|
||||
You can use https://www.mail-tester.com to test the new DKIM signature passes
|
||||
validation. They allow you to view the message source where you can check that
|
||||
the correct number of ``DKIM-Signature`` keys are present in the mail header.
|
||||
|
||||
|
||||
3. Stop signing with the old DKIM keypair
|
||||
*****************************************
|
||||
|
||||
Once validation passes we need to stop signing with the old DKIM keypair, so
|
||||
mail in transit eventually stops depending on the key material we want to rotate
|
||||
out. Removing the selector will not remove the key material from disk, but it
|
||||
will stop using it to sign outgoing mail.
|
||||
|
||||
.. code:: nix
|
||||
|
||||
{
|
||||
mailserver = {
|
||||
domains = [ "example.com" ];
|
||||
dkim = {
|
||||
enable = true;
|
||||
domains."example.com".selectors = {
|
||||
"rsa-2026-03" = {
|
||||
keyType = "rsa";
|
||||
keyLength = 2048;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Apply the configuration.
|
||||
|
||||
|
||||
4. Remove the old DKIM selector from DNS
|
||||
****************************************
|
||||
|
||||
.. warning::
|
||||
Do not remove the DNS records for the old selector immediately. Keeping them
|
||||
in place is essential to ensure that messages still in transit can be verified
|
||||
and delivered successfully.
|
||||
|
||||
Mail delivery is not always instantaneous. In the worst case, multiple retries
|
||||
over several days may be required. According to `RFC5321 4.5.4.1`_ delivery
|
||||
should be retried for at least 4-5 days.
|
||||
|
||||
This means messages signed only with the old DKIM key could still be in transit
|
||||
and rely on the old selector to verify their signatures. To ensure reliable
|
||||
delivery, we recommend waiting **at least five days** before removing the old
|
||||
DKIM selector from DNS.
|
||||
|
||||
.. _RFC5321 4.5.4.1: https://www.rfc-editor.org/rfc/rfc5321#section-4.5.4.1
|
||||
@@ -25,6 +25,7 @@ Welcome to NixOS Mailserver's documentation!
|
||||
:maxdepth: 1
|
||||
:caption: Features
|
||||
|
||||
dkim
|
||||
fts
|
||||
ldap
|
||||
srs
|
||||
|
||||
@@ -11,6 +11,12 @@ NixOS 26.05
|
||||
:option:`mailserver.x509.privateKeyFile` instead. Support for automatic
|
||||
creation of self-signed certificates has been removed.
|
||||
Check the updated `setup guide`_ for a basic ACME HTTP-01 example.
|
||||
- `DKIM key management`_ is now available with multiple concurrent selectors per
|
||||
domain enabling proper DKIM key rotation. While we still generate a default
|
||||
key for backwards compatibility we now also support passing pre-created
|
||||
key material. If your DKIM keys were automatically created before the 25.11
|
||||
release they are 1024 bit RSA keys and should be rotated out.
|
||||
See :option:`mailserver.dkim.domains` for further relevant options.
|
||||
- Cleartext password files can now be configured for login accounts. This
|
||||
is an alternative to hashed passwords that integrates well with workflows
|
||||
established by `agenix`_/`sops-nix`_ that instead rely on encryption. This
|
||||
@@ -18,6 +24,7 @@ NixOS 26.05
|
||||
See :option:`mailserver.loginAccounts.<name>.passwordFile`.
|
||||
|
||||
.. _setup guide: setup-guide.html#setup-the-server
|
||||
.. _DKIM key management: dkim.html
|
||||
.. _agenix: https://github.com/ryantm/agenix
|
||||
.. _sops-nix: https://github.com/Mic92/sops-nix
|
||||
|
||||
|
||||
@@ -53,6 +53,20 @@ in
|
||||
message = "Configure either an ACME certificate (`mailserver.x509.useACMEHost`) or pass an existing certificate (`mailserver.x509.certificateFile`, `mailserver.x509.privateKeyFile`).";
|
||||
}
|
||||
]
|
||||
++ lib.optionals config.mailserver.dkim.enable (
|
||||
lib.flatten (
|
||||
lib.mapAttrsToList (
|
||||
domain: domainAttrs:
|
||||
lib.mapAttrsToList (selector: selectorAttrs: [
|
||||
{
|
||||
assertion =
|
||||
selectorAttrs.keyFile != null -> (selectorAttrs.keyType == null && selectorAttrs.keyLength == null);
|
||||
message = "${domain} DKIM selector ${selector} can only use either `keyType`, `keyLength` OR `keyFile` not both.";
|
||||
}
|
||||
]) domainAttrs.selectors
|
||||
) config.mailserver.dkim.domains
|
||||
)
|
||||
)
|
||||
++ lib.optionals config.mailserver.ldap.enable [
|
||||
{
|
||||
assertion = config.mailserver.loginAccounts == { };
|
||||
|
||||
@@ -450,7 +450,7 @@ in
|
||||
smtpd_tls_loglevel = "1";
|
||||
|
||||
smtpd_milters = smtpdMilters;
|
||||
non_smtpd_milters = lib.mkIf cfg.dkimSigning [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
||||
non_smtpd_milters = lib.mkIf cfg.dkim.enable [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
||||
milter_protocol = "6";
|
||||
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_authen}";
|
||||
};
|
||||
|
||||
+93
-22
@@ -31,27 +31,80 @@ let
|
||||
rspamdGroup = config.services.rspamd.group;
|
||||
|
||||
createDkimKeypair =
|
||||
domain:
|
||||
{
|
||||
domain,
|
||||
selector,
|
||||
type,
|
||||
bits,
|
||||
...
|
||||
}:
|
||||
let
|
||||
privateKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.key";
|
||||
publicKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.txt";
|
||||
privkey = "${cfg.dkim.keyDirectory}/${domain}.${selector}.key";
|
||||
pubkey = "${cfg.dkim.keyDirectory}/${domain}.${selector}.txt";
|
||||
in
|
||||
pkgs.writeShellScript "dkim-keygen-${domain}" ''
|
||||
if [ ! -f "${privateKey}" ]
|
||||
pkgs.writeShellScript "dkim-keygen-${domain}-${selector}" ''
|
||||
if [ ! -f "${privkey}" ]
|
||||
then
|
||||
export PATH=${lib.makeBinPath [ pkgs.openssl ]}
|
||||
${lib.getExe' pkgs.rspamd "rspamadm"} dkim_keygen \
|
||||
--domain "${domain}" \
|
||||
--selector "${cfg.dkimSelector}" \
|
||||
--type "${cfg.dkimKeyType}" \
|
||||
--bits ${toString cfg.dkimKeyBits} \
|
||||
--privkey "${privateKey}" > "${publicKey}"
|
||||
chmod 0644 "${publicKey}"
|
||||
echo "Generated key for domain ${domain} and selector ${cfg.dkimSelector}"
|
||||
${lib.getExe' pkgs.rspamd "rspamadm"} dkim_keygen ${
|
||||
lib.cli.toCommandLineShellGNU { } {
|
||||
inherit
|
||||
domain
|
||||
selector
|
||||
type
|
||||
bits
|
||||
privkey
|
||||
;
|
||||
}
|
||||
} > "${pubkey}"
|
||||
chmod 0644 "${pubkey}"
|
||||
echo "Generated key for domain ${domain} and selector ${selector}"
|
||||
fi
|
||||
'';
|
||||
|
||||
dkimDomains = lib.unique (cfg.domains ++ (lib.optionals cfg.srs.enable [ cfg.srs.domain ]));
|
||||
mailDomains = lib.unique (
|
||||
# primary mailserver domains
|
||||
config.mailserver.domains
|
||||
# all dkim domains, even extra domains specified
|
||||
++ lib.attrNames cfg.dkim.domains
|
||||
# and the srs domain, if one is configured
|
||||
++ lib.optionals (cfg.srs.domain != null) [ cfg.srs.domain ]
|
||||
);
|
||||
|
||||
dkimKeys = lib.concatMap (
|
||||
domain:
|
||||
let
|
||||
configuredSelectors = config.mailserver.dkim.domains.${domain}.selectors or { };
|
||||
|
||||
finalSelectors =
|
||||
if configuredSelectors == { } then
|
||||
# synthesize default dkim key, if none configured
|
||||
{
|
||||
"${config.mailserver.dkim.defaults.selector}" = {
|
||||
keyType = null;
|
||||
keyLength = null;
|
||||
keyFile = null;
|
||||
};
|
||||
}
|
||||
else
|
||||
configuredSelectors;
|
||||
in
|
||||
lib.mapAttrsToList (selector: settings: rec {
|
||||
inherit domain selector;
|
||||
keyFile = settings.keyFile;
|
||||
keyPath = if keyFile != null then keyFile else "${cfg.dkim.keyDirectory}/${domain}.${selector}.key";
|
||||
bits =
|
||||
if settings.keyLength != null then
|
||||
settings.keyLength
|
||||
else
|
||||
config.mailserver.dkim.defaults.keyLength;
|
||||
type =
|
||||
if settings.keyType != null then settings.keyType else config.mailserver.dkim.defaults.keyType;
|
||||
}) finalSelectors
|
||||
) mailDomains;
|
||||
|
||||
dkimKeysToGenerate = lib.filter (key: key.keyFile == null) dkimKeys;
|
||||
|
||||
dkimKeysByDomain = lib.groupBy (item: item.domain) dkimKeys;
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
@@ -110,13 +163,31 @@ in
|
||||
};
|
||||
"dkim_signing.conf" = {
|
||||
text = ''
|
||||
enabled = ${lib.boolToString cfg.dkimSigning};
|
||||
path = "${cfg.dkimKeyDirectory}/$domain.$selector.key";
|
||||
selector = "${cfg.dkimSelector}";
|
||||
enabled = ${lib.boolToString cfg.dkim.enable};
|
||||
# Only sign explicitly configured domains
|
||||
try_fallback = false;
|
||||
# Allow for usernames w/o domain part
|
||||
allow_username_mismatch = true;
|
||||
# Don't normalize DKIM key selection for subdomains
|
||||
use_esld = false;
|
||||
|
||||
domain {
|
||||
${lib.concatStringsSep "\n\n" (
|
||||
map (domain: ''
|
||||
${domain} {
|
||||
selectors [
|
||||
${lib.concatStringsSep ",\n" (
|
||||
map (selector: ''
|
||||
{
|
||||
path: "${selector.keyPath}";
|
||||
selector: "${selector.selector}";
|
||||
}'') dkimKeysByDomain.${domain}
|
||||
)}
|
||||
]
|
||||
}
|
||||
'') (lib.attrNames dkimKeysByDomain)
|
||||
)}
|
||||
}
|
||||
'';
|
||||
};
|
||||
"dmarc.conf" = {
|
||||
@@ -183,7 +254,7 @@ in
|
||||
services.redis.servers.rspamd.enable = lib.mkDefault cfg.redis.configureLocally;
|
||||
|
||||
systemd.tmpfiles.settings."10-rspamd.conf" = {
|
||||
"${cfg.dkimKeyDirectory}" = {
|
||||
"${cfg.dkim.keyDirectory}" = {
|
||||
d = {
|
||||
# Create /var/dkim owned by rspamd user/group
|
||||
user = rspamdUser;
|
||||
@@ -204,9 +275,9 @@ in
|
||||
{
|
||||
SupplementaryGroups = [ config.services.redis.servers.rspamd.group ];
|
||||
}
|
||||
(lib.optionalAttrs cfg.dkimSigning {
|
||||
ExecStartPre = map createDkimKeypair dkimDomains;
|
||||
ReadWritePaths = [ cfg.dkimKeyDirectory ];
|
||||
(lib.optionalAttrs cfg.dkim.enable {
|
||||
ExecStartPre = map createDkimKeypair dkimKeysToGenerate;
|
||||
ReadWritePaths = [ cfg.dkim.keyDirectory ];
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
@@ -66,9 +66,9 @@ in
|
||||
after = [
|
||||
"dovecot.service"
|
||||
]
|
||||
++ lib.optional cfg.dkimSigning "rspamd.service"
|
||||
++ lib.optional cfg.dkim.enable "rspamd.service"
|
||||
++ certificateDeps;
|
||||
requires = [ "dovecot.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service";
|
||||
requires = [ "dovecot.service" ] ++ lib.optional cfg.dkim.enable "rspamd.service";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
+23
-4
@@ -49,7 +49,21 @@
|
||||
"example2.com"
|
||||
];
|
||||
rewriteMessageId = true;
|
||||
dkimKeyBits = 1535;
|
||||
dkim = {
|
||||
defaults.keyLength = 1535;
|
||||
domains."example2.com".selectors = {
|
||||
"dkim-rsa" = {
|
||||
# rsa 1535 bits via defaults
|
||||
};
|
||||
"dkim-ed25519" = {
|
||||
keyType = "ed25519";
|
||||
keyLength = null;
|
||||
};
|
||||
"dkim-file" = {
|
||||
keyFile = "/run/rspamd/dkim-test.key";
|
||||
};
|
||||
};
|
||||
};
|
||||
dmarcReporting.enable = true;
|
||||
|
||||
loginAccounts = {
|
||||
@@ -369,6 +383,9 @@
|
||||
"set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
|
||||
)
|
||||
|
||||
server.succeed("rspamadm dkim_keygen > /run/rspamd/dkim-test.key")
|
||||
server.succeed("chown rspamd: /run/rspamd/dkim-test.key")
|
||||
|
||||
client.execute("cp -p /etc/root/.* ~/")
|
||||
client.succeed("mkdir -p ~/mail")
|
||||
client.succeed("ls -la ~/ >&2")
|
||||
@@ -404,10 +421,10 @@
|
||||
|
||||
with subtest("dkim has user-specified size"):
|
||||
server.succeed(
|
||||
"openssl rsa -in /var/dkim/example.com.mail.key -text -noout | grep 'Private-Key: (1535 bit'"
|
||||
"openssl rsa -in /var/dkim/example2.com.dkim-rsa.key -text -noout | grep 'Private-Key: (1535 bit'"
|
||||
)
|
||||
|
||||
with subtest("dkim singing, multiple domains"):
|
||||
with subtest("dkim signing, multiple domains"):
|
||||
client.execute("rm ~/mail/*")
|
||||
# send email from user2 to user1
|
||||
client.succeed(
|
||||
@@ -418,7 +435,9 @@
|
||||
client.succeed("fetchmail --nosslcertck -v")
|
||||
client.succeed("cat ~/mail/* >&2")
|
||||
# make sure it is dkim signed
|
||||
client.succeed("grep DKIM-Signature: ~/mail/*")
|
||||
client.succeed("grep 's=dkim-rsa' ~/mail/*")
|
||||
client.succeed("grep 's=dkim-ed25519' ~/mail/*")
|
||||
client.succeed("grep 's=dkim-file' ~/mail/*")
|
||||
|
||||
with subtest("aliases"):
|
||||
client.execute("rm ~/mail/*")
|
||||
|
||||
Reference in New Issue
Block a user