nixpkgs/nixos/modules/services/security/kanidm.nix
pennae 2e751c0772 treewide: automatically md-convert option descriptions
the conversion procedure is simple:

 - find all things that look like options, ie calls to either `mkOption`
   or `lib.mkOption` that take an attrset. remember the attrset as the
   option
 - for all options, find a `description` attribute who's value is not a
   call to `mdDoc` or `lib.mdDoc`
 - textually convert the entire value of the attribute to MD with a few
   simple regexes (the set from mdize-module.sh)
 - if the change produced a change in the manual output, discard
 - if the change kept the manual unchanged, add some text to the
   description to make sure we've actually found an option. if the
   manual changes this time, keep the converted description

this procedure converts 80% of nixos options to markdown. around 2000
options remain to be inspected, but most of those fail the "does not
change the manual output check": currently the MD conversion process
does not faithfully convert docbook tags like <code> and <package>, so
any option using such tags will not be converted at all.
2022-07-30 15:16:34 +02:00

346 lines
13 KiB
Nix

{ config, lib, options, pkgs, ... }:
let
cfg = config.services.kanidm;
settingsFormat = pkgs.formats.toml { };
# Remove null values, so we can document optional values that don't end up in the generated TOML file.
filterConfig = lib.converge (lib.filterAttrsRecursive (_: v: v != null));
serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings);
clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings);
unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings);
defaultServiceConfig = {
BindReadOnlyPaths = [
"/nix/store"
"-/etc/resolv.conf"
"-/etc/nsswitch.conf"
"-/etc/hosts"
"-/etc/localtime"
];
CapabilityBoundingSet = "";
# ProtectClock= adds DeviceAllow=char-rtc r
DeviceAllow = "";
# Implies ProtectSystem=strict, which re-mounts all paths
# DynamicUser = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateNetwork = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectHome = true;
ProtectHostname = true;
# Would re-mount paths ignored by temporary root
#ProtectSystem = "strict";
ProtectControlGroups = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RestrictAddressFamilies = [ ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
# Does not work well with the temporary root
#UMask = "0066";
};
in
{
options.services.kanidm = {
enableClient = lib.mkEnableOption "the Kanidm client";
enableServer = lib.mkEnableOption "the Kanidm server";
enablePam = lib.mkEnableOption "the Kanidm PAM and NSS integration.";
serverSettings = lib.mkOption {
type = lib.types.submodule {
freeformType = settingsFormat.type;
options = {
bindaddress = lib.mkOption {
description = lib.mdDoc "Address/port combination the webserver binds to.";
example = "[::1]:8443";
type = lib.types.str;
};
# Should be optional but toml does not accept null
ldapbindaddress = lib.mkOption {
description = lib.mdDoc ''
Address and port the LDAP server is bound to. Setting this to `null` disables the LDAP interface.
'';
example = "[::1]:636";
default = null;
type = lib.types.nullOr lib.types.str;
};
origin = lib.mkOption {
description = lib.mdDoc "The origin of your Kanidm instance. Must have https as protocol.";
example = "https://idm.example.org";
type = lib.types.strMatching "^https://.*";
};
domain = lib.mkOption {
description = lib.mdDoc ''
The `domain` that Kanidm manages. Must be below or equal to the domain
specified in `serverSettings.origin`.
This can be left at `null`, only if your instance has the role `ReadOnlyReplica`.
While it is possible to change the domain later on, it requires extra steps!
Please consider the warnings and execute the steps described
[in the documentation](https://kanidm.github.io/kanidm/stable/administrivia.html#rename-the-domain).
'';
example = "example.org";
default = null;
type = lib.types.nullOr lib.types.str;
};
db_path = lib.mkOption {
description = lib.mdDoc "Path to Kanidm database.";
default = "/var/lib/kanidm/kanidm.db";
readOnly = true;
type = lib.types.path;
};
log_level = lib.mkOption {
description = lib.mdDoc "Log level of the server.";
default = "default";
type = lib.types.enum [ "default" "verbose" "perfbasic" "perffull" ];
};
role = lib.mkOption {
description = lib.mdDoc "The role of this server. This affects the replication relationship and thereby available features.";
default = "WriteReplica";
type = lib.types.enum [ "WriteReplica" "WriteReplicaNoUI" "ReadOnlyReplica" ];
};
};
};
default = { };
description = lib.mdDoc ''
Settings for Kanidm, see
[the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/server_configuration.md)
and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/server.toml)
for possible values.
'';
};
clientSettings = lib.mkOption {
type = lib.types.submodule {
freeformType = settingsFormat.type;
options.uri = lib.mkOption {
description = lib.mdDoc "Address of the Kanidm server.";
example = "http://127.0.0.1:8080";
type = lib.types.str;
};
};
description = lib.mdDoc ''
Configure Kanidm clients, needed for the PAM daemon. See
[the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/client_tools.md#kanidm-configuration)
and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/config)
for possible values.
'';
};
unixSettings = lib.mkOption {
type = lib.types.submodule {
freeformType = settingsFormat.type;
options.pam_allowed_login_groups = lib.mkOption {
description = lib.mdDoc "Kanidm groups that are allowed to login using PAM.";
example = "my_pam_group";
type = lib.types.listOf lib.types.str;
};
};
description = lib.mdDoc ''
Configure Kanidm unix daemon.
See [the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/pam_and_nsswitch.md#the-unix-daemon)
and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/unixd)
for possible values.
'';
};
};
config = lib.mkIf (cfg.enableClient || cfg.enableServer || cfg.enablePam) {
assertions =
[
{
assertion = !cfg.enableServer || ((cfg.serverSettings.tls_chain or null) == null) || (!lib.isStorePath cfg.serverSettings.tls_chain);
message = ''
<option>services.kanidm.serverSettings.tls_chain</option> points to
a file in the Nix store. You should use a quoted absolute path to
prevent this.
'';
}
{
assertion = !cfg.enableServer || ((cfg.serverSettings.tls_key or null) == null) || (!lib.isStorePath cfg.serverSettings.tls_key);
message = ''
<option>services.kanidm.serverSettings.tls_key</option> points to
a file in the Nix store. You should use a quoted absolute path to
prevent this.
'';
}
{
assertion = !cfg.enableClient || options.services.kanidm.clientSettings.isDefined;
message = ''
<option>services.kanidm.clientSettings</option> needs to be configured
if the client is enabled.
'';
}
{
assertion = !cfg.enablePam || options.services.kanidm.clientSettings.isDefined;
message = ''
<option>services.kanidm.clientSettings</option> needs to be configured
for the PAM daemon to connect to the Kanidm server.
'';
}
{
assertion = !cfg.enableServer || (cfg.serverSettings.domain == null
-> cfg.serverSettings.role == "WriteReplica" || cfg.serverSettings.role == "WriteReplicaNoUI");
message = ''
<option>services.kanidm.serverSettings.domain</option> can only be set if this instance
is not a ReadOnlyReplica. Otherwise the db would inherit it from
the instance it follows.
'';
}
];
environment.systemPackages = lib.mkIf cfg.enableClient [ pkgs.kanidm ];
systemd.services.kanidm = lib.mkIf cfg.enableServer {
description = "kanidm identity management daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = defaultServiceConfig // {
StateDirectory = "kanidm";
StateDirectoryMode = "0700";
ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}";
User = "kanidm";
Group = "kanidm";
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
# This would otherwise override the CAP_NET_BIND_SERVICE capability.
PrivateUsers = false;
# Port needs to be exposed to the host network
PrivateNetwork = false;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
TemporaryFileSystem = "/:ro";
};
environment.RUST_LOG = "info";
};
systemd.services.kanidm-unixd = lib.mkIf cfg.enablePam {
description = "Kanidm PAM daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
restartTriggers = [ unixConfigFile clientConfigFile ];
serviceConfig = defaultServiceConfig // {
CacheDirectory = "kanidm-unixd";
CacheDirectoryMode = "0700";
RuntimeDirectory = "kanidm-unixd";
ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd";
User = "kanidm-unixd";
Group = "kanidm-unixd";
BindReadOnlyPaths = [
"/nix/store"
"-/etc/resolv.conf"
"-/etc/nsswitch.conf"
"-/etc/hosts"
"-/etc/localtime"
"-/etc/kanidm"
"-/etc/static/kanidm"
];
BindPaths = [
# To create the socket
"/run/kanidm-unixd:/var/run/kanidm-unixd"
];
# Needs to connect to kanidmd
PrivateNetwork = false;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
TemporaryFileSystem = "/:ro";
};
environment.RUST_LOG = "info";
};
systemd.services.kanidm-unixd-tasks = lib.mkIf cfg.enablePam {
description = "Kanidm PAM home management daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "kanidm-unixd.service" ];
partOf = [ "kanidm-unixd.service" ];
restartTriggers = [ unixConfigFile clientConfigFile ];
serviceConfig = {
ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd_tasks";
BindReadOnlyPaths = [
"/nix/store"
"-/etc/resolv.conf"
"-/etc/nsswitch.conf"
"-/etc/hosts"
"-/etc/localtime"
"-/etc/kanidm"
"-/etc/static/kanidm"
];
BindPaths = [
# To manage home directories
"/home"
# To connect to kanidm-unixd
"/run/kanidm-unixd:/var/run/kanidm-unixd"
];
# CAP_DAC_OVERRIDE is needed to ignore ownership of unixd socket
CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_DAC_READ_SEARCH" ];
IPAddressDeny = "any";
# Need access to users
PrivateUsers = false;
# Need access to home directories
ProtectHome = false;
RestrictAddressFamilies = [ "AF_UNIX" ];
TemporaryFileSystem = "/:ro";
};
environment.RUST_LOG = "info";
};
# These paths are hardcoded
environment.etc = lib.mkMerge [
(lib.mkIf options.services.kanidm.clientSettings.isDefined {
"kanidm/config".source = clientConfigFile;
})
(lib.mkIf cfg.enablePam {
"kanidm/unixd".source = unixConfigFile;
})
];
system.nssModules = lib.mkIf cfg.enablePam [ pkgs.kanidm ];
system.nssDatabases.group = lib.optional cfg.enablePam "kanidm";
system.nssDatabases.passwd = lib.optional cfg.enablePam "kanidm";
users.groups = lib.mkMerge [
(lib.mkIf cfg.enableServer {
kanidm = { };
})
(lib.mkIf cfg.enablePam {
kanidm-unixd = { };
})
];
users.users = lib.mkMerge [
(lib.mkIf cfg.enableServer {
kanidm = {
description = "Kanidm server";
isSystemUser = true;
group = "kanidm";
packages = with pkgs; [ kanidm ];
};
})
(lib.mkIf cfg.enablePam {
kanidm-unixd = {
description = "Kanidm PAM daemon";
isSystemUser = true;
group = "kanidm-unixd";
};
})
];
};
meta.maintainers = with lib.maintainers; [ erictapen Flakebi ];
meta.buildDocsInSandbox = false;
}