python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / security / kanidm.nix
blob788e06ffecf01afe888feac612263dafaf8ee0eb
1 { config, lib, options, pkgs, ... }:
2 let
3   cfg = config.services.kanidm;
4   settingsFormat = pkgs.formats.toml { };
5   # Remove null values, so we can document optional values that don't end up in the generated TOML file.
6   filterConfig = lib.converge (lib.filterAttrsRecursive (_: v: v != null));
7   serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings);
8   clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings);
9   unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings);
11   defaultServiceConfig = {
12     BindReadOnlyPaths = [
13       "/nix/store"
14       "-/etc/resolv.conf"
15       "-/etc/nsswitch.conf"
16       "-/etc/hosts"
17       "-/etc/localtime"
18     ];
19     CapabilityBoundingSet = "";
20     # ProtectClock= adds DeviceAllow=char-rtc r
21     DeviceAllow = "";
22     # Implies ProtectSystem=strict, which re-mounts all paths
23     # DynamicUser = true;
24     LockPersonality = true;
25     MemoryDenyWriteExecute = true;
26     NoNewPrivileges = true;
27     PrivateDevices = true;
28     PrivateMounts = true;
29     PrivateNetwork = true;
30     PrivateTmp = true;
31     PrivateUsers = true;
32     ProcSubset = "pid";
33     ProtectClock = true;
34     ProtectHome = true;
35     ProtectHostname = true;
36     # Would re-mount paths ignored by temporary root
37     #ProtectSystem = "strict";
38     ProtectControlGroups = true;
39     ProtectKernelLogs = true;
40     ProtectKernelModules = true;
41     ProtectKernelTunables = true;
42     ProtectProc = "invisible";
43     RestrictAddressFamilies = [ ];
44     RestrictNamespaces = true;
45     RestrictRealtime = true;
46     RestrictSUIDSGID = true;
47     SystemCallArchitectures = "native";
48     SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
49     # Does not work well with the temporary root
50     #UMask = "0066";
51   };
55   options.services.kanidm = {
56     enableClient = lib.mkEnableOption (lib.mdDoc "the Kanidm client");
57     enableServer = lib.mkEnableOption (lib.mdDoc "the Kanidm server");
58     enablePam = lib.mkEnableOption (lib.mdDoc "the Kanidm PAM and NSS integration.");
60     serverSettings = lib.mkOption {
61       type = lib.types.submodule {
62         freeformType = settingsFormat.type;
64         options = {
65           bindaddress = lib.mkOption {
66             description = lib.mdDoc "Address/port combination the webserver binds to.";
67             example = "[::1]:8443";
68             type = lib.types.str;
69           };
70           # Should be optional but toml does not accept null
71           ldapbindaddress = lib.mkOption {
72             description = lib.mdDoc ''
73               Address and port the LDAP server is bound to. Setting this to `null` disables the LDAP interface.
74             '';
75             example = "[::1]:636";
76             default = null;
77             type = lib.types.nullOr lib.types.str;
78           };
79           origin = lib.mkOption {
80             description = lib.mdDoc "The origin of your Kanidm instance. Must have https as protocol.";
81             example = "https://idm.example.org";
82             type = lib.types.strMatching "^https://.*";
83           };
84           domain = lib.mkOption {
85             description = lib.mdDoc ''
86               The `domain` that Kanidm manages. Must be below or equal to the domain
87               specified in `serverSettings.origin`.
88               This can be left at `null`, only if your instance has the role `ReadOnlyReplica`.
89               While it is possible to change the domain later on, it requires extra steps!
90               Please consider the warnings and execute the steps described
91               [in the documentation](https://kanidm.github.io/kanidm/stable/administrivia.html#rename-the-domain).
92             '';
93             example = "example.org";
94             default = null;
95             type = lib.types.nullOr lib.types.str;
96           };
97           db_path = lib.mkOption {
98             description = lib.mdDoc "Path to Kanidm database.";
99             default = "/var/lib/kanidm/kanidm.db";
100             readOnly = true;
101             type = lib.types.path;
102           };
103           log_level = lib.mkOption {
104             description = lib.mdDoc "Log level of the server.";
105             default = "default";
106             type = lib.types.enum [ "default" "verbose" "perfbasic" "perffull" ];
107           };
108           role = lib.mkOption {
109             description = lib.mdDoc "The role of this server. This affects the replication relationship and thereby available features.";
110             default = "WriteReplica";
111             type = lib.types.enum [ "WriteReplica" "WriteReplicaNoUI" "ReadOnlyReplica" ];
112           };
113         };
114       };
115       default = { };
116       description = lib.mdDoc ''
117         Settings for Kanidm, see
118         [the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/server_configuration.md)
119         and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/server.toml)
120         for possible values.
121       '';
122     };
124     clientSettings = lib.mkOption {
125       type = lib.types.submodule {
126         freeformType = settingsFormat.type;
128         options.uri = lib.mkOption {
129           description = lib.mdDoc "Address of the Kanidm server.";
130           example = "http://127.0.0.1:8080";
131           type = lib.types.str;
132         };
133       };
134       description = lib.mdDoc ''
135         Configure Kanidm clients, needed for the PAM daemon. See
136         [the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/client_tools.md#kanidm-configuration)
137         and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/config)
138         for possible values.
139       '';
140     };
142     unixSettings = lib.mkOption {
143       type = lib.types.submodule {
144         freeformType = settingsFormat.type;
146         options.pam_allowed_login_groups = lib.mkOption {
147           description = lib.mdDoc "Kanidm groups that are allowed to login using PAM.";
148           example = "my_pam_group";
149           type = lib.types.listOf lib.types.str;
150         };
151       };
152       description = lib.mdDoc ''
153         Configure Kanidm unix daemon.
154         See [the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/pam_and_nsswitch.md#the-unix-daemon)
155         and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/unixd)
156         for possible values.
157       '';
158     };
159   };
161   config = lib.mkIf (cfg.enableClient || cfg.enableServer || cfg.enablePam) {
162     assertions =
163       [
164         {
165           assertion = !cfg.enableServer || ((cfg.serverSettings.tls_chain or null) == null) || (!lib.isStorePath cfg.serverSettings.tls_chain);
166           message = ''
167             <option>services.kanidm.serverSettings.tls_chain</option> points to
168             a file in the Nix store. You should use a quoted absolute path to
169             prevent this.
170           '';
171         }
172         {
173           assertion = !cfg.enableServer || ((cfg.serverSettings.tls_key or null) == null) || (!lib.isStorePath cfg.serverSettings.tls_key);
174           message = ''
175             <option>services.kanidm.serverSettings.tls_key</option> points to
176             a file in the Nix store. You should use a quoted absolute path to
177             prevent this.
178           '';
179         }
180         {
181           assertion = !cfg.enableClient || options.services.kanidm.clientSettings.isDefined;
182           message = ''
183             <option>services.kanidm.clientSettings</option> needs to be configured
184             if the client is enabled.
185           '';
186         }
187         {
188           assertion = !cfg.enablePam || options.services.kanidm.clientSettings.isDefined;
189           message = ''
190             <option>services.kanidm.clientSettings</option> needs to be configured
191             for the PAM daemon to connect to the Kanidm server.
192           '';
193         }
194         {
195           assertion = !cfg.enableServer || (cfg.serverSettings.domain == null
196             -> cfg.serverSettings.role == "WriteReplica" || cfg.serverSettings.role == "WriteReplicaNoUI");
197           message = ''
198             <option>services.kanidm.serverSettings.domain</option> can only be set if this instance
199             is not a ReadOnlyReplica. Otherwise the db would inherit it from
200             the instance it follows.
201           '';
202         }
203       ];
205     environment.systemPackages = lib.mkIf cfg.enableClient [ pkgs.kanidm ];
207     systemd.services.kanidm = lib.mkIf cfg.enableServer {
208       description = "kanidm identity management daemon";
209       wantedBy = [ "multi-user.target" ];
210       after = [ "network.target" ];
211       serviceConfig = defaultServiceConfig // {
212         StateDirectory = "kanidm";
213         StateDirectoryMode = "0700";
214         ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}";
215         User = "kanidm";
216         Group = "kanidm";
218         AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
219         CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
220         # This would otherwise override the CAP_NET_BIND_SERVICE capability.
221         PrivateUsers = false;
222         # Port needs to be exposed to the host network
223         PrivateNetwork = false;
224         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
225         TemporaryFileSystem = "/:ro";
226       };
227       environment.RUST_LOG = "info";
228     };
230     systemd.services.kanidm-unixd = lib.mkIf cfg.enablePam {
231       description = "Kanidm PAM daemon";
232       wantedBy = [ "multi-user.target" ];
233       after = [ "network.target" ];
234       restartTriggers = [ unixConfigFile clientConfigFile ];
235       serviceConfig = defaultServiceConfig // {
236         CacheDirectory = "kanidm-unixd";
237         CacheDirectoryMode = "0700";
238         RuntimeDirectory = "kanidm-unixd";
239         ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd";
240         User = "kanidm-unixd";
241         Group = "kanidm-unixd";
243         BindReadOnlyPaths = [
244           "/nix/store"
245           "-/etc/resolv.conf"
246           "-/etc/nsswitch.conf"
247           "-/etc/hosts"
248           "-/etc/localtime"
249           "-/etc/kanidm"
250           "-/etc/static/kanidm"
251           "-/etc/ssl"
252           "-/etc/static/ssl"
253         ];
254         BindPaths = [
255           # To create the socket
256           "/run/kanidm-unixd:/var/run/kanidm-unixd"
257         ];
258         # Needs to connect to kanidmd
259         PrivateNetwork = false;
260         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
261         TemporaryFileSystem = "/:ro";
262       };
263       environment.RUST_LOG = "info";
264     };
266     systemd.services.kanidm-unixd-tasks = lib.mkIf cfg.enablePam {
267       description = "Kanidm PAM home management daemon";
268       wantedBy = [ "multi-user.target" ];
269       after = [ "network.target" "kanidm-unixd.service" ];
270       partOf = [ "kanidm-unixd.service" ];
271       restartTriggers = [ unixConfigFile clientConfigFile ];
272       serviceConfig = {
273         ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd_tasks";
275         BindReadOnlyPaths = [
276           "/nix/store"
277           "-/etc/resolv.conf"
278           "-/etc/nsswitch.conf"
279           "-/etc/hosts"
280           "-/etc/localtime"
281           "-/etc/kanidm"
282           "-/etc/static/kanidm"
283         ];
284         BindPaths = [
285           # To manage home directories
286           "/home"
287           # To connect to kanidm-unixd
288           "/run/kanidm-unixd:/var/run/kanidm-unixd"
289         ];
290         # CAP_DAC_OVERRIDE is needed to ignore ownership of unixd socket
291         CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_DAC_READ_SEARCH" ];
292         IPAddressDeny = "any";
293         # Need access to users
294         PrivateUsers = false;
295         # Need access to home directories
296         ProtectHome = false;
297         RestrictAddressFamilies = [ "AF_UNIX" ];
298         TemporaryFileSystem = "/:ro";
299       };
300       environment.RUST_LOG = "info";
301     };
303     # These paths are hardcoded
304     environment.etc = lib.mkMerge [
305       (lib.mkIf options.services.kanidm.clientSettings.isDefined {
306         "kanidm/config".source = clientConfigFile;
307       })
308       (lib.mkIf cfg.enablePam {
309         "kanidm/unixd".source = unixConfigFile;
310       })
311     ];
313     system.nssModules = lib.mkIf cfg.enablePam [ pkgs.kanidm ];
315     system.nssDatabases.group = lib.optional cfg.enablePam "kanidm";
316     system.nssDatabases.passwd = lib.optional cfg.enablePam "kanidm";
318     users.groups = lib.mkMerge [
319       (lib.mkIf cfg.enableServer {
320         kanidm = { };
321       })
322       (lib.mkIf cfg.enablePam {
323         kanidm-unixd = { };
324       })
325     ];
326     users.users = lib.mkMerge [
327       (lib.mkIf cfg.enableServer {
328         kanidm = {
329           description = "Kanidm server";
330           isSystemUser = true;
331           group = "kanidm";
332           packages = with pkgs; [ kanidm ];
333         };
334       })
335       (lib.mkIf cfg.enablePam {
336         kanidm-unixd = {
337           description = "Kanidm PAM daemon";
338           isSystemUser = true;
339           group = "kanidm-unixd";
340         };
341       })
342     ];
343   };
345   meta.maintainers = with lib.maintainers; [ erictapen Flakebi ];
346   meta.buildDocsInSandbox = false;