infrastructure/modules/extern/nixos/krb5.nix
2024-09-15 13:22:41 -07:00

478 lines
15 KiB
Nix

{
config,
options,
lib,
gensokyo-zone,
pkgs,
...
}: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault mapOptionDefaults mapAlmostOptionDefaults;
inherit (lib.options) mkOption mkEnableOption mkPackageOption;
inherit (lib.modules) mkIf mkMerge mkBefore mkAfter mkForce mkDefault mkOptionDefault;
inherit (lib.lists) optional elem;
inherit (lib.strings) toUpper concatStringsSep;
inherit (gensokyo-zone.lib) unmerged;
cfg = config.gensokyo-zone.krb5;
krb5Module = {
gensokyo-zone,
nixosConfig,
nixosOptions,
config,
pkgs,
...
}: let
inherit (gensokyo-zone.lib) unmerged mkBaseDn;
inherit (nixosConfig.gensokyo-zone) access;
enabled = {
krb5 = nixosConfig.security.krb5.enable;
ipa = config.ipa.enable && nixosConfig.security.ipa.enable;
sssd = config.sssd.enable && nixosConfig.services.sssd.enable;
};
in {
options = with lib.types; {
enable = mkEnableOption "kerberos settings";
domain = mkOption {
type = str;
default = gensokyo-zone.lib.domain;
};
realm = mkOption {
type = str;
default = toUpper config.domain;
};
ca = {
trust =
mkEnableOption "trust CA"
// {
default = true;
};
pem = mkOption {
type = path;
};
};
host = mkOption {
type = str;
default = config.ipa.host;
};
ldap = {
host = mkOption {
type = str;
default = "ldap.${config.domain}";
example = "ldap.local.${config.domain}";
};
urls = mkOption {
type = listOf str;
default = ["ldaps://${config.ldap.host}"];
};
baseDn = mkOption {
type = str;
default = mkBaseDn config.domain;
};
bind = {
dn = mkOption {
type = str;
default = "uid=peep,cn=sysaccounts,cn=etc,${config.ldap.baseDn}";
};
passwordFile = mkOption {
type = path;
};
passwordFileKrb5 = mkOption {
type = path;
example = lib.literalExpression "\${pkgs.writeText " ldap.kdb5 " ''
${config.bind.dn}#{HEX}616e6f6e796d6f7573
''}";
};
passwordFileSssdEnv = mkOption {
type = path;
example = lib.literalExpression "\${pkgs.writeText " ldap.kdb5 " ''
${"SSSD_AUTHTOK_" + replaceStrings ["."] ["_"] (toUpper config.domain)}=verysecretpassword
''}";
};
};
};
db = {
backend = mkOption {
type = enum ["kldap" "ipa"];
default = "kldap";
};
};
logLevel = mkOption {
type = str;
default = "NOTICE";
};
authToLocalNames = mkOption {
type = attrsOf str;
default = {};
example = {
"arc@${config.realm}" = "arc";
};
};
sssd = {
enable = mkEnableOption "sssd";
pam.enable = mkEnableOption "PAM";
backend = mkOption {
type = enum ["ipa" "ldap"];
default =
{
ipa = "ipa";
kldap = "ldap";
}
.${config.db.backend};
};
};
ntp = {
enable =
mkEnableOption "ntp"
// {
default = true;
};
servers = mkOption {
type = listOf str;
example = [config.ipa.host];
default = ["2.fedora.pool.ntp.org"];
};
};
nfs = {
enable = mkEnableOption "nfs";
package = mkPackageOption pkgs "nfs-utils" {};
idmapd = {
localDomain = mkOption {
type = bool;
default = enabled.sssd && nixosConfig.services.sssd.services.nss.enable;
};
localRealms = mkOption {
type = listOf str;
default = [config.realm];
};
methods = mkOption {
type = listOf str;
default = ["nsswitch"];
};
authToLocalNames = mkOption {
type = attrsOf str;
default = config.authToLocalNames;
};
};
debug.enable = mkEnableOption "nfs debug logs";
};
ipa = {
enable = mkEnableOption "IPA";
httpHost = mkOption {
type = str;
default = "ipa.${config.domain}";
};
host = mkOption {
type = str;
default = "idp.${config.domain}";
};
};
set = {
krb5Settings = mkOption {
type = unmerged.type;
default = {};
};
sssdSettings = mkOption {
type = unmerged.type;
default = {};
};
ipaSettings = mkOption {
type = unmerged.type;
default = {};
};
nfsSettings = mkOption {
type = unmerged.type;
default = {};
};
};
};
config = {
ca.pem = let
caPem = pkgs.fetchurl {
name = "${config.ipa.host}.ca.pem";
url = "https://${config.ipa.httpHost}/ipa/config/ca.crt";
sha256 = "sha256-PKjnjn1jIq9x4BX8+WGkZfj4HQtmnHqmFSALqggo91o=";
};
in
mkOptionDefault caPem;
ldap = {
urls = mkMerge [
(mkIf access.local.enable (mkOptionDefault (mkBefore [
"ldaps://ldap.local.${config.domain}"
])))
(mkIf enabled.ipa (mkOptionDefault (mkBefore [
"ldaps://${config.ipa.host}"
])))
(mkIf access.tail.enabled (mkOptionDefault (mkAfter [
"ldap://ldap.tail.${config.domain}"
])))
];
bind = let
inherit (nixosConfig.sops) secrets;
in
mkIf (nixosOptions ? sops.secrets && secrets ? gensokyo-zone-krb5-passwords) {
passwordFileKrb5 = mkOptionDefault nixosConfig.sops.secrets.gensokyo-zone-krb5-passwords.path;
passwordFile = mkOptionDefault nixosConfig.sops.secrets.gensokyo-zone-krb5-peep-password.path;
passwordFileSssdEnv = mkOptionDefault nixosConfig.sops.secrets.gensokyo-zone-sssd-passwords.path;
};
};
db.backend = mkIf enabled.ipa (mkAlmostOptionDefault "ipa");
nfs = {
package = mkIf (elem "umich_ldap" config.nfs.idmapd.methods) (mkAlmostOptionDefault pkgs.nfs-utils-ldap);
idmapd = {
methods = mkMerge [
(mkIf (config.nfs.idmapd.authToLocalNames != {}) (
mkOptionDefault (mkBefore ["static"])
))
(mkIf (!enabled.sssd) (
mkOptionDefault ["umich_ldap"]
))
];
};
};
set = {
krb5Settings = {
enable = mkAlmostOptionDefault true;
gensokyo-zone = {
enable = mkAlmostOptionDefault true;
host = mkAlmostOptionDefault config.host;
canonHost = mkAlmostOptionDefault config.ipa.host;
domain = mkAlmostOptionDefault config.domain;
realm = mkAlmostOptionDefault config.realm;
ca.cert = mkAlmostOptionDefault config.ca.pem;
db.backend = mkAlmostOptionDefault config.db.backend;
ldap = {
baseDn = mkAlmostOptionDefault config.ldap.baseDn;
urls = mkAlmostOptionDefault config.ldap.urls;
bind = mapAlmostOptionDefaults {
dn = config.ldap.bind.dn;
passwordFile = config.ldap.bind.passwordFileKrb5;
};
};
authToLocalNames = mkAlmostOptionDefault config.authToLocalNames;
};
};
sssdSettings = let
servers =
optional access.local.enable "idp.local.${config.domain}"
++ ["_srv"];
backups = mkMerge [
(mkIf access.tail.enabled (mkAlmostOptionDefault ["ipa.tail.${config.domain}"]))
(mkIf access.local.enable (mkAlmostOptionDefault ["ipa.local.${config.domain}"]))
];
in
mkIf config.sssd.enable {
enable = mkAlmostOptionDefault true;
gensokyo-zone = {
backend = mkAlmostOptionDefault config.sssd.backend;
krb5.servers = {
servers = servers ++ [config.host];
inherit backups;
};
ipa.servers = {
servers = servers ++ [config.ipa.host];
inherit backups;
};
ldap = {
bind.passwordFile = mkAlmostOptionDefault config.ldap.bind.passwordFile;
uris.backups = mkIf access.tail.enabled (mkAlmostOptionDefault (mkAfter [
"ldaps://ldap.tail.${config.domain}"
]));
};
};
environmentFile = mkIf (config.sssd.backend == "ldap") (
mkAlmostOptionDefault
config.ldap.bind.passwordFileSssdEnv
);
services = {
ifp.enable = mkAlmostOptionDefault true;
pam.enable = mkIf (!config.sssd.pam.enable) (mkDefault false);
};
};
ipaSettings = mkIf config.ipa.enable (mapAlmostOptionDefaults {
enable = true;
certificate = config.ca.pem;
basedn = config.ldap.baseDn;
domain = config.domain;
realm = config.realm;
server = config.ipa.server;
# TODO: dyndns?
}
// {
overrideConfigs = mapAlmostOptionDefaults {
sssd = false;
krb5 = false;
};
});
nfsSettings = mkIf config.nfs.enable {
${
if nixosOptions ? services.nfs.settings
then "settings"
else null
} = mkMerge [
{
gssd = mapOptionDefaults {
#use-machine-creds = false;
avoid-dns = true;
preferred-realm = config.realm;
};
}
(mkIf config.nfs.debug.enable {
mountd.debug = mkOptionDefault "all";
exportfs.debug = mkOptionDefault "all";
exportd.debug = mkOptionDefault "all";
general.idmap-verbosity = mkOptionDefault 3;
idmapd = mapOptionDefaults {
verbosity = 3;
idmap-verbosity = 3;
};
gssd = mapOptionDefaults {
verbosity = 3;
rpc-verbosity = 3;
};
})
];
${
if nixosOptions ? services.nfs.settings
then null
else "extraConfig"
} = mkMerge [
''
[gssd]
#use-machine-creds = false
avoid-dns = true
preferred-realm = ${config.realm}
''
(mkIf config.nfs.debug.enable ''
[mountd]
debug = all
[exportfs]
debug = all
[exportd]
debug = all
[general]
idmap-verbosity = 3
[idmapd]
verbosity = 3
idmap-verbosity = 3
[gssd]
verbosity = 3
rpc-verbosity = 3
'')
];
# TODO: move this into a more generic /modules/nixos/nfs that gets configured...
idmapd.settings = {
General = mkIf config.nfs.idmapd.localDomain {
Domain = mkForce config.domain;
Local-Realms = concatStringsSep "," config.nfs.idmapd.localRealms;
};
Translation.Method = mkIf (config.nfs.idmapd.methods != ["nsswitch"]) (mkForce (
concatStringsSep "," config.nfs.idmapd.methods
));
Static = mkIf (config.nfs.idmapd.authToLocalNames != {}) config.nfs.idmapd.authToLocalNames;
UMICH_SCHEMA = mkIf (elem "umich_ldap" config.nfs.idmapd.methods) (mapOptionDefaults {
LDAP_server = config.ldap.host;
LDAP_use_ssl = true;
LDAP_ca_cert = "/etc/ssl/certs/ca-bundle.crt";
LDAP_base = config.ldap.baseDn;
LDAP_people_base = "cn=users,cn=accounts,${config.ldap.baseDn}";
LDAP_group_base = "cn=groups,cn=accounts,${config.ldap.baseDn}";
NFSv4_person_objectclass = "posixaccount"; # or "person"?
NFSv4_name_attr = "krbCanonicalName"; # uid? cn? gecos?
GSS_principal_attr = "krbPrincipalName";
NFSv4_group_objectclass = "posixgroup";
NFSv4_group_attr = "cn";
#LDAP_use_memberof_for_groups = true;
#NFSv4_member_attr = "member";
LDAP_canonicalize_name = false;
});
};
};
};
};
};
in {
imports = [
./access.nix
../misc/sssd.nix
../misc/ipa.nix
../misc/netgroups.nix
../../nixos/krb5/genso.nix
../../nixos/sssd/genso.nix
];
options.gensokyo-zone.krb5 = mkOption {
type = lib.types.submoduleWith {
modules = [krb5Module];
specialArgs = {
inherit gensokyo-zone pkgs;
inherit (gensokyo-zone) inputs;
nixosConfig = config;
nixosOptions = options;
};
};
default = {};
};
config = {
nixpkgs = mkIf cfg.enable {
overlays = mkMerge [
[gensokyo-zone.overlays.krb5]
(mkIf cfg.nfs.enable [gensokyo-zone.overlays.nfs])
];
};
security = {
krb5 = mkIf cfg.enable (unmerged.merge cfg.set.krb5Settings);
ipa = mkIf cfg.enable (unmerged.merge cfg.set.ipaSettings);
pki.certificateFiles = mkIf (cfg.enable && cfg.ca.trust && !cfg.ipa.enable) [
cfg.ca.pem
];
};
services.sssd = mkIf cfg.enable (unmerged.merge cfg.set.sssdSettings);
services.nfs = mkIf cfg.enable (unmerged.merge cfg.set.nfsSettings);
services.ntp.enable = mkIf (cfg.enable && cfg.ntp.enable) (mkAlmostOptionDefault true);
networking = {
timeServers = mkIf (cfg.enable && cfg.ntp.enable) cfg.ntp.servers;
hosts = let
inherit (gensokyo-zone.systems) freeipa;
# TODO: consider hakurei instead...
in
mkIf (cfg.enable && !config.gensokyo-zone.dns.enable or false && config.gensokyo-zone.access.local.enable) {
${freeipa.access.address6ForNetwork.local} = mkIf config.networking.enableIPv6 (mkBefore [cfg.host]);
${freeipa.access.address4ForNetwork.local} = mkBefore [cfg.host];
};
};
environment.etc = {
"request-key.conf" = mkIf (cfg.enable && cfg.nfs.enable && cfg.sssd.enable) {
source = let
nfsidmap = pkgs.writeShellScript "nfsidmap" ''
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${config.system.nssModules.path}"
exec ${cfg.nfs.package}/bin/nfsidmap "$@"
'';
in
mkForce (pkgs.writeText "request-key.conf" ''
create id_resolver * * ${nfsidmap} -t 600 %k %d
'');
};
};
${
if options ? sops.secrets
then "sops"
else null
}.secrets = let
sopsFile = mkDefault ../secrets/krb5.yaml;
in
mkIf cfg.enable {
gensokyo-zone-krb5-passwords = mkIf (cfg.db.backend == "kldap") {
inherit sopsFile;
};
gensokyo-zone-krb5-peep-password = mkIf (cfg.sssd.backend == "ldap") {
inherit sopsFile;
};
gensokyo-zone-sssd-passwords = mkIf (cfg.sssd.backend == "ldap") {
inherit sopsFile;
};
};
lib.gensokyo-zone.krb5 = {
inherit cfg krb5Module;
};
};
}