pam_u2f: fix usage with polkit 127 (#486044)
This commit is contained in:
@@ -80,6 +80,28 @@ in
|
||||
|
||||
systemd.sockets."polkit-agent-helper".wantedBy = [ "sockets.target" ];
|
||||
|
||||
systemd.services."polkit-agent-helper@".serviceConfig = lib.mkMerge [
|
||||
# The upstream unit inherits stderr to the polkit agent, which causes
|
||||
# agent processes to misinterpret diagnostic output from PAM modules
|
||||
# as protocol errors, resulting in tight re-execution loops.
|
||||
{ StandardError = "journal"; }
|
||||
|
||||
# The upstream unit uses PrivateDevices=yes and ProtectHome=yes,
|
||||
# which prevents PAM modules from accessing hardware (e.g. FIDO
|
||||
# tokens via /dev/hidraw*) or reading key files from home directories.
|
||||
(lib.mkIf config.security.pam.u2f.enable {
|
||||
# Override upstream PrivateDevices=yes to allow access to /dev/hidraw*
|
||||
PrivateDevices = false;
|
||||
DeviceAllow = [
|
||||
"/dev/urandom r"
|
||||
"char-hidraw rw"
|
||||
];
|
||||
# Override upstream ProtectHome=yes so pam_u2f can read
|
||||
# ~/.config/Yubico/u2f_keys (the default key file location)
|
||||
ProtectHome = "read-only";
|
||||
})
|
||||
];
|
||||
|
||||
# The polkit daemon reads action/rule files
|
||||
environment.pathsToLink = [ "/share/polkit-1" ];
|
||||
|
||||
|
||||
@@ -1220,6 +1220,7 @@ in
|
||||
pam-oath-login = runTest ./pam/pam-oath-login.nix;
|
||||
pam-pgsql = runTest ./pam/pam-pgsql.nix;
|
||||
pam-u2f = runTest ./pam/pam-u2f.nix;
|
||||
pam-u2f-polkit = runTest ./pam/pam-u2f-polkit.nix;
|
||||
pam-ussh = runTest ./pam/pam-ussh.nix;
|
||||
pam-zfs-key = runTest ./pam/zfs-key.nix;
|
||||
pangolin = runTest ./pangolin.nix;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
{ hostPkgs, ... }:
|
||||
|
||||
{
|
||||
name = "pam-u2f-polkit";
|
||||
|
||||
qemu.package = hostPkgs.qemu_test.override { u2fEmuSupport = true; };
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
virtualisation.qemu.options = [
|
||||
"-usb"
|
||||
"-device u2f-emulated"
|
||||
];
|
||||
|
||||
security.polkit.enable = true;
|
||||
security.pam.u2f.enable = true;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
libfido2
|
||||
pam_u2f
|
||||
];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
# The upstream polkit-agent-helper@.service has PrivateDevices=yes and
|
||||
# DevicePolicy=strict with only /dev/null allowed. This blocks hidraw.
|
||||
# Verify that: run a command under the upstream defaults and show it fails.
|
||||
machine.fail(
|
||||
"systemd-run --wait --pipe "
|
||||
"--property=PrivateDevices=yes "
|
||||
"--property=DevicePolicy=strict "
|
||||
"--property='DeviceAllow=/dev/null rw' "
|
||||
"test -c /dev/hidraw0"
|
||||
)
|
||||
|
||||
# The PR overrides PrivateDevices=no and adds DeviceAllow for hidraw.
|
||||
# Verify that the actual polkit-agent-helper@ unit got these overrides.
|
||||
props = machine.succeed("systemctl show polkit-agent-helper@dummy.service")
|
||||
assert "PrivateDevices=no" in props, f"Expected PrivateDevices=no, got: {props}"
|
||||
assert "ProtectHome=read-only" in props, f"Expected ProtectHome=read-only, got: {props}"
|
||||
|
||||
# Run fido2-token under the same constraints as the fixed service.
|
||||
# This proves the device is not just visible but actually usable
|
||||
# inside the polkit-agent-helper@ sandbox.
|
||||
machine.succeed(
|
||||
"systemd-run --wait --pipe "
|
||||
"--property=PrivateDevices=no "
|
||||
"--property=DevicePolicy=strict "
|
||||
"--property='DeviceAllow=/dev/null rw' "
|
||||
"--property='DeviceAllow=/dev/urandom r' "
|
||||
"--property='DeviceAllow=char-hidraw rw' "
|
||||
"--property=ProtectHome=read-only "
|
||||
"--property=PrivateNetwork=yes "
|
||||
"--property=ProtectSystem=strict "
|
||||
"--property=ProtectKernelModules=yes "
|
||||
"--property=ProtectKernelLogs=yes "
|
||||
"--property=ProtectKernelTunables=yes "
|
||||
"--property=ProtectControlGroups=yes "
|
||||
"--property=ProtectClock=yes "
|
||||
"--property=ProtectHostname=yes "
|
||||
"--property=LockPersonality=yes "
|
||||
"--property=MemoryDenyWriteExecute=yes "
|
||||
"--property=NoNewPrivileges=yes "
|
||||
"--property=PrivateTmp=yes "
|
||||
"--property=RemoveIPC=yes "
|
||||
"--property='RestrictAddressFamilies=AF_UNIX' "
|
||||
"--property=RestrictNamespaces=yes "
|
||||
"--property=RestrictRealtime=yes "
|
||||
"--property=RestrictSUIDSGID=yes "
|
||||
"--property=SystemCallArchitectures=native "
|
||||
"fido2-token -I /dev/hidraw0"
|
||||
)
|
||||
|
||||
# Also verify that pamu2fcfg can register a credential inside the sandbox
|
||||
# (needs hidraw + urandom access)
|
||||
machine.succeed(
|
||||
"systemd-run --wait --pipe "
|
||||
"--property=PrivateDevices=no "
|
||||
"--property=DevicePolicy=strict "
|
||||
"--property='DeviceAllow=/dev/null rw' "
|
||||
"--property='DeviceAllow=/dev/urandom r' "
|
||||
"--property='DeviceAllow=char-hidraw rw' "
|
||||
"--property=ProtectHome=read-only "
|
||||
"pamu2fcfg"
|
||||
)
|
||||
'';
|
||||
}
|
||||
@@ -1,29 +1,70 @@
|
||||
{ ... }:
|
||||
{ hostPkgs, ... }:
|
||||
|
||||
{
|
||||
name = "pam-u2f";
|
||||
|
||||
qemu.package = hostPkgs.qemu_test.override { u2fEmuSupport = true; };
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
virtualisation.qemu.options = [
|
||||
"-usb"
|
||||
"-device u2f-emulated"
|
||||
];
|
||||
|
||||
security.pam.u2f = {
|
||||
enable = true;
|
||||
control = "required";
|
||||
control = "sufficient";
|
||||
settings = {
|
||||
cue = true;
|
||||
debug = true;
|
||||
interactive = true;
|
||||
origin = "nixos-test";
|
||||
# Freeform option
|
||||
userpresence = 1;
|
||||
origin = "pam://nixos-test";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
uid = 1000;
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
libfido2
|
||||
pam_u2f
|
||||
];
|
||||
|
||||
# Allow non-root users to access the virtual U2F device
|
||||
services.udev.extraRules = ''
|
||||
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0666"
|
||||
'';
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
|
||||
|
||||
# The virtual U2F device should be recognized
|
||||
machine.succeed("fido2-token -L | grep -q hidraw")
|
||||
|
||||
# Register a U2F credential for alice
|
||||
machine.succeed("mkdir -p /home/alice/.config/Yubico")
|
||||
machine.succeed(
|
||||
'egrep "auth required .*/lib/security/pam_u2f.so.*cue.*debug.*interactive.*origin=nixos-test.*userpresence=1" /etc/pam.d/ -R'
|
||||
"pamu2fcfg -u alice -o pam://nixos-test"
|
||||
" > /home/alice/.config/Yubico/u2f_keys"
|
||||
)
|
||||
machine.succeed("chown -R alice:users /home/alice/.config")
|
||||
|
||||
# Log in as alice on tty2. With control=sufficient, pam_u2f runs
|
||||
# before pam_unix. The emulated device auto-approves user presence,
|
||||
# so alice is authenticated by her U2F key — no password needed.
|
||||
machine.send_key("alt-f2")
|
||||
machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
|
||||
machine.wait_for_unit("getty@tty2.service")
|
||||
machine.wait_until_tty_matches("2", "login: ")
|
||||
machine.send_chars("alice\n")
|
||||
|
||||
# alice should get a shell without being asked for a password
|
||||
machine.wait_until_succeeds("pgrep -u alice bash")
|
||||
machine.send_chars("touch /tmp/u2f-login-success\n")
|
||||
machine.wait_for_file("/tmp/u2f-login-success")
|
||||
'';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user