Compare commits

...

11 Commits

Author SHA1 Message Date
Jakub Skokan
5914595fb6 Allow TLSv1 for compatibility with older devices 2022-12-02 09:39:11 +01:00
Antoine Eiche
70a970f5a0 Release 22.11 2022-11-30 21:03:13 +01:00
Antoine Eiche
31eadb6388 doc: regenerate it 2022-11-30 21:03:13 +01:00
Antoine Eiche
033b3d2a45 Removing 22.05 release
Because of some incompabilities with the 22.11 release.
2022-11-30 20:59:39 +01:00
Naïm Favier
694e7d34f6
docs: option docs improvements
- add missing description and defaultText fields
- add dmarcReporting option group
- render examples
2022-11-30 12:30:29 +01:00
Martin Weinelt
fe36e7ae0d rspamd: allow configuring dmarc reporting
Enabling collects DMARC results in Redis and sends out aggregated
reports (RUA) on a daily basis.
2022-11-27 20:34:38 +00:00
Antoine Eiche
3f0b7a1b5c ci: pin nixpkgs to 22.05
Because hydra-cli build is currently broken on unstable.
2022-11-27 20:43:25 +01:00
Antoine Eiche
737eb4f398 docs: explicitly mention a reverse DNS entry is required
Fixes https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/issues/234
2022-11-27 19:14:52 +00:00
Linus Heckemann
a40e9c3abb htpasswd -> mkpasswd 2022-11-27 19:14:22 +00:00
Martin Weinelt
004c229ca4
Convert minimal test to python test driver 2022-07-19 23:54:04 +02:00
Antoine Eiche
f535d8123c Release 22.05 2022-06-22 22:39:06 +02:00
19 changed files with 643 additions and 253 deletions

View File

@ -3,11 +3,11 @@ hydra-pr:
- merge_requests
image: nixos/nix
script:
- nix --extra-experimental-features nix-command run -f channel:nixos-unstable hydra-cli -- -H https://hydra.nix-community.org jobset-wait simple-nixos-mailserver ${CI_MERGE_REQUEST_IID}
- nix-shell -I nixpkgs=channel:nixos-22.05 -p hydra-cli --run 'hydra-cli -H https://hydra.nix-community.org jobset-wait simple-nixos-mailserver ${CI_MERGE_REQUEST_IID}'
hydra-master:
only:
- master
image: nixos/nix
script:
- nix --extra-experimental-features nix-command run -f channel:nixos-unstable hydra-cli -- -H https://hydra.nix-community.org jobset-wait simple-nixos-mailserver master
- nix-shell -I nixpkgs=channel:nixos-22.05 -p hydra-cli --run 'hydra-cli -H https://hydra.nix-community.org jobset-wait simple-nixos-mailserver master'

View File

@ -32,6 +32,8 @@ let
desc = prJobsets // {
"master" = mkFlakeJobset "master";
"nixos-22.05" = mkFlakeJobset "nixos-22.05";
"nixos-22.11" = mkFlakeJobset "nixos-22.11";
};
log = {

View File

@ -8,14 +8,14 @@
For each NixOS release, we publish a branch. You then have to use the
SNM branch corresponding to your NixOS version.
* For NixOS 22.05
- Use the [SNM branch `nixos-22.05`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-22.05)
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-22.05/)
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-22.05/release-notes.html#nixos-22-05)
* For NixOS 21.11
- Use the [SNM branch `nixos-21.11`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-21.11)
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-21.11/)
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-21.11/release-notes.html#nixos-21-11)
* For NixOS 21.05
- Use the [SNM branch `nixos-21.05`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-21.05)
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-21.05/)
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-21.05/release-notes.html#nixos-21-05)
* For NixOS unstable
- Use the [SNM branch `master`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/master)
- [Documentation](https://nixos-mailserver.readthedocs.io/en/latest/)
@ -96,7 +96,7 @@ D9FE 4119 F082 6F15 93BD BD36 6162 DBA5 635E A16A
domains = [ "example.com" "example2.com" ];
loginAccounts = {
"user1@example.com" = {
# nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2 > /hashed/password/file/location
# nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt' > /hashed/password/file/location
hashedPasswordFile = "/hashed/password/file/location";
aliases = [

View File

@ -72,10 +72,10 @@ in
default = null;
example = "$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/";
description = ''
The user's hashed password. Use `htpasswd` as follows
The user's hashed password. Use `mkpasswd` as follows
```
nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2
nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
```
Warning: this is stored in plaintext in the Nix store!
@ -88,10 +88,10 @@ in
default = null;
example = "/run/keys/user1-passwordhash";
description = ''
A file containing the user's hashed password. Use `htpasswd` as follows
A file containing the user's hashed password. Use `mkpasswd` as follows
```
nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2
nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
```
'';
};
@ -184,11 +184,11 @@ in
};
description = ''
The login account of the domain. Every account is mapped to a unix user,
e.g. `user1@example.com`. To generate the passwords use `htpasswd` as
e.g. `user1@example.com`. To generate the passwords use `mkpasswd` as
follows
```
nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2
nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
```
'';
default = {};
@ -627,6 +627,67 @@ in
'';
};
dmarcReporting = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to send out aggregated, daily DMARC reports in response to incoming
mail, when the sender domain defines a DMARC policy including the RUA tag.
This is helpful for the mail ecosystem, because it allows third parties to
get notified about SPF/DKIM violations originating from their sender domains.
See https://rspamd.com/doc/modules/dmarc.html#reporting
'';
};
localpart = mkOption {
type = types.str;
default = "dmarc-noreply";
example = "dmarc-report";
description = ''
The local part of the email address used for outgoing DMARC reports.
'';
};
domain = mkOption {
type = types.enum (cfg.domains);
example = "example.com";
description = ''
The domain from which outgoing DMARC reports are served.
'';
};
email = mkOption {
type = types.str;
default = with cfg.dmarcReporting; "${localpart}@${domain}";
defaultText = literalExpression ''"''${localpart}@''${domain}"'';
readOnly = true;
description = ''
The email address used for outgoing DMARC reports. Read-only.
'';
};
organizationName = mkOption {
type = types.str;
example = "ACME Corp.";
description = ''
The name of your organization used in the <literal>org_name</literal> attribute in
DMARC reports.
'';
};
fromName = mkOption {
type = types.str;
default = cfg.dmarcReporting.organizationName;
defaultText = literalExpression "organizationName";
description = ''
The sender name for DMARC reports. Defaults to the organization name.
'';
};
};
debug = mkOption {
type = types.bool;
default = false;

View File

@ -4,8 +4,8 @@ Add Radicale
Configuration by @dotlambda
Starting with Radicale 3 (first introduced in NixOS 20.09) the traditional
crypt passwords, as generated by `mkpasswd`, are no longer supported. Instead
bcrypt passwords have to be used which can be generated using `htpasswd`.
crypt passwords are no longer supported. Instead bcrypt passwords
have to be used. These can still be generated using `mkpasswd -m bcrypt`.
.. code:: nix

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,12 @@
Release Notes
=============
NixOS 22.05
-----------
- Make NixOS Mailserver options discoverable from search.nixos.org
- Add a roundcube setup guide in the documentation
NixOS 21.11
-----------

View File

@ -70,7 +70,7 @@ these should be the most common ones.
domains = [ "example.com" ];
# A list of all login accounts. To create the password hashes, use
# nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2
# nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
loginAccounts = {
"user1@example.com" = {
hashedPasswordFile = "/a/file/containing/a/hashed/password";
@ -96,7 +96,14 @@ Set rDNS (reverse DNS) entry for server
Wherever you have rented your server, you should be able to set reverse
DNS entries for the IPs you own. Add an entry resolving ``1.2.3.4``
to ``mail.example.com``
to ``mail.example.com``.
.. warning::
We don't recommend setting up a mail server if you are not able to
set a reverse DNS on your public IP because sent emails would be
mostly marked as spam. Note that many residential ISP providers
don't allow you to set a reverse DNS entry.
You can check this with

22
flake.lock generated
View File

@ -18,11 +18,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1642635915,
"narHash": "sha256-vabPA32j81xBO5m3+qXndWp5aqepe+vu96Wkd9UnngM=",
"lastModified": 1669542132,
"narHash": "sha256-DRlg++NJAwPh8io3ExBJdNW7Djs3plVI5jgYQ+iXAZQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6d8215281b2f87a5af9ed7425a26ac575da0438f",
"rev": "a115bb9bd56831941be3776c8a94005867f316a7",
"type": "github"
},
"original": {
@ -31,10 +31,26 @@
"type": "indirect"
}
},
"nixpkgs-22_11": {
"locked": {
"lastModified": 1669558522,
"narHash": "sha256-yqxn+wOiPqe6cxzOo4leeJOp1bXE/fjPEi/3F/bBHv8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ce5fe99df1f15a09a91a86be9738d68fadfbad82",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.11",
"type": "indirect"
}
},
"root": {
"inputs": {
"blobs": "blobs",
"nixpkgs": "nixpkgs",
"nixpkgs-22_11": "nixpkgs-22_11",
"utils": "utils"
}
},

View File

@ -4,13 +4,14 @@
inputs = {
utils.url = "github:numtide/flake-utils";
nixpkgs.url = "flake:nixpkgs/nixos-unstable";
nixpkgs-22_11.url = "flake:nixpkgs/nixos-22.11";
blobs = {
url = "gitlab:simple-nixos-mailserver/blobs";
flake = false;
};
};
outputs = { self, utils, blobs, nixpkgs }: let
outputs = { self, utils, blobs, nixpkgs, nixpkgs-22_11 }: let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
releases = [
@ -18,6 +19,10 @@
name = "unstable";
pkgs = nixpkgs.legacyPackages.${system};
}
{
name = "22.11";
pkgs = nixpkgs-22_11.legacyPackages.${system};
}
];
testNames = [
"internal"
@ -54,7 +59,16 @@
# don't care about this package but it is part of the
# NixOS module evaluation)
nixpkgs.config.allowBroken = true;
mailserver.fqdn = "mx.example.com";
mailserver = {
fqdn = "mx.example.com";
domains = [
"example.com"
];
dmarcReporting = {
organizationName = "Example Corp";
domain = "example.com";
};
};
}
];

View File

@ -192,7 +192,7 @@ in
mail_access_groups = ${vmailGroupName}
ssl = required
ssl_min_protocol = TLSv1.2
ssl_min_protocol = TLSv1
ssl_prefer_server_ciphers = yes
service lmtp {

View File

@ -206,10 +206,10 @@ in
smtpd_tls_eecdh_grade = "ultra";
# Disable obselete protocols
smtpd_tls_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
smtp_tls_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
smtpd_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
smtp_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
smtpd_tls_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, !SSLv2, !SSLv3";
smtp_tls_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, !SSLv2, !SSLv3";
smtpd_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, !SSLv2, !SSLv3";
smtp_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, !SSLv2, !SSLv3";
smtp_tls_ciphers = "high";
smtpd_tls_ciphers = "high";

View File

@ -56,6 +56,17 @@ in
# Disable outbound email signing, we use opendkim for this
enabled = false;
''; };
"dmarc.conf" = { text = ''
${lib.optionalString cfg.dmarcReporting.enable ''
reporting {
enabled = true;
email = "${cfg.dmarcReporting.email}";
domain = "${cfg.dmarcReporting.domain}";
org_name = "${cfg.dmarcReporting.organizationName}";
from_name = "${cfg.dmarcReporting.fromName}";
msgid_from = "dmarc-rua";
}''}
''; };
};
overrides = {
@ -108,6 +119,64 @@ in
after = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service");
};
systemd.services.rspamd-dmarc-reporter = lib.optionalAttrs (cfg.dmarcReporting.enable) {
# Explicitly select yesterday's date to work around broken
# default behaviour when called without a date.
# https://github.com/rspamd/rspamd/issues/4062
script = ''
${pkgs.rspamd}/bin/rspamadm dmarc_report $(date -d "yesterday" "+%Y%m%d")
'';
serviceConfig = {
User = "${config.services.rspamd.user}";
Group = "${config.services.rspamd.group}";
AmbientCapabilities = [];
CapabilityBoundingSet = "";
DevicePolicy = "closed";
IPAddressAllow = "localhost";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
};
systemd.timers.rspamd-dmarc-reporter = lib.optionalAttrs (cfg.dmarcReporting.enable) {
description = "Daily delivery of aggregated DMARC reports";
wantedBy = [
"timers.target"
];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = 86400;
FixedRandomDelay = true;
};
};
systemd.services.postfix = {
after = [ rspamdSocket ];
requires = [ rspamdSocket ];

View File

@ -1,6 +1,7 @@
import json
import sys
import re
import textwrap
header = """
Mailserver Options
@ -19,6 +20,7 @@ template = """
{type}
{default}
{example}
"""
f = open(sys.argv[1])
@ -30,36 +32,44 @@ options = {k: v for k, v in options.items()
groups = ["mailserver.loginAccount",
"mailserver.certificate",
"mailserver.dkim",
"mailserver.dmarcReporting",
"mailserver.fullTextSearch",
"mailserver.redis",
"mailserver.monitoring",
"mailserver.backup",
"mailserver.borg"]
def render_option_value(opt, attr):
if attr in opt:
if isinstance(opt[attr], dict) and '_type' in opt[attr]:
if opt[attr]['_type'] == 'literalExpression':
if '\n' in opt[attr]['text']:
res = '\n.. code:: nix\n\n' + textwrap.indent(opt[attr]['text'], ' ') + '\n'
else:
res = '``{}``'.format(opt[attr]['text'])
elif opt[attr]['_type'] == 'literalDocBook':
res = opt[attr]['text']
else:
s = str(opt[attr])
if s == "":
res = '``""``'
elif '\n' in s:
res = '\n.. code::\n\n' + textwrap.indent(s, ' ') + '\n'
else:
res = '``{}``'.format(s)
res = '- ' + attr + ': ' + res
else:
res = ""
return res
def print_option(name, value):
if 'default' in value:
if value['default'] == "":
default = '``""``'
elif isinstance(value['default'], dict) and '_type' in value['default']:
if value['default']['_type'] == 'literalExpression':
default = '``{}``'.format(value['default']['text'])
if value['default']['_type'] == 'literalDocBook':
default = value['default']['text']
else:
default = '``{}``'.format(value['default'])
# Some default values contains OUTPUTPATHS which make the
# output not stable across nixpkgs updates.
default = re.sub('/nix/store/[\w.-]*/', '<OUTPUT-PATH>/', default) # noqa
default = '- Default: ' + default
else:
default = ""
print(template.format(
key=name,
line="-"*len(name),
description=value['description'],
type="- Type: ``{}``".format(value['type']),
default=default))
description=value['description'] or "",
type="- type: ``{}``".format(value['type']),
default=render_option_value(value, 'default'),
example=render_option_value(value, 'example')))
print(header)

View File

@ -189,10 +189,10 @@ pkgs.nixosTest {
# TODO put this blocking into the systemd units? I am not sure if rspamd already waits for the clamd socket.
server.wait_until_succeeds(
"set +e; timeout 1 ${nodes.server.pkgs.netcat}/bin/nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
"set +e; timeout 1 ${nodes.server.nixpkgs.pkgs.netcat}/bin/nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
)
server.wait_until_succeeds(
"set +e; timeout 1 ${nodes.server.pkgs.netcat}/bin/nc -U /run/clamav/clamd.ctl < /dev/null; [ $? -eq 124 ]"
"set +e; timeout 1 ${nodes.server.nixpkgs.pkgs.netcat}/bin/nc -U /run/clamav/clamd.ctl < /dev/null; [ $? -eq 124 ]"
)
client.execute("cp -p /etc/root/.* ~/")

View File

@ -43,6 +43,11 @@ pkgs.nixosTest {
domains = [ "example.com" "example2.com" ];
rewriteMessageId = true;
dkimKeyBits = 1535;
dmarcReporting = {
enable = true;
domain = "example.com";
organizationName = "ACME Corp";
};
loginAccounts = {
"user1@example.com" = {
@ -345,7 +350,7 @@ pkgs.nixosTest {
# TODO put this blocking into the systemd units?
server.wait_until_succeeds(
"set +e; timeout 1 ${nodes.server.pkgs.netcat}/bin/nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
"set +e; timeout 1 ${nodes.server.nixpkgs.pkgs.netcat}/bin/nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
)
client.execute("cp -p /etc/root/.* ~/")
@ -494,6 +499,10 @@ pkgs.nixosTest {
# check that Junk is not indexed
server.fail("journalctl -u dovecot2 | grep 'indexer-worker' | grep -i 'JUNK' >&2")
with subtest("dmarc reporting"):
server.systemctl("start rspamd-dmarc-reporter.service")
server.wait_until_succeeds("journalctl -eu rspamd-dmarc-reporter.service -o cat | grep -q 'No reports for '")
with subtest("no warnings or errors"):
server.fail("journalctl -u postfix | grep -i error >&2")
server.fail("journalctl -u postfix | grep -i warning >&2")

View File

@ -29,8 +29,8 @@ let
hashPassword = password: pkgs.runCommand
"password-${password}-hashed"
{ buildInputs = [ pkgs.apacheHttpd ]; } ''
htpasswd -nbB "" "${password}" | cut -d: -f2 > $out
{ buildInputs = [ pkgs.mkpasswd ]; inherit password; } ''
mkpasswd -sm bcrypt <<<"$password" > $out
'';
hashedPasswordFile = hashPassword "my-password";

View File

@ -14,9 +14,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
import <nixpkgs/nixos/tests/make-test.nix> {
import <nixpkgs/nixos/tests/make-test-python.nix> {
machine =
nodes.machine =
{ config, pkgs, ... }:
{
imports = [
@ -26,6 +26,6 @@ import <nixpkgs/nixos/tests/make-test.nix> {
testScript =
''
$machine->waitForUnit("multi-user.target");
machine.wait_for_unit("multi-user.target");
'';
}

View File

@ -5,9 +5,9 @@
let
hashPassword = password: pkgs.runCommand
"password-${password}-hashed"
{ buildInputs = [ pkgs.apacheHttpd ]; }
{ buildInputs = [ pkgs.mkpasswd ]; inherit password; }
''
htpasswd -nbB "" "${password}" | cut -d: -f2 > $out
mkpasswd -sm bcrypt <<<"$password" > $out
'';
password = pkgs.writeText "password" "password";