diff mbox series

[bug#65002,v2,1/2] mapped-devices: Allow unlocking by a key file

Message ID 058b41c5060e1811048fe44c20278c64fdfc3ece.1690981365.git.wolf@wolfsden.cz
State New
Headers show
Series [bug#65002,v2,1/2] mapped-devices: Allow unlocking by a key file | expand

Commit Message

Tomas Volf Aug. 2, 2023, 1:02 p.m. UTC
Requiring the user to input their password in order to unlock a device is not
always reasonable, so having an option to unlock the device using a key file
is a nice quality of life change.

* gnu/system/mapped-devices.scm (luks-device-mapping): New keyword argument
* gnu/system/mapped-devices.scm (luks-device-mapping-with-options): New
procedure
---
untabify
 doc/guix.texi                 | 12 +++++++
 gnu/system/mapped-devices.scm | 67 ++++++++++++++++++++++-------------
 2 files changed, 54 insertions(+), 25 deletions(-)


base-commit: 5a293d0830aa9369e388d37fe767d5bf98af01b7

Comments

Ludovic Courtès Jan. 9, 2024, 11:21 p.m. UTC | #1
Hello!

I know, I know, it’s taken way too long… My apologies!

Tomas Volf <wolf@wolfsden.cz> skribis:

> Requiring the user to input their password in order to unlock a device is not
> always reasonable, so having an option to unlock the device using a key file
> is a nice quality of life change.

Agreed; there’s interest for this feature, I’ve heard it quite a few
times.

> * gnu/system/mapped-devices.scm (luks-device-mapping): New keyword argument
> * gnu/system/mapped-devices.scm (luks-device-mapping-with-options): New
> procedure

No need to repeat the file name here.  Please also mention the
doc/guix.texi changes.

> +@deffn {Procedure} luks-device-mapping-with-options [#:key-file]
> +Return a @code{luks-device-mapping} object, which defines LUKS block
> +device encryption using the @command{cryptsetup} command from the
> +package with the same name.  It relies on the @code{dm-crypt} Linux
> +kernel module.
> +
> +If @code{key-file} is provided, unlocking is first attempted using that
> +key file.  If it fails, password unlock is attempted as well.  Key file
> +is not stored in the store and needs to be available at the specified
> +path at the time of the unlock attempt.

s/specified path/given location/

Perhaps add a sentence or two saying that the advantage is that it
allows you to avoid typing the passphrase, for instance by passing the
key file on a USB key (would that work?), but that this may not be
suitable for all use cases.

I’d also add a short commented config example.

I wonder if we could have a system test; it doesn’t sound very easy so
maybe we’ll skip, but you can check that the “encrypted-root-os” test,
which exercises ‘luks-device-mapping’, still passes (it takes time and
disk space).

The rest LGTM!

Ludo’.
Tomas Volf Jan. 11, 2024, 12:39 p.m. UTC | #2
On 2024-01-10 00:21:19 +0100, Ludovic Courtès wrote:
> Hello!
> 
> I know, I know, it’s taken way too long… My apologies!

No worries, thank you for getting to it. :)
> 
> > * gnu/system/mapped-devices.scm (luks-device-mapping): New keyword argument
> > * gnu/system/mapped-devices.scm (luks-device-mapping-with-options): New
> > procedure
> 
> No need to repeat the file name here.  Please also mention the
> doc/guix.texi changes.

Adjusted.  I also fixed the name of the first procedure (should have been
open-luks-device).

> 
> > +@deffn {Procedure} luks-device-mapping-with-options [#:key-file]
> > +Return a @code{luks-device-mapping} object, which defines LUKS block
> > +device encryption using the @command{cryptsetup} command from the
> > +package with the same name.  It relies on the @code{dm-crypt} Linux
> > +kernel module.
> > +
> > +If @code{key-file} is provided, unlocking is first attempted using that
> > +key file.  If it fails, password unlock is attempted as well.  Key file
> > +is not stored in the store and needs to be available at the specified
> > +path at the time of the unlock attempt.
> 
> s/specified path/given location/
> 
> Perhaps add a sentence or two saying that the advantage is that it
> allows you to avoid typing the passphrase, for instance by passing the
> key file on a USB key (would that work?), but that this may not be
> suitable for all use cases.

Added a sentence. As for the USB key, that would not currently work.  The file
needs to be accessible to the init script, so the USB would need to be mounted
first.  I believe extending the code to support it would not be hard (adding
e.g. #:device to luks-device-mapping-with-options), but I have not use for it,
so I did not intend to do it in this series.  Maybe later.

> 
> I’d also add a short commented config example.

Done.

> 
> I wonder if we could have a system test; it doesn’t sound very easy so
> maybe we’ll skip, but you can check that the “encrypted-root-os” test,
> which exercises ‘luks-device-mapping’, still passes (it takes time and
> disk space).

It does not pass, but it fails even on master ¯\_ (ツ)_/¯:

    guix system: warning: at least 1526.8 MB needed but only 1408.3 MB available in /mnt

It seems somewhat hard to do it based on encrypted-root-os, but should be much
easier basing it on encrypted-home-os.  I might give it a try.

> 
> The rest LGTM!
> 
> Ludo’.
Tomas Volf Jan. 11, 2024, 5:39 p.m. UTC | #3
On 2024-01-11 13:39:36 +0100, Tomas Volf wrote:

> 
> > 
> > I wonder if we could have a system test; it doesn’t sound very easy so
> > maybe we’ll skip, but you can check that the “encrypted-root-os” test,
> > which exercises ‘luks-device-mapping’, still passes (it takes time and
> > disk space).
> 
> It does not pass, but it fails even on master ¯\_ (ツ)_/¯:
> 
>     guix system: warning: at least 1526.8 MB needed but only 1408.3 MB available in /mnt
> 
> It seems somewhat hard to do it based on encrypted-root-os, but should be much
> easier basing it on encrypted-home-os.  I might give it a try.

I managed to figure out the system test for this, however it required unrelated
changes, since encrypted-root-os and encrypted-home-os were broken even on
master.  I included my new test (together with the fixes) in v3.

Also, I messed up, and sent this *without* the v3 by accident.  When I realized,
I sent it once more, this time properly as v3.  Sorry for the noise.

Tomas
diff mbox series

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index 58cc3d7aad..a857654191 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -17622,6 +17622,18 @@  Mapped Devices
 @code{dm-crypt} Linux kernel module.
 @end defvar
 
+@deffn {Procedure} luks-device-mapping-with-options [#:key-file]
+Return a @code{luks-device-mapping} object, which defines LUKS block
+device encryption using the @command{cryptsetup} command from the
+package with the same name.  It relies on the @code{dm-crypt} Linux
+kernel module.
+
+If @code{key-file} is provided, unlocking is first attempted using that
+key file.  If it fails, password unlock is attempted as well.  Key file
+is not stored in the store and needs to be available at the specified
+path at the time of the unlock attempt.
+@end deffn
+
 @defvar raid-device-mapping
 This defines a RAID device, which is assembled using the @code{mdadm}
 command from the package with the same name.  It requires a Linux kernel
diff --git a/gnu/system/mapped-devices.scm b/gnu/system/mapped-devices.scm
index e6b8970c12..0755036763 100644
--- a/gnu/system/mapped-devices.scm
+++ b/gnu/system/mapped-devices.scm
@@ -2,6 +2,7 @@ 
 ;;; Copyright © 2014-2022 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2016 Andreas Enge <andreas@enge.fr>
 ;;; Copyright © 2017, 2018 Mark H Weaver <mhw@netris.org>
+;;; Copyright © 2023 Tomas Volf <wolf@wolfsden.cz>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -64,6 +65,7 @@  (define-module (gnu system mapped-devices)
             check-device-initrd-modules           ;XXX: needs a better place
 
             luks-device-mapping
+            luks-device-mapping-with-options
             raid-device-mapping
             lvm-device-mapping))
 
@@ -188,7 +190,7 @@  (define (check-device-initrd-modules device linux-modules location)
 ;;; Common device mappings.
 ;;;
 
-(define (open-luks-device source targets)
+(define* (open-luks-device source targets #:key key-file)
   "Return a gexp that maps SOURCE to TARGET as a LUKS device, using
 'cryptsetup'."
   (with-imported-modules (source-module-closure
@@ -198,7 +200,8 @@  (define (open-luks-device source targets)
       ((target)
        #~(let ((source #$(if (uuid? source)
                              (uuid-bytevector source)
-                             source)))
+                             source))
+               (keyfile #$key-file))
            ;; XXX: 'use-modules' should be at the top level.
            (use-modules (rnrs bytevectors) ;bytevector?
                         ((gnu build file-systems)
@@ -215,29 +218,35 @@  (define (open-luks-device source targets)
            ;; 'cryptsetup open' requires standard input to be a tty to allow
            ;; for interaction but shepherd sets standard input to /dev/null;
            ;; thus, explicitly request a tty.
-           (zero? (system*/tty
-                   #$(file-append cryptsetup-static "/sbin/cryptsetup")
-                   "open" "--type" "luks"
-
-                   ;; Note: We cannot use the "UUID=source" syntax here
-                   ;; because 'cryptsetup' implements it by searching the
-                   ;; udev-populated /dev/disk/by-id directory but udev may
-                   ;; be unavailable at the time we run this.
-                   (if (bytevector? source)
-                       (or (let loop ((tries-left 10))
-                             (and (positive? tries-left)
-                                  (or (find-partition-by-luks-uuid source)
-                                      ;; If the underlying partition is
-                                      ;; not found, try again after
-                                      ;; waiting a second, up to ten
-                                      ;; times.  FIXME: This should be
-                                      ;; dealt with in a more robust way.
-                                      (begin (sleep 1)
-                                             (loop (- tries-left 1))))))
-                           (error "LUKS partition not found" source))
-                       source)
-
-                   #$target)))))))
+           (let ((partition
+                  ;; Note: We cannot use the "UUID=source" syntax here
+                  ;; because 'cryptsetup' implements it by searching the
+                  ;; udev-populated /dev/disk/by-id directory but udev may
+                  ;; be unavailable at the time we run this.
+                  (if (bytevector? source)
+                      (or (let loop ((tries-left 10))
+                            (and (positive? tries-left)
+                                 (or (find-partition-by-luks-uuid source)
+                                     ;; If the underlying partition is
+                                     ;; not found, try again after
+                                     ;; waiting a second, up to ten
+                                     ;; times.  FIXME: This should be
+                                     ;; dealt with in a more robust way.
+                                     (begin (sleep 1)
+                                            (loop (- tries-left 1))))))
+                          (error "LUKS partition not found" source))
+                      source)))
+             ;; We want to fallback to the password unlock if the keyfile fails.
+             (or (and keyfile
+                      (zero? (system*/tty
+                              #$(file-append cryptsetup-static "/sbin/cryptsetup")
+                              "open" "--type" "luks"
+                              "--key-file" keyfile
+                              partition #$target)))
+                 (zero? (system*/tty
+                         #$(file-append cryptsetup-static "/sbin/cryptsetup")
+                         "open" "--type" "luks"
+                         partition #$target)))))))))
 
 (define (close-luks-device source targets)
   "Return a gexp that closes TARGET, a LUKS device."
@@ -276,6 +285,14 @@  (define luks-device-mapping
    (close close-luks-device)
    (check check-luks-device)))
 
+(define* (luks-device-mapping-with-options #:key key-file)
+  "Return a luks-device-mapping object with open modified to pass the arguments
+into the open-luks-device procedure."
+  (mapped-device-kind
+   (inherit luks-device-mapping)
+   (open (λ (source targets) (open-luks-device source targets
+                                               #:key-file key-file)))))
+
 (define (open-raid-device sources targets)
   "Return a gexp that assembles SOURCES (a list of devices) to the RAID device
 TARGET (e.g., \"/dev/md0\"), using 'mdadm'."