Merge pull request #227401 from onny/maddytls2

nixos/maddy: Add tls option
This commit is contained in:
Jonas Heinrich 2023-05-02 07:32:56 +02:00 committed by GitHub
commit d932d6929b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 186 additions and 7 deletions

View file

@ -322,7 +322,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 replacement. It stores backups as volume dump files and thus better integrates
into contemporary backup solutions. 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 - The `dnsmasq` service now takes configuration via the
`services.dnsmasq.settings` attribute set. The option `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 # configuration here https://github.com/foxcpp/maddy/blob/master/maddy.conf
# Do not use this in production! # Do not use this in production!
tls off
auth.pass_table local_authdb { auth.pass_table local_authdb {
table sql_table { table sql_table {
driver sqlite3 driver sqlite3
@ -35,6 +33,7 @@ let
} }
optional_step file /etc/maddy/aliases optional_step file /etc/maddy/aliases
} }
msgpipeline local_routing { msgpipeline local_routing {
destination postmaster $(local_domains) { destination postmaster $(local_domains) {
modify { 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 { openFirewall = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
@ -224,7 +280,7 @@ in {
}; };
ensureAccounts = mkOption { ensureAccounts = mkOption {
type = types.listOf types.str; type = with types; listOf str;
default = []; default = [];
description = lib.mdDoc '' description = lib.mdDoc ''
List of IMAP accounts which get automatically created. Note that for List of IMAP accounts which get automatically created. Note that for
@ -270,6 +326,16 @@ in {
config = mkIf cfg.enable { 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 = { systemd = {
packages = [ pkgs.maddy ]; packages = [ pkgs.maddy ];
@ -318,6 +384,17 @@ in {
$(primary_domain) = ${cfg.primaryDomain} $(primary_domain) = ${cfg.primaryDomain}
$(local_domains) = ${toString cfg.localDomains} $(local_domains) = ${toString cfg.localDomains}
hostname ${cfg.hostname} 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} ${cfg.config}
''; '';
}; };

View file

@ -393,7 +393,7 @@ in {
lxd-image-server = handleTest ./lxd-image-server.nix {}; lxd-image-server = handleTest ./lxd-image-server.nix {};
#logstash = handleTest ./logstash.nix {}; #logstash = handleTest ./logstash.nix {};
lorri = handleTest ./lorri/default.nix {}; lorri = handleTest ./lorri/default.nix {};
maddy = handleTest ./maddy.nix {}; maddy = discoverTests (import ./maddy { inherit handleTest; });
maestral = handleTest ./maestral.nix {}; maestral = handleTest ./maestral.nix {};
magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {}; magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
magnetico = handleTest ./magnetico.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, ... }: { import ../make-test-python.nix ({ pkgs, ... }: {
name = "maddy"; name = "maddy-unencrypted";
meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; }; meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
nodes = { nodes = {