nixos/pam: support fscrypt login protectors
fscrypt can automatically unlock directories with the user's login password. To do this it ships a PAM module which reads the user's password and loads the respective keys into the user's kernel keyring. Significant inspiration was taken from the ecryptfs implementation.
This commit is contained in:
parent
db3f2b35d3
commit
f046cc0923
3 changed files with 81 additions and 0 deletions
|
@ -526,6 +526,7 @@ let
|
||||||
# We use try_first_pass the second time to avoid prompting password twice
|
# We use try_first_pass the second time to avoid prompting password twice
|
||||||
(optionalString (cfg.unixAuth &&
|
(optionalString (cfg.unixAuth &&
|
||||||
(config.security.pam.enableEcryptfs
|
(config.security.pam.enableEcryptfs
|
||||||
|
|| config.security.pam.enableFscrypt
|
||||||
|| cfg.pamMount
|
|| cfg.pamMount
|
||||||
|| cfg.enableKwallet
|
|| cfg.enableKwallet
|
||||||
|| cfg.enableGnomeKeyring
|
|| cfg.enableGnomeKeyring
|
||||||
|
@ -539,6 +540,9 @@ let
|
||||||
optionalString config.security.pam.enableEcryptfs ''
|
optionalString config.security.pam.enableEcryptfs ''
|
||||||
auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap
|
auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap
|
||||||
'' +
|
'' +
|
||||||
|
optionalString config.security.pam.enableFscrypt ''
|
||||||
|
auth optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
|
||||||
|
'' +
|
||||||
optionalString cfg.pamMount ''
|
optionalString cfg.pamMount ''
|
||||||
auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
|
auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
|
||||||
'' +
|
'' +
|
||||||
|
@ -584,6 +588,9 @@ let
|
||||||
optionalString config.security.pam.enableEcryptfs ''
|
optionalString config.security.pam.enableEcryptfs ''
|
||||||
password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
|
password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
|
||||||
'' +
|
'' +
|
||||||
|
optionalString config.security.pam.enableFscrypt ''
|
||||||
|
password optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
|
||||||
|
'' +
|
||||||
optionalString cfg.pamMount ''
|
optionalString cfg.pamMount ''
|
||||||
password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
|
password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
|
||||||
'' +
|
'' +
|
||||||
|
@ -630,6 +637,14 @@ let
|
||||||
optionalString config.security.pam.enableEcryptfs ''
|
optionalString config.security.pam.enableEcryptfs ''
|
||||||
session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
|
session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
|
||||||
'' +
|
'' +
|
||||||
|
optionalString config.security.pam.enableFscrypt ''
|
||||||
|
# Work around https://github.com/systemd/systemd/issues/8598
|
||||||
|
# Skips the pam_fscrypt module for systemd-user sessions which do not have a password
|
||||||
|
# anyways.
|
||||||
|
# See also https://github.com/google/fscrypt/issues/95
|
||||||
|
session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
|
||||||
|
session optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
|
||||||
|
'' +
|
||||||
optionalString cfg.pamMount ''
|
optionalString cfg.pamMount ''
|
||||||
session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
|
session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
|
||||||
'' +
|
'' +
|
||||||
|
@ -1146,6 +1161,14 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
security.pam.enableEcryptfs = mkEnableOption (lib.mdDoc "eCryptfs PAM module (mounting ecryptfs home directory on login)");
|
security.pam.enableEcryptfs = mkEnableOption (lib.mdDoc "eCryptfs PAM module (mounting ecryptfs home directory on login)");
|
||||||
|
security.pam.enableFscrypt = mkEnableOption (lib.mdDoc ''
|
||||||
|
Enables fscrypt to automatically unlock directories with the user's login password.
|
||||||
|
|
||||||
|
This also enables a service at security.pam.services.fscrypt which is used by
|
||||||
|
fscrypt to verify the user's password when setting up a new protector. If you
|
||||||
|
use something other than pam_unix to verify user passwords, please remember to
|
||||||
|
adjust this PAM service.
|
||||||
|
'');
|
||||||
|
|
||||||
users.motd = mkOption {
|
users.motd = mkOption {
|
||||||
default = null;
|
default = null;
|
||||||
|
@ -1170,6 +1193,7 @@ in
|
||||||
++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
|
++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
|
||||||
++ optionals config.security.pam.oath.enable [ pkgs.oath-toolkit ]
|
++ optionals config.security.pam.oath.enable [ pkgs.oath-toolkit ]
|
||||||
++ optionals config.security.pam.p11.enable [ pkgs.pam_p11 ]
|
++ optionals config.security.pam.p11.enable [ pkgs.pam_p11 ]
|
||||||
|
++ optionals config.security.pam.enableFscrypt [ pkgs.fscrypt-experimental ]
|
||||||
++ optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ];
|
++ optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ];
|
||||||
|
|
||||||
boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
|
boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
|
||||||
|
@ -1211,6 +1235,9 @@ in
|
||||||
it complains "Cannot create session: Already running in a
|
it complains "Cannot create session: Already running in a
|
||||||
session". */
|
session". */
|
||||||
runuser-l = { rootOK = true; unixAuth = false; };
|
runuser-l = { rootOK = true; unixAuth = false; };
|
||||||
|
} // optionalAttrs (config.security.pam.enableFscrypt) {
|
||||||
|
# Allow fscrypt to verify login passphrase
|
||||||
|
fscrypt = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
security.apparmor.includes."abstractions/pam" = let
|
security.apparmor.includes."abstractions/pam" = let
|
||||||
|
@ -1275,6 +1302,9 @@ in
|
||||||
optionalString config.security.pam.enableEcryptfs ''
|
optionalString config.security.pam.enableEcryptfs ''
|
||||||
mr ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so,
|
mr ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so,
|
||||||
'' +
|
'' +
|
||||||
|
optionalString config.security.pam.enableFscrypt ''
|
||||||
|
mr ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so,
|
||||||
|
'' +
|
||||||
optionalString (isEnabled (cfg: cfg.pamMount)) ''
|
optionalString (isEnabled (cfg: cfg.pamMount)) ''
|
||||||
mr ${pkgs.pam_mount}/lib/security/pam_mount.so,
|
mr ${pkgs.pam_mount}/lib/security/pam_mount.so,
|
||||||
'' +
|
'' +
|
||||||
|
|
|
@ -178,6 +178,7 @@ in {
|
||||||
ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
|
ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
|
||||||
ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
|
ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
|
||||||
ecryptfs = handleTest ./ecryptfs.nix {};
|
ecryptfs = handleTest ./ecryptfs.nix {};
|
||||||
|
fscrypt = handleTest ./fscrypt.nix {};
|
||||||
ejabberd = handleTest ./xmpp/ejabberd.nix {};
|
ejabberd = handleTest ./xmpp/ejabberd.nix {};
|
||||||
elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
|
elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
|
||||||
emacs-daemon = handleTest ./emacs-daemon.nix {};
|
emacs-daemon = handleTest ./emacs-daemon.nix {};
|
||||||
|
|
50
nixos/tests/fscrypt.nix
Normal file
50
nixos/tests/fscrypt.nix
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import ./make-test-python.nix ({ ... }:
|
||||||
|
{
|
||||||
|
name = "fscrypt";
|
||||||
|
|
||||||
|
nodes.machine = { pkgs, ... }: {
|
||||||
|
imports = [ ./common/user-account.nix ];
|
||||||
|
security.pam.enableFscrypt = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
def login_as_alice():
|
||||||
|
machine.wait_until_tty_matches("1", "login: ")
|
||||||
|
machine.send_chars("alice\n")
|
||||||
|
machine.wait_until_tty_matches("1", "Password: ")
|
||||||
|
machine.send_chars("foobar\n")
|
||||||
|
machine.wait_until_tty_matches("1", "alice\@machine")
|
||||||
|
|
||||||
|
|
||||||
|
def logout():
|
||||||
|
machine.send_chars("logout\n")
|
||||||
|
machine.wait_until_tty_matches("1", "login: ")
|
||||||
|
|
||||||
|
|
||||||
|
machine.wait_for_unit("default.target")
|
||||||
|
|
||||||
|
with subtest("Enable fscrypt on filesystem"):
|
||||||
|
machine.succeed("tune2fs -O encrypt /dev/vda")
|
||||||
|
machine.succeed("fscrypt setup --quiet --force --time=1ms")
|
||||||
|
|
||||||
|
with subtest("Set up alice with an fscrypt-enabled home directory"):
|
||||||
|
machine.succeed("(echo foobar; echo foobar) | passwd alice")
|
||||||
|
machine.succeed("chown -R alice.users ~alice")
|
||||||
|
machine.succeed("echo foobar | fscrypt encrypt --skip-unlock --source=pam_passphrase --user=alice /home/alice")
|
||||||
|
|
||||||
|
with subtest("Create file as alice"):
|
||||||
|
login_as_alice()
|
||||||
|
machine.succeed("echo hello > /home/alice/world")
|
||||||
|
logout()
|
||||||
|
# Wait for logout to be processed
|
||||||
|
machine.sleep(1)
|
||||||
|
|
||||||
|
with subtest("File should not be readable without being logged in as alice"):
|
||||||
|
machine.fail("cat /home/alice/world")
|
||||||
|
|
||||||
|
with subtest("File should be readable again as alice"):
|
||||||
|
login_as_alice()
|
||||||
|
machine.succeed("cat /home/alice/world")
|
||||||
|
logout()
|
||||||
|
'';
|
||||||
|
})
|
Loading…
Reference in a new issue