nixos/maddy: Add tls option

This commit is contained in:
Jonas Heinrich 2023-04-21 08:59:13 +02:00
parent 8d82c9c90f
commit 616ba4ae5c
6 changed files with 186 additions and 7 deletions

View File

@ -292,7 +292,9 @@ In addition to numerous new and upgraded packages, this release has the followin
replacement. It stores backups as volume dump files and thus better integrates
into contemporary backup solutions.
- `services.maddy` now allows to configure users and their credentials using `services.maddy.ensureCredentials`.
- `services.maddy` got several updates:
- Configuration of users and their credentials using `services.maddy.ensureCredentials`.
- Configuration of TLS key and certificate files using `services.maddy.tls`.
- The `dnsmasq` service now takes configuration via the
`services.dnsmasq.settings` attribute set. The option

View File

@ -13,8 +13,6 @@ let
# configuration here https://github.com/foxcpp/maddy/blob/master/maddy.conf
# Do not use this in production!
tls off
auth.pass_table local_authdb {
table sql_table {
driver sqlite3
@ -35,6 +33,7 @@ let
}
optional_step file /etc/maddy/aliases
}
msgpipeline local_routing {
destination postmaster $(local_domains) {
modify {
@ -215,6 +214,63 @@ in {
'';
};
tls = {
loader = mkOption {
type = with types; nullOr (enum [ "file" "off" ]);
default = "off";
description = lib.mdDoc ''
TLS certificates are obtained by modules called "certificate
loaders". Currently only the file loader is supported which reads
certificates from files specifying the options `keyPaths` and
`certPaths`.
'';
};
certificates = mkOption {
type = with types; listOf (submodule {
options = {
keyPath = mkOption {
type = types.path;
example = "/etc/ssl/mx1.example.org.key";
description = lib.mdDoc ''
Path to the private key used for TLS.
'';
};
certPath = mkOption {
type = types.path;
example = "/etc/ssl/mx1.example.org.crt";
description = lib.mdDoc ''
Path to the certificate used for TLS.
'';
};
};
});
default = [];
example = lib.literalExpression ''
[{
keyPath = "/etc/ssl/mx1.example.org.key";
certPath = "/etc/ssl/mx1.example.org.crt";
}]
'';
description = lib.mdDoc ''
A list of attribute sets containing paths to TLS certificates and
keys. Maddy will use SNI if multiple pairs are selected.
'';
};
extraConfig = mkOption {
type = with types; nullOr lines;
description = lib.mdDoc ''
Arguments for the specific certificate loader. Note that Maddy uses
secure defaults for the TLS configuration so there is no need to
change anything in most cases.
See [upstream manual](https://maddy.email/reference/tls/) for
available options.
'';
default = "";
};
};
openFirewall = mkOption {
type = types.bool;
default = false;
@ -224,7 +280,7 @@ in {
};
ensureAccounts = mkOption {
type = types.listOf types.str;
type = with types; listOf str;
default = [];
description = lib.mdDoc ''
List of IMAP accounts which get automatically created. Note that for
@ -270,6 +326,16 @@ in {
config = mkIf cfg.enable {
assertions = [{
assertion = cfg.tls.loader == "file" -> cfg.tls.certificates != [];
message = ''
If maddy is configured to use TLS, tls.certificates with attribute sets
of certPath and keyPath must be provided.
Read more about obtaining TLS certificates here:
https://maddy.email/tutorials/setting-up/#tls-certificates
'';
}];
systemd = {
packages = [ pkgs.maddy ];
@ -318,6 +384,17 @@ in {
$(primary_domain) = ${cfg.primaryDomain}
$(local_domains) = ${toString cfg.localDomains}
hostname ${cfg.hostname}
${if (cfg.tls.loader == "file") then ''
tls file ${concatStringsSep " " (
map (x: x.certPath + " " + x.keyPath
) cfg.tls.certificates)} ${optionalString (cfg.tls.extraConfig != "") ''
{ ${cfg.tls.extraConfig} }
''}
'' else if (cfg.tls.loader == "off") then ''
tls off
'' else ""}
${cfg.config}
'';
};

View File

@ -390,7 +390,7 @@ in {
lxd-image-server = handleTest ./lxd-image-server.nix {};
#logstash = handleTest ./logstash.nix {};
lorri = handleTest ./lorri/default.nix {};
maddy = handleTest ./maddy.nix {};
maddy = discoverTests (import ./maddy { inherit handleTest; });
maestral = handleTest ./maestral.nix {};
magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
magnetico = handleTest ./magnetico.nix {};

View File

@ -0,0 +1,6 @@
{ handleTest }:
{
unencrypted = handleTest ./unencrypted.nix { };
tls = handleTest ./tls.nix { };
}

94
nixos/tests/maddy/tls.nix Normal file
View File

@ -0,0 +1,94 @@
import ../make-test-python.nix ({ pkgs, ... }:
let
certs = import ../common/acme/server/snakeoil-certs.nix;
domain = certs.domain;
in {
name = "maddy-tls";
meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
nodes = {
server = { options, ... }: {
services.maddy = {
enable = true;
hostname = domain;
primaryDomain = domain;
openFirewall = true;
ensureAccounts = [ "postmaster@${domain}" ];
ensureCredentials = {
# Do not use this in production. This will make passwords world-readable
# in the Nix store
"postmaster@${domain}".passwordFile = "${pkgs.writeText "postmaster" "test"}";
};
tls = {
loader = "file";
certificates = [{
certPath = "${certs.${domain}.cert}";
keyPath = "${certs.${domain}.key}";
}];
};
# Enable TLS listeners. Configuring this via the module is not yet
# implemented.
config = builtins.replaceStrings [
"imap tcp://0.0.0.0:143"
"submission tcp://0.0.0.0:587"
] [
"imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
"submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
] options.services.maddy.config.default;
};
# Not covered by openFirewall yet
networking.firewall.allowedTCPPorts = [ 993 465 ];
};
client = { nodes, ... }: {
security.pki.certificateFiles = [
certs.ca.cert
];
networking.extraHosts = ''
${nodes.server.networking.primaryIPAddress} ${domain}
'';
environment.systemPackages = [
(pkgs.writers.writePython3Bin "send-testmail" { } ''
import smtplib
import ssl
from email.mime.text import MIMEText
context = ssl.create_default_context()
msg = MIMEText("Hello World")
msg['Subject'] = 'Test'
msg['From'] = "postmaster@${domain}"
msg['To'] = "postmaster@${domain}"
with smtplib.SMTP_SSL(host='${domain}', port=465, context=context) as smtp:
smtp.login('postmaster@${domain}', 'test')
smtp.sendmail(
'postmaster@${domain}', 'postmaster@${domain}', msg.as_string()
)
'')
(pkgs.writers.writePython3Bin "test-imap" { } ''
import imaplib
with imaplib.IMAP4_SSL('${domain}') as imap:
imap.login('postmaster@${domain}', 'test')
imap.select()
status, refs = imap.search(None, 'ALL')
assert status == 'OK'
assert len(refs) == 1
status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
assert status == 'OK'
assert msg[0][1].strip() == b"Hello World"
'')
];
};
};
testScript = ''
start_all()
server.wait_for_unit("maddy.service")
server.wait_for_open_port(143)
server.wait_for_open_port(993)
server.wait_for_open_port(587)
server.wait_for_open_port(465)
client.succeed("send-testmail")
client.succeed("test-imap")
'';
})

View File

@ -1,5 +1,5 @@
import ./make-test-python.nix ({ pkgs, ... }: {
name = "maddy";
import ../make-test-python.nix ({ pkgs, ... }: {
name = "maddy-unencrypted";
meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
nodes = {