From 06e15e59f950590b4005b293081823d2efaec9e5 Mon Sep 17 00:00:00 2001 From: Ruben Maher Date: Sat, 23 Sep 2017 12:48:44 +0930 Subject: nixos/krb5: complete rewrite The `krb5` service was a bit lacking. Addresses NixOS/nixpkgs#11268, partially addresses NixOS/nixpkgs#29623. --- nixos/modules/config/krb5.nix | 206 ------------------ nixos/modules/config/krb5/default.nix | 367 +++++++++++++++++++++++++++++++++ nixos/modules/module-list.nix | 2 +- nixos/tests/krb5/default.nix | 5 + nixos/tests/krb5/deprecated-config.nix | 48 +++++ nixos/tests/krb5/example-config.nix | 106 ++++++++++ 6 files changed, 527 insertions(+), 207 deletions(-) delete mode 100644 nixos/modules/config/krb5.nix create mode 100644 nixos/modules/config/krb5/default.nix create mode 100644 nixos/tests/krb5/default.nix create mode 100644 nixos/tests/krb5/deprecated-config.nix create mode 100644 nixos/tests/krb5/example-config.nix diff --git a/nixos/modules/config/krb5.nix b/nixos/modules/config/krb5.nix deleted file mode 100644 index d318b720742..00000000000 --- a/nixos/modules/config/krb5.nix +++ /dev/null @@ -1,206 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.krb5; - -in - -{ - ###### interface - - options = { - - krb5 = { - - enable = mkOption { - default = false; - description = "Whether to enable Kerberos V."; - }; - - defaultRealm = mkOption { - default = "ATENA.MIT.EDU"; - description = "Default realm."; - }; - - domainRealm = mkOption { - default = "atena.mit.edu"; - description = "Default domain realm."; - }; - - kdc = mkOption { - default = "kerberos.mit.edu"; - description = "Key Distribution Center"; - }; - - kerberosAdminServer = mkOption { - default = "kerberos.mit.edu"; - description = "Kerberos Admin Server."; - }; - - }; - - }; - - ###### implementation - - config = mkIf config.krb5.enable { - - environment.systemPackages = [ pkgs.krb5Full ]; - - environment.etc."krb5.conf".text = - '' - [libdefaults] - default_realm = ${cfg.defaultRealm} - encrypt = true - - # The following krb5.conf variables are only for MIT Kerberos. - krb4_config = /etc/krb.conf - krb4_realms = /etc/krb.realms - kdc_timesync = 1 - ccache_type = 4 - forwardable = true - proxiable = true - - # The following encryption type specification will be used by MIT Kerberos - # if uncommented. In general, the defaults in the MIT Kerberos code are - # correct and overriding these specifications only serves to disable new - # encryption types as they are added, creating interoperability problems. - - # default_tgs_enctypes = aes256-cts arcfour-hmac-md5 des3-hmac-sha1 des-cbc-crc des-cbc-md5 - # default_tkt_enctypes = aes256-cts arcfour-hmac-md5 des3-hmac-sha1 des-cbc-crc des-cbc-md5 - # permitted_enctypes = aes256-cts arcfour-hmac-md5 des3-hmac-sha1 des-cbc-crc des-cbc-md5 - - # The following libdefaults parameters are only for Heimdal Kerberos. - v4_instance_resolve = false - v4_name_convert = { - host = { - rcmd = host - ftp = ftp - } - plain = { - something = something-else - } - } - fcc-mit-ticketflags = true - - [realms] - ${cfg.defaultRealm} = { - kdc = ${cfg.kdc} - admin_server = ${cfg.kerberosAdminServer} - #kpasswd_server = ${cfg.kerberosAdminServer} - } - ATHENA.MIT.EDU = { - kdc = kerberos.mit.edu:88 - kdc = kerberos-1.mit.edu:88 - kdc = kerberos-2.mit.edu:88 - admin_server = kerberos.mit.edu - default_domain = mit.edu - } - MEDIA-LAB.MIT.EDU = { - kdc = kerberos.media.mit.edu - admin_server = kerberos.media.mit.edu - } - ZONE.MIT.EDU = { - kdc = casio.mit.edu - kdc = seiko.mit.edu - admin_server = casio.mit.edu - } - MOOF.MIT.EDU = { - kdc = three-headed-dogcow.mit.edu:88 - kdc = three-headed-dogcow-1.mit.edu:88 - admin_server = three-headed-dogcow.mit.edu - } - CSAIL.MIT.EDU = { - kdc = kerberos-1.csail.mit.edu - kdc = kerberos-2.csail.mit.edu - admin_server = kerberos.csail.mit.edu - default_domain = csail.mit.edu - krb524_server = krb524.csail.mit.edu - } - IHTFP.ORG = { - kdc = kerberos.ihtfp.org - admin_server = kerberos.ihtfp.org - } - GNU.ORG = { - kdc = kerberos.gnu.org - kdc = kerberos-2.gnu.org - kdc = kerberos-3.gnu.org - admin_server = kerberos.gnu.org - } - 1TS.ORG = { - kdc = kerberos.1ts.org - admin_server = kerberos.1ts.org - } - GRATUITOUS.ORG = { - kdc = kerberos.gratuitous.org - admin_server = kerberos.gratuitous.org - } - DOOMCOM.ORG = { - kdc = kerberos.doomcom.org - admin_server = kerberos.doomcom.org - } - ANDREW.CMU.EDU = { - kdc = vice28.fs.andrew.cmu.edu - kdc = vice2.fs.andrew.cmu.edu - kdc = vice11.fs.andrew.cmu.edu - kdc = vice12.fs.andrew.cmu.edu - admin_server = vice28.fs.andrew.cmu.edu - default_domain = andrew.cmu.edu - } - CS.CMU.EDU = { - kdc = kerberos.cs.cmu.edu - kdc = kerberos-2.srv.cs.cmu.edu - admin_server = kerberos.cs.cmu.edu - } - DEMENTIA.ORG = { - kdc = kerberos.dementia.org - kdc = kerberos2.dementia.org - admin_server = kerberos.dementia.org - } - stanford.edu = { - kdc = krb5auth1.stanford.edu - kdc = krb5auth2.stanford.edu - kdc = krb5auth3.stanford.edu - admin_server = krb5-admin.stanford.edu - default_domain = stanford.edu - } - - [domain_realm] - .${cfg.domainRealm} = ${cfg.defaultRealm} - ${cfg.domainRealm} = ${cfg.defaultRealm} - .mit.edu = ATHENA.MIT.EDU - mit.edu = ATHENA.MIT.EDU - .exchange.mit.edu = EXCHANGE.MIT.EDU - exchange.mit.edu = EXCHANGE.MIT.EDU - .media.mit.edu = MEDIA-LAB.MIT.EDU - media.mit.edu = MEDIA-LAB.MIT.EDU - .csail.mit.edu = CSAIL.MIT.EDU - csail.mit.edu = CSAIL.MIT.EDU - .whoi.edu = ATHENA.MIT.EDU - whoi.edu = ATHENA.MIT.EDU - .stanford.edu = stanford.edu - - [logging] - kdc = SYSLOG:INFO:DAEMON - admin_server = SYSLOG:INFO:DAEMON - default = SYSLOG:INFO:DAEMON - krb4_convert = true - krb4_get_tickets = false - - [appdefaults] - pam = { - debug = false - ticket_lifetime = 36000 - renew_lifetime = 36000 - max_timeout = 30 - timeout_shift = 2 - initial_timeout = 1 - } - ''; - - }; - -} diff --git a/nixos/modules/config/krb5/default.nix b/nixos/modules/config/krb5/default.nix new file mode 100644 index 00000000000..bdcc2d48cd1 --- /dev/null +++ b/nixos/modules/config/krb5/default.nix @@ -0,0 +1,367 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.krb5; + + # This is to provide support for old configuration options (as much as is + # reasonable). This can probably be removed after some time. + defaultConfig = { + libdefaults = optionalAttrs (cfg.defaultRealm != null) + { default_realm = cfg.defaultRealm; }; + + realms = optionalAttrs (lib.all (value: value != null) [ + cfg.defaultRealm cfg.kdc cfg.kerberosAdminServer + ]) { + "${cfg.defaultRealm}" = { + kdc = cfg.kdc; + admin_server = cfg.kerberosAdminServer; + }; + }; + + domain_realm = optionalAttrs (lib.all (value: value != null) [ + cfg.domainRealm cfg.defaultRealm + ]) { + ".${cfg.domainRealm}" = cfg.defaultRealm; + "${cfg.domainRealm}" = cfg.defaultRealm; + }; + }; + + mergedConfig = (recursiveUpdate defaultConfig { + inherit (config.krb5) + kerberos libdefaults realms domain_realm capaths appdefaults plugins + extraConfig config; + }); + + filterEmbeddedMetadata = value: if isAttrs value then + (filterAttrs + (attrName: attrValue: attrName != "_module" && attrValue != null) + value) + else value; + + mkIndent = depth: concatStrings (builtins.genList (_: " ") (2 * depth)); + + mkRelation = name: value: "${name} = ${mkVal { inherit value; }}"; + + mkVal = { value, depth ? 0 }: + if (value == true) then "true" + else if (value == false) then "false" + else if (isInt value) then (toString value) + else if (isList value) then + concatMapStringsSep " " mkVal { inherit value depth; } + else if (isAttrs value) then + (concatStringsSep "\n${mkIndent (depth + 1)}" + ([ "{" ] ++ (mapAttrsToList + (attrName: attrValue: let + mappedAttrValue = mkVal { + value = attrValue; + depth = depth + 1; + }; + in "${attrName} = ${mappedAttrValue}") + value))) + "\n${mkIndent depth}}" + else value; + + mkMappedAttrsOrString = value: concatMapStringsSep "\n" + (line: if builtins.stringLength line > 0 + then "${mkIndent 1}${line}" + else line) + (splitString "\n" + (if isAttrs value then + concatStringsSep "\n" + (mapAttrsToList mkRelation value) + else value)); + +in { + + ###### interface + + options = { + krb5 = { + enable = mkEnableOption "Whether to enable Kerberos V."; + + kerberos = mkOption { + type = types.package; + default = pkgs.krb5Full; + defaultText = "pkgs.krb5Full"; + example = literalExample "pkgs.heimdalFull"; + description = '' + 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 = literalExample '' + { + default_realm = "ATHENA.MIT.EDU"; + }; + ''; + description = '' + Settings used by the Kerberos V5 library. + ''; + }; + + realms = mkOption { + type = with types; either attrs lines; + default = {}; + example = literalExample '' + { + "ATHENA.MIT.EDU" = { + admin_server = "athena.mit.edu"; + kdc = "athena.mit.edu"; + }; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = "Realm-specific contact information and settings."; + }; + + domain_realm = mkOption { + type = with types; either attrs lines; + default = {}; + example = literalExample '' + { + "example.com" = "EXAMPLE.COM"; + ".example.com" = "EXAMPLE.COM"; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = '' + Map of server hostnames to Kerberos realms. + ''; + }; + + capaths = mkOption { + type = with types; either attrs lines; + default = {}; + example = literalExample '' + { + "ATHENA.MIT.EDU" = { + "EXAMPLE.COM" = "."; + }; + "EXAMPLE.COM" = { + "ATHENA.MIT.EDU" = "."; + }; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = '' + Authentication paths for non-hierarchical cross-realm authentication. + ''; + }; + + appdefaults = mkOption { + type = with types; either attrs lines; + default = {}; + example = literalExample '' + { + pam = { + debug = false; + ticket_lifetime = 36000; + renew_lifetime = 36000; + max_timeout = 30; + timeout_shift = 2; + initial_timeout = 1; + }; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = '' + Settings used by some Kerberos V5 applications. + ''; + }; + + plugins = mkOption { + type = with types; either attrs lines; + default = {}; + example = literalExample '' + { + ccselect = { + disable = "k5identity"; + }; + }; + ''; + apply = attrs: filterEmbeddedMetadata attrs; + description = '' + 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 = '' + 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 = '' + 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. + ''; + }; + + defaultRealm = mkOption { + type = with types; nullOr str; + default = null; + example = "ATHENA.MIT.EDU"; + description = '' + DEPRECATED, please use + krb5.libdefaults.default_realm. + ''; + }; + + domainRealm = mkOption { + type = with types; nullOr str; + default = null; + example = "athena.mit.edu"; + description = '' + DEPRECATED, please create a map of server hostnames to Kerberos realms + in krb5.domain_realm. + ''; + }; + + kdc = mkOption { + type = with types; nullOr str; + default = null; + example = "kerberos.mit.edu"; + description = '' + DEPRECATED, please pass a kdc attribute to a realm + in krb5.realms. + ''; + }; + + kerberosAdminServer = mkOption { + type = with types; nullOr str; + default = null; + example = "kerberos.mit.edu"; + description = '' + DEPRECATED, please pass an admin_server attribute + to a realm in krb5.realms. + ''; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + + environment.systemPackages = [ cfg.kerberos ]; + + environment.etc."krb5.conf".text = if isString cfg.config + then cfg.config + else ('' + [libdefaults] + ${mkMappedAttrsOrString mergedConfig.libdefaults} + + [realms] + ${mkMappedAttrsOrString mergedConfig.realms} + + [domain_realm] + ${mkMappedAttrsOrString mergedConfig.domain_realm} + + [capaths] + ${mkMappedAttrsOrString mergedConfig.capaths} + + [appdefaults] + ${mkMappedAttrsOrString mergedConfig.appdefaults} + + [plugins] + ${mkMappedAttrsOrString mergedConfig.plugins} + '' + optionalString (mergedConfig.extraConfig != null) + ("\n" + mergedConfig.extraConfig)); + + warnings = flatten [ + (optional (cfg.defaultRealm != null) '' + The option krb5.defaultRealm is deprecated, please use + krb5.libdefaults.default_realm. + '') + (optional (cfg.domainRealm != null) '' + The option krb5.domainRealm is deprecated, please use krb5.domain_realm. + '') + (optional (cfg.kdc != null) '' + The option krb5.kdc is deprecated, please pass a kdc attribute to a + realm in krb5.realms. + '') + (optional (cfg.kerberosAdminServer != null) '' + The option krb5.kerberosAdminServer is deprecated, please pass an + admin_server attribute to a realm in krb5.realms. + '') + ]; + + assertions = [ + { assertion = !((builtins.any (value: value != null) [ + cfg.defaultRealm cfg.domainRealm cfg.kdc cfg.kerberosAdminServer + ]) && ((builtins.any (value: value != {}) [ + cfg.libdefaults cfg.realms cfg.domain_realm cfg.capaths + cfg.appdefaults cfg.plugins + ]) || (builtins.any (value: value != null) [ + cfg.config cfg.extraConfig + ]))); + message = '' + Configuration of krb5.conf by deprecated options is mutually exclusive + with configuration by section. Please migrate your config using the + attributes suggested in the warnings. + ''; + } + { assertion = !(cfg.config != null + && ((builtins.any (value: value != {}) [ + cfg.libdefaults cfg.realms cfg.domain_realm cfg.capaths + cfg.appdefaults cfg.plugins + ]) || (builtins.any (value: value != null) [ + cfg.extraConfig cfg.defaultRealm cfg.domainRealm cfg.kdc + cfg.kerberosAdminServer + ]))); + 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/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index e849e634fc9..06dcd70d166 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -9,7 +9,7 @@ ./config/fonts/ghostscript.nix ./config/gnu.nix ./config/i18n.nix - ./config/krb5.nix + ./config/krb5/default.nix ./config/ldap.nix ./config/networking.nix ./config/no-x-libs.nix diff --git a/nixos/tests/krb5/default.nix b/nixos/tests/krb5/default.nix new file mode 100644 index 00000000000..dd5b2f37202 --- /dev/null +++ b/nixos/tests/krb5/default.nix @@ -0,0 +1,5 @@ +{ system ? builtins.currentSystem }: +{ + example-config = import ./example-config.nix { inherit system; }; + deprecated-config = import ./deprecated-config.nix { inherit system; }; +} diff --git a/nixos/tests/krb5/deprecated-config.nix b/nixos/tests/krb5/deprecated-config.nix new file mode 100644 index 00000000000..980b3e762dc --- /dev/null +++ b/nixos/tests/krb5/deprecated-config.nix @@ -0,0 +1,48 @@ +# Verifies that the configuration suggested in deprecated example values +# will result in the expected output. + +import ../make-test.nix ({ pkgs, ...} : { + name = "krb5-with-deprecated-config"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ eqyiel ]; + }; + + machine = + { config, pkgs, ... }: { + krb5 = { + enable = true; + defaultRealm = "ATHENA.MIT.EDU"; + domainRealm = "athena.mit.edu"; + kdc = "kerberos.mit.edu"; + kerberosAdminServer = "kerberos.mit.edu"; + }; + }; + + testScript = + let snapshot = pkgs.writeText "krb5-with-deprecated-config.conf" '' + [libdefaults] + default_realm = ATHENA.MIT.EDU + + [realms] + ATHENA.MIT.EDU = { + admin_server = kerberos.mit.edu + kdc = kerberos.mit.edu + } + + [domain_realm] + .athena.mit.edu = ATHENA.MIT.EDU + athena.mit.edu = ATHENA.MIT.EDU + + [capaths] + + + [appdefaults] + + + [plugins] + + ''; + in '' + $machine->succeed("diff /etc/krb5.conf ${snapshot}"); + ''; +}) diff --git a/nixos/tests/krb5/example-config.nix b/nixos/tests/krb5/example-config.nix new file mode 100644 index 00000000000..d5328720931 --- /dev/null +++ b/nixos/tests/krb5/example-config.nix @@ -0,0 +1,106 @@ +# Verifies that the configuration suggested in (non-deprecated) example values +# will result in the expected output. + +import ../make-test.nix ({ pkgs, ...} : { + name = "krb5-with-example-config"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ eqyiel ]; + }; + + machine = + { config, pkgs, ... }: { + krb5 = { + enable = true; + kerberos = pkgs.krb5Full; + libdefaults = { + default_realm = "ATHENA.MIT.EDU"; + }; + realms = { + "ATHENA.MIT.EDU" = { + admin_server = "athena.mit.edu"; + kdc = "athena.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 + ''; + }; + }; + + testScript = + 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 = athena.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 '' + $machine->succeed("diff /etc/krb5.conf ${snapshot}"); + ''; +}) -- cgit 1.4.1