nixos/system/boot/tzpfms: init
This commit is contained in:
@@ -1961,6 +1961,7 @@
|
||||
./system/boot/systemd/userdbd.nix
|
||||
./system/boot/timesyncd.nix
|
||||
./system/boot/tmp.nix
|
||||
./system/boot/tzpfms.nix
|
||||
./system/boot/uki.nix
|
||||
./system/boot/unl0kr.nix
|
||||
./system/boot/uvesafb.nix
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
|
||||
# NOTE: the loading of keys is done in separate tzpfms systemd services
|
||||
# defined below rather than inline in the ZFS import scripts.
|
||||
|
||||
let
|
||||
cfgZFS = config.boot.zfs;
|
||||
cfg = cfgZFS.tzpfms;
|
||||
|
||||
datasetToPool = x: lib.elemAt (lib.splitString "/" x) 0;
|
||||
|
||||
pools = lib.unique (map datasetToPool cfg.datasets);
|
||||
|
||||
# All ZFS filesystems
|
||||
zfsFilesystems = lib.filter (x: x.fsType == "zfs") config.system.build.fileSystems;
|
||||
|
||||
# Pools that are already imported in initrd (have neededForBoot filesystems)
|
||||
fsToPool = fs: lib.elemAt (lib.splitString "/" fs.device) 0;
|
||||
rootPools = lib.unique (map fsToPool (lib.filter utils.fsNeededForBoot zfsFilesystems));
|
||||
|
||||
# Only include initrd resources if datasets belong to pools that need initrd import.
|
||||
# A pool needs initrd import if it has neededForBoot filesystems.
|
||||
initrdPools = lib.filter (pool: lib.elem pool rootPools) pools;
|
||||
systemPools = lib.filter (pool: !(lib.elem pool rootPools)) pools;
|
||||
|
||||
needsInitrd = initrdPools != [ ];
|
||||
|
||||
datasetsByPool = lib.groupBy datasetToPool cfg.datasets;
|
||||
|
||||
# Goup neededForBoot filesystems by pool → initrd mount units
|
||||
initrdMountsByPool = lib.foldl' (
|
||||
acc: fs:
|
||||
let
|
||||
p = lib.elemAt (lib.splitString "/" fs.device) 0;
|
||||
mount = "${utils.escapeSystemdPath ("/sysroot" + (lib.removeSuffix "/" fs.mountPoint))}.mount";
|
||||
in
|
||||
if utils.fsNeededForBoot fs then acc // { ${p} = (acc.${p} or [ ]) ++ [ mount ]; } else acc
|
||||
) { } zfsFilesystems;
|
||||
|
||||
# Group all ZFS filesystems by pool → system mount units
|
||||
systemMountsByPool = lib.foldl' (
|
||||
acc: fs:
|
||||
let
|
||||
pool = lib.elemAt (lib.splitString "/" fs.device) 0;
|
||||
mount = "${utils.escapeSystemdPath (lib.removeSuffix "/" fs.mountPoint)}.mount";
|
||||
in
|
||||
acc // { ${pool} = (acc.${pool} or [ ]) ++ [ mount ]; }
|
||||
) { } zfsFilesystems;
|
||||
|
||||
# Generate tzpfms key-loading bash script
|
||||
mkTzpfmsScript = datasets: /* bash */ ''
|
||||
tzpfms_load_key() {
|
||||
zfs-tpm-list -H ${backendArgs} "$@" 2>/dev/null | while IFS=$'\t' read -r name backend status _; do
|
||||
case "$backend" in
|
||||
${lib.optionalString (lib.elem "TPM2" cfg.backends) /* bash */ ''
|
||||
TPM2)
|
||||
zfs-tpm2-load-key "$name" || true
|
||||
;;
|
||||
''}
|
||||
${lib.optionalString (lib.elem "TPM1.X" cfg.backends) /* bash */ ''
|
||||
TPM1.X)
|
||||
zfs-tpm1x-load-key "$name" || true
|
||||
;;
|
||||
''}
|
||||
*)
|
||||
echo "[WARN] boot.zfs.tzpfms: Unsupported tzpfms backend: “$backend”; “$name” not unlocked" >&2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
${lib.concatMapStringsSep "\n" (ds: "tzpfms_load_key -u ${lib.escapeShellArg ds}") datasets}
|
||||
'';
|
||||
|
||||
mkTzpfmsService =
|
||||
{
|
||||
pool,
|
||||
mountUnits,
|
||||
script,
|
||||
}:
|
||||
{
|
||||
description = "Load TPM keys for ZFS pool “${pool}”";
|
||||
after = [ "zfs-import-${pool}.service" ];
|
||||
before = mountUnits ++ [ "zfs-import.target" ];
|
||||
requiredBy = mountUnits ++ [ "zfs-import.target" ];
|
||||
unitConfig.DefaultDependencies = "no";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
inherit script;
|
||||
};
|
||||
|
||||
backendArgs = lib.escapeShellArgs (
|
||||
lib.concatMap (b: [
|
||||
"-b"
|
||||
b
|
||||
]) cfg.backends
|
||||
);
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ toastal ];
|
||||
|
||||
options = {
|
||||
boot.zfs.tzpfms = {
|
||||
enable = lib.mkEnableOption ''
|
||||
TPM-backed ZFS encryption using tzpfms.
|
||||
Supports both TPM 2.0 & TPM 1.x.
|
||||
'';
|
||||
|
||||
package = lib.mkPackageOption pkgs "tzpfms" { };
|
||||
|
||||
backends = lib.mkOption {
|
||||
type =
|
||||
with lib.types;
|
||||
nonEmptyListOf (enum [
|
||||
"TPM2"
|
||||
"TPM1.X"
|
||||
]);
|
||||
default = [
|
||||
"TPM2"
|
||||
];
|
||||
description = ''
|
||||
TPM backends to include in for tzpfms.
|
||||
'';
|
||||
};
|
||||
|
||||
datasets = lib.mkOption {
|
||||
# Needs to be explicit so we can build thy systemd services
|
||||
type = with lib.types; nonEmptyListOf str;
|
||||
example = [
|
||||
"tank/root"
|
||||
"tank/var"
|
||||
];
|
||||
description = ''
|
||||
Explicit list of ZFS datasets to unlock with TPM at boot.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
config.boot.supportedFilesystems.zfs or config.boot.initrd.supportedFilesystems.zfs or false;
|
||||
message = "ZFS filesystem support needs to be enabled for boot.tzpfms to work";
|
||||
}
|
||||
{
|
||||
assertion = initrdPools != { } -> config.boot.initrd.systemd.enable;
|
||||
message = "boot.zfs.tzpfms requires boot.initrd.systemd.enable = true";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
!(cfgZFS.requestEncryptionCredentials == true) || cfgZFS.requestEncryptionCredentials == [ ];
|
||||
message = ''
|
||||
boot.zfs.requestEncryptionCredentials = true would prompt for all
|
||||
encrypted dataset passphrases at boot, which conflicts with automatic
|
||||
TPM unlock via tzpfms. Either set it to false, or explicitly list the
|
||||
datasets that still need passphrase prompting.
|
||||
'';
|
||||
}
|
||||
(
|
||||
let
|
||||
intersected = lib.intersectLists cfg.datasets (
|
||||
if lib.isList cfgZFS.requestEncryptionCredentials then cfgZFS.requestEncryptionCredentials else [ ]
|
||||
);
|
||||
in
|
||||
{
|
||||
assertion = builtins.length intersected == 0;
|
||||
message = ''
|
||||
The following datasets are listed in both boot.zfs.tzpfms.datasets
|
||||
& boot.zfs.requestEncryptionCredentials, which would cause a
|
||||
passphrase prompt to block boot before tzpfms can unlock them via
|
||||
TPM:
|
||||
|
||||
${lib.concatMapStringsSep "\n" (d: "• ${d}") intersected}
|
||||
|
||||
Remove them from boot.zfs.requestEncryptionCredentials to allow
|
||||
automatic TPM unlock.
|
||||
'';
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
# Automatically register pools from tzpfms datasets as extraPools
|
||||
boot.zfs.extraPools = pools;
|
||||
|
||||
boot.initrd = lib.mkMerge [
|
||||
(lib.mkIf cfg.enable {
|
||||
availableKernelModules = [
|
||||
"tpm_tis"
|
||||
"tpm_crb"
|
||||
];
|
||||
})
|
||||
(lib.mkIf needsInitrd (
|
||||
lib.mkMerge [
|
||||
(lib.mkIf config.boot.initrd.systemd.enable {
|
||||
systemd.extraBin = {
|
||||
zfs-tpm-list = "${lib.getBin cfg.package}/bin/zfs-tpm-list";
|
||||
}
|
||||
// lib.optionalAttrs (lib.elem "TPM2" cfg.backends) {
|
||||
zfs-tpm2-load-key = "${lib.getBin cfg.package}/bin/zfs-tpm2-load-key";
|
||||
}
|
||||
// lib.optionalAttrs (lib.elem "TPM1.X" cfg.backends) {
|
||||
zfs-tpm1x-load-key = "${lib.getBin cfg.package}/bin/zfs-tpm1x-load-key";
|
||||
};
|
||||
systemd.storePaths =
|
||||
lib.optional (lib.elem "TPM2" cfg.backends) pkgs.tpm2-tss
|
||||
++ lib.optional (lib.elem "TPM1.X" cfg.backends) pkgs.trousers;
|
||||
systemd.services = lib.genAttrs' initrdPools (pool: {
|
||||
name = "tzpfms-load-${pool}";
|
||||
value = mkTzpfmsService {
|
||||
inherit pool;
|
||||
mountUnits = initrdMountsByPool.${pool} or [ ];
|
||||
script = mkTzpfmsScript (datasetsByPool.${pool} or [ ]);
|
||||
};
|
||||
});
|
||||
})
|
||||
]
|
||||
))
|
||||
];
|
||||
|
||||
systemd.services = lib.genAttrs' systemPools (
|
||||
pool:
|
||||
let
|
||||
mnts = systemMountsByPool.${pool} or [ ];
|
||||
in
|
||||
{
|
||||
name = "tzpfms-load-${pool}";
|
||||
value = mkTzpfmsService {
|
||||
inherit pool;
|
||||
mountUnits = mnts;
|
||||
script = mkTzpfmsScript (datasetsByPool.${pool} or [ ]);
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user