1{ config, lib, ... }:
2
3with lib;
4
5let
6 fileSystems = config.system.build.fileSystems ++ config.swapDevices;
7 encDevs = filter (dev: dev.encrypted.enable) fileSystems;
8
9 # With scripted initrd, devices with a keyFile have to be opened
10 # late, after file systems are mounted, because that could be where
11 # the keyFile is located. With systemd initrd, each individual
12 # systemd-cryptsetup@ unit has RequiresMountsFor= to delay until all
13 # the mount units for the key file are done; i.e. no special
14 # treatment is needed.
15 lateEncDevs =
16 if config.boot.initrd.systemd.enable
17 then { }
18 else filter (dev: dev.encrypted.keyFile != null) encDevs;
19 earlyEncDevs =
20 if config.boot.initrd.systemd.enable
21 then encDevs
22 else filter (dev: dev.encrypted.keyFile == null) encDevs;
23
24 anyEncrypted =
25 foldr (j: v: v || j.encrypted.enable) false encDevs;
26
27 encryptedFSOptions = {
28
29 options.encrypted = {
30 enable = mkOption {
31 default = false;
32 type = types.bool;
33 description = "The block device is backed by an encrypted one, adds this device as a initrd luks entry.";
34 };
35
36 blkDev = mkOption {
37 default = null;
38 example = "/dev/sda1";
39 type = types.nullOr types.str;
40 description = "Location of the backing encrypted device.";
41 };
42
43 label = mkOption {
44 default = null;
45 example = "rootfs";
46 type = types.nullOr types.str;
47 description = "Label of the unlocked encrypted device. Set `fileSystems.<name?>.device` to `/dev/mapper/<label>` to mount the unlocked device.";
48 };
49
50 keyFile = mkOption {
51 default = null;
52 example = "/mnt-root/root/.swapkey";
53 type = types.nullOr types.str;
54 description = ''
55 Path to a keyfile used to unlock the backing encrypted
56 device. When systemd stage 1 is not enabled, at the time
57 this keyfile is accessed, the `neededForBoot` filesystems
58 (see `utils.fsNeededForBoot`) will have been mounted under
59 `/mnt-root`, so the keyfile path should usually start with
60 "/mnt-root/". When systemd stage 1 is enabled,
61 `fsNeededForBoot` file systems will be mounted as needed
62 under `/sysroot`, and the keyfile will not be accessed until
63 its requisite mounts are done.
64 '';
65 };
66 };
67 };
68in
69
70{
71
72 options = {
73 fileSystems = mkOption {
74 type = with lib.types; attrsOf (submodule encryptedFSOptions);
75 };
76 swapDevices = mkOption {
77 type = with lib.types; listOf (submodule encryptedFSOptions);
78 };
79 };
80
81 config = mkIf anyEncrypted {
82 assertions = concatMap (dev: [
83 {
84 assertion = dev.encrypted.label != null;
85 message = ''
86 The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set
87 '';
88 }
89 {
90 assertion =
91 config.boot.initrd.systemd.enable -> (
92 dev.encrypted.keyFile == null
93 || !lib.any (x: lib.hasPrefix x dev.encrypted.keyFile) ["/mnt-root" "$targetRoot"]
94 );
95 message = ''
96 Bad use of '/mnt-root' or '$targetRoot` in 'keyFile'.
97
98 When 'boot.initrd.systemd.enable' is enabled, file systems
99 are mounted at '/sysroot' instead of '/mnt-root'.
100 '';
101 }
102 ]) encDevs;
103
104 boot.initrd = {
105 luks = {
106 devices =
107 builtins.listToAttrs (map (dev: {
108 name = dev.encrypted.label;
109 value = { device = dev.encrypted.blkDev; inherit (dev.encrypted) keyFile; };
110 }) earlyEncDevs);
111 forceLuksSupportInInitrd = true;
112 };
113 # TODO: systemd stage 1
114 postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
115 (concatMapStrings (dev:
116 "cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n"
117 ) lateEncDevs);
118 };
119 };
120}