Is it possible to use this commit somehow to create a fallback to prompt the user for a passphrase in case an age identity to decrypt a zpool passphrase isn’t available?
I don’t have an answer, sorry. But, perhaps you can do a tiny experiment in a VM?
IIUC, that contrib/initramfs/scripts/zfs script is for other initramfs implementations, so not applicable in NixOS. I do wish we had a more robust system for ZFS key management in NixOS though; the script we have for this now isn’t great, and doesn’t leave much room for custom key management. I’m curious how you have this working with age currently? In the meantime, I think the way to customize this is basically to set the requestEncryptionCredentials option to exclude your dataset and add a custom systemd service ordered after the zfs-import-${pool}.service unit and before the corresponding mount units that handles zfs load-key the way you want (make sure to set DefaultDependencies=false).
At the moment, I’m using a simple passphrase, but I’m thinking I could either use an identity created by the TPM, or more preferably, a Yubikey, since that wouldn’t be as exposed. However, I can’t get pcscd to work in stage 1. Could you possibly help with that?
… Apparently not!
Not to mention, my current system’s mouse freezes after intense CPU load, but it’s a little hard using the keyboard for everything on Gnome…
You might look into the tzpfms option that was added recently. IIUC, it doesn’t have any sort of fallback to passphrase, but it stores a TPM2-sealed version of the key in a ZFS property on the dataset, and unseals and loads that key during boot.
I’m not familiar enough with PIV / pcscd to be of much help there, though I can help with whatever I do happen to know about more specific questions. Feel free to drop into the NixOS systemd matrix room; the people there are typically eager to figure out interesting stuff like this.
That said, what I do for this kind of thing is a little silly but very useful. I create a zvol that does not use ZFS native encryption, and I use LUKS to encrypt that volume. On it, I store a file system that contains a key file for unlocking a ZFS dataset that is using ZFS native encryption. The idea here is that LUKS is very useful and has a very nice UX, so you can just use LUKS for those things but use ZFS for the actual dataset encryption, just so that you don’t have to reimplement them for ZFS. e.g. This way I get a fallback passphrase for free, as systemd’s LUKS implementation already knows how to do that for a recovery key slot. I described one of these systems in detail here, and the idea should be the same for a Yubikey. The difference is just that instead of setting up the LUKS wrapper using the TPM2 and systemd-cryptenroll --tpm2-device ..., you would use FIDO2 with systemd-cryptenroll --fido2-device ...
If my method fails, I’ll probably end up using yours, but I prefer the Yubikeys because if I lose one, I’ll have a backup, and the laptop can’t be booted without it. However, I think I outlined my problem in my response here; is it as simple as creating the /var/lib directory?
Well, that’s why I mentioned that the LUKS+zvol method described works with Yubikeys via FIDO2 as well.
This is something that all of these methods are able to accomplish. However you provide key material to zfs load-key, that material can be backed up. With tzpfms, it can create a backup of the unsealed key. With systemd-cryptenroll, you can have an additional “recovery” or “passphrase” key slot to unlock the same volume.
Ah, I forgot about that thread, sorry.
loading "/nix/store/qpmypc2hndyyxk30zv0xi0qcfx739f5l-pcsclite-2.4.1-lib/lib/libpcsclite_real.so.1" failed: /nix/store/qpmypc2hndyyxk30zv0xi0qcfx739f5l-pcsclite-2.4.1-lib/lib/libpcsclite_real.so.1: cannot open shared object file: No such file or directory
This error just means this library file isn’t in the initrd for whatever reason. Would have to see the configuration you tried to know for sure what’s going on, but my guess is that it’s used with dlopen and isn’t recorded with metadata telling initrd to include the file automatically, so you might just need to manually include it in boot.initrd.systemd.storePaths.
Got it. Thanks for the clarification for the other parts!
And will the rest of the script in that other reply work as well, such as creating /var/lib, linking the drivers, etc.?
I’m not familiar enough, but that script doesn’t seem like it should be necessary. Activation scripts in general are a code smell. The fact that sops-nix does secret decryption during activation is probably a bad thing, and it even has an option to have that happen in a systemd unit instead, which could just be ordered after the ordinary pcscd.service unit (though that would likely require you to add DefaultDependencies=false to pcscd.service and manually add whatever more specific dependencies it needs). That activation script having its own pcscd configuration and process management, duplicating the logic of the normal pcscd daemon you get in nixos, is exactly the kind of clutter and difficulty that makes activation scripts an antipattern in most cases.
Similarly, to have pcscd working in systemd stage 1, I would expect that writing your own script like that would be more of an antipattern considering you could instead just use boot.initrd.systemd.packages = [ pkgs.pcsclite ]; and boot.initrd.systemd.contents... to specify the service and config files it needs idiomatically.
I was attempting to use the systemd scripts for agenix and sops-nix myself, but at the moment, there’s a bug that prevents you from installing while using userborn, while sysusers only allows for declarative system users. But even then I was having trouble with order cycles when putting the services after the pcscd mount.
I’ll post what I already have in a second…
Ah, no, wait; I was using your previous example, but with that pcscd script in the space where you said you didn’t know what I’d put. Could you give me an example of what I could do with the new information I gave you?
Sorry for all the hassle; learning disabilities are a bitch.
I think you would just want to replicate this part of this module but for stage 1: nixpkgs/nixos/modules/services/hardware/pcscd.nix at e72e4f299401a3689d4b3d5fc6496b11db7064eb · NixOS/nixpkgs · GitHub
In general, the systemd.* options can become boot.initrd.systemd.*. services.udev.packages can become boot.initrd.services.udev.packages, environment.etc.foo.source can become boot.initrd.systemd.contents."/etc/foo".source. For the user, I think you don’t want to try to make a user in stage 1 (because there’s problems with that sort of thing) so instead you’d probably just want to try setting serviceConfig.DynamicUser = true;. I think you’d also want unitConfig.DefaultDependencies = false;, though I’m less sure about that.
Anyway, I still think… this seems a lot harder than just not using PIV
FIDO2 stuff seems much more straightforward, there’s an age plugin for that as well, and systemd-cryptenroll also supports it out of the box.
… Fuck it, let’s do it.
I’ll look into your method. I’m just worried I may have trouble with implementing it with disko. Do you use disko to set it up on new systems?
Also, in your explanation, where is the systemd-cryptenroll call that I’d have to change to the FIDO variant?
Also also, you mentioned using Tailscale in that post? Could you show me that as well?
For disko, this is what I have for the luks zvolume on my root pool at the moment:
datasets.boot = {
type = "zfs_volume";
size = "4M";
content = {
type = "luks";
name = "zboot";
settings = {
allowDiscards = true;
fallbackToPassword = true;
};
# TODO
passwordFile = "${config.flake.paths.initrd-boot}/password";
additionalKeyFiles = [
# TODO
];
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/zboot";
};
};
};
And this is what I have for the corresponding initrd section:
{ config, lib, hostName, ... }: {
boot.initrd.luks = let
inherit (config.disko.devices.zpool.${hostName}.datasets.boot.content) name content;
in lib.mkIf config.disko.enable.zpool.${hostName} {
fido2Support = false;
devices."${name}" = {
device = content.mountpoint;
crypttabExtraOpts = [ " fido2-device=auto" ];
};
};
}
After this I’m not entirely sure what to do; I’m assuming the passwordFile is how luks will verify the fallback password? If so, how will it access the file if it’s on the encrypted zvolume?
Wait, what exactly is passwordFile, additionalKeyFiles and settings.keyFile used for…?
@ElvishJerricco: I just realized: can I not simply add another 4 megabyte partition to my drive and skip using a zvol entirely? That way I can just order the luks service before the zpool import and not worry about modifying the current service. Are there any drawbacks to that plan? I did want to use a zvolume, though… It sounded fun…
There is a PR on disko repo that has the same approach for using a luks zvol to decrypt the dataset. You can use it as a reference.
Got it. It seems to incorporate ElvishJerricco’s setup as well. I mostly had trouble figuring out what device you use for the cryptenroll command. Thanks for the heads up!
It should be /dev/zvol/<the_luks_zvol>. From the example of that PR, it will be /dev/zvol/rpool/credstore.
For fido2 unlocking, you just have to set boot.initrd.luks.devices.credstore.crypttabExtraOpts = [“fido2-device=auto”] and you can enroll multiple keys for unlocking while still have a fallback passphrase to unlock the luks zvol.