summaryrefslogtreecommitdiff
path: root/gnu/system
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/system')
-rw-r--r--gnu/system/mapped-devices.scm209
1 files changed, 159 insertions, 50 deletions
diff --git a/gnu/system/mapped-devices.scm b/gnu/system/mapped-devices.scm
index d568bddc4ff..8c32c3d3d4d 100644
--- a/gnu/system/mapped-devices.scm
+++ b/gnu/system/mapped-devices.scm
@@ -209,58 +209,162 @@ requests is allowed for the underlying device. EXTRA-OPTIONS is a list of
209additional options to be passed to the 'cryptsetup open' command." 209additional options to be passed to the 'cryptsetup open' command."
210 (with-imported-modules (source-module-closure 210 (with-imported-modules (source-module-closure
211 '((gnu build file-systems) 211 '((gnu build file-systems)
212 (guix base16)
212 (guix build utils))) ;; For mkdir-p 213 (guix build utils))) ;; For mkdir-p
213 (match targets 214 (match targets
214 ((target) 215 ((target)
215 #~(let ((source #$(if (uuid? source) 216 #~(begin
216 (uuid-bytevector source) 217 (use-modules (guix base16))
217 source)) 218
218 (keyfile #$key-file)) 219 (let ((source #$(if (uuid? source)
219 220 (uuid-bytevector source)
220 ;; Create '/run/cryptsetup/' if it does not exist, as device locking 221 source))
221 ;; is mandatory for LUKS2. 222 (keyfile #$key-file))
222 (mkdir-p "/run/cryptsetup/") 223
223 224 (define (luks-script-lookup-key partition)
224 ;; Use 'cryptsetup-static', not 'cryptsetup', to avoid pulling the 225 "Parse /etc/luks_script and return the master key as a
225 ;; whole world inside the initrd (for when we're in an initrd). 226bytevector for PARTITION. Throw to 'luks-script-error with a message
226 ;; 'cryptsetup open' requires standard input to be a tty to allow 227string if the file is missing, the partition UUID cannot be read, or
227 ;; for interaction but shepherd sets standard input to /dev/null; 228no matching entry is found."
228 ;; thus, explicitly request a tty. 229 (let ((script-file "/etc/luks_script"))
229 (let ((partition 230 (unless (file-exists? script-file)
230 ;; Note: We cannot use the "UUID=source" syntax here 231 (throw 'luks-script-error
231 ;; because 'cryptsetup' implements it by searching the 232 (format #f "~a not found, skipping"
232 ;; udev-populated /dev/disk/by-id directory but udev may 233 script-file)))
233 ;; be unavailable at the time we run this. 234 (let ((part-uuid-hex
234 (if (bytevector? source) 235 (or (and=> (read-luks-partition-uuid partition)
235 (or (let loop ((tries-left 10)) 236 bytevector->base16-string)
236 (and (positive? tries-left) 237 (throw 'luks-script-error
237 (or (find-partition-by-luks-uuid source) 238 (format #f
238 ;; If the underlying partition is 239 "could not read UUID from ~a"
239 ;; not found, try again after 240 partition)))))
240 ;; waiting a second, up to ten 241 (call-with-input-file script-file
241 ;; times. FIXME: This should be 242 (lambda (port)
242 ;; dealt with in a more robust way. 243 (let loop ((line (read-line port)))
243 (begin (sleep 1) 244 (when (eof-object? line)
244 (loop (- tries-left 1)))))) 245 (throw 'luks-script-error
245 (error "LUKS partition not found" source)) 246 (format #f
246 source))) 247 "no matching UUID ~a in ~a"
247 (let ((cryptsetup #$(file-append cryptsetup-static 248 part-uuid-hex script-file)))
248 "/sbin/cryptsetup")) 249 (match (string-tokenize line)
249 (cryptsetup-flags (cons* 250 (((or "luks_mount" "luks2_mount")
250 "open" "--type" "luks" 251 script-uuid _ ... hex-key)
251 (append 252 (if (string-ci=? (string-delete #\- script-uuid)
252 (if #$allow-discards? 253 part-uuid-hex)
253 '("--allow-discards") 254 (base16-string->bytevector hex-key)
254 '()) 255 (loop (read-line port))))
255 '#$extra-options 256 (_ (loop (read-line port))))))))))
256 (list partition #$target))))) 257
257 ;; We want to fallback to the password unlock if the keyfile 258 (define (open-luks-with-volume-key cryptsetup-program key-bv
258 ;; fails. 259 partition target
259 (or (and keyfile 260 extra-open-flags)
260 (zero? (apply system*/tty cryptsetup 261 "Open LUKS device PARTITION as TARGET using volume key KEY-BV.
261 "--key-file" keyfile cryptsetup-flags))) 262Return the exit status of cryptsetup. KEY-BV is securely wiped and
262 (zero? (apply system*/tty cryptsetup 263the temporary key file removed regardless of outcome."
263 cryptsetup-flags)))))))))) 264 (let ((key-file "/run/.luks-master-key"))
265 (dynamic-wind
266 (const #t)
267 (lambda ()
268 (call-with-port (open-file key-file "wb")
269 (lambda (p) (put-bytevector p key-bv)))
270 (chmod key-file #o400)
271 (apply system*/tty cryptsetup-program
272 "open" "--type" "luks"
273 "--volume-key-file" key-file
274 (append extra-open-flags
275 (list partition target))))
276 (lambda ()
277 (bytevector-fill! key-bv 0)
278 (when (file-exists? key-file)
279 (delete-file key-file))))))
280
281 (define (try-luks-script-master-key cryptsetup-program
282 partition target
283 extra-open-flags)
284 "Try to open LUKS device PARTITION as TARGET using a master key
285from /etc/luks_script (injected by GRUB). EXTRA-OPEN-FLAGS is a list of
286additional flags to pass to 'cryptsetup open' (e.g., \"--allow-discards\").
287Return #t on success, #f if /etc/luks_script does not exist or the UUID
288does not match. Any other error (parse failure, cryptsetup failure) is
289NOT caught--it will be visible so bugs cannot hide."
290 (catch 'luks-script-error
291 (lambda ()
292 (let* ((key-bv (luks-script-lookup-key partition))
293 (status (open-luks-with-volume-key
294 cryptsetup-program key-bv
295 partition target extra-open-flags)))
296 (if (zero? status)
297 (begin
298 (format (current-error-port)
299 "luks-master-key: unlocked ~a as ~a~%"
300 partition target)
301 #t)
302 (begin
303 (format (current-error-port)
304 "luks-master-key: cryptsetup failed (status ~a) for ~a~%"
305 status partition)
306 #f))))
307 (lambda (key msg . rest)
308 (format (current-error-port)
309 "luks-master-key: ~a~%" msg)
310 #f)))
311
312 ;; Create '/run/cryptsetup/' if it does not exist, as device locking
313 ;; is mandatory for LUKS2.
314 (mkdir-p "/run/cryptsetup/")
315
316 ;; Use 'cryptsetup-static', not 'cryptsetup', to avoid pulling the
317 ;; whole world inside the initrd (for when we're in an initrd).
318 ;; 'cryptsetup open' requires standard input to be a tty to allow
319 ;; for interaction but shepherd sets standard input to /dev/null;
320 ;; thus, explicitly request a tty.
321 (let ((partition
322 ;; Note: We cannot use the "UUID=source" syntax here
323 ;; because 'cryptsetup' implements it by searching the
324 ;; udev-populated /dev/disk/by-id directory but udev may
325 ;; be unavailable at the time we run this.
326 (if (bytevector? source)
327 (or (let loop ((tries-left 10))
328 (and (positive? tries-left)
329 (or (find-partition-by-luks-uuid source)
330 ;; If the underlying partition is
331 ;; not found, try again after
332 ;; waiting a second, up to ten
333 ;; times. FIXME: This should be
334 ;; dealt with in a more robust way.
335 (begin (sleep 1)
336 (loop (- tries-left 1))))))
337 (error "LUKS partition not found" source))
338 source)))
339 (let ((cryptsetup #$(file-append cryptsetup-static
340 "/sbin/cryptsetup"))
341 (cryptsetup-flags (cons*
342 "open" "--type" "luks"
343 (append
344 (if #$allow-discards?
345 '("--allow-discards")
346 '())
347 '#$extra-options
348 (list partition #$target)))))
349 ;; Try the GRUB-provided LUKS master key first (from
350 ;; /etc/luks_script, injected into the initrd via GRUB's
351 ;; newc: mechanism). This avoids prompting for the password
352 ;; a second time when GRUB already decrypted the same LUKS
353 ;; volume. Fall back to keyfile or interactive password on
354 ;; any failure.
355 (or (try-luks-script-master-key cryptsetup partition #$target
356 (append
357 (if #$allow-discards?
358 '("--allow-discards")
359 '())
360 '#$extra-options))
361 ;; We want to fallback to the password unlock if the
362 ;; keyfile fails.
363 (and keyfile
364 (zero? (apply system*/tty cryptsetup
365 "--key-file" keyfile cryptsetup-flags)))
366 (zero? (apply system*/tty cryptsetup
367 cryptsetup-flags)))))))))))
264 368
265(define* (close-luks-device source targets #:rest _) 369(define* (close-luks-device source targets #:rest _)
266 "Return a gexp that closes TARGET, a LUKS device." 370 "Return a gexp that closes TARGET, a LUKS device."
@@ -310,8 +414,13 @@ argument of `open-luks-device'")
310 (close close-luks-device) 414 (close close-luks-device)
311 (check check-luks-device) 415 (check check-luks-device)
312 (modules '((rnrs bytevectors) ;bytevector? 416 (modules '((rnrs bytevectors) ;bytevector?
417 (rnrs io ports) ;put-bytevector
418 (ice-9 match) ;match
419 (ice-9 rdelim) ;read-line
313 ((gnu build file-systems) 420 ((gnu build file-systems)
314 #:select (find-partition-by-luks-uuid system*/tty)))))) 421 #:select (find-partition-by-luks-uuid
422 read-luks-partition-uuid
423 system*/tty))))))
315 424
316(define-deprecated (luks-device-mapping-with-options #:key 425(define-deprecated (luks-device-mapping-with-options #:key
317 key-file allow-discards?) 426 key-file allow-discards?)