blob: efe6a055880ac94961c58d909049d479c14466ee (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
{
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 = {
# If pam-any succeeds (fingerprint or password in parallel), auth is done.
# If it fails/crashes, fall through to normal password prompt as safety net.
pam-any = {
order = cfg.order;
control = "[success=done default=ignore]";
modulePath = "${pam-any}/lib/security/pam_any.so";
args = [ "${pamAnyConfigFile}" ];
};
# Fallback: keep normal password auth but without nullok
# so empty passwords are never accepted.
unix = {
order = 11700;
control = "sufficient";
modulePath = "${pkgs.linux-pam}/lib/security/pam_unix.so";
args = lib.mkForce [ "likeauth" "try_first_pass" ];
};
};
});
};
};
};
}
|