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.
This commit is contained in:
Majiir Paktu 2023-09-16 15:29:11 -04:00
parent e86487e579
commit 077cdcc7e9

View File

@ -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;