pam_oath: require OATH and pam_unix credentials to be valid

This commit is contained in:
Graham Christensen 2017-02-12 18:17:08 -05:00
parent 59e77daf5b
commit 96d767de62
No known key found for this signature in database
GPG Key ID: 06121D366FE9435C
3 changed files with 129 additions and 2 deletions

View File

@ -253,6 +253,8 @@ let
"auth sufficient ${pkgs.pam_u2f}/lib/security/pam_u2f.so"}
${optionalString cfg.usbAuth
"auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"}
${let oath = config.security.pam.oath; in optionalString cfg.oathAuth
"auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"}
'' +
# Modules in this block require having the password set in PAM_AUTHTOK.
# pam_unix is marked as 'sufficient' on NixOS which means nothing will run
@ -271,8 +273,6 @@ let
"auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"}
${optionalString cfg.otpwAuth
"auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"}
${let oath = config.security.pam.oath; in optionalString cfg.oathAuth
"auth sufficient ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"}
${optionalString use_ldap
"auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"}
${optionalString config.services.sssd.enable

View File

@ -283,6 +283,7 @@ in rec {
tests.leaps = callTest tests/leaps.nix { };
tests.nsd = callTest tests/nsd.nix {};
tests.openssh = callTest tests/openssh.nix {};
tests.pam-oath-login = callTest tests/pam-oath-login.nix {};
#tests.panamax = hydraJob (import tests/panamax.nix { system = "x86_64-linux"; });
tests.peerflix = callTest tests/peerflix.nix {};
tests.postgresql = callTest tests/postgresql.nix {};

View File

@ -0,0 +1,126 @@
import ./make-test.nix ({ pkgs, latestKernel ? false, ... }:
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 oathToolkit
# via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3
# and picking a the first 4:
oathSnakeOilPassword1 = "143349";
oathSnakeOilPassword2 = "801753";
oathSnakeOilPassword3 = "019933";
oathSnakeOilPassword4 = "403895";
alicePassword = "foobar";
# Generated via: mkpasswd -m sha-512 and passing in "foobar"
hashedAlicePassword = "$6$MsMrE1q.1HrCgTS$Vq2e/uILzYjSN836TobAyN9xh9oi7EmCmucnZID25qgPoibkw8qTCugiAPnn4eCGvn1A.7oEBFJaaGUaJsQQY.";
in
{
name = "pam-oath-login";
machine =
{ config, pkgs, lib, ... }:
{
security.pam.oath = {
enable = true;
};
users.extraUsers.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 =
''
$machine->waitForUnit('multi-user.target');
$machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'");
$machine->screenshot("postboot");
subtest "Invalid password", sub {
$machine->fail("pgrep -f 'agetty.*tty2'");
$machine->sendKeys("alt-f2");
$machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
$machine->waitForUnit('getty@tty2.service');
$machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'");
$machine->waitUntilTTYMatches(2, "login: ");
$machine->sendChars("alice\n");
$machine->waitUntilTTYMatches(2, "login: alice");
$machine->waitUntilSucceeds("pgrep login");
$machine->waitUntilTTYMatches(2, "One-time password");
$machine->sendChars("${oathSnakeOilPassword1}\n");
$machine->waitUntilTTYMatches(2, "Password: ");
$machine->sendChars("blorg\n");
$machine->waitUntilTTYMatches(2, "Login incorrect");
};
subtest "Invalid oath token", sub {
$machine->fail("pgrep -f 'agetty.*tty3'");
$machine->sendKeys("alt-f3");
$machine->waitUntilSucceeds("[ \$(fgconsole) = 3 ]");
$machine->waitForUnit('getty@tty3.service');
$machine->waitUntilSucceeds("pgrep -f 'agetty.*tty3'");
$machine->waitUntilTTYMatches(3, "login: ");
$machine->sendChars("alice\n");
$machine->waitUntilTTYMatches(3, "login: alice");
$machine->waitUntilSucceeds("pgrep login");
$machine->waitUntilTTYMatches(3, "One-time password");
$machine->sendChars("000000\n");
$machine->waitUntilTTYMatches(3, "Login incorrect");
$machine->waitUntilTTYMatches(3, "login:");
};
subtest "Happy path (both passwords are mandatory to get us in)", sub {
$machine->fail("pgrep -f 'agetty.*tty4'");
$machine->sendKeys("alt-f4");
$machine->waitUntilSucceeds("[ \$(fgconsole) = 4 ]");
$machine->waitForUnit('getty@tty4.service');
$machine->waitUntilSucceeds("pgrep -f 'agetty.*tty4'");
$machine->waitUntilTTYMatches(4, "login: ");
$machine->sendChars("alice\n");
$machine->waitUntilTTYMatches(4, "login: alice");
$machine->waitUntilSucceeds("pgrep login");
$machine->waitUntilTTYMatches(4, "One-time password");
$machine->sendChars("${oathSnakeOilPassword2}\n");
$machine->waitUntilTTYMatches(4, "Password: ");
$machine->sendChars("${alicePassword}\n");
$machine->waitUntilSucceeds("pgrep -u alice bash");
$machine->sendChars("touch done4\n");
$machine->waitForFile("/home/alice/done4");
};
'';
})