Skip to content

Commit

Permalink
nixos/pam/u2f: implement RFC0042
Browse files Browse the repository at this point in the history
This module has a lot of options, so it's a good candidate for freeform
settings.
  • Loading branch information
9ary committed Dec 23, 2023
1 parent 66bda59 commit 8b233c9
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 88 deletions.
3 changes: 3 additions & 0 deletions nixos/doc/manual/release-notes/rl-2405.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
- If [`system.stateVersion`](#opt-system.stateVersion) is >=23.11, `pkgs.nextcloud27` will be installed by default.
- Please note that an upgrade from v26 (or older) to v28 directly is not possible. Please upgrade to `nextcloud27` (or earlier) first. Nextcloud prohibits skipping major versions while upgrading. You can upgrade by declaring [`services.nextcloud.package = pkgs.nextcloud27;`](options.html#opt-services.nextcloud.package).

- `security.pam.u2f` now follows RFC42.
All module options are now settable through the freeform `.settings`.

- `services.resolved.fallbackDns` can now be used to disable the upstream fallback servers entirely by setting it to an empty list. To get the previous behaviour of the upstream defaults set it to null, the new default, instead.

- `services.avahi.nssmdns` got split into `services.avahi.nssmdns4` and `services.avahi.nssmdns6` which enable the mDNS NSS switch for IPv4 and IPv6 respectively.
Expand Down
189 changes: 107 additions & 82 deletions nixos/modules/security/pam.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ with lib;

let

moduleArgsType = with types; attrsOf (nullOr (oneOf [ bool str int pathInStore ]));
mkRulesTypeOption = type: mkOption {
# These options are experimental and subject to breaking changes without notice.
description = lib.mdDoc ''
Expand Down Expand Up @@ -71,7 +72,7 @@ let
'';
};
settings = mkOption {
type = with types; attrsOf (nullOr (oneOf [ bool str int pathInStore ]));
type = moduleArgsType;
default = {};
description = lib.mdDoc ''
Settings to add as `module-arguments`.
Expand Down Expand Up @@ -660,11 +661,7 @@ let
(let p11 = config.security.pam.p11; in { name = "p11"; enable = cfg.p11Auth; control = p11.control; modulePath = "${pkgs.pam_p11}/lib/security/pam_p11.so"; args = [
"${pkgs.opensc}/lib/opensc-pkcs11.so"
]; })
(let u2f = config.security.pam.u2f; in { name = "u2f"; enable = cfg.u2fAuth; control = u2f.control; modulePath = "${pkgs.pam_u2f}/lib/security/pam_u2f.so"; settings = {
inherit (u2f) debug interactive cue origin;
authfile = u2f.authFile;
appid = u2f.appId;
}; })
(let u2f = config.security.pam.u2f; in { name = "u2f"; enable = cfg.u2fAuth; control = u2f.control; modulePath = "${pkgs.pam_u2f}/lib/security/pam_u2f.so"; inherit (u2f) settings; })
{ name = "usb"; enable = cfg.usbAuth; control = "sufficient"; modulePath = "${pkgs.pam_usb}/lib/security/pam_usb.so"; }
(let ussh = config.security.pam.ussh; in { name = "ussh"; enable = config.security.pam.ussh.enable && cfg.usshAuth; control = ussh.control; modulePath = "${pkgs.pam_ussh}/lib/security/pam_ussh.so"; settings = {
ca_file = ussh.caFile;
Expand Down Expand Up @@ -956,6 +953,12 @@ in

imports = [
(mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
(mkRenamedOptionModule [ "security" "pam" "u2f" "authFile" ] [ "security" "pam" "u2f" "settings" "authfile" ])
(mkRenamedOptionModule [ "security" "pam" "u2f" "appId" ] [ "security" "pam" "u2f" "settings" "appid" ])
(mkRenamedOptionModule [ "security" "pam" "u2f" "origin" ] [ "security" "pam" "u2f" "settings" "origin" ])
(mkRenamedOptionModule [ "security" "pam" "u2f" "debug" ] [ "security" "pam" "u2f" "settings" "debug" ])
(mkRenamedOptionModule [ "security" "pam" "u2f" "interactive" ] [ "security" "pam" "u2f" "settings" "interactive" ])
(mkRenamedOptionModule [ "security" "pam" "u2f" "cue" ] [ "security" "pam" "u2f" "settings" "cue" ])
];

###### interface
Expand Down Expand Up @@ -1135,57 +1138,6 @@ in
'';
};

authFile = mkOption {
default = null;
type = with types; nullOr path;
description = lib.mdDoc ''
By default `pam-u2f` module reads the keys from
{file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
{file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
not set).
If you want to change auth file locations or centralize database (for
example use {file}`/etc/u2f-mappings`) you can set this
option.
File format is:
`username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
This file can be generated using {command}`pamu2fcfg` command.
More information can be found [here](https://developers.yubico.com/pam-u2f/).
'';
};

appId = mkOption {
default = null;
type = with types; nullOr str;
description = lib.mdDoc ''
By default `pam-u2f` module sets the application
ID to `pam://$HOSTNAME`.
When using {command}`pamu2fcfg`, you can specify your
application ID with the `-i` flag.
More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
'';
};

origin = mkOption {
default = null;
type = with types; nullOr str;
description = lib.mdDoc ''
By default `pam-u2f` module sets the origin
to `pam://$HOSTNAME`.
Setting origin to an host independent value will allow you to
reuse credentials across machines
When using {command}`pamu2fcfg`, you can specify your
application ID with the `-o` flag.
More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
'';
};

control = mkOption {
default = "sufficient";
type = types.enum [ "required" "requisite" "sufficient" "optional" ];
Expand All @@ -1200,33 +1152,106 @@ in
'';
};

debug = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
Debug output to stderr.
'';
};

interactive = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
Set to prompt a message and wait before testing the presence of a U2F device.
Recommended if your device doesn’t have a tactile trigger.
'';
};

cue = mkOption {
default = false;
type = types.bool;
settings = mkOption {
type = types.submodule {
freeformType = moduleArgsType;

options = {
authfile = mkOption {
default = null;
type = with types; nullOr path;
description = lib.mdDoc ''
By default `pam-u2f` module reads the keys from
{file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
{file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
not set).
If you want to change auth file locations or centralize database (for
example use {file}`/etc/u2f-mappings`) you can set this
option.
File format is:
`username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
This file can be generated using {command}`pamu2fcfg` command.
More information can be found [here](https://developers.yubico.com/pam-u2f/).
'';
};

appid = mkOption {
default = null;
type = with types; nullOr str;
description = lib.mdDoc ''
By default `pam-u2f` module sets the application
ID to `pam://$HOSTNAME`.
When using {command}`pamu2fcfg`, you can specify your
application ID with the `-i` flag.
More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
'';
};

origin = mkOption {
default = null;
type = with types; nullOr str;
description = lib.mdDoc ''
By default `pam-u2f` module sets the origin
to `pam://$HOSTNAME`.
Setting origin to an host independent value will allow you to
reuse credentials across machines
When using {command}`pamu2fcfg`, you can specify your
application ID with the `-o` flag.
More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
'';
};

debug = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
Debug output to stderr.
'';
};

interactive = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
Set to prompt a message and wait before testing the presence of a U2F device.
Recommended if your device doesn’t have a tactile trigger.
'';
};

cue = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
By default `pam-u2f` module does not inform user
that he needs to use the u2f device, it just waits without a prompt.
If you set this option to `true`,
`cue` option is added to `pam-u2f`
module and reminder message will be displayed.
'';
};
};
};
default = { };
example = {
authfile = "/etc/u2f_keys";
authpending_file = "";
userpresence = 0;
pinverification = 1;
};
description = lib.mdDoc ''
By default `pam-u2f` module does not inform user
that he needs to use the u2f device, it just waits without a prompt.
Options to pass to the PAM module.
If you set this option to `true`,
`cue` option is added to `pam-u2f`
module and reminder message will be displayed.
Boolean values render just the key if true, and nothing if false.
Null values are ignored.
All other values are rendered as key-value pairs.
'';
};
};
Expand Down
16 changes: 10 additions & 6 deletions nixos/tests/pam/pam-u2f.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@ import ../make-test-python.nix ({ ... }:
{ ... }:
{
security.pam.u2f = {
control = "required";
cue = true;
debug = true;
enable = true;
interactive = true;
origin = "nixos-test";
control = "required";
settings = {
cue = true;
debug = true;
interactive = true;
origin = "nixos-test";
# Freeform option
userpresence = 1;
};
};
};

testScript =
''
machine.wait_for_unit("multi-user.target")
machine.succeed(
'egrep "auth required .*/lib/security/pam_u2f.so.*cue.*debug.*interactive.*origin=nixos-test" /etc/pam.d/ -R'
'egrep "auth required .*/lib/security/pam_u2f.so.*cue.*debug.*interactive.*origin=nixos-test.*userpresence=1" /etc/pam.d/ -R'
)
'';
})

0 comments on commit 8b233c9

Please sign in to comment.