Merge branch 'certmgmt-next' into 'master'
Switch to NixOS ACME module for certificate management Closes #256 and #267 See merge request simple-nixos-mailserver/nixos-mailserver!457
This commit is contained in:
+43
-95
@@ -32,7 +32,6 @@ let
|
||||
mkRemovedOptionModule
|
||||
mkRenamedOptionModule
|
||||
types
|
||||
warn
|
||||
;
|
||||
|
||||
cfg = config.mailserver;
|
||||
@@ -131,20 +130,6 @@ in
|
||||
description = "The domains that this mail server serves.";
|
||||
};
|
||||
|
||||
certificateDomains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
example = [
|
||||
"imap.example.com"
|
||||
"pop3.example.com"
|
||||
];
|
||||
default = [ ];
|
||||
description = ''
|
||||
({option}`mailserver.certificateScheme` == `acme-nginx`)
|
||||
|
||||
Secondary domains and subdomains for which it is necessary to generate a certificate.
|
||||
'';
|
||||
};
|
||||
|
||||
messageSizeLimit = mkOption {
|
||||
type = types.int;
|
||||
example = 52428800;
|
||||
@@ -167,12 +152,12 @@ in
|
||||
hashedPassword = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/";
|
||||
example = "$y$j9T$vfGrwkAaXCjCEWtVNMQck1$383uIXQmn2z0hnmVAA8kwFQmjNj78.nYbvWeyNLIaP1";
|
||||
description = ''
|
||||
The user's hashed password. Use `mkpasswd` as follows
|
||||
|
||||
```
|
||||
nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
|
||||
nix-shell -p mkpasswd --run 'mkpasswd -s'
|
||||
```
|
||||
|
||||
Warning: this is stored in plaintext in the Nix store!
|
||||
@@ -188,7 +173,7 @@ in
|
||||
A file containing the user's hashed password. Use `mkpasswd` as follows
|
||||
|
||||
```
|
||||
nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
|
||||
nix-shell -p mkpasswd --run 'mkpasswd -s'
|
||||
```
|
||||
'';
|
||||
};
|
||||
@@ -212,7 +197,8 @@ in
|
||||
example = [ ''/^tom\..*@domain\.com$/'' ];
|
||||
default = [ ];
|
||||
description = ''
|
||||
Same as {option}`mailserver.aliases` but using PCRE (Perl compatible regex).
|
||||
Same as {option}`mailserver.loginAccounts.<name>.aliases` but
|
||||
using PCRE (Perl compatible regex).
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -290,10 +276,10 @@ in
|
||||
);
|
||||
example = {
|
||||
user1 = {
|
||||
hashedPassword = "$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/";
|
||||
hashedPassword = "$y$j9T$y6eZ1o.IvVNfdGMAsUEvh1$6K/llP52uw2iDh4iSwtAn54/JYy7FzCcoCHmjmx00H5";
|
||||
};
|
||||
user2 = {
|
||||
hashedPassword = "$6$oE0ZNv2n7Vk9gOf$9xcZWCCLGdMflIfuA0vR1Q1Xblw6RZqPrP94mEit2/81/7AKj2bqUai5yPyWE.QYPyv6wLMHZvjw3Rlg7yTCD/";
|
||||
hashedPassword = "$y$j9T$hZ.ubq0M897Hw.znxnGG9.$14EJBoOwbwKeWt.W4vpnBPEBZC9mYz4fWI9kOCLoZf4";
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
@@ -302,7 +288,7 @@ in
|
||||
follows
|
||||
|
||||
```
|
||||
nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
|
||||
nix-shell -p mkpasswd --run 'mkpasswd -s'
|
||||
```
|
||||
'';
|
||||
default = { };
|
||||
@@ -788,91 +774,43 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
certificateScheme =
|
||||
let
|
||||
schemes = [
|
||||
"manual"
|
||||
"selfsigned"
|
||||
"acme-nginx"
|
||||
"acme"
|
||||
];
|
||||
translate =
|
||||
i:
|
||||
warn
|
||||
"Setting mailserver.certificateScheme by number is deprecated, please use names instead: 'mailserver.certificateScheme = ${builtins.toString i}' can be replaced by 'mailserver.certificateScheme = \"${
|
||||
(builtins.elemAt schemes (i - 1))
|
||||
}\"'."
|
||||
(builtins.elemAt schemes (i - 1));
|
||||
in
|
||||
mkOption {
|
||||
type =
|
||||
with types;
|
||||
coercedTo (enum [
|
||||
1
|
||||
2
|
||||
3
|
||||
]) translate (enum schemes);
|
||||
default = "selfsigned";
|
||||
x509 = {
|
||||
useACMEHost = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = literalExpression "config.mailserver.fqdn";
|
||||
description = ''
|
||||
The scheme to use for managing TLS certificates:
|
||||
Common name used in the relevant `security.acme.certs` configuration.
|
||||
|
||||
1. `manual`: you specify locations via {option}`mailserver.certificateFile` and
|
||||
{option}`mailserver.keyFile` and manually copy certificates there.
|
||||
2. `selfsigned`: you let the server create new (self-signed) certificates on the fly.
|
||||
3. `acme-nginx`: you let the server request certificates from [Let's Encrypt](https://letsencrypt.org)
|
||||
via NixOS' ACME module. By default, this will set up a stripped-down Nginx server for
|
||||
{option}`mailserver.fqdn` and open port 80. For this to work, the FQDN must be properly
|
||||
configured to point to your server (see the [setup guide](setup-guide.rst) for more information).
|
||||
4. `acme`: you already have an ACME certificate set up (for example, you're already running a TLS-enabled
|
||||
Nginx server on the FQDN). This is better than `manual` because the appropriate services will be reloaded
|
||||
when the certificate is renewed.
|
||||
Mutually exclusive with {option}`mailserver.x509.certificateFile` and {option}`mailserver.x509.privateKeyFile`.
|
||||
'';
|
||||
};
|
||||
|
||||
certificateFile = mkOption {
|
||||
type = types.path;
|
||||
example = "/root/mail-server.crt";
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
example = "/var/keys/certs/fullchain.pem";
|
||||
description = ''
|
||||
({option}`mailserver.certificateScheme` == `manual`)
|
||||
Path to the signed X509 certificate including intermediate certificates.
|
||||
|
||||
Location of the certificate.
|
||||
This is commonly referred to as {file}`fullchain.pem`.
|
||||
|
||||
Mutually exclusive with {option}`mailserver.x509.useACMEHost`.
|
||||
'';
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
type = types.path;
|
||||
example = "/root/mail-server.key";
|
||||
privateKeyFile = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "/var/keys/certs/privkey.pem";
|
||||
description = ''
|
||||
({option}`mailserver.certificateScheme` == `manual`)
|
||||
Path to the X509 private key.
|
||||
|
||||
Location of the key file.
|
||||
This is commonly referred to as {file}`privkey.pem`.
|
||||
|
||||
Mutually exclusive with {option}`mailserver.x509.useACMEHost`.
|
||||
'';
|
||||
};
|
||||
|
||||
certificateDirectory = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/certs";
|
||||
description = ''
|
||||
({option}`mailserver.certificateScheme` == `selfsigned`)
|
||||
|
||||
This is the folder where the self-signed certificate will be created. The name is
|
||||
hardcoded to "cert-DOMAIN.pem" and "key-DOMAIN.pem" and the
|
||||
certificate is valid for 10 years.
|
||||
'';
|
||||
};
|
||||
|
||||
acmeCertificateName = mkOption {
|
||||
type = types.str;
|
||||
default = cfg.fqdn;
|
||||
defaultText = literalExpression "config.mailserver.fqdn";
|
||||
example = "example.com";
|
||||
description = ''
|
||||
({option}`mailserver.certificateScheme` == `acme`)
|
||||
|
||||
When the `acme` `certificateScheme` is selected, you can use this option
|
||||
to override the default certificate name. This is useful if you've
|
||||
generated a wildcard certificate, for example.
|
||||
'';
|
||||
};
|
||||
|
||||
enableImap = mkOption {
|
||||
@@ -1247,8 +1185,8 @@ in
|
||||
if failed port 25 protocol smtp for 5 cycles then restart
|
||||
|
||||
check process dovecot with pidfile /var/run/dovecot2/master.pid
|
||||
start program = "${pkgs.systemd}/bin/systemctl start dovecot2"
|
||||
stop program = "${pkgs.systemd}/bin/systemctl stop dovecot2"
|
||||
start program = "${pkgs.systemd}/bin/systemctl start dovecot"
|
||||
stop program = "${pkgs.systemd}/bin/systemctl stop dovecot"
|
||||
if failed host ${cfg.fqdn} port 993 type tcpssl sslauto protocol imap for 5 cycles then restart
|
||||
|
||||
check process rspamd with matching "rspamd: main process"
|
||||
@@ -1502,7 +1440,6 @@ in
|
||||
./mail-server/dovecot.nix
|
||||
./mail-server/postfix.nix
|
||||
./mail-server/rspamd.nix
|
||||
./mail-server/nginx.nix
|
||||
./mail-server/kresd.nix
|
||||
(mkRemovedOptionModule [ "mailserver" "policydSPFExtraConfig" ] ''
|
||||
SPF checking has been migrated to Rspamd, which makes this config redundant. Please look into the rspamd config to migrate your settings.
|
||||
@@ -1531,5 +1468,16 @@ in
|
||||
(mkRemovedOptionModule [ "mailserver" "dmarcReporting" "fromName" ] ''
|
||||
The name in the `FROM` field for DMARC report now uses the `mailserver.systemName`.
|
||||
'')
|
||||
|
||||
(mkRemovedOptionModule [ "mailserver" "certificateDomains" ] ''
|
||||
Configure `security.acme.certs.''${config.mailserver.fqdn}.extraDomains` instead.
|
||||
'')
|
||||
(mkRemovedOptionModule [ "mailserver" "certificateScheme" ] "")
|
||||
(mkRemovedOptionModule [ "mailserver" "certificateDirectory" ] ''
|
||||
Automatic creation of self-signed certificates is no longer supported.
|
||||
'')
|
||||
(mkRenamedOptionModule [ "mailserver" "acmeCertificateName" ] [ "mailserver" "x509" "useACMEHost" ])
|
||||
(mkRenamedOptionModule [ "mailserver" "certificateFile" ] [ "mailserver" "x509" "certificateFile" ])
|
||||
(mkRenamedOptionModule [ "mailserver" "keyFile" ] [ "mailserver" "x509" "privateKeyFile" ])
|
||||
];
|
||||
}
|
||||
|
||||
+3
-3
@@ -45,11 +45,11 @@ For remediating this issue the following steps are required:
|
||||
wcurl https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/raw/master/migrations/nixos-mailserver-migration-03.py
|
||||
chmod +x nixos-mailserver-migration-03.py
|
||||
|
||||
2. Stop the ``dovecot2.service``.
|
||||
2. Stop the ``dovecot.service``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
systemctl stop dovecot2.service
|
||||
systemctl stop dovecot.service
|
||||
|
||||
3. Create a backup or snapshot of your ``mailserver.mailDirectory``, so you can restore
|
||||
should anything go wrong.
|
||||
@@ -101,7 +101,7 @@ This migration is required if you both:
|
||||
|
||||
For remediating this issue the following steps are required:
|
||||
|
||||
1. Stop ``dovecot2.service``.
|
||||
1. Stop ``dovecot.service``.
|
||||
2. Move ``/var/vmail/ldap`` below your ``mailserver.mailDirectory``.
|
||||
3. Update the ``mailserver.stateVersion`` to ``2``.
|
||||
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
NixOS 26.05
|
||||
-----------
|
||||
|
||||
- Certificate handling was simplified. We recommend setting
|
||||
:option:`mailserver.x509.useACMEHost` to a ``security.acme.certs``
|
||||
configuration. If that does not fit your requirements, configure certificate
|
||||
and private key using :option:`mailserver.x509.certificateFile` and
|
||||
:option:`mailserver.x509.privateKeyFile` instead. Support for automatic
|
||||
creation of self-signed certificates has been removed.
|
||||
|
||||
NixOS 25.11
|
||||
-----------
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
(builtins.fetchTarball {
|
||||
# Pick a release version you are interested in and set its hash, e.g.
|
||||
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/nixos-25.11/nixos-mailserver-nixos-25.11.tar.gz";
|
||||
# To get the sha256 of the nixos-mailserver tarball, we can use the nix-prefetch-url command:
|
||||
# release="nixos-25.11"; nix-prefetch-url "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/${release}/nixos-mailserver-${release}.tar.gz" --unpack
|
||||
sha256 = "0000000000000000000000000000000000000000000000000000";
|
||||
})
|
||||
];
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "security@example.com";
|
||||
certs.${config.mailserver.fqdn} = {
|
||||
# Further setup required, check the manual:
|
||||
# https://nixos.org/manual/nixos/stable/#module-security-acme
|
||||
};
|
||||
};
|
||||
|
||||
mailserver = {
|
||||
enable = true;
|
||||
stateVersion = 3;
|
||||
fqdn = "mail.example.com";
|
||||
domains = [ "example.com" ];
|
||||
|
||||
# reference an existing ACME configuration
|
||||
x509.useACMEHost = config.mailserver.fqdn;
|
||||
|
||||
# A list of all login accounts. To create the password hashes, use
|
||||
# nix-shell -p mkpasswd --run 'mkpasswd -s'
|
||||
loginAccounts = {
|
||||
"user1@example.com" = {
|
||||
hashedPasswordFile = "/a/file/containing/a/hashed/password";
|
||||
aliases = [ "postmaster@example.com" ];
|
||||
};
|
||||
"user2@example.com" = {
|
||||
# ...
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
+2
-36
@@ -57,42 +57,8 @@ though there are more possible options (see the `NixOS Mailserver
|
||||
options documentation <options.html>`_), these should be the most
|
||||
common ones.
|
||||
|
||||
.. code:: nix
|
||||
|
||||
{ config, pkgs, ... }: {
|
||||
imports = [
|
||||
(builtins.fetchTarball {
|
||||
# Pick a release version you are interested in and set its hash, e.g.
|
||||
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/nixos-25.11/nixos-mailserver-nixos-25.11.tar.gz";
|
||||
# To get the sha256 of the nixos-mailserver tarball, we can use the nix-prefetch-url command:
|
||||
# release="nixos-25.11"; nix-prefetch-url "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/${release}/nixos-mailserver-${release}.tar.gz" --unpack
|
||||
sha256 = "0000000000000000000000000000000000000000000000000000";
|
||||
})
|
||||
];
|
||||
|
||||
mailserver = {
|
||||
enable = true;
|
||||
stateVersion = 3;
|
||||
fqdn = "mail.example.com";
|
||||
domains = [ "example.com" ];
|
||||
|
||||
# A list of all login accounts. To create the password hashes, use
|
||||
# nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
|
||||
loginAccounts = {
|
||||
"user1@example.com" = {
|
||||
hashedPasswordFile = "/a/file/containing/a/hashed/password";
|
||||
aliases = ["postmaster@example.com"];
|
||||
};
|
||||
"user2@example.com" = { ... };
|
||||
};
|
||||
|
||||
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
|
||||
# down nginx and opens port 80.
|
||||
certificateScheme = "acme-nginx";
|
||||
};
|
||||
security.acme.acceptTerms = true;
|
||||
security.acme.defaults.email = "security@example.com";
|
||||
}
|
||||
.. literalinclude:: ./setup-example.nix
|
||||
:language: nix
|
||||
|
||||
After a ``nixos-rebuild switch`` your server should be running all
|
||||
mail components.
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
"logo\\.png"
|
||||
"conf\\.py"
|
||||
"Makefile"
|
||||
".*\\.nix"
|
||||
".*\\.rst"
|
||||
];
|
||||
buildInputs = [
|
||||
|
||||
@@ -38,6 +38,20 @@ in
|
||||
assertion = config.mailserver.stateVersion != null;
|
||||
message = "The `mailserver.stateVersion` option is not set. Check https://nixos-mailserver.readthedocs.io/en/latest/migrations.html to determine the proper value to initialize it at.";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
config.mailserver.x509.useACMEHost != null
|
||||
-> config.mailserver.x509.certificateFile == null && config.mailserver.x509.privateKeyFile == null;
|
||||
message = "Configuring an ACME certificate (`mailserver.x509.useACMEHost`) is not possible while also passing an existing certificate (`mailserver.x509.certificateFile`, `mailserver.x509.privateKeyFile`).";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
config.mailserver.x509.useACMEHost != null
|
||||
|| (
|
||||
config.mailserver.x509.certificateFile != null && config.mailserver.x509.privateKeyFile != null
|
||||
);
|
||||
message = "Configure either an ACME certificate (`mailserver.x509.useACMEHost`) or pass an existing certificate (`mailserver.x509.certificateFile`, `mailserver.x509.privateKeyFile`).";
|
||||
}
|
||||
]
|
||||
++ lib.optionals config.mailserver.ldap.enable [
|
||||
{
|
||||
@@ -57,7 +71,7 @@ in
|
||||
message = ''
|
||||
Issue: The dovecot homedir for LDAP users was previously not respecting `mailserver.mailDirectory`.
|
||||
Remediation:
|
||||
- Stop the `dovecot2.service`
|
||||
- Stop the `dovecot.service`
|
||||
- Move `/var/vmail/ldap` below your `mailserver.mailDirectory`
|
||||
- Increase the `stateVersion` to 2.
|
||||
|
||||
@@ -75,11 +89,5 @@ in
|
||||
'';
|
||||
}
|
||||
]
|
||||
++ lib.optionals (config.mailserver.certificateScheme != "acme") [
|
||||
{
|
||||
assertion = config.mailserver.acmeCertificateName == config.mailserver.fqdn;
|
||||
message = "When the certificate scheme is not 'acme' (mailserver.certificateScheme != \"acme\"), it is not possible to define mailserver.acmeCertificateName";
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
+12
-20
@@ -24,28 +24,20 @@
|
||||
let
|
||||
cfg = config.mailserver;
|
||||
in
|
||||
{
|
||||
# cert :: PATH
|
||||
certificatePath =
|
||||
if cfg.certificateScheme == "manual" then
|
||||
cfg.certificateFile
|
||||
else if cfg.certificateScheme == "selfsigned" then
|
||||
"${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx" then
|
||||
"${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/fullchain.pem"
|
||||
else
|
||||
throw "unknown certificate scheme";
|
||||
rec {
|
||||
withACME = cfg.x509.useACMEHost != null;
|
||||
|
||||
# key :: PATH
|
||||
keyPath =
|
||||
if cfg.certificateScheme == "manual" then
|
||||
cfg.keyFile
|
||||
else if cfg.certificateScheme == "selfsigned" then
|
||||
"${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx" then
|
||||
"${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/key.pem"
|
||||
x509CertificateFile =
|
||||
if withACME then
|
||||
"${config.security.acme.certs.${cfg.x509.useACMEHost}.directory}/fullchain.pem"
|
||||
else
|
||||
throw "unknown certificate scheme";
|
||||
cfg.x509.certificateFile;
|
||||
|
||||
x509PrivateKeyFile =
|
||||
if withACME then
|
||||
"${config.security.acme.certs.${cfg.x509.useACMEHost}.directory}/key.pem"
|
||||
else
|
||||
cfg.x509.privateKeyFile;
|
||||
|
||||
passwordFiles =
|
||||
let
|
||||
|
||||
+12
-2
@@ -196,6 +196,12 @@ in
|
||||
multiple languages are present in the configuration.
|
||||
'';
|
||||
|
||||
security.acme.certs = lib.mkIf withACME {
|
||||
${cfg.x509.useACMEHost} = {
|
||||
reloadServices = [ "dovecot.service" ];
|
||||
};
|
||||
};
|
||||
|
||||
# for sieve-test. Shelling it in on demand usually doesnt' work, as it reads
|
||||
# the global config and tries to open shared libraries configured in there,
|
||||
# which are usually not compatible.
|
||||
@@ -216,8 +222,8 @@ in
|
||||
mailGroup = cfg.vmailGroupName;
|
||||
mailUser = cfg.vmailUserName;
|
||||
mailLocation = dovecotMaildir;
|
||||
sslServerCert = certificatePath;
|
||||
sslServerKey = keyPath;
|
||||
sslServerCert = x509CertificateFile;
|
||||
sslServerKey = x509PrivateKeyFile;
|
||||
enableDHE = lib.mkDefault false;
|
||||
enableLmtp = true;
|
||||
mailPlugins.globally.enable = lib.optionals cfg.fullTextSearch.enable [
|
||||
@@ -455,6 +461,10 @@ in
|
||||
${genPasswdScript}
|
||||
''
|
||||
+ (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
|
||||
reloadTriggers = lib.mkIf (!withACME) [
|
||||
x509CertificateFile
|
||||
x509PrivateKeyFile
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.postfix.restartTriggers = [
|
||||
|
||||
@@ -26,14 +26,11 @@ let
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
environment.systemPackages =
|
||||
with pkgs;
|
||||
[
|
||||
environment.systemPackages = with pkgs; [
|
||||
dovecot
|
||||
openssh
|
||||
postfix
|
||||
rspamd
|
||||
]
|
||||
++ (if cfg.certificateScheme == "selfsigned" then [ openssl ] else [ ]);
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@ in
|
||||
++ lib.optional cfg.enableImapSsl 993
|
||||
++ lib.optional cfg.enablePop3 110
|
||||
++ lib.optional cfg.enablePop3Ssl 995
|
||||
++ lib.optional cfg.enableManageSieve 4190
|
||||
++ lib.optional (cfg.certificateScheme == "acme-nginx") 80;
|
||||
++ lib.optional cfg.enableManageSieve 4190;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# nixos-mailserver: a simple mail server
|
||||
# Copyright (C) 2016-2018 Robin Raymond
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
{
|
||||
config,
|
||||
options,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
with (import ./common.nix {
|
||||
inherit
|
||||
config
|
||||
options
|
||||
lib
|
||||
pkgs
|
||||
;
|
||||
});
|
||||
|
||||
let
|
||||
cfg = config.mailserver;
|
||||
in
|
||||
{
|
||||
config =
|
||||
lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"))
|
||||
{
|
||||
services.nginx = lib.mkIf (cfg.certificateScheme == "acme-nginx") {
|
||||
enable = true;
|
||||
virtualHosts."${cfg.fqdn}" = {
|
||||
serverName = cfg.fqdn;
|
||||
serverAliases = cfg.certificateDomains;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
};
|
||||
|
||||
security.acme.certs."${cfg.acmeCertificateName}" = {
|
||||
extraDomainNames = lib.mkIf (cfg.certificateScheme == "acme") cfg.certificateDomains;
|
||||
reloadServices = [
|
||||
"postfix.service"
|
||||
"dovecot.service"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
+13
-2
@@ -279,6 +279,17 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
security.acme.certs = lib.mkIf withACME {
|
||||
${cfg.x509.useACMEHost} = {
|
||||
reloadServices = [ "postfix.service" ];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.postfix.reloadTriggers = lib.mkIf (!withACME) [
|
||||
x509CertificateFile
|
||||
x509PrivateKeyFile
|
||||
];
|
||||
|
||||
systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable {
|
||||
preStart = ''
|
||||
${appendPwdInVirtualMailboxMap}
|
||||
@@ -364,8 +375,8 @@ in
|
||||
|
||||
# The X509 private key followed by the corresponding certificate
|
||||
smtpd_tls_chain_files = [
|
||||
"${keyPath}"
|
||||
"${certificatePath}"
|
||||
"${x509PrivateKeyFile}"
|
||||
"${x509CertificateFile}"
|
||||
];
|
||||
|
||||
# TLS for incoming mail is optional
|
||||
|
||||
@@ -40,6 +40,7 @@ let
|
||||
pkgs.writeShellScript "dkim-keygen-${domain}" ''
|
||||
if [ ! -f "${privateKey}" ]
|
||||
then
|
||||
export PATH=${lib.makeBinPath [ pkgs.openssl ]}
|
||||
${lib.getExe' pkgs.rspamd "rspamadm"} dkim_keygen \
|
||||
--domain "${domain}" \
|
||||
--selector "${cfg.dkimSelector}" \
|
||||
|
||||
+7
-38
@@ -33,47 +33,16 @@ with (import ./common.nix {
|
||||
|
||||
let
|
||||
cfg = config.mailserver;
|
||||
certificatesDeps =
|
||||
if cfg.certificateScheme == "manual" then
|
||||
[ ]
|
||||
else if cfg.certificateScheme == "selfsigned" then
|
||||
[ "mailserver-selfsigned-certificate.service" ]
|
||||
else
|
||||
[ "acme-finished-${cfg.fqdn}.target" ];
|
||||
|
||||
certificateDeps = lib.optionals withACME [
|
||||
"acme-order-renew-${cfg.x509.useACMEHost}.service"
|
||||
];
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
# Create self signed certificate
|
||||
systemd.services.mailserver-selfsigned-certificate =
|
||||
lib.mkIf (cfg.certificateScheme == "selfsigned")
|
||||
{
|
||||
after = [ "local-fs.target" ];
|
||||
script = ''
|
||||
# Create certificates if they do not exist yet
|
||||
dir="${cfg.certificateDirectory}"
|
||||
fqdn="${cfg.fqdn}"
|
||||
[[ $fqdn == /* ]] && fqdn=$(< "$fqdn")
|
||||
key="$dir/key-${cfg.fqdn}.pem";
|
||||
cert="$dir/cert-${cfg.fqdn}.pem";
|
||||
|
||||
if [[ ! -f $key || ! -f $cert ]]; then
|
||||
mkdir -p "${cfg.certificateDirectory}"
|
||||
(umask 077; "${pkgs.openssl}/bin/openssl" genrsa -out "$key" 2048) &&
|
||||
"${pkgs.openssl}/bin/openssl" req -new -key "$key" -x509 -subj "/CN=$fqdn" \
|
||||
-days 3650 -out "$cert"
|
||||
fi
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
PrivateTmp = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Create maildir folder before dovecot startup
|
||||
systemd.services.dovecot = {
|
||||
wants = certificatesDeps;
|
||||
after = certificatesDeps;
|
||||
wants = certificateDeps;
|
||||
after = certificateDeps;
|
||||
preStart =
|
||||
let
|
||||
directories = lib.strings.escapeShellArgs (
|
||||
@@ -93,12 +62,12 @@ in
|
||||
|
||||
# Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
|
||||
systemd.services.postfix = {
|
||||
wants = certificatesDeps;
|
||||
wants = certificateDeps;
|
||||
after = [
|
||||
"dovecot.service"
|
||||
]
|
||||
++ lib.optional cfg.dkimSigning "rspamd.service"
|
||||
++ certificatesDeps;
|
||||
++ certificateDeps;
|
||||
requires = [ "dovecot.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ header = """
|
||||
"""
|
||||
|
||||
template = """
|
||||
({key})=
|
||||
`````{{option}} {key}
|
||||
{description}
|
||||
|
||||
@@ -25,7 +26,7 @@ options = json.load(f)
|
||||
|
||||
groups = [
|
||||
"mailserver.loginAccounts",
|
||||
"mailserver.certificate",
|
||||
"mailserver.x509",
|
||||
"mailserver.dkim",
|
||||
"mailserver.srs",
|
||||
"mailserver.dmarcReporting",
|
||||
|
||||
+2
-2
@@ -248,7 +248,7 @@
|
||||
with subtest("no warnings or errors"):
|
||||
server.fail("journalctl -u postfix | grep -i error >&2")
|
||||
server.fail("journalctl -u postfix | grep -i warning >&2")
|
||||
server.fail("journalctl -u dovecot2 | grep -i error >&2")
|
||||
server.fail("journalctl -u dovecot2 | grep -i warning >&2")
|
||||
server.fail("journalctl -u dovecot | grep -i error >&2")
|
||||
server.fail("journalctl -u dovecot | grep -i warning >&2")
|
||||
'';
|
||||
}
|
||||
|
||||
+10
-7
@@ -26,7 +26,10 @@
|
||||
./lib/config.nix
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [ netcat ];
|
||||
environment.systemPackages = with pkgs; [
|
||||
netcat
|
||||
openssl
|
||||
];
|
||||
|
||||
virtualisation.memorySize = 1024;
|
||||
|
||||
@@ -486,9 +489,9 @@
|
||||
server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
|
||||
|
||||
client.succeed("imap-mark-spam >&2")
|
||||
server.wait_until_succeeds("journalctl -u dovecot -u dovecot2 | grep -i rspamd-learn-spam.sh >&2")
|
||||
server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-spam.sh >&2")
|
||||
client.succeed("imap-mark-ham >&2")
|
||||
server.wait_until_succeeds("journalctl -u dovecot -u dovecot2 | grep -i rspamd-learn-ham.sh >&2")
|
||||
server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-ham.sh >&2")
|
||||
|
||||
with subtest("full text search and indexation"):
|
||||
# send 2 email from user2 to user1
|
||||
@@ -506,9 +509,9 @@
|
||||
# should fail because this folder is not indexed
|
||||
client.fail("search Junk a >&2")
|
||||
# check that search really goes through the indexer
|
||||
server.succeed("journalctl -u dovecot -u dovecot2 | grep 'fts-flatcurve(INBOX): Query ' >&2")
|
||||
server.succeed("journalctl -u dovecot | grep 'fts-flatcurve(INBOX): Query ' >&2")
|
||||
# check that Junk is not indexed
|
||||
server.fail("journalctl -u dovecot -u dovecot2 | grep 'fts-flatcurve(JUNK): Indexing ' >&2")
|
||||
server.fail("journalctl -u dovecot | grep 'fts-flatcurve(JUNK): Indexing ' >&2")
|
||||
|
||||
with subtest("dmarc reporting"):
|
||||
server.systemctl("start rspamd-dmarc-reporter.service")
|
||||
@@ -516,10 +519,10 @@
|
||||
with subtest("no warnings or errors"):
|
||||
server.fail("journalctl -u postfix | grep -i error >&2")
|
||||
server.fail("journalctl -u postfix | grep -i warning >&2")
|
||||
server.fail("journalctl -u dovecot -u dovecot2 | grep -v 'imap-login: Debug: SSL error: Connection closed' | grep -i error >&2")
|
||||
server.fail("journalctl -u dovecot | grep -v 'imap-login: Debug: SSL error: Connection closed' | grep -i error >&2")
|
||||
# harmless ? https://dovecot.org/pipermail/dovecot/2020-August/119575.html
|
||||
server.fail(
|
||||
"journalctl -u dovecot -u dovecot2 | \
|
||||
"journalctl -u dovecot | \
|
||||
grep -v 'Expunged message reappeared, giving a new UID' | \
|
||||
grep -v 'Time moved forwards' | \
|
||||
grep -i warning >&2"
|
||||
|
||||
+2
-1
@@ -38,7 +38,7 @@ let
|
||||
inherit password;
|
||||
}
|
||||
''
|
||||
mkpasswd -sm bcrypt <<<"$password" > $out
|
||||
mkpasswd -s <<<"$password" > $out
|
||||
'';
|
||||
|
||||
hashedPasswordFile = hashPassword "my-password";
|
||||
@@ -113,6 +113,7 @@ in
|
||||
''
|
||||
machine.start()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("dovecot.service")
|
||||
|
||||
# Regression test for https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/issues/205
|
||||
with subtest("mail forwarded can are locally kept"):
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBizCCATGgAwIBAgIUN4ncJfMVIQSSurMkdE73x4aefTMwCgYIKoZIzj0EAwIw
|
||||
GzEZMBcGA1UEAwwQdGVzdC5sb2NhbGRvbWFpbjAeFw0yNTEwMTgyMTQ4MTNaFw0z
|
||||
NTEwMTYyMTQ4MTNaMBsxGTAXBgNVBAMMEHRlc3QubG9jYWxkb21haW4wWTATBgcq
|
||||
hkjOPQIBBggqhkjOPQMBBwNCAARCJUj4j7eC/7Xso3REUscqHlWPvW9zvl5I6TIy
|
||||
zEXFsWxM0QxMuNW4oXE56UiCyJklcpk0JfQUGat+kKQqSUJyo1MwUTAdBgNVHQ4E
|
||||
FgQUW3CnmBf3n/Y30vfj3ERsIQnXu9QwHwYDVR0jBBgwFoAUW3CnmBf3n/Y30vfj
|
||||
3ERsIQnXu9QwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAhwAi
|
||||
K4xdr8KxD5xRvvzShheh48i8X7NtBIQ3bd01Jx4CIG/kYTDK5nDZri7UYOMsgz2l
|
||||
iWss56p2dGWTL7LrBHgM
|
||||
-----END CERTIFICATE-----
|
||||
@@ -10,6 +10,17 @@
|
||||
# Keep testing submission with explicit TLS
|
||||
mailserver.enableSubmission = true;
|
||||
|
||||
# Certificate created for testing purposes from RFC9500 private key
|
||||
# https://datatracker.ietf.org/doc/rfc9500/
|
||||
# openssl req -x509 -new -key key.pem \
|
||||
# -subj "/CN=test.localdomain" \
|
||||
# -sha256 -days 3650 \
|
||||
# -out cert.pem
|
||||
mailserver.x509 = {
|
||||
certificateFile = "${./cert.pem}";
|
||||
privateKeyFile = "${./key.pem}";
|
||||
};
|
||||
|
||||
# Enable second CPU core
|
||||
virtualisation.cores = lib.mkDefault 2;
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIObLW92AqkWunJXowVR2Z5/+yVPBaFHnEedDk5WJxk/BoAoGCCqGSM49
|
||||
AwEHoUQDQgAEQiVI+I+3gv+17KN0RFLHKh5Vj71vc75eSOkyMsxFxbFsTNEMTLjV
|
||||
uKFxOelIgsiZJXKZNCX0FBmrfpCkKklCcg==
|
||||
-----END EC PRIVATE KEY-----
|
||||
+4
-3
@@ -15,7 +15,7 @@ let
|
||||
inherit password;
|
||||
}
|
||||
''
|
||||
mkpasswd -sm bcrypt <<<"$password" > $out
|
||||
mkpasswd -s <<<"$password" > $out
|
||||
'';
|
||||
|
||||
password = pkgs.writeText "password" "password";
|
||||
@@ -93,8 +93,9 @@ in
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
domain1.wait_for_unit("multi-user.target")
|
||||
domain2.wait_for_unit("multi-user.target")
|
||||
for domain in [domain1, domain2]:
|
||||
domain.wait_for_unit("multi-user.target")
|
||||
domain.wait_for_unit("dovecot.service")
|
||||
|
||||
# TODO put this blocking into the systemd units?
|
||||
domain1.wait_until_succeeds(
|
||||
|
||||
Reference in New Issue
Block a user