about summary refs log tree commit diff
path: root/nixos/modules/security/pam.nix
diff options
context:
space:
mode:
authorMajiir Paktu <majiir@nabaal.net>2023-09-16 15:29:11 -0400
committerMajiir Paktu <majiir@nabaal.net>2023-10-10 21:11:34 -0400
commit077cdcc7e9a9ed406860e97ce3590fba15aed18b (patch)
tree9101baf6da6804c7d75731e1f7f6281c0a367e02 /nixos/modules/security/pam.nix
parente86487e579271dc5f0d7627a8f7f5a496d133d59 (diff)
nixos/pam: convert rules to attrs, add order field
Makes it possible to override properties of a rule by name. Introduces
an 'order' field that can be overridden to change the sequence of rules.

For now, the order value for each built-in rule is derived from its
place in the hardcoded list of rules.
Diffstat (limited to 'nixos/modules/security/pam.nix')
-rw-r--r--nixos/modules/security/pam.nix50
1 files changed, 43 insertions, 7 deletions
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 8c9e17bd9cf21..b7a5d7131b578 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -11,14 +11,18 @@ let
     # These options are experimental and subject to breaking changes without notice.
     description = lib.mdDoc ''
       PAM `${type}` rules for this service.
+
+      Attribute keys are the name of each rule.
     '';
-    type = types.listOf (types.submodule ({ config, ... }: {
+    type = types.attrsOf (types.submodule ({ name, config, ... }: {
       options = {
         name = mkOption {
           type = types.str;
           description = lib.mdDoc ''
             Name of this rule.
           '';
+          internal = true;
+          readOnly = true;
         };
         enable = mkOption {
           type = types.bool;
@@ -27,6 +31,23 @@ let
             Whether this rule is added to the PAM service config file.
           '';
         };
+        order = mkOption {
+          type = types.int;
+          description = lib.mdDoc ''
+            Order of this rule in the service file. Rules are arranged in ascending order of this value.
+
+            ::: {.warning}
+            The `order` values for the built-in rules are subject to change. If you assign a constant value to this option, a system update could silently reorder your rule. You could be locked out of your system, or your system could be left wide open. When using this option, set it to a relative offset from another rule's `order` value:
+
+            ```nix
+            {
+              security.pam.services.login.rules.auth.foo.order =
+                config.security.pam.services.login.rules.auth.unix.order + 10;
+            }
+            ```
+            :::
+          '';
+        };
         control = mkOption {
           type = types.str;
           description = lib.mdDoc ''
@@ -60,6 +81,7 @@ let
         };
       };
       config = {
+        inherit name;
         # Formats an attrset of settings as args for use as `module-arguments`.
         args = concatLists (flip mapAttrsToList config.settings (name: value:
           if isBool value
@@ -557,13 +579,21 @@ let
       limits = mkDefault config.security.pam.loginLimits;
 
       text = let
+        ensureUniqueOrder = type: rules:
+          let
+            checkPair = a: b: assert assertMsg (a.order != b.order) "security.pam.services.${name}.rules.${type}: rules '${a.name}' and '${b.name}' cannot have the same order value (${toString a.order})"; b;
+            checked = zipListsWith checkPair rules (drop 1 rules);
+          in take 1 rules ++ checked;
         # Formats a string for use in `module-arguments`. See `man pam.conf`.
         formatModuleArgument = token:
           if hasInfix " " token
           then "[${replaceStrings ["]"] ["\\]"] token}]"
           else token;
         formatRules = type: pipe cfg.rules.${type} [
+          attrValues
           (filter (rule: rule.enable))
+          (sort (a: b: a.order < b.order))
+          (ensureUniqueOrder type)
           (map (rule: concatStringsSep " " (
             [ type rule.control rule.modulePath ]
             ++ map formatModuleArgument rule.args
@@ -587,8 +617,14 @@ let
       # !!! TODO: move the LDAP stuff to the LDAP module, and the
       # Samba stuff to the Samba module.  This requires that the PAM
       # module provides the right hooks.
-      rules = {
-        account = [
+      rules = let
+        autoOrderRules = flip pipe [
+          (imap1 (index: rule: rule // { order = mkDefault (10000 + index * 100); } ))
+          (map (rule: nameValuePair rule.name (removeAttrs rule [ "name" ])))
+          listToAttrs
+        ];
+      in {
+        account = autoOrderRules [
           { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
             config_file = "/etc/security/pam_mysql.conf";
@@ -607,7 +643,7 @@ let
           { name = "unix"; control = "required"; modulePath = "pam_unix.so"; }
         ];
 
-        auth = [
+        auth = autoOrderRules ([
           { name = "oslogin_login"; enable = cfg.googleOsLoginAuthentication; control = "[success=done perm_denied=die default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
           { name = "rootok"; enable = cfg.rootOK; control = "sufficient"; modulePath = "pam_rootok.so"; }
           { name = "wheel"; enable = cfg.requireWheel; control = "required"; modulePath = "pam_wheel.so"; settings = {
@@ -730,9 +766,9 @@ let
             use_first_pass = true;
           }; }
           { name = "deny"; control = "required"; modulePath = "pam_deny.so"; }
-        ];
+        ]);
 
-        password = [
+        password = autoOrderRules [
           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
           { name = "unix"; control = "sufficient"; modulePath = "pam_unix.so"; settings = {
             nullok = true;
@@ -758,7 +794,7 @@ let
           }; }
         ];
 
-        session = [
+        session = autoOrderRules [
           { name = "env"; enable = cfg.setEnvironment; control = "required"; modulePath = "pam_env.so"; settings = {
             conffile = "/etc/pam/environment";
             readenv = 0;