Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions example/luks-tpm2.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
disko.devices = {
disk = {
main = {
type = "disk";
device = "/dev/vdb";
content = {
type = "gpt";
partitions = {
ESP = {
size = "500M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted";
settings.allowDiscards = true;
enrollFinalTPM2PostInstall = true;
# Do not wait for recovery displaying and blocking formatting.
enrollRecovery = false;
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
};
}
1 change: 1 addition & 0 deletions lib/tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ let
) testConfigInstall.networking.hostId;

virtualisation = {
tpm.enable = true;
emptyDiskImages = builtins.genList (_: 4096) num-disks;
qemu.options = lib.mkIf enableCanokey [
"-device pci-ohci,id=usb-bus"
Expand Down
91 changes: 84 additions & 7 deletions lib/types/luks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}:
let
# These options will automatically generate a temporary password and remove it later on.
autogeneratedPassword = config.enrollFido2;
autogeneratedPassword = config.enrollFido2 || config.enrollTPM2;

keyFile =
if config.settings ? "keyFile" then
Expand Down Expand Up @@ -122,7 +122,7 @@ in
&& config.passwordFile == null
&& (!config.settings ? "keyFile")
&& !autogeneratedPassword;
defaultText = "true if neither keyFile nor passwordFile nor enrollFido2 are set";
defaultText = "true if neither keyFile nor passwordFile nor enrollFido2 nor enrollTPM2 are set";
description = "Whether to ask for a password for initial encryption";
};
enrollFido2 = lib.mkOption {
Expand All @@ -138,10 +138,28 @@ in
];
description = "Extra arguments to pass to `systemd-cryptenroll` when enrolling the FIDO2 device";
};
enrollTPM2 = lib.mkOption {
type = lib.types.bool;
default = config.enrollFinalTPM2PostInstall;
description = "Whether to enroll a TPM2 token. This enrollment is intended for bootstrapping, thus it omits PCR bindings. For stronger security with PCR-based policies, use `enrollFinalTPM2PostInstall`.";
};
enrollFinalTPM2PostInstall = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to generate a NixOS systemd service to enroll a final TPM2 token after first boot, this allows proper PCRs to be configured via `extraFinalTPM2EnrollArgs`";
};
extraFinalTPM2EnrollArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
example = [
"--tpm2-pcrs=4+7"
];
description = "Extra arguments to pass to `systemd-cryptenroll` when enrolling the final TPM2 token";
};
enrollRecovery = lib.mkOption {
type = lib.types.bool;
default = config.enrollFido2;
defaultText = "true if fido2 is enabled";
default = autogeneratedPassword;
defaultText = "true if fido2 or tpm2 enrollment is enabled";
description = "Whether to enroll an automatic (keyboard layout independent) recovery passphrase with high entropy and print a QR code on screen to take it";
};
settings = lib.mkOption {
Expand Down Expand Up @@ -272,6 +290,14 @@ in
fi
fi
''}
${lib.optionalString config.enrollTPM2 ''
if ! systemd-cryptenroll "${config.device}" 2>/dev/null | grep -qw tpm2; then
systemd-cryptenroll \
--tpm2-device=auto \
--unlock-key-file=${formatKeyFile} \
"${config.device}"
fi
''}
${lib.optionalString config.enrollFido2 ''
if ! systemd-cryptenroll "${config.device}" 2>/dev/null | grep -qw fido2; then
wait_for_token() {
Expand Down Expand Up @@ -339,19 +365,70 @@ in
internal = true;
readOnly = true;
default =
[ ]
[
{
assertions = [
{
assertion = !config.enrollFinalTPM2PostInstall || config.enrollTPM2;
message = "`enrollFinalTPM2PostInstall` requires `enrollTPM2`.";
}
];
}
]
# If initrdUnlock is true, then add a device entry to the initrd.luks.devices config.
++ (lib.optional config.initrdUnlock [
{
boot.initrd.luks.devices.${config.name} = {
inherit (config) device;
crypttabExtraOpts = lib.mkIf config.enrollFido2 [ "fido2-device=auto" ];
crypttabExtraOpts = lib.mkIf config.enrollFido2 [ "fido2-device=auto" ]
// lib.mkIf config.enrollTPM2 ["tpm2-device=auto"];
}
// config.settings;

# If FIDO2 is used, systemd stage 1 is absolutely necessary.
# Should we turn this into an assertion?
boot.initrd.systemd.enable = lib.mkIf config.enrollFido2 true;
boot.initrd.systemd.enable = lib.mkIf (config.enrollFido2 || config.enrollTPM2) true;

# Should we turn this into an assertion?
security.tpm2.enable = lib.mkIf config.enrollTPM2 true;
}
])
++ (lib.optional config.enrollFinalTPM2PostInstall [
{
systemd.services."disko-tpm2-enroll-${config.name}" = {
description = "Finalize TPM2 unlock key for ${config.name}";

wantedBy = ["multi-user.target"];
after = ["tpm2.target"];
wants = ["tpm2.target"];

unitConfig = {
ConditionPathExists = "!/var/lib/disko/tpm2-enrolled-${config.name}";
};

serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};

script = ''
set -euo pipefail

echo "Removing temporary TPM2 token"

systemd-cryptenroll \
"${config.device}" \
--unlock-tpm2-device=auto \
--tpm2-device=auto \
--wipe-slot=tpm2 \
${toString config.extraFinalTPM2EnrollArgs}

mkdir -p /var/lib/disko
touch /var/lib/disko/tpm2-enrolled-${config.name}

echo "TPM2 finalization complete"
'';
};
}
])
++ (lib.optional (config.content != null) config.content._config);
Expand Down
23 changes: 14 additions & 9 deletions module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,18 @@ in
};

config = {
assertions = [
{
assertion = config.disko.imageBuilder.qemu != null -> diskoLib.vmToolsSupportsCustomQemu lib;
message = ''
You have set config.disko.imageBuild.qemu, but vmTools in your nixpkgs version "${lib.version}"
does not support overriding the qemu package with the customQemu option yet.
Please upgrade nixpkgs so that `lib.version` is at least "24.11.20240709".
'';
}
assertions = lib.mkMerge [
cfg.devices._config.assertions
[
{
assertion = config.disko.imageBuilder.qemu != null -> diskoLib.vmToolsSupportsCustomQemu lib;
message = ''
You have set config.disko.imageBuild.qemu, but vmTools in your nixpkgs version "${lib.version}"
does not support overriding the qemu package with the customQemu option yet.
Please upgrade nixpkgs so that `lib.version` is at least "24.11.20240709".
'';
}
]
];

_module.args.imagePkgs = pkgs;
Expand Down Expand Up @@ -307,5 +310,7 @@ in
fileSystems = lib.mkIf cfg.enableConfig cfg.devices._config.fileSystems or { };
boot = lib.mkIf cfg.enableConfig cfg.devices._config.boot or { };
swapDevices = lib.mkIf cfg.enableConfig cfg.devices._config.swapDevices or [ ];
systemd = lib.mkIf cfg.enableConfig cfg.devices._config.systemd or { };
security = lib.mkIf cfg.enableConfig cfg.devices._config.security or { };
};
}
18 changes: 18 additions & 0 deletions tests/luks-tpm2.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
pkgs ? import <nixpkgs> { },
diskoLib ? pkgs.callPackage ../lib { },
}:
diskoLib.testLib.makeDiskoTest {
inherit pkgs;
name = "luks-tpm2";
disko-config = ../example/luks-tpm2.nix;
enableCanokey = true;
extraTestScript = ''
machine.succeed("cryptsetup isLuks /dev/vda2");
machine.succeed("mountpoint /");

machine.succeed("systemd-cryptenroll /dev/vda2 | grep -qw tpm2")
# Recovery should be disabled
machine.fail("systemd-cryptenroll /dev/vda2 | grep -qw recovery")
'';
}
Loading