Commit f046cc09 authored by Lorenz Brun's avatar Lorenz Brun
Browse files

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.
parent db3f2b35
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -526,6 +526,7 @@ let
          # We use try_first_pass the second time to avoid prompting password twice
          (optionalString (cfg.unixAuth &&
            (config.security.pam.enableEcryptfs
              || config.security.pam.enableFscrypt
              || cfg.pamMount
              || cfg.enableKwallet
              || cfg.enableGnomeKeyring
@@ -539,6 +540,9 @@ let
              optionalString config.security.pam.enableEcryptfs ''
                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 ''
                auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
              '' +
@@ -584,6 +588,9 @@ let
          optionalString config.security.pam.enableEcryptfs ''
            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 ''
            password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
          '' +
@@ -630,6 +637,14 @@ let
          optionalString config.security.pam.enableEcryptfs ''
            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 ''
            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.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 {
      default = null;
@@ -1170,6 +1193,7 @@ in
      ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
      ++ optionals config.security.pam.oath.enable [ pkgs.oath-toolkit ]
      ++ 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 ];

    boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
@@ -1211,6 +1235,9 @@ in
           it complains "Cannot create session: Already running in a
           session". */
        runuser-l = { rootOK = true; unixAuth = false; };
      } // optionalAttrs (config.security.pam.enableFscrypt) {
        # Allow fscrypt to verify login passphrase
        fscrypt = {};
      };

    security.apparmor.includes."abstractions/pam" = let
@@ -1275,6 +1302,9 @@ in
      optionalString config.security.pam.enableEcryptfs ''
        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)) ''
        mr ${pkgs.pam_mount}/lib/security/pam_mount.so,
      '' +
+1 −0
Original line number Diff line number Diff line
@@ -178,6 +178,7 @@ in {
  ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
  ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
  ecryptfs = handleTest ./ecryptfs.nix {};
  fscrypt = handleTest ./fscrypt.nix {};
  ejabberd = handleTest ./xmpp/ejabberd.nix {};
  elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
  emacs-daemon = handleTest ./emacs-daemon.nix {};
+50 −0
Original line number Diff line number Diff line
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()
  '';
})