chore: ipa and sssd modules

This commit is contained in:
arcnmx 2024-04-05 15:01:05 -07:00
parent 0a48d9cf5d
commit 95e903697a
14 changed files with 983 additions and 65 deletions

View file

@ -4,10 +4,7 @@
...
}: let
hasConfigLib = options ? lib;
gensokyo-zone = {
inherit inputs;
inherit (inputs.self.lib) tree meta lib;
};
gensokyo-zone = inputs.self.lib.gensokyo-zone // {};
in {
config = {
${

5
modules/nixos/args.nix Normal file
View file

@ -0,0 +1,5 @@
{ gensokyo-zone, ... }: {
config.lib = {
inherit gensokyo-zone;
};
}

142
modules/nixos/ipa.nix Normal file
View file

@ -0,0 +1,142 @@
{
pkgs,
config,
lib,
gensokyo-zone,
modulesPath,
...
}: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault mkAlmostForce mapOptionDefaults;
inherit (lib.options) mkOption mkPackageOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault mkForce;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.strings) toLower;
cfg = config.security.ipa;
in {
options.security.ipa = with lib.types; {
package = mkPackageOption pkgs "freeipa" { };
overrideConfigs = {
krb5 = mkOption {
type = bool;
default = true;
description = "allow the ipa module to override krb5.conf";
};
sssd = mkOption {
type = bool;
default = true;
description = "allow the ipa module to override the sssd configuration";
};
ntp = mkOption {
type = bool;
default = false;
description = "allow the ipa module to override the ntp configuration";
};
};
};
config.services.sssd = let
inherit (config.services) sssd;
ipaDebugLevel = 65510;
in mkIf cfg.enable {
debugLevel = mkAlmostOptionDefault ipaDebugLevel;
domains = {
${cfg.domain} = {
ldap.extraAttrs.user = {
mail = "mail";
sn = "sn";
givenname = "givenname";
telephoneNumber = "telephoneNumber";
lock = "nsaccountlock";
};
settings = mapOptionDefaults {
id_provider = "ipa";
auth_provider = "ipa";
access_provider = "ipa";
chpass_provider = "ipa";
ipa_domain = cfg.domain;
ipa_server = [ "_srv_" cfg.server ];
ipa_hostname = "${config.networking.hostName}.${cfg.domain}";
cache_credentials = cfg.cacheCredentials;
krb5_store_password_if_offline = cfg.offlinePasswords;
dyndns_update = cfg.dyndns.enable;
dyndns_iface = cfg.dyndns.interface;
ldap_tls_cacert = "/etc/ipa/ca.crt";
} // {
krb5_realm = mkIf (toLower cfg.domain != toLower cfg.realm) (mkOptionDefault cfg.realm);
};
};
};
services = {
nss.settings = mapOptionDefaults {
homedir_substring = "/home";
};
pam.settings = mapOptionDefaults {
pam_pwd_expiration_warning = 3;
pam_verbosity = 3;
};
sudo = {
enable = mkAlmostOptionDefault true;
settings = mapOptionDefaults {
debug_level = ipaDebugLevel;
};
};
ssh.enable = mkAlmostOptionDefault true;
ifp = {
enable = mkAlmostOptionDefault true;
settings = mapOptionDefaults {
allowed_uids = cfg.ifpAllowedUids;
};
};
};
configText = mkIf (cfg.overrideConfigs.sssd) (mkAlmostOptionDefault null);
config = mkIf (sssd.configText != null) (mkAlmostForce sssd.configText);
};
config.security.krb5 = mkIf cfg.enable {
package = mkAlmostOptionDefault pkgs.krb5Full;
settings = {
libdefaults = mapOptionDefaults {
default_realm = cfg.realm;
dns_lookup_realm = false;
dns_lookup_kdc = true;
rdns = false;
ticket_lifetime = "24h";
forwardable = true;
udp_preference_limit = 0;
};
realms.${cfg.realm} = mapOptionDefaults {
kdc = "${cfg.server}:88";
master_kdc = "${cfg.server}:88";
admin_server = "${cfg.server}:749";
default_domain = cfg.domain;
pkinit_anchors = "/etc/ipa/ca.crt";
};
domain_realm = mkMerge [
(mapOptionDefaults {
".${cfg.domain}" = cfg.realm;
${cfg.domain} = cfg.realm;
})
(mapOptionDefaults {
${cfg.server} = cfg.realm;
})
];
dbmodules.${cfg.realm} = {
db_library = "${cfg.package}/lib/krb5/plugins/kdb/ipadb.so";
};
};
};
config.services.ntp = mkIf (cfg.enable && !cfg.overrideConfigs.ntp) {
servers = mkForce config.networking.timeServers;
};
config.environment.etc."krb5.conf" = let
inherit (config.security) krb5;
format = import (modulesPath + "/security/krb5/krb5-conf-format.nix") { inherit pkgs lib; } { };
in mkIf (cfg.enable && !cfg.overrideConfigs.krb5) {
text = mkForce (format.generate "krb5.conf" krb5.settings).text;
};
}

View file

@ -0,0 +1,136 @@
{ gensokyo-zone, pkgs, config, lib, ... }: let
inherit (gensokyo-zone.lib) mkBaseDn mapDefaults mkAlmostOptionDefault mapOptionDefaults domain;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkDefault mkOptionDefault mkForce;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.strings) toUpper concatStringsSep concatStrings;
inherit (config.security) krb5 ipa;
cfg = krb5.gensokyo-zone;
enabled = krb5.enable || ipa.enable;
subsection = attrs: "{\n" + concatStrings (mapAttrsToList (key: value: " ${key} = ${value}\n") attrs) + "}";
in {
options.security.krb5.gensokyo-zone = with lib.types; {
enable = mkEnableOption "realm";
host = mkOption {
type = str;
default = cfg.canonHost;
};
canonHost = mkOption {
type = str;
default = "idp.${cfg.domain}";
};
domain = mkOption {
type = str;
default = domain;
};
realm = mkOption {
type = str;
default = toUpper cfg.domain;
};
ca.cert = mkOption {
type = path;
};
ldap = {
baseDn = mkOption {
type = str;
default = mkBaseDn cfg.domain;
};
bind = {
dn = mkOption {
type = str;
default = "uid=peep,cn=sysaccounts,cn=etc,${cfg.ldap.base}";
};
passwordFile = mkOption {
type = nullOr str;
default = null;
};
};
urls = mkOption {
type = listOf str;
};
};
db.backend = mkOption {
type = enum [ "kldap" "ipa" ];
default = "kldap";
};
authToLocalNames = mkOption {
type = attrsOf str;
default = { };
};
};
config = {
security.krb5 = {
package = let
krb5-ldap = pkgs.krb5.override {
withLdap = true;
};
in mkIf (cfg.enable && cfg.db.backend == "kldap") (mkDefault pkgs.krb5-ldap or krb5-ldap);
settings = mkIf cfg.enable {
dbmodules = {
genso-kldap = mkIf (cfg.db.backend == "kldap") (mapDefaults {
db_library = "kldap";
ldap_servers = concatStringsSep " " cfg.ldap.urls;
ldap_kdc_dn = cfg.ldap.bind.dn;
ldap_kerberos_container_dn = cfg.ldap.baseDn;
} // {
ldap_service_password_file = mkIf (cfg.ldap.bind.passwordFile != null) (mkDefault cfg.ldap.bind.passwordFile);
});
genso-ipa = mkIf (cfg.db.backend == "ipa") (mapDefaults {
db_library = "${ipa.package}/lib/krb5/plugins/kdb/ipadb.so";
});
${cfg.realm} = mkIf ipa.enable (mkForce { });
};
realms.${cfg.realm} = mapDefaults {
kdc = "${cfg.host}:88";
master_kdc = "${cfg.host}:88";
admin_server = "${cfg.host}:749";
default_domain = cfg.domain;
pkinit_anchors = [ "FILE:${cfg.ca.cert}" ];
} // {
database_module = mkOptionDefault "genso-${cfg.db.backend}";
auth_to_local_names = mkIf (cfg.authToLocalNames != { }) (mkDefault (subsection cfg.authToLocalNames));
};
domain_realm = mapOptionDefaults {
${cfg.domain} = cfg.realm;
".${cfg.domain}" = cfg.realm;
};
libdefaults = mapOptionDefaults {
default_realm = cfg.realm;
dns_lookup_realm = false;
dns_lookup_kdc = true;
rdns = false;
ticket_lifetime = "24h";
forwardable = true;
udp_preference_limit = 0;
ignore_acceptor_hostname = true;
};
};
gensokyo-zone = {
ca.cert = let
caPem = pkgs.fetchurl {
name = "${cfg.canonHost}.ca.pem";
url = "https://freeipa.${cfg.domain}/ipa/config/ca.crt";
sha256 = "sha256-PKjnjn1jIq9x4BX8+WGkZfj4HQtmnHqmFSALqggo91o=";
};
in mkOptionDefault caPem;
db.backend = mkIf ipa.enable (mkAlmostOptionDefault "ipa");
ldap.urls = mkOptionDefault [
"ldaps://ldap.${cfg.domain}"
"ldaps://${cfg.canonHost}"
];
};
};
networking.timeServers = mkIf (cfg.enable && enabled) [ "2.fedora.pool.ntp.org" ];
security.ipa = mkIf cfg.enable {
certificate = mkDefault cfg.ca.cert;
basedn = mkDefault cfg.ldap.baseDn;
domain = mkDefault cfg.domain;
realm = mkDefault cfg.realm;
server = mkDefault cfg.canonHost;
ifpAllowedUids = [
"root"
] ++ config.users.groups.wheel.members;
dyndns.enable = mkDefault false;
};
};
}

View file

@ -73,7 +73,6 @@ in {
netgroup = mkMerge [
(mkBefore [ "files" ])
(mkAfter [ "nis" ])
(mkIf config.services.sssd.enable [ "sss" ])
];
};
environment.etc."nsswitch.conf".text = mkIf (nssDatabases.netgroup != [ ]) (mkAfter ''

View file

@ -0,0 +1,194 @@
{ gensokyo-zone, pkgs, config, lib, ... }: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault mapOptionDefaults mapAlmostOptionDefaults;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkAfter mkDefault mkOptionDefault;
inherit (config.security) krb5 ipa;
inherit (config.services) sssd;
genso = krb5.gensokyo-zone;
cfg = sssd.gensokyo-zone;
serverModule = { config, ... }: {
options = with lib.types; {
servers = mkOption {
type = nullOr (listOf str);
default = null;
};
backups = mkOption {
type = listOf str;
default = [ ];
};
serverName = mkOption {
type = str;
internal = true;
};
serverKind = mkOption {
type = enum [ "server" "uri" ];
default = "server";
internal = true;
};
settings = mkOption {
type = attrsOf (listOf str);
};
};
config = let
key = "${config.serverName}_${config.serverKind}";
keyBackups = "${config.serverName}_backup_${config.serverKind}";
in {
settings = {
${key} = mkIf (config.servers != null) (mkOptionDefault config.servers);
${keyBackups} = mkIf (config.backups != [ ]) (mkOptionDefault config.backups);
};
};
};
mkServerType = { modules }: lib.types.submoduleWith {
modules = [ serverModule ] ++ modules;
specialArgs = {
inherit gensokyo-zone pkgs;
nixosConfig = config;
};
};
mkServerOption = { name, kind ? "server" }: let
serverInfoModule = { ... }: {
config = {
serverName = mkOptionDefault name;
serverKind = mkAlmostOptionDefault kind;
};
};
in mkOption {
type = mkServerType {
modules = [ serverInfoModule ];
};
default = { };
};
in {
options.services.sssd.gensokyo-zone = with lib.types; {
enable = mkEnableOption "realm" // {
default = genso.enable;
};
ldap = {
bind = {
passwordFile = mkOption {
type = nullOr str;
default = null;
};
};
uris = mkServerOption { name = "ldap"; kind = "uri"; };
};
krb5 = {
servers = mkServerOption { name = "krb5"; };
};
ipa = {
servers = mkServerOption { name = "ipa"; } // {
default = {
inherit (cfg.krb5.servers) servers backups;
};
};
hostName = mkOption {
type = str;
default = config.networking.fqdn;
};
};
backend = mkOption {
type = enum [ "ldap" "ipa" ];
default = "ipa";
};
};
config = {
services.sssd = let
# or "ipaNTSecurityIdentifier" which isn't set for most groups, maybe check netgroups..?
objectsid = "sambaSID";
backendDomainSettings = {
ldap = mapAlmostOptionDefaults {
id_provider = mkDefault "ldap";
auth_provider = mkDefault "krb5";
access_provider = "ldap";
ldap_tls_cacert = "/etc/ssl/certs/ca-bundle.crt";
} // mapOptionDefaults {
ldap_access_order = [ "host" ];
ldap_schema = "IPA";
ldap_default_bind_dn = genso.ldap.bind.dn;
ldap_search_base = genso.ldap.baseDn;
ldap_user_search_base = "cn=users,cn=accounts,${genso.ldap.baseDn}";
ldap_group_search_base = "cn=groups,cn=accounts,${config.ldap.baseDn}";
ldap_user_uuid = "ipaUniqueID";
ldap_user_ssh_public_key = "ipaSshPubKey";
ldap_user_objectsid = objectsid;
ldap_group_uuid = "ipaUniqueID";
ldap_group_objectsid = objectsid;
};
ipa = mapOptionDefaults {
id_provider = "ipa";
auth_provider = "ipa";
access_provider = "ipa";
chpass_provider = "ipa";
dyndns_update = ipa.dyndns.enable;
dyndns_iface = ipa.dyndns.interface;
};
};
domainSettings = mapAlmostOptionDefaults {
ipa_hostname = cfg.ipa.hostName;
} // mapOptionDefaults {
enumerate = true;
ipa_domain = genso.domain;
krb5_realm = genso.realm;
cache_credentials = ipa.cacheCredentials;
krb5_store_password_if_offline = ipa.offlinePasswords;
#min_id = 8000;
#max_id = 8999;
};
in {
gensokyo-zone = {
krb5.servers.servers = mkMerge [
[ genso.host ]
(mkAfter [ "_srv" genso.canonHost ])
];
ldap.uris = {
servers = mkMerge [
(mkAfter [ "_srv" ])
genso.ldap.urls
];
};
};
domains = mkIf cfg.enable {
${genso.domain} = {
ldap = {
authtok = mkIf (cfg.backend == "ldap") {
passwordFile = mkIf (cfg.ldap.bind.passwordFile != null) (mkAlmostOptionDefault cfg.ldap.bind.passwordFile);
};
extraAttrs.user = {
mail = "mail";
sn = "sn";
givenname = "givenname";
telephoneNumber = "telephoneNumber";
lock = "nsaccountlock";
};
};
settings = mkMerge [
domainSettings
backendDomainSettings.${cfg.backend}
(mapAlmostOptionDefaults cfg.ldap.uris.settings)
(mapAlmostOptionDefaults cfg.krb5.servers.settings)
(mkIf (cfg.backend == "ipa") (mapAlmostOptionDefaults cfg.ipa.servers.settings))
];
};
};
services = mkIf cfg.enable {
nss.settings = mapOptionDefaults {
homedir_substring = "/home";
};
pam.settings = mapOptionDefaults {
pam_pwd_expiration_warning = 3;
pam_verbosity = 3;
};
sudo.enable = mkIf (!sssd.services.pam.enable) (mkDefault false);
ssh.enable = mkIf (!sssd.services.pam.enable) (mkDefault false);
ifp = {
enable = mkAlmostOptionDefault true;
settings = mapOptionDefaults {
allowed_uids = ipa.ifpAllowedUids;
};
};
};
};
};
}

210
modules/nixos/sssd/sssd.nix Normal file
View file

@ -0,0 +1,210 @@
{
config,
options,
lib,
gensokyo-zone,
...
}: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault mapOptionDefaults;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault mkForce;
inherit (lib.attrsets) mapAttrs filterAttrs attrNames attrValues listToAttrs mapAttrsToList nameValuePair;
inherit (lib.lists) filter isList concatMap;
inherit (lib.strings) toUpper concatMapStringsSep replaceStrings;
inherit (lib.trivial) flip;
inherit (lib) generators;
cfg = config.services.sssd;
mkValuePrimitive = value:
if value == true then "True"
else if value == false then "False"
else toString value;
toINI = generators.toINI {
mkKeyValue = generators.mkKeyValueDefault {
mkValueString = value:
if isList value then concatMapStringsSep ", " mkValuePrimitive value
else mkValuePrimitive value;
} " = ";
};
primitiveType = with lib.types; oneOf [ str int bool ];
valueType = with lib.types; oneOf [ primitiveType (listOf primitiveType) ];
settingsType = lib.types.attrsOf valueType;
serviceModule = { name, ... }: {
options = with lib.types; {
enable = mkEnableOption "${name} service";
name = mkOption {
type = str;
default = name;
readOnly = true;
};
settings = mkOption {
type = settingsType;
default = { };
};
};
};
nssModule = { nixosConfig, ... }: {
options = {
# TODO: passwd.enable = mkEnableOption "passwd" // { default = true; };
shadow.enable = mkEnableOption "shadow" // { default = nixosConfig.services.sssd.services.pam.enable; };
netgroup.enable = mkEnableOption "netgroup" // { default = true; };
};
};
domainModule = { name, ... }: {
options = with lib.types; {
enable = mkEnableOption "domain" // {
default = true;
};
domain = mkOption {
type = str;
default = name;
};
settings = mkOption {
type = settingsType;
};
};
};
domainLdapModule = { config, ... }: let
cfg = config.ldap;
in {
options.ldap = with lib.types; {
extraAttrs.user = mkOption {
type = attrsOf str;
default = { };
};
authtok = {
type = mkOption {
type = enum [ "password" "obfuscated_password" ];
default = "password";
};
password = mkOption {
type = nullOr str;
default = null;
};
passwordFile = mkOption {
type = nullOr path;
default = null;
};
passwordVar = mkOption {
type = str;
internal = true;
default = "SSSD_AUTHTOK_" + replaceStrings [ "-" "." ] [ "_" "_" ] (toUpper config.domain);
};
};
};
config = let
authtokConfig = mkIf (cfg.authtok.password != null || cfg.authtok.passwordFile != null) {
ldap_default_authtok_type = mkOptionDefault cfg.authtok.type;
ldap_default_authtok = mkOptionDefault (
if cfg.authtok.passwordFile != null then "\$${cfg.authtok.passwordVar}"
else cfg.authtok.password
);
};
extraAttrsConfig = mkIf (cfg.extraAttrs.user != { }) {
ldap_user_extra_attrs = let
mkAttr = name: attr: "${name}:${attr}";
in mapAttrsToList mkAttr cfg.extraAttrs.user;
};
in {
settings = mkMerge [
authtokConfig
extraAttrsConfig
];
};
};
in {
options.services.sssd = with lib.types; {
debugLevel = mkOption {
type = ints.between 16 65520;
default = 16;
};
domains = mkOption {
type = attrsOf (submoduleWith {
modules = [ domainModule domainLdapModule ];
specialArgs = {
nixosConfig = config;
};
});
default = {
shadowutils.settings = mapOptionDefaults {
id_provider = "proxy";
proxy_lib_name = "files";
auth_provider = "proxy";
proxy_pam_target = "sssd-shadowutils";
proxy_fast_alias = true;
};
};
};
services = let
mkServiceOption = name: { modules ? [ ] }: mkOption {
type = submoduleWith {
modules = [ serviceModule ] ++ modules;
specialArgs = {
inherit name;
nixosConfig = config;
};
};
};
services = {
nss = { modules = [ nssModule ]; };
pam = { };
ifp = { };
sudo = { };
autofs = { };
ssh = { };
pac = { };
};
in mapAttrs mkServiceOption services;
settings = mkOption {
type = attrsOf settingsType;
};
configText = mkOption {
type = nullOr lines;
};
};
config.services.sssd = let
enabledDomains = filter (domain: domain.enable) (attrValues cfg.domains);
enabledServices = filterAttrs (_: service: service.enable) cfg.services;
in {
settings = let
serviceSettings = mapAttrs (name: service: mapOptionDefaults service.settings) enabledServices;
defaultSettings = {
sssd = mapOptionDefaults {
config_file_version = 2;
debug_level = cfg.debugLevel;
services = mapAttrsToList (_: service: service.name) enabledServices;
domains = map (domain: domain.domain) enabledDomains;
};
};
domainSettings = map (domain: {
"domain/${domain.domain}" = mapAttrs (_: mkOptionDefault) domain.settings;
}) enabledDomains;
settings = [ defaultSettings serviceSettings ] ++ domainSettings;
in mkMerge settings;
services = {
nss.enable = mkAlmostOptionDefault true;
pam.enable = mkAlmostOptionDefault true;
ifp.settings = let
extraUserAttrs = listToAttrs (concatMap (domain: map (flip nameValuePair {}) (attrNames domain.ldap.extraAttrs.user)) enabledDomains);
mkExtraAttr = name: _: "+${name}";
in {
user_attributes = mkIf (extraUserAttrs != { }) (mkOptionDefault (
mapAttrsToList mkExtraAttr extraUserAttrs
));
};
sudo = { };
autofs = { };
ssh = { };
pac = { };
};
configText = mkOptionDefault (toINI cfg.settings);
config = mkIf (cfg.configText != null) (mkAlmostOptionDefault cfg.configText);
};
config.system.nssDatabases = let
inherit (cfg.services) nss;
in mkIf cfg.enable {
${if options ? system.nssDatabases.netgroup then "netgroup" else null} = mkIf (nss.enable && nss.netgroup.enable) [ "sss" ];
shadow = mkIf (!nss.enable || !nss.shadow.enable) (
mkForce [ "files" ]
);
};
}

View file

@ -100,6 +100,7 @@ in {
}) config.builder);
specialArgs = {
inherit name inputs std meta;
inherit (inputs.self.lib) gensokyo-zone;
systemType = config.folder;
system = config;
};