From 88477df52194cd966325ca7acaf9b06c053db2f6 Mon Sep 17 00:00:00 2001 From: arcnmx Date: Sun, 17 Mar 2024 21:16:46 -0700 Subject: [PATCH] fix(nfs): sec=krb5 --- modules/nixos/network/netgroups.nix | 89 ++++++++++++++++++ modules/nixos/nfs.nix | 139 ++++++++++++++++++++++++++++ nixos/access/freeipa.nix | 3 +- nixos/ipa.nix | 14 +-- nixos/kyuuto/nfs.nix | 88 ++++++++++-------- nixos/nfs.nix | 67 +++++++++++--- 6 files changed, 336 insertions(+), 64 deletions(-) create mode 100644 modules/nixos/network/netgroups.nix create mode 100644 modules/nixos/nfs.nix diff --git a/modules/nixos/network/netgroups.nix b/modules/nixos/network/netgroups.nix new file mode 100644 index 00000000..d04902d6 --- /dev/null +++ b/modules/nixos/network/netgroups.nix @@ -0,0 +1,89 @@ +{ + config, + lib, + ... +}: let + inherit (lib.options) mkOption; + inherit (lib.modules) mkIf mkMerge mkBefore mkAfter mkOptionDefault; + inherit (lib.attrsets) mapAttrsToList; + inherit (lib.strings) concatStringsSep; + inherit (config.system) nssDatabases; + inherit (config) networking; + netgroupMemberModule = { config, name, ... }: { + options = with lib.types; { + hostname = mkOption { + type = str; + default = name; + }; + user = mkOption { + type = either (enum [ null "-" ]) str; + default = "-"; + }; + domain = mkOption { + type = str; + default = networking.domain; + description = "NIS domain"; + }; + triple = mkOption { + type = str; + }; + }; + config = { + triple = mkOptionDefault "(${config.hostname},${toString config.user},${config.domain})"; + }; + }; + netgroupModule = { config, name, ... }: { + options = with lib.types; { + name = mkOption { + type = str; + default = name; + }; + members = mkOption { + type = attrsOf (submodule netgroupMemberModule); + default = { }; + }; + fileLine = mkOption { + type = str; + }; + }; + config = { + fileLine = mkOptionDefault (concatStringsSep " " ([ config.name ] ++ mapAttrsToList (_: member: member.triple) config.members)); + }; + }; +in { + options = with lib.types; { + system.nssDatabases = { + netgroup = mkOption { + type = listOf str; + }; + }; + networking = { + netgroups = mkOption { + type = attrsOf (submodule netgroupModule); + default = { }; + }; + extraNetgroups = mkOption { + type = lines; + default = ""; + }; + }; + }; + config = { + system.nssDatabases = { + netgroup = mkMerge [ + (mkBefore [ "files" ]) + (mkAfter [ "nis" ]) + (mkIf config.services.sssd.enable [ "sss" ]) + ]; + }; + environment.etc."nssswitch.conf".text = mkIf (nssDatabases.netgroup != [ ]) (mkAfter '' + netgroup: ${concatStringsSep " " nssDatabases.netgroup} + ''); + environment.etc."netgroup" = mkIf (networking.netgroups != { } || networking.extraNetgroups != "") { + text = mkMerge ( + mapAttrsToList (_: ng: ng.fileLine) networking.netgroups + ++ [ networking.extraNetgroups ] + ); + }; + }; +} diff --git a/modules/nixos/nfs.nix b/modules/nixos/nfs.nix new file mode 100644 index 00000000..8a845f67 --- /dev/null +++ b/modules/nixos/nfs.nix @@ -0,0 +1,139 @@ +{ + pkgs, + config, + lib, + ... +}: let + inherit (lib.options) mkOption; + inherit (lib.modules) mkMerge mkIf mkBefore mkForce mkOptionDefault; + inherit (lib.lists) optional; + inherit (lib.attrsets) mapAttrsToList; + inherit (lib.lists) toList; + inherit (lib.strings) optionalString concatStringsSep concatMapStringsSep; + cfg = config.services.nfs; + clientEnabled = config.boot.supportedFilesystems.nfs or config.boot.supportedFilesystems.nfs4 or false; + enabled = cfg.server.enable || clientEnabled; + openPorts = [ + (mkIf cfg.server.enable 2049) + (mkIf config.services.rpcbind.enable 111) + (mkIf (cfg.server.statdPort != null) cfg.server.statdPort) + (mkIf (cfg.server.lockdPort != null) cfg.server.lockdPort) + (mkIf (cfg.server.mountdPort != null) cfg.server.mountdPort) + ]; + concatFlags = concatStringsSep ","; + clientModule = { config, name, ... }: { + options = with lib.types; { + machine = mkOption { + type = oneOf [ str (listOf str) ]; + default = name; + example = "*"; + }; + flags = mkOption { + type = listOf str; + default = [ ]; + }; + entry = mkOption { + type = str; + }; + }; + config = { + entry = let + flags = optionalString (config.flags != [ ]) "(${concatFlags config.flags})"; + machines = toList config.machine; + in mkOptionDefault (concatMapStringsSep " " (machine: machine + flags) machines); + }; + }; + exportModule = { config, name, ... }: { + options = with lib.types; { + path = mkOption { + type = path; + default = name; + }; + flags = mkOption { + type = listOf str; + }; + clients = mkOption { + type = attrsOf (submodule clientModule); + }; + fileLine = mkOption { + type = str; + }; + }; + config = { + flags = mkOptionDefault (cfg.export.flagSets.common or [ ]); + fileLine = let + parts = [ config.path ] + ++ optional (config.flags != [ ]) "-${concatFlags config.flags}" + ++ mapAttrsToList (_: client: client.entry) config.clients; + in mkOptionDefault (concatStringsSep " " parts); + }; + }; +in { + options.services.nfs = with lib.types; { + export = { + flagSets = mkOption { + type = lazyAttrsOf (listOf str); + default = { + common = [ "no_subtree_check" ]; + }; + }; + root = mkOption { + type = nullOr (submodule [ + exportModule + ({ ... }: { + flags = mkMerge [ + (cfg.export.flagSets.common or [ ]) + ]; + }) + ]); + default = null; + }; + paths = mkOption { + type = attrsOf (submodule exportModule); + default = { }; + }; + }; + }; + config = { + services.nfs = { + server.exports = mkMerge ( + optional (cfg.export.root != null) (mkBefore cfg.export.root.fileLine) + ++ mapAttrsToList (_: export: export.fileLine) cfg.export.paths + ); + }; + networking.firewall.interfaces.local = mkIf enabled { + allowedTCPPorts = openPorts; + allowedUDPPorts = openPorts; + }; + systemd.services = { + auth-rpcgss-module = mkIf (enabled && !config.boot.modprobeConfig.enable) { + serviceConfig.ExecStart = mkForce [ + "" + "${pkgs.coreutils}/bin/true" + ]; + }; + rpc-svcgssd = mkIf enabled { + enable = mkIf (!cfg.server.enable) false; + wantedBy = mkIf (cfg.server.enable && (config.security.krb5.enable || config.security.ipa.enable)) [ + "nfs-server.service" + ]; + }; + nfs-mountd = mkIf cfg.server.enable { + environment.LD_LIBRARY_PATH = config.system.nssModules.path; + }; + }; + systemd.mounts = mkIf (cfg.server.enable && cfg.export.root != null) [ + rec { + type = "tmpfs"; + options = "rw,size=256k"; + what = "none"; + where = cfg.export.root.path; + requiredBy = [ + "nfs-server.service" + "nfs-mountd.service" + ]; + before = requiredBy; + } + ]; + }; +} diff --git a/nixos/access/freeipa.nix b/nixos/access/freeipa.nix index 588176e9..b4f37c58 100644 --- a/nixos/access/freeipa.nix +++ b/nixos/access/freeipa.nix @@ -195,8 +195,7 @@ in { map $ssl_preread_server_name $ldap_upstream { hostnames; - # TODO: ${access.domain} ${upstreams.ldap_freeipa}; - ${access.globalDomain} ${upstreams.ldap_freeipa}; + ${access.domain} ${upstreams.ldap_freeipa}; default ${upstreams.ldap}; } diff --git a/nixos/ipa.nix b/nixos/ipa.nix index a65f0184..ff97d028 100644 --- a/nixos/ipa.nix +++ b/nixos/ipa.nix @@ -1,7 +1,7 @@ { inputs, pkgs, config, lib, ... }: let inherit (inputs.self.lib.lib) mkBaseDn; - inherit (lib.modules) mkIf mkForce mkDefault; - inherit (lib.strings) toUpper splitString concatMapStringsSep; + inherit (lib.modules) mkIf mkBefore mkForce mkDefault; + inherit (lib.strings) toUpper; inherit (config.networking) domain; cfg = config.security.ipa; baseDn = mkBaseDn domain; @@ -32,14 +32,8 @@ in { ] ++ config.users.groups.wheel.members; dyndns.enable = mkDefault false; }; - networking.extraHosts = mkIf cfg.enable '' - 10.1.1.46 idp.${domain} - ''; - systemd.services.auth-rpcgss-module = mkIf (cfg.enable && !config.boot.modprobeConfig.enable) { - serviceConfig.ExecStart = mkForce [ - "" - "${pkgs.coreutils}/bin/true" - ]; + networking.hosts = mkIf cfg.enable { + "10.1.1.46" = mkBefore [ "idp.${domain}" ]; }; sops.secrets = { krb5-keytab = mkIf cfg.enable { diff --git a/nixos/kyuuto/nfs.nix b/nixos/kyuuto/nfs.nix index a9386c16..91140c32 100644 --- a/nixos/kyuuto/nfs.nix +++ b/nixos/kyuuto/nfs.nix @@ -3,48 +3,60 @@ lib, ... }: let + inherit (lib.modules) mkIf; inherit (lib.lists) optionals; inherit (lib.strings) concatStringsSep; inherit (config.networking.access) cidrForNetwork; inherit (config) kyuuto; + inherit (config.services.nfs.export) flagSets; + nfsRoot = { + __toString = _: config.services.nfs.export.root.path; + transfer = "${nfsRoot}/kyuuto/transfer"; + media = "${nfsRoot}/kyuuto/media"; + }; in { - services.nfs.server.exports = let - mapPerm = perm: map (addr: "${addr}(${concatStringsSep "," perm})"); - toPerms = concatStringsSep " "; - localAddrs = cidrForNetwork.loopback.all ++ cidrForNetwork.local.all; - tailAddrs = optionals config.services.tailscale.enable cidrForNetwork.tail.all; - allAddrs = localAddrs ++ tailAddrs; - globalAddrs = [ - "@peeps" + services.nfs = { + export = { + paths = { + ${nfsRoot.media} = { + flags = flagSets.common ++ [ "fsid=128" ] ++ flagSets.secip ++ [ "rw" ] ++ flagSets.anon_ro; + clients = { + local = { + machine = flagSets.allClients; + flags = flagSets.seclocal ++ [ "rw" "no_all_squash" ]; + }; + }; + }; + ${nfsRoot.transfer} = { + flags = flagSets.common ++ [ "fsid=129" ] ++ [ "rw" "async" ]; + clients = { + local = { + machine = flagSets.allClients; + flags = flagSets.secanon; + }; + }; + }; + }; + }; + }; + systemd.mounts = let + type = "none"; + options = "bind"; + wantedBy = [ + "nfs-server.service" + "nfs-mountd.service" ]; - common = [ - "no_subtree_check" - ]; - sec = [ - "sec=${concatStringsSep ":" [ "krb5i" "krb5" "krb5p" ]}" - # TODO: no_root_squash..? - ]; - anon = [ - "sec=sys" - "all_squash" - "anonuid=${toString config.users.users.guest.uid}" - "anongid=${toString config.users.groups.${config.users.users.guest.group}.gid}" - ]; - # TODO: this can be simplified by specifying `sec=` multiple times, with restrictive options following sec=sys,all_squash,ro,etc - kyuutoOpts = common; - kyuutoPerms = - mapPerm (kyuutoOpts ++ [ "rw" ] ++ sec) globalAddrs - ++ mapPerm (kyuutoOpts ++ [ "ro" ] ++ anon) localAddrs - # XXX: remove me once kerberos is set up! - ++ mapPerm (kyuutoOpts ++ [ "rw" "sec=sys" ]) tailAddrs - ; - transferOpts = common ++ [ "rw" "async" ]; - transferPerms = - mapPerm (transferOpts ++ sec) globalAddrs - ++ mapPerm (transferOpts ++ anon) allAddrs - ; - in '' - ${kyuuto.mountDir} ${toPerms kyuutoPerms} - ${kyuuto.transferDir} ${toPerms transferPerms} - ''; + before = wantedBy; + in mkIf config.services.nfs.server.enable [ + { + inherit type options wantedBy before; + what = kyuuto.mountDir; + where = nfsRoot.media; + } + { + inherit type options wantedBy before; + what = kyuuto.transferDir; + where = nfsRoot.transfer; + } + ]; } diff --git a/nixos/nfs.nix b/nixos/nfs.nix index ddd22e07..ef692103 100644 --- a/nixos/nfs.nix +++ b/nixos/nfs.nix @@ -6,27 +6,70 @@ }: let inherit (inputs.self.lib.lib) mkBaseDn; inherit (lib.modules) mkIf mkForce mkDefault; - inherit (lib.lists) optional; - inherit (lib.strings) toUpper concatStringsSep concatMapStringsSep splitString; + inherit (lib.lists) optional optionals; + inherit (lib.strings) toUpper concatStringsSep; + inherit (config.networking.access) cidrForNetwork; cfg = config.services.nfs; + inherit (cfg.export) flagSets; inherit (config.networking) domain; - openPorts = [ - (mkIf cfg.server.enable 2049) - (mkIf config.services.rpcbind.enable 111) - (mkIf (cfg.server.statdPort != null) cfg.server.statdPort) - (mkIf (cfg.server.lockdPort != null) cfg.server.lockdPort) - (mkIf (cfg.server.mountdPort != null) cfg.server.mountdPort) - ]; enableLdap = false; baseDn = mkBaseDn domain; in { - services.nfs = { + config.services.nfs = { server = { enable = mkDefault true; statdPort = mkDefault 4000; lockdPort = mkDefault 4001; mountdPort = mkDefault 4002; }; + export = { + flagSets = let + localAddrs = cidrForNetwork.loopback.all ++ cidrForNetwork.local.all; + in { + common = [ + "no_subtree_check" + "anonuid=${toString config.users.users.guest.uid}" + "anongid=${toString config.users.groups.${config.users.users.guest.group}.gid}" + ]; + sec = [ + "sec=${concatStringsSep ":" [ "krb5i" "krb5" "krb5p" ]}" + ]; + seclocal = [ + "sec=${concatStringsSep ":" [ "krb5" ]}" + ]; + secip = [ + "sec=${concatStringsSep ":" [ "krb5i" "krb5p" ]}" + ]; + secanon = [ + "sec=${concatStringsSep ":" [ "krb5i" "krb5" "krb5p" "sys" ]}" + ]; + anon_ro = [ + "sec=sys" + "all_squash" + "ro" + ]; + # client machines + clientGroups = [ + "@peeps" + "@infra" + ]; + trustedClients = [ + "@trusted" + ]; + tailClients = optionals config.services.tailscale.enable cidrForNetwork.tail.all; + localClients = localAddrs ++ flagSets.tailClients; + allClients = flagSets.clientGroups ++ flagSets.trustedClients ++ flagSets.localClients; + }; + root = { + path = "/srv/fs"; + clients = { + trusted = { + machine = flagSets.trustedClients; + flags = flagSets.secip ++ [ "rw" ]; + }; + }; + }; + }; idmapd.settings = { General = { Domain = mkForce domain; @@ -61,8 +104,4 @@ in { }; }; }; - networking.firewall.interfaces.local = { - allowedTCPPorts = openPorts; - allowedUDPPorts = openPorts; - }; }