Merge pull request #228956 from tensor5/pam-zfs-key
nixos/pam: enable unlocking ZFS home dataset
This commit is contained in:
commit
8c4a3f67b5
5 changed files with 148 additions and 3 deletions
|
@ -410,6 +410,8 @@ In addition to numerous new and upgraded packages, this release has the followin
|
|||
}
|
||||
```
|
||||
|
||||
- New option `security.pam.zfs` to enable unlocking and mounting of encrypted ZFS home dataset at login.
|
||||
|
||||
- `services.peertube` now requires you to specify the secret file `secrets.secretsFile`. It can be generated by running `openssl rand -hex 32`.
|
||||
Before upgrading, read the release notes for PeerTube:
|
||||
- [Release v5.0.0](https://github.com/Chocobozzz/PeerTube/releases/tag/v5.0.0)
|
||||
|
|
|
@ -446,6 +446,15 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
zfs = mkOption {
|
||||
default = config.security.pam.zfs.enable;
|
||||
defaultText = literalExpression "config.security.pam.zfs.enable";
|
||||
type = types.bool;
|
||||
description = lib.mdDoc ''
|
||||
Enable unlocking and mounting of encrypted ZFS home dataset at login.
|
||||
'';
|
||||
};
|
||||
|
||||
text = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
description = lib.mdDoc "Contents of the PAM service file.";
|
||||
|
@ -556,7 +565,8 @@ let
|
|||
|| cfg.googleAuthenticator.enable
|
||||
|| cfg.gnupg.enable
|
||||
|| cfg.failDelay.enable
|
||||
|| cfg.duoSecurity.enable))
|
||||
|| cfg.duoSecurity.enable
|
||||
|| cfg.zfs))
|
||||
(
|
||||
optionalString config.services.homed.enable ''
|
||||
auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so
|
||||
|
@ -570,6 +580,9 @@ let
|
|||
optionalString config.security.pam.enableFscrypt ''
|
||||
auth optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
|
||||
'' +
|
||||
optionalString cfg.zfs ''
|
||||
auth optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes}
|
||||
'' +
|
||||
optionalString cfg.pamMount ''
|
||||
auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
|
||||
'' +
|
||||
|
@ -628,6 +641,9 @@ let
|
|||
optionalString config.security.pam.enableFscrypt ''
|
||||
password optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
|
||||
'' +
|
||||
optionalString cfg.zfs ''
|
||||
password optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes}
|
||||
'' +
|
||||
optionalString cfg.pamMount ''
|
||||
password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
|
||||
'' +
|
||||
|
@ -685,6 +701,10 @@ let
|
|||
session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
|
||||
session optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
|
||||
'' +
|
||||
optionalString cfg.zfs ''
|
||||
session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
|
||||
session optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes} ${optionalString config.security.pam.zfs.noUnmount "nounmount"}
|
||||
'' +
|
||||
optionalString cfg.pamMount ''
|
||||
session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
|
||||
'' +
|
||||
|
@ -1202,6 +1222,34 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
security.pam.zfs = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = lib.mdDoc ''
|
||||
Enable unlocking and mounting of encrypted ZFS home dataset at login.
|
||||
'';
|
||||
};
|
||||
|
||||
homes = mkOption {
|
||||
example = "rpool/home";
|
||||
default = "rpool/home";
|
||||
type = types.str;
|
||||
description = lib.mdDoc ''
|
||||
Prefix of home datasets. This value will be concatenated with
|
||||
`"/" + <username>` in order to determine the home dataset to unlock.
|
||||
'';
|
||||
};
|
||||
|
||||
noUnmount = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = lib.mdDoc ''
|
||||
Do not unmount home dataset on logout.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
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.
|
||||
|
@ -1238,6 +1286,12 @@ in
|
|||
Only one of users.motd and users.motdFile can be set.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = config.security.pam.zfs.enable && (config.boot.zfs.enabled || config.boot.zfs.enableUnstable);
|
||||
message = ''
|
||||
`security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled` or `boot.zfs.enableUnstable`).
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages =
|
||||
|
@ -1378,7 +1432,10 @@ in
|
|||
mr ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so,
|
||||
'' +
|
||||
optionalString config.virtualisation.lxc.lxcfs.enable ''
|
||||
mr ${pkgs.lxc}/lib/security/pam_cgfs.so
|
||||
mr ${pkgs.lxc}/lib/security/pam_cgfs.so,
|
||||
'' +
|
||||
optionalString (isEnabled (cfg: cfg.zfs)) ''
|
||||
mr ${config.boot.zfs.package}/lib/security/pam_zfs_key.so,
|
||||
'' +
|
||||
optionalString config.services.homed.enable ''
|
||||
mr ${config.systemd.package}/lib/security/pam_systemd_home.so
|
||||
|
|
|
@ -564,6 +564,7 @@ in {
|
|||
pam-oath-login = handleTest ./pam/pam-oath-login.nix {};
|
||||
pam-u2f = handleTest ./pam/pam-u2f.nix {};
|
||||
pam-ussh = handleTest ./pam/pam-ussh.nix {};
|
||||
pam-zfs-key = handleTest ./pam/zfs-key.nix {};
|
||||
pass-secret-service = handleTest ./pass-secret-service.nix {};
|
||||
patroni = handleTestOn ["x86_64-linux"] ./patroni.nix {};
|
||||
pantalaimon = handleTest ./matrix/pantalaimon.nix {};
|
||||
|
|
83
nixos/tests/pam/zfs-key.nix
Normal file
83
nixos/tests/pam/zfs-key.nix
Normal file
|
@ -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")
|
||||
'';
|
||||
}
|
||||
)
|
|
@ -11,6 +11,7 @@
|
|||
, smartmontools, enableMail ? false
|
||||
, sysstat, pkg-config
|
||||
, curl
|
||||
, pam
|
||||
|
||||
# Kernel dependencies
|
||||
, kernel ? null
|
||||
|
@ -111,7 +112,7 @@ stdenv'.mkDerivation {
|
|||
nativeBuildInputs = [ autoreconfHook269 nukeReferences ]
|
||||
++ optionals buildKernel (kernel.moduleBuildDependencies ++ [ perl ])
|
||||
++ optional buildUser pkg-config;
|
||||
buildInputs = optionals buildUser [ zlib libuuid attr libtirpc ]
|
||||
buildInputs = optionals buildUser [ zlib libuuid attr libtirpc pam ]
|
||||
++ optional buildUser openssl
|
||||
++ optional buildUser curl
|
||||
++ optional (buildUser && enablePython) python3;
|
||||
|
@ -136,6 +137,7 @@ stdenv'.mkDerivation {
|
|||
"--sysconfdir=/etc"
|
||||
"--localstatedir=/var"
|
||||
"--enable-systemd"
|
||||
"--enable-pam"
|
||||
] ++ optionals buildKernel ([
|
||||
"--with-linux=${kernel.dev}/lib/modules/${kernel.modDirVersion}/source"
|
||||
"--with-linux-obj=${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"
|
||||
|
|
Loading…
Reference in a new issue