nixos/radicale: harden systemd unit

Changed files
+51 -8
nixos
doc
manual
release-notes
modules
services
networking
tests
+7
nixos/doc/manual/release-notes/rl-2105.xml
···
automatically based on <option>system.stateVersion</option>, the latest
version is always used because old versions are not officially supported.
</para>
+
<para>
+
Furthermore, Radicale's systemd unit was hardened which might break some
+
deployments. In particular, a non-default
+
<literal>filesystem_folder</literal> has to be added to
+
<option>systemd.services.radicale.serviceConfig.ReadWritePaths</option> if
+
the deprecated <option>services.radicale.config</option> is used.
+
</para>
</listitem>
</itemizedlist>
</section>
+39 -8
nixos/modules/services/networking/radicale.nix
···
rightsFile = format.generate "radicale.rights" cfg.rights;
+
bindLocalhost = cfg.settings != { } && !hasAttrByPath [ "server" "hosts" ] cfg.settings;
+
in {
options.services.radicale = {
enable = mkEnableOption "Radicale CalDAV and CardDAV server";
···
environment.systemPackages = [ pkg ];
-
users.users.radicale =
-
{ uid = config.ids.uids.radicale;
-
description = "radicale user";
-
home = "/var/lib/radicale";
-
createHome = true;
-
};
+
users.users.radicale.uid = config.ids.uids.radicale;
-
users.groups.radicale =
-
{ gid = config.ids.gids.radicale; };
+
users.groups.radicale.gid = config.ids.gids.radicale;
systemd.services.radicale = {
description = "A Simple Calendar and Contact Server";
···
));
User = "radicale";
Group = "radicale";
+
StateDirectory = "radicale/collections";
+
StateDirectoryMode = "0750";
+
# Hardening
+
CapabilityBoundingSet = [ "" ];
+
DeviceAllow = [ "/dev/stdin" ];
+
DevicePolicy = "strict";
+
IPAddressAllow = mkIf bindLocalhost "localhost";
+
IPAddressDeny = mkIf bindLocalhost "any";
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
NoNewPrivileges = true;
+
PrivateDevices = true;
+
PrivateTmp = true;
+
PrivateUsers = true;
+
ProcSubset = "pid";
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectProc = "invisible";
+
ProtectSystem = "strict";
+
ReadWritePaths = lib.optional
+
(hasAttrByPath [ "storage" "filesystem_folder" ] cfg.settings)
+
cfg.settings.storage.filesystem_folder;
+
RemoveIPC = true;
+
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+
RestrictNamespaces = true;
+
RestrictRealtime = true;
+
RestrictSUIDSGID = true;
+
SystemCallArchitectures = "native";
+
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+
UMask = "0027";
};
};
};
+5
nixos/tests/radicale.nix
···
with subtest("Test web interface"):
machine.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/")
+
+
with subtest("Test security"):
+
output = machine.succeed("systemd-analyze security radicale.service")
+
machine.log(output)
+
assert output[-9:-1] == "SAFE :-}"
'';
})