{ description = "PAM module that runs multiple PAM modules in parallel, succeeding if any one succeeds"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; outputs = { self, nixpkgs }: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; in { packages.${system}.default = pkgs.callPackage ./default.nix {}; nixosModules.default = { config, pkgs, lib, ... }: let cfg = config.security.pam-any; pam-any = self.packages.${pkgs.stdenv.hostPlatform.system}.default; pamAnyConfigFile = pkgs.writeText "pam-any-config.json" (builtins.toJSON { mode = cfg.mode; modules = cfg.modules; }); in { options.security.pam-any = { enable = lib.mkEnableOption "pam-any parallel authentication"; mode = lib.mkOption { type = lib.types.enum [ "One" "All" ]; default = "One"; description = "\"One\" succeeds if any module succeeds, \"All\" requires all to succeed."; }; modules = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = { pam-any-fingerprint = "Fingerprint"; pam-any-password = "Password"; }; description = "Map of PAM service names to display labels for parallel auth."; }; services = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "sudo" "login" ]; description = "PAM services to apply pam-any to."; }; order = lib.mkOption { type = lib.types.int; default = 1000; description = "Order of the pam-any rule in the auth stack."; }; }; config = lib.mkIf cfg.enable { security.pam.services = { pam-any-fingerprint.text = '' auth required ${pkgs.fprintd}/lib/security/pam_fprintd.so ''; pam-any-password.text = '' auth required ${pkgs.linux-pam}/lib/security/pam_unix.so ''; } // lib.genAttrs cfg.services (_name: { fprintAuth = false; rules.auth = { pam-any = { order = cfg.order; control = "required"; modulePath = "${pam-any}/lib/security/pam_any.so"; args = [ "${pamAnyConfigFile}" ]; }; # Disable the default unix password auth since pam-any # handles it via the pam-any-password helper service. unix.enable = lib.mkForce false; # Keep deny as a safety net (it won't be reached if # pam-any is required and returns success/failure). }; }); }; }; }; }