Merge pull request #227401 from onny/maddytls2
nixos/maddy: Add tls option
This commit is contained in:
commit
d932d6929b
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {};
|
||||||
|
|
6
nixos/tests/maddy/default.nix
Normal file
6
nixos/tests/maddy/default.nix
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{ handleTest }:
|
||||||
|
|
||||||
|
{
|
||||||
|
unencrypted = handleTest ./unencrypted.nix { };
|
||||||
|
tls = handleTest ./tls.nix { };
|
||||||
|
}
|
94
nixos/tests/maddy/tls.nix
Normal file
94
nixos/tests/maddy/tls.nix
Normal 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")
|
||||||
|
'';
|
||||||
|
})
|
|
@ -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 = {
|
Loading…
Reference in a new issue