diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix index a54471c0b956..db929c62fed9 100644 --- a/nixos/modules/services/security/kanidm.nix +++ b/nixos/modules/services/security/kanidm.nix @@ -182,6 +182,12 @@ let fi ''; + finalJson = + if cfg.provision.extraJsonFile != null then + "<(${lib.getExe pkgs.jq} -s '.[0] * .[1]' ${provisionStateJson} ${cfg.provision.extraJsonFile})" + else + provisionStateJson; + postStartScript = pkgs.writeShellScript "post-start" '' set -euo pipefail @@ -207,7 +213,7 @@ let ${optionalString (!cfg.provision.autoRemove) "--no-auto-remove"} \ ${optionalString cfg.provision.acceptInvalidCerts "--accept-invalid-certs"} \ --url "${cfg.provision.instanceUrl}" \ - --state ${provisionStateJson} + --state ${finalJson} ''; serverPort = @@ -431,6 +437,19 @@ in default = true; }; + extraJsonFile = mkOption { + description = '' + A JSON file for provisioning persons, groups & systems. + Options set in this file take precedence over values set using the other options. + In the case of duplicates, `jq` will remove all but the last one + when merging this file with the options. + The accepted JSON schema can be found at . + Note: theoretically `jq` cannot merge nested types, but this does not pose an issue as kanidm-provision's JSON scheme does not use nested types. + ''; + type = types.nullOr types.path; + default = null; + }; + groups = mkOption { description = "Provisioning of kanidm groups"; default = { }; @@ -756,38 +775,46 @@ in } ) ] - ++ flip mapAttrsToList (filterPresent cfg.provision.persons) ( - person: personCfg: - assertGroupsKnown "services.kanidm.provision.persons.${person}.groups" personCfg.groups - ) - ++ flip mapAttrsToList (filterPresent cfg.provision.groups) ( - group: groupCfg: - assertEntitiesKnown "services.kanidm.provision.groups.${group}.members" groupCfg.members - ) + ++ (optionals (cfg.provision.extraJsonFile == null) ( + flip mapAttrsToList (filterPresent cfg.provision.persons) ( + person: personCfg: + assertGroupsKnown "services.kanidm.provision.persons.${person}.groups" personCfg.groups + ) + )) + ++ (optionals (cfg.provision.extraJsonFile == null) ( + flip mapAttrsToList (filterPresent cfg.provision.groups) ( + group: groupCfg: + assertEntitiesKnown "services.kanidm.provision.groups.${group}.members" groupCfg.members + ) + )) ++ concatLists ( flip mapAttrsToList (filterPresent cfg.provision.systems.oauth2) ( oauth2: oauth2Cfg: - [ - (assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.scopeMaps" ( + (optionals (cfg.provision.extraJsonFile == null) ( + assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.scopeMaps" ( attrNames oauth2Cfg.scopeMaps - )) - (assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.supplementaryScopeMaps" ( + ) + )) + ++ (optionals (cfg.provision.extraJsonFile == null) ( + assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.supplementaryScopeMaps" ( attrNames oauth2Cfg.supplementaryScopeMaps - )) - ] + ) + )) ++ concatLists ( flip mapAttrsToList oauth2Cfg.claimMaps ( claim: claimCfg: [ - (assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim}.valuesByGroup" ( - attrNames claimCfg.valuesByGroup + (mkIf (cfg.provision.extraJsonFile == null) ( + assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim}.valuesByGroup" ( + attrNames claimCfg.valuesByGroup + ) )) # At least one group must map to a value in each claim map - { + (mkIf (cfg.provision.extraJsonFile == null) { assertion = (cfg.provision.enable && cfg.enableServer) -> any (xs: xs != [ ]) (attrValues claimCfg.valuesByGroup); message = "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim} does not specify any values for any group"; - } + }) # Public clients cannot define a basic secret { assertion =