diff options
Diffstat (limited to 'nixpkgs/nixos/tests/pam')
-rw-r--r-- | nixpkgs/nixos/tests/pam/pam-file-contents.nix | 26 | ||||
-rw-r--r-- | nixpkgs/nixos/tests/pam/pam-oath-login.nix | 108 | ||||
-rw-r--r-- | nixpkgs/nixos/tests/pam/pam-u2f.nix | 26 | ||||
-rw-r--r-- | nixpkgs/nixos/tests/pam/pam-ussh.nix | 70 | ||||
-rw-r--r-- | nixpkgs/nixos/tests/pam/test_chfn.py | 28 | ||||
-rw-r--r-- | nixpkgs/nixos/tests/pam/zfs-key.nix | 83 |
6 files changed, 341 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/pam/pam-file-contents.nix b/nixpkgs/nixos/tests/pam/pam-file-contents.nix new file mode 100644 index 000000000000..accaa4cc70a9 --- /dev/null +++ b/nixpkgs/nixos/tests/pam/pam-file-contents.nix @@ -0,0 +1,26 @@ +let + name = "pam"; +in +import ../make-test-python.nix ({ pkgs, ... }: { + name = "pam-file-contents"; + + nodes.machine = { ... }: { + imports = [ ../../modules/profiles/minimal.nix ]; + + security.krb5.enable = true; + + users = { + mutableUsers = false; + users = { + user = { + isNormalUser = true; + }; + }; + }; + }; + + testScript = builtins.replaceStrings + [ "@@pam_ccreds@@" "@@pam_krb5@@" ] + [ pkgs.pam_ccreds.outPath pkgs.pam_krb5.outPath ] + (builtins.readFile ./test_chfn.py); +}) diff --git a/nixpkgs/nixos/tests/pam/pam-oath-login.nix b/nixpkgs/nixos/tests/pam/pam-oath-login.nix new file mode 100644 index 000000000000..dd6ef4a0abcb --- /dev/null +++ b/nixpkgs/nixos/tests/pam/pam-oath-login.nix @@ -0,0 +1,108 @@ +import ../make-test-python.nix ({ ... }: + +let + oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3"; + + # With HOTP mode the password is calculated based on a counter of + # how many passwords have been made. In this env, we'll always be on + # the 0th counter, so the password is static. + # + # Generated in nix-shell -p oath-toolkit + # via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3 + # and picking a the first 4: + oathSnakeOilPassword1 = "143349"; + oathSnakeOilPassword2 = "801753"; + + alicePassword = "foobar"; + # Generated via: mkpasswd -m sha-512 and passing in "foobar" + hashedAlicePassword = "$6$MsMrE1q.1HrCgTS$Vq2e/uILzYjSN836TobAyN9xh9oi7EmCmucnZID25qgPoibkw8qTCugiAPnn4eCGvn1A.7oEBFJaaGUaJsQQY."; + +in +{ + name = "pam-oath-login"; + + nodes.machine = + { ... }: + { + security.pam.oath = { + enable = true; + }; + + users.users.alice = { + isNormalUser = true; + name = "alice"; + uid = 1000; + hashedPassword = hashedAlicePassword; + extraGroups = [ "wheel" ]; + createHome = true; + home = "/home/alice"; + }; + + + systemd.services.setupOathSnakeoilFile = { + wantedBy = [ "default.target" ]; + before = [ "default.target" ]; + unitConfig = { + type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + touch /etc/users.oath + chmod 600 /etc/users.oath + chown root /etc/users.oath + echo "HOTP/E/6 alice - ${oathSnakeoilSecret}" > /etc/users.oath + ''; + }; + }; + + testScript = '' + def switch_to_tty(tty_number): + machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'") + machine.send_key(f"alt-f{tty_number}") + machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]") + machine.wait_for_unit(f"getty@tty{tty_number}.service") + machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'") + + + def enter_user_alice(tty_number): + machine.wait_until_tty_matches(tty_number, "login: ") + machine.send_chars("alice\n") + machine.wait_until_tty_matches(tty_number, "login: alice") + machine.wait_until_succeeds("pgrep login") + machine.wait_until_tty_matches(tty_number, "One-time password") + + + machine.wait_for_unit("multi-user.target") + machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + machine.screenshot("postboot") + + with subtest("Invalid password"): + switch_to_tty("2") + enter_user_alice("2") + + machine.send_chars("${oathSnakeOilPassword1}\n") + machine.wait_until_tty_matches("2", "Password: ") + machine.send_chars("blorg\n") + machine.wait_until_tty_matches("2", "Login incorrect") + + with subtest("Invalid oath token"): + switch_to_tty("3") + enter_user_alice("3") + + machine.send_chars("000000\n") + machine.wait_until_tty_matches("3", "Login incorrect") + machine.wait_until_tty_matches("3", "login:") + + with subtest("Happy path: Both passwords are mandatory to get us in"): + switch_to_tty("4") + enter_user_alice("4") + + machine.send_chars("${oathSnakeOilPassword2}\n") + machine.wait_until_tty_matches("4", "Password: ") + machine.send_chars("${alicePassword}\n") + + machine.wait_until_succeeds("pgrep -u alice bash") + machine.send_chars("touch done4\n") + machine.wait_for_file("/home/alice/done4") + ''; +}) diff --git a/nixpkgs/nixos/tests/pam/pam-u2f.nix b/nixpkgs/nixos/tests/pam/pam-u2f.nix new file mode 100644 index 000000000000..46e307a3f125 --- /dev/null +++ b/nixpkgs/nixos/tests/pam/pam-u2f.nix @@ -0,0 +1,26 @@ +import ../make-test-python.nix ({ ... }: + +{ + name = "pam-u2f"; + + nodes.machine = + { ... }: + { + security.pam.u2f = { + control = "required"; + cue = true; + debug = true; + enable = true; + interactive = true; + origin = "nixos-test"; + }; + }; + + 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' + ) + ''; +}) diff --git a/nixpkgs/nixos/tests/pam/pam-ussh.nix b/nixpkgs/nixos/tests/pam/pam-ussh.nix new file mode 100644 index 000000000000..ba0570dbf97d --- /dev/null +++ b/nixpkgs/nixos/tests/pam/pam-ussh.nix @@ -0,0 +1,70 @@ +import ../make-test-python.nix ({ pkgs, lib, ... }: + +let + testOnlySSHCredentials = pkgs.runCommand "pam-ussh-test-ca" { + nativeBuildInputs = [ pkgs.openssh ]; + } '' + mkdir $out + ssh-keygen -t ed25519 -N "" -f $out/ca + + ssh-keygen -t ed25519 -N "" -f $out/alice + ssh-keygen -s $out/ca -I "alice user key" -n "alice,root" -V 19700101:forever $out/alice.pub + + ssh-keygen -t ed25519 -N "" -f $out/bob + ssh-keygen -s $out/ca -I "bob user key" -n "bob" -V 19700101:forever $out/bob.pub + ''; + makeTestScript = user: pkgs.writeShellScript "pam-ussh-${user}-test-script" '' + set -euo pipefail + + eval $(${pkgs.openssh}/bin/ssh-agent) + + mkdir -p $HOME/.ssh + chmod 700 $HOME/.ssh + cp ${testOnlySSHCredentials}/${user}{,.pub,-cert.pub} $HOME/.ssh + chmod 600 $HOME/.ssh/${user} + chmod 644 $HOME/.ssh/${user}{,-cert}.pub + + set -x + + ${pkgs.openssh}/bin/ssh-add $HOME/.ssh/${user} + ${pkgs.openssh}/bin/ssh-add -l &>2 + + exec sudo id -u -n + ''; +in { + name = "pam-ussh"; + meta.maintainers = with lib.maintainers; [ lukegb ]; + + machine = + { ... }: + { + users.users.alice = { isNormalUser = true; extraGroups = [ "wheel" ]; }; + users.users.bob = { isNormalUser = true; extraGroups = [ "wheel" ]; }; + + security.pam.ussh = { + enable = true; + authorizedPrincipals = "root"; + caFile = "${testOnlySSHCredentials}/ca.pub"; + }; + + security.sudo = { + enable = true; + extraConfig = '' + Defaults lecture="never" + ''; + }; + }; + + testScript = + '' + with subtest("alice should be allowed to escalate to root"): + machine.succeed( + 'su -c "${makeTestScript "alice"}" -l alice | grep root' + ) + + with subtest("bob should not be allowed to escalate to root"): + machine.fail( + 'su -c "${makeTestScript "bob"}" -l bob | grep root' + ) + ''; +}) diff --git a/nixpkgs/nixos/tests/pam/test_chfn.py b/nixpkgs/nixos/tests/pam/test_chfn.py new file mode 100644 index 000000000000..3cfbb3908e9d --- /dev/null +++ b/nixpkgs/nixos/tests/pam/test_chfn.py @@ -0,0 +1,28 @@ +expected_lines = { + "account required pam_unix.so", + "account sufficient @@pam_krb5@@/lib/security/pam_krb5.so", + "auth [default=die success=done] @@pam_ccreds@@/lib/security/pam_ccreds.so action=validate use_first_pass", + "auth [default=ignore success=1 service_err=reset] @@pam_krb5@@/lib/security/pam_krb5.so use_first_pass", + "auth required pam_deny.so", + "auth sufficient @@pam_ccreds@@/lib/security/pam_ccreds.so action=store use_first_pass", + "auth sufficient pam_rootok.so", + "auth sufficient pam_unix.so likeauth try_first_pass", + "password sufficient @@pam_krb5@@/lib/security/pam_krb5.so use_first_pass", + "password sufficient pam_unix.so nullok yescrypt", + "session optional @@pam_krb5@@/lib/security/pam_krb5.so", + "session required pam_env.so conffile=/etc/pam/environment readenv=0", + "session required pam_unix.so", +} +actual_lines = set(machine.succeed("cat /etc/pam.d/chfn").splitlines()) + +stripped_lines = set([line.split("#")[0].rstrip() for line in actual_lines]) +missing_lines = expected_lines - stripped_lines +extra_lines = stripped_lines - expected_lines +non_functional_lines = set([line for line in extra_lines if line == ""]) +unexpected_functional_lines = extra_lines - non_functional_lines + +with subtest("All expected lines are in the file"): + assert not missing_lines, f"Missing lines: {missing_lines}" + +with subtest("All remaining lines are empty or comments"): + assert not unexpected_functional_lines, f"Unexpected lines: {unexpected_functional_lines}" diff --git a/nixpkgs/nixos/tests/pam/zfs-key.nix b/nixpkgs/nixos/tests/pam/zfs-key.nix new file mode 100644 index 000000000000..4f54c287e91a --- /dev/null +++ b/nixpkgs/nixos/tests/pam/zfs-key.nix @@ -0,0 +1,83 @@ +import ../make-test-python.nix ({ ... }: + + let + userPassword = "password"; + mismatchPass = "mismatch"; + in + { + name = "pam-zfs-key"; + + nodes.machine = + { ... }: { + boot.supportedFilesystems = [ "zfs" ]; + + networking.hostId = "12345678"; + + security.pam.zfs.enable = true; + + users.users = { + alice = { + isNormalUser = true; + password = userPassword; + }; + bob = { + isNormalUser = true; + password = userPassword; + }; + }; + }; + + testScript = { nodes, ... }: + let + homes = nodes.machine.security.pam.zfs.homes; + pool = builtins.head (builtins.split "/" homes); + in + '' + machine.wait_for_unit("multi-user.target") + machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + + with subtest("Create encrypted ZFS datasets"): + machine.succeed("truncate -s 64M /testpool.img") + machine.succeed("zpool create -O canmount=off '${pool}' /testpool.img") + machine.succeed("zfs create -o canmount=off -p '${homes}'") + machine.succeed("echo ${userPassword} | zfs create -o canmount=noauto -o encryption=on -o keyformat=passphrase '${homes}/alice'") + machine.succeed("zfs unload-key '${homes}/alice'") + machine.succeed("echo ${mismatchPass} | zfs create -o canmount=noauto -o encryption=on -o keyformat=passphrase '${homes}/bob'") + machine.succeed("zfs unload-key '${homes}/bob'") + + with subtest("Switch to tty2"): + machine.fail("pgrep -f 'agetty.*tty2'") + machine.send_key("alt-f2") + machine.wait_until_succeeds("[ $(fgconsole) = 2 ]") + machine.wait_for_unit("getty@tty2.service") + machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'") + + with subtest("Log in as user with home locked by login password"): + machine.wait_until_tty_matches("2", "login: ") + machine.send_chars("alice\n") + machine.wait_until_tty_matches("2", "login: alice") + machine.wait_until_succeeds("pgrep login") + machine.wait_until_tty_matches("2", "Password: ") + machine.send_chars("${userPassword}\n") + machine.wait_until_succeeds("pgrep -u alice bash") + machine.succeed("mount | grep ${homes}/alice") + + with subtest("Switch to tty3"): + machine.fail("pgrep -f 'agetty.*tty3'") + machine.send_key("alt-f3") + machine.wait_until_succeeds("[ $(fgconsole) = 3 ]") + machine.wait_for_unit("getty@tty3.service") + machine.wait_until_succeeds("pgrep -f 'agetty.*tty3'") + + with subtest("Log in as user with home locked by password different from login"): + machine.wait_until_tty_matches("3", "login: ") + machine.send_chars("bob\n") + machine.wait_until_tty_matches("3", "login: bob") + machine.wait_until_succeeds("pgrep login") + machine.wait_until_tty_matches("3", "Password: ") + machine.send_chars("${userPassword}\n") + machine.wait_until_succeeds("pgrep -u bob bash") + machine.fail("mount | grep ${homes}/bob") + ''; + } +) |