diff --git a/modules/module-list.nix b/modules/module-list.nix index 93c63bf81..49ae255ec 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -58,6 +58,7 @@ ./services/hercules-ci-agent ./services/ipfs.nix ./services/karabiner-elements + ./services/krb5 ./services/khd ./services/kwm ./services/lorri.nix diff --git a/modules/services/krb5/default.nix b/modules/services/krb5/default.nix new file mode 100644 index 000000000..dc05c673b --- /dev/null +++ b/modules/services/krb5/default.nix @@ -0,0 +1,274 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.krb5; + + filterEmbeddedMetadata = value: + if isAttrs value then + (filterAttrs + (attrName: attrValue: attrName != "_module" && attrValue != null) value) + else + value; + + indent = " "; + + mkRelation = name: value: + if (isList value) then + concatMapStringsSep "\n" (mkRelation name) value + else + "${name} = ${mkVal value}"; + + mkVal = value: + if (value == true) then + "true" + else if (value == false) then + "false" + else if (isInt value) then + (toString value) + else if (isAttrs value) then + let + configLines = concatLists + (map (splitString "\n") (mapAttrsToList mkRelation value)); + in (concatStringsSep '' + + ${indent}'' ([ "{" ] ++ configLines)) + '' + + }'' + else + value; + + mkMappedAttrsOrString = value: + concatMapStringsSep "\n" + (line: if builtins.stringLength line > 0 then "${indent}${line}" else line) + (splitString "\n" (if isAttrs value then + concatStringsSep "\n" (mapAttrsToList mkRelation value) + else + value)); + +in { + + ###### interface + + options = { + krb5 = { + enable = mkEnableOption + (lib.mdDoc "building krb5.conf, configuration file for Kerberos V"); + + kerberos = mkOption { + type = types.package; + default = pkgs.krb5; + defaultText = literalExpression "pkgs.krb5"; + example = literalExpression "pkgs.heimdal"; + description = lib.mdDoc '' + The Kerberos implementation that will be present in + `environment.systemPackages` after enabling this + service. + ''; + }; + + libdefaults = mkOption { + type = with types; either attrs lines; + default = { }; + apply = attrs: filterEmbeddedMetadata attrs; + example = literalExpression '' + { + default_realm = "ATHENA.MIT.EDU"; + }; + ''; + description = lib.mdDoc '' + Settings used by the Kerberos V5 library. + ''; + }; + + realms = mkOption { + type = with types; either attrs lines; + default = { }; + example = literalExpression '' + { + "ATHENA.MIT.EDU" = { + admin_server = "athena.mit.edu"; + kdc = [ + "athena01.mit.edu" + "athena02.mit.edu" + ]; + }; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = + lib.mdDoc "Realm-specific contact information and settings."; + }; + + domain_realm = mkOption { + type = with types; either attrs lines; + default = { }; + example = literalExpression '' + { + "example.com" = "EXAMPLE.COM"; + ".example.com" = "EXAMPLE.COM"; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = lib.mdDoc '' + Map of server hostnames to Kerberos realms. + ''; + }; + + capaths = mkOption { + type = with types; either attrs lines; + default = { }; + example = literalExpression '' + { + "ATHENA.MIT.EDU" = { + "EXAMPLE.COM" = "."; + }; + "EXAMPLE.COM" = { + "ATHENA.MIT.EDU" = "."; + }; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = lib.mdDoc '' + Authentication paths for non-hierarchical cross-realm authentication. + ''; + }; + + appdefaults = mkOption { + type = with types; either attrs lines; + default = { }; + example = literalExpression '' + { + pam = { + debug = false; + ticket_lifetime = 36000; + renew_lifetime = 36000; + max_timeout = 30; + timeout_shift = 2; + initial_timeout = 1; + }; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = lib.mdDoc '' + Settings used by some Kerberos V5 applications. + ''; + }; + + plugins = mkOption { + type = with types; either attrs lines; + default = { }; + example = literalExpression '' + { + ccselect = { + disable = "k5identity"; + }; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = lib.mdDoc '' + Controls plugin module registration. + ''; + }; + + extraConfig = mkOption { + type = with types; nullOr lines; + default = null; + example = '' + [logging] + kdc = SYSLOG:NOTICE + admin_server = SYSLOG:NOTICE + default = SYSLOG:NOTICE + ''; + description = lib.mdDoc '' + These lines go to the end of `krb5.conf` verbatim. + `krb5.conf` may include any of the relations that are + valid for `kdc.conf` (see `man kdc.conf`), + but it is not a recommended practice. + ''; + }; + + config = mkOption { + type = with types; nullOr lines; + default = null; + example = '' + [libdefaults] + default_realm = EXAMPLE.COM + + [realms] + EXAMPLE.COM = { + admin_server = kerberos.example.com + kdc = kerberos.example.com + default_principal_flags = +preauth + } + + [domain_realm] + example.com = EXAMPLE.COM + .example.com = EXAMPLE.COM + + [logging] + kdc = SYSLOG:NOTICE + admin_server = SYSLOG:NOTICE + default = SYSLOG:NOTICE + ''; + description = lib.mdDoc '' + Verbatim `krb5.conf` configuration. Note that this + is mutually exclusive with configuration via + `libdefaults`, `realms`, + `domain_realm`, `capaths`, + `appdefaults`, `plugins` and + `extraConfig` configuration options. Consult + `man krb5.conf` for documentation. + ''; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + + environment.systemPackages = [ cfg.kerberos ]; + + environment.etc."krb5.conf".text = if isString cfg.config then + cfg.config + else + ('' + [libdefaults] + ${mkMappedAttrsOrString cfg.libdefaults} + + [realms] + ${mkMappedAttrsOrString cfg.realms} + + [domain_realm] + ${mkMappedAttrsOrString cfg.domain_realm} + + [capaths] + ${mkMappedAttrsOrString cfg.capaths} + + [appdefaults] + ${mkMappedAttrsOrString cfg.appdefaults} + + [plugins] + ${mkMappedAttrsOrString cfg.plugins} + '' + optionalString (cfg.extraConfig != null) ("\n" + cfg.extraConfig)); + + assertions = [{ + assertion = !(cfg.config != null && ((builtins.any (value: value != { }) [ + cfg.libdefaults + cfg.realms + cfg.domain_realm + cfg.capaths + cfg.appdefaults + cfg.plugins + ]) || cfg.extraConfig != null)); + message = '' + Configuration of krb5.conf using krb.config is mutually exclusive with + configuration by section. If you want to mix the two, you can pass + lines to any configuration section or lines to krb5.extraConfig. + ''; + }]; + }; +} diff --git a/tests/krb5.nix b/tests/krb5.nix new file mode 100644 index 000000000..acd7e8266 --- /dev/null +++ b/tests/krb5.nix @@ -0,0 +1,86 @@ +{ config, pkgs, ... }: { + krb5 = { + enable = true; + kerberos = pkgs.krb5; + libdefaults = { default_realm = "ATHENA.MIT.EDU"; }; + realms = { + "ATHENA.MIT.EDU" = { + admin_server = "athena.mit.edu"; + kdc = [ "athena01.mit.edu" "athena02.mit.edu" ]; + }; + }; + domain_realm = { + "example.com" = "EXAMPLE.COM"; + ".example.com" = "EXAMPLE.COM"; + }; + capaths = { + "ATHENA.MIT.EDU" = { "EXAMPLE.COM" = "."; }; + "EXAMPLE.COM" = { "ATHENA.MIT.EDU" = "."; }; + }; + appdefaults = { + pam = { + debug = false; + ticket_lifetime = 36000; + renew_lifetime = 36000; + max_timeout = 30; + timeout_shift = 2; + initial_timeout = 1; + }; + }; + plugins = { ccselect = { disable = "k5identity"; }; }; + extraConfig = '' + [logging] + kdc = SYSLOG:NOTICE + admin_server = SYSLOG:NOTICE + default = SYSLOG:NOTICE + ''; + }; + test = let + snapshot = pkgs.writeText "krb5-with-example-config.conf" '' + [libdefaults] + default_realm = ATHENA.MIT.EDU + + [realms] + ATHENA.MIT.EDU = { + admin_server = athena.mit.edu + kdc = athena01.mit.edu + kdc = athena02.mit.edu + } + + [domain_realm] + .example.com = EXAMPLE.COM + example.com = EXAMPLE.COM + + [capaths] + ATHENA.MIT.EDU = { + EXAMPLE.COM = . + } + EXAMPLE.COM = { + ATHENA.MIT.EDU = . + } + + [appdefaults] + pam = { + debug = false + initial_timeout = 1 + max_timeout = 30 + renew_lifetime = 36000 + ticket_lifetime = 36000 + timeout_shift = 2 + } + + [plugins] + ccselect = { + disable = k5identity + } + + [logging] + kdc = SYSLOG:NOTICE + admin_server = SYSLOG:NOTICE + default = SYSLOG:NOTICE + ''; + in '' + echo "checking correctness of krb5.conf" >&2 + diff /etc/krb5.conf ${snapshot} + ''; +}