New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nixos: initrd/luks: allow to reuse passphrases, cleanup #29441
Conversation
Status? This seems really useful. |
I'd like to test this but I don't have a Yubikey (that was a problem for me too when I made my changes to this code). Is there someone with the hardware who uses LUKS and doesn't mind to get pinged if a test is needed? |
Status?
It works, I like it, I use it. But the leaky `read` concern is an actual
concern which never happens to me, because my machine is just too fast
for those two comparisons to have enough time to realistically skip a
character, but it could happen.
If somebody tests my changes to work with an actual Yubikey
- I can just disable the echoing of characters to the terminal for
the whole of that `while true`, if you are not uber-paranoid about
busybox leaving your passphrase in memory, it would totally work as
expected (and if you are, you should have enabled memory poisoning
anyway),
- or I can replace the `read` with a tiny C util to satisfy even the
uber-paranoid.
|
Rebased onto master. |
I reworked the thing to resolve the input echo issue (turned out to be super-easy). I also added syntax checks to stage-1 build and checked that my Yubikey changes pass it. And tested booting, still works flawlessly for me. I think this can now be merged, especially since I'm absolutely sure nobody uses that Yubikey code anyway because it is broken on master (see commit messages), hence unmarking this as [WIP]. |
@oxij is there a reason why you are not using keyfiles? I currently have a boot.initrd.luks.devices = {
cryptkey = {
device = "/dev/disk/by-uuid/3830842b-a589-45e0-a51d-4ed516472575";
};
cryptroot = {
device = "/dev/disk/by-uuid/ec133595-6294-4756-95ea-3891297f6477";
keyFile = "/dev/mapper/cryptkey";
};
cryptswap = {
device = "/dev/disk/by-uuid/71aab354-3872-4316-a26c-b4090d73275c";
keyFile = "/dev/mapper/cryptkey";
};
}; With this technique, I only have to type my password once and the rest of the devices use the shared partition as the key-file. |
1) I agree that `cryptkey` has its uses, but it feels like a hack to me, as it works only because attsets get sorted by nix.
(SLNOS has a toposorting patch for that, but I didn't get around to contributing it to nixos.)
2) This PR gives more freedom than `cryptkey` approach, with this PR I can still change passphrases to LUKS containers independently.
This PR also adds some useful things like syntax checking initrd scripts and disabling tty echo for the whole passphrase entry part of the initrd so that you won't leak characters between cryptsetup invocations even if you type at the speed of light.
|
@@ -243,6 +243,11 @@ let | |||
|
|||
isExecutable = true; | |||
|
|||
postInstall = '' | |||
echo checking syntax | |||
${pkgs.bash}/bin/sh -n $target |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Just validated, works on my machine™ when applied on top of current My setup is:
|
It probably should be checking with the shell it's going to use, just for complete correctness sake.
Yeah, but that shell is `ash` from `busybox` and I don't see it mentioning `-n` option anywhere in the docs.
Just validated, works on my machine™ when applied on top of current `nixos-unstable` (even though there were no doubts about it).
Thanks! Independent validation is a good thing regardless.
So, @matthewbauer @rnhmjoj @abbradar, can we merge this yet? This is more than a year old already.
|
@oxij I first checked empirically whether Additionally, here's the part where it happens: This is as described in The Open Group Base Specifications Issue 7, 2018 edition, as linked from the busybox shell's readme
|
@oxij I can't say much because I don't use LUKS but this is a really useful feature and it has been verified to work so yes, I would just merge this. 👍 from me for what it's worth. |
@samueldr I see, thanks. I added both checks to be sure. (And rebased onto master.) See the new version. If it looks ok to you, let's merge.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, a final review. Took more time to actually take in the changed code, and not just run it.
I'm asking for release notes as this removes options. I do understand that it is dubious they worked in the first place, so it may be hard to write such notes. If you can't write-up any notes, you can reply with everything you have in mind with those options and I'll try to write-up something appropriate.
I would like to add that I called for help for testing with yubikey hardware on the IRC channel, but I have low hopes about that. Searching on google finds about zero users having this option set within public configs. The only user having said option active is within a configuration untouched since 2014.
Finally, it looks like the previous maintainer of that code left around 2014, recently removed itself from maintainers. It's possible that changes between then and now caused the code to rot.
description = "Time in seconds to wait before attempting to find the Yubikey."; | ||
}; | ||
|
||
ramfsMountPoint = mkOption { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
@@ -414,12 +509,6 @@ in | |||
description = "The filesystem of the unencrypted device."; | |||
}; | |||
|
|||
mountPoint = mkOption { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
type = types.str; | ||
description = "Path where the unencrypted device will be mounted during early boot."; | ||
}; | ||
|
||
path = mkOption { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: (nothing to change here)
The path
is written as if an absolute path, bit this seems to be relative to the root of the yubikey. The semantics of the option did not change with the updated code. The only difference I can see is that the mount point is now hard-coded to /crypt-storage
instead of being an option, but *the code will look at /crypt-storage/crypt-storage/default
using the default options, both previously and now.
I'm leaving this note as I was about to ask for changes in the confusion this caused.
release notes
Done.
The `path` is written as if an absolute path, bit this seems to be relative to the root *of the yubikey*.
That's correct, I agree that that part is pretty confusing, I doubted my own sanity when I was editing it. Which is why I'm of the opinion that this code was never actually used by anybody except the original author and I've spent way too much time trying not to break it.
|
Also fixed a typo and a tiny bug. LGTM. |
Aww, I hate to be the bearer of bad news: Two PRs were merged (one fixing an issue in the other :/) today, and the first one added a feature. I don't think the merge will be too hard. Tell me if you won't or can't merge. I was about to merge this to master since seemingly nobody will be able to test the Yubikey support (tried getting in touch with a possible user, they didn't reply). Once updated, I'll do one last build+test to confirm everything's in working order and merge. |
Sorry for that merge! I didn't see this PR until @samueldr told me about it. Here's a patch for the feature in master, adapted to this PR: diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index f44ff0bd3cc..27c1f891f48 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -91,7 +91,7 @@ let
umount /crypt-ramfs 2>/dev/null
'';
- openCommand = name': { name, device, header, keyFile, keyFileSize, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name;
+ openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name;
let
csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}";
cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}";
@@ -155,7 +155,9 @@ let
open_normally() {
${if (keyFile != null) then ''
if wait_target "key file" ${keyFile}; then
- ${csopen} --key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}
+ ${csopen} --key-file=${keyFile} \
+ ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \
+ ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"}
else
${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable"
echo " - failing back to interactive password prompt"
@@ -417,6 +419,19 @@ in
'';
};
+ keyFileOffset = mkOption {
+ default = null;
+ example = 4096;
+ type = types.nullOr types.int;
+ description = ''
+ The offset of the key file. Use this in combination with
+ <literal>keyFileSize</literal> to use part of a file as key file
+ (often the case if a raw device or partition is used as a key file).
+ If not specified, the key begins at the first byte of
+ <literal>keyFile</literal>.
+ '';
+ };
+
# FIXME: get rid of this option.
preLVM = mkOption {
default = true; |
Also fix Yubikey timeout handling mess.
Also reuse common cryptsetup invocation subexpressions. - Passphrase reading is done via the shell now, not by cryptsetup. This way the same passphrase can be reused between cryptsetup invocations, which this module now tries to do by default (can be disabled). - Number of retries is now infinity, it makes no sense to make users reboot when they fail to type in their passphrase.
From reading the source I'm pretty sure it doesn't support multiple Yubikeys, hence those options are useless. Also, I'm pretty sure nobody actually uses this feature, because enabling it causes extra utils' checks to fail (even before applying any patches of this branch). As I don't have the hardware to test this, I'm too lazy to fix the utils, but I did test that with extra utils checks commented out and Yubikey enabled the resulting script still passes the syntax check.
Luckily, it was a pretty easy rebase even with the split of changes in this patchset.
I have not built nor tested this version yet.
|
Built, tested. It works.
|
I confirm, built overnight on top of master and worked. |
Yay! Cheers!
|
Since a9d69a7, the passphrase prompt now no longer starts with "Enter passphrase for" but now it's just "Passphrase for", which causes the luksroot installer test to fail. I've tested this on a x86_64-linux machine and the test now succeeds. Signed-off-by: aszlig <aszlig@nix.build> Cc: @oxij, @samueldr Issue: #29441
if [ -n "$passphrase" ]; then | ||
${if luks.reusePassphrases then '' | ||
# remember it for the next device | ||
echo -n "$passphrase" > /crypt-ramfs/passphrase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oxij: Keep in mind that this might leave artifacts of the passphrase in memory. In cryptsetup
, the memory for the passphrase is explicitly zeroed - here it is not - plus I wouldn't trust the shell to actually zero the memory even if you unset or overwrite the passphrase
variable.
@aszlig
Yeah, I mentioned that above #29441 (comment). However, AFAIK, with Linux that memory will be zeroed out anyway before being reused by any process after `ash` `exec`s into the next stage. Also note that even with `cryptsetup` zeroing its memory that passphrase stays for a while copied around kernel ring buffers from traveling from the keyboard to the /dev/console. So, in practice, I feel like there's no actual difference. Enabling memory poisoning by default is a good idea anyway, however.
I can also just patch ash's variable assignment (`set_local_var` in `busybox/shell/hush.c`) if you still think that this zeroing out has to be immediate. This would also be useful for other related code here.
|
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/disk-encryption-on-nixos-servers-how-when-to-unlock/5030/20 |
Motivation for this change
I hate typing long passphrases multiple times.
Works for me, but marking this as [WIP] because
read -t 1 -rs
like "What happens if you type a char exactly when read fails on timeout? Would it echo a piece of password?" I tested it extensively and couldn't make it echo anything, but feel free to voice your opinion on that.Things done
build-use-sandbox
innix.conf
on non-NixOS)nix-shell -p nox --run "nox-review wip"
./result/bin/
)/cc @primeos @Calrama @Phreedom @abbradar from git blame