vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / image / repart-verity-store.nix
blobe8706a5f79d07203dee1e62f5be09d14b97e4a67
1 # opinionated module that can be used to build nixos images with
2 # a dm-verity protected nix store
4   config,
5   pkgs,
6   lib,
7   ...
8 }:
9 let
10   cfg = config.image.repart.verityStore;
12   verityMatchKey = "store";
14   # TODO: make these and other arch mappings available from systemd-lib for example
15   partitionTypes = {
16     usr =
17       {
18         "x86_64" = "usr-x86-64";
19         "arm64" = "usr-arm64";
20       }
21       ."${pkgs.stdenv.hostPlatform.linuxArch}";
23     usr-verity =
24       {
25         "x86_64" = "usr-x86-64-verity";
26         "arm64" = "usr-arm64-verity";
27       }
28       ."${pkgs.stdenv.hostPlatform.linuxArch}";
29   };
31   verityHashCheck =
32     pkgs.buildPackages.writers.writePython3Bin "assert_uki_repart_match.py"
33       {
34         flakeIgnore = [ "E501" ]; # ignores PEP8's line length limit of 79 (black defaults to 88 characters)
35       }
36       (
37         builtins.replaceStrings [ "@NIX_STORE_VERITY@" ] [
38           partitionTypes.usr-verity
39         ] (builtins.readFile ./assert_uki_repart_match.py)
40       );
43   options.image.repart.verityStore = {
44     enable = lib.mkEnableOption "building images with a dm-verity protected nix store";
46     ukiPath = lib.mkOption {
47       type = lib.types.str;
48       default = "/EFI/Linux/${config.system.boot.loader.ukiFile}";
49       defaultText = "/EFI/Linux/\${config.system.boot.loader.ukiFile}";
50       description = ''
51         Specify the location on the ESP where the UKI is placed.
52       '';
53     };
55     partitionIds = {
56       esp = lib.mkOption {
57         type = lib.types.str;
58         default = "00-esp";
59         description = ''
60           Specify the attribute name of the ESP.
61         '';
62       };
63       store-verity = lib.mkOption {
64         type = lib.types.str;
65         default = "10-store-verity";
66         description = ''
67           Specify the attribute name of the store's dm-verity hash partition.
68         '';
69       };
70       store = lib.mkOption {
71         type = lib.types.str;
72         default = "20-store";
73         description = ''
74           Specify the attribute name of the store partition.
75         '';
76       };
77     };
78   };
80   config = lib.mkIf cfg.enable {
81     boot.initrd.systemd.dmVerity.enable = true;
83     image.repart.partitions = {
84       # dm-verity hash partition
85       ${cfg.partitionIds.store-verity}.repartConfig = {
86         Type = partitionTypes.usr-verity;
87         Verity = "hash";
88         VerityMatchKey = lib.mkDefault verityMatchKey;
89         Label = lib.mkDefault "store-verity";
90       };
91       # dm-verity data partition that contains the nix store
92       ${cfg.partitionIds.store} = {
93         storePaths = [ config.system.build.toplevel ];
94         repartConfig = {
95           Type = partitionTypes.usr;
96           Verity = "data";
97           Format = lib.mkDefault "erofs";
98           VerityMatchKey = lib.mkDefault verityMatchKey;
99           Label = lib.mkDefault "store";
100         };
101       };
103     };
105     system.build = {
107       # intermediate system image without ESP
108       intermediateImage =
109         (config.system.build.image.override {
110           # always disable compression for the intermediate image
111           compression.enable = false;
112         }).overrideAttrs
113           (
114             _: previousAttrs: {
115               # make it easier to identify the intermediate image in build logs
116               pname = "${previousAttrs.pname}-intermediate";
118               # do not prepare the ESP, this is done in the final image
119               systemdRepartFlags = previousAttrs.systemdRepartFlags ++ [ "--defer-partitions=esp" ];
121               # the image will be self-contained so we can drop references
122               # to the closure that was used to build it
123               unsafeDiscardReferences.out = true;
124             }
125           );
127       # UKI with embedded usrhash from intermediateImage
128       uki =
129         let
130           inherit (config.system.boot.loader) ukiFile;
131           cmdline = "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}";
132         in
133         # override the default UKI
134         lib.mkOverride 99 (
135           pkgs.runCommand ukiFile
136             {
137               nativeBuildInputs = [
138                 pkgs.jq
139                 pkgs.systemdUkify
140               ];
141             }
142             ''
143               mkdir -p $out
145               # Extract the usrhash from the output of the systemd-repart invocation for the intermediate image.
146               usrhash=$(jq -r \
147                 '.[] | select(.type=="${partitionTypes.usr-verity}") | .roothash' \
148                 ${config.system.build.intermediateImage}/repart-output.json
149               )
151               # Build UKI with the embedded usrhash.
152               ukify build \
153                   --config=${config.boot.uki.configFile} \
154                   --cmdline="${cmdline} usrhash=$usrhash" \
155                   --output="$out/${ukiFile}"
156             ''
157         );
159       # final system image that is created from the intermediate image by injecting the UKI from above
160       finalImage =
161         (config.system.build.image.override {
162           # continue building with existing intermediate image
163           createEmpty = false;
164         }).overrideAttrs
165           (
166             finalAttrs: previousAttrs: {
167               # add entry to inject UKI into ESP
168               finalPartitions = lib.recursiveUpdate previousAttrs.finalPartitions {
169                 ${cfg.partitionIds.esp}.contents = {
170                   "${cfg.ukiPath}".source = "${config.system.build.uki}/${config.system.boot.loader.ukiFile}";
171                 };
172               };
174               nativeBuildInputs = previousAttrs.nativeBuildInputs ++ [
175                 pkgs.systemdUkify
176                 verityHashCheck
177                 pkgs.jq
178               ];
180               preBuild = ''
181                 # check that we build the final image with the same intermediate image for
182                 # which the injected UKI was built by comparing the UKI cmdline with the repart output
183                 # of the intermediate image
184                 #
185                 # This is necessary to notice incompatible substitutions of
186                 # non-reproducible store paths, for example when working with distributed
187                 # builds, or when offline-signing the UKI.
188                 ukify --json=short inspect ${config.system.build.uki}/${config.system.boot.loader.ukiFile} \
189                   | assert_uki_repart_match.py "${config.system.build.intermediateImage}/repart-output.json"
191                 # copy the uncompressed intermediate image, so that systemd-repart picks it up
192                 cp -v ${config.system.build.intermediateImage}/${config.image.repart.imageFileBasename}.raw .
193                 chmod +w ${config.image.repart.imageFileBasename}.raw
194               '';
196               # replace "TBD" with the original roothash values
197               preInstall = ''
198                 mv -v repart-output{.json,_orig.json}
200                 jq --slurp --indent -1 \
201                   '.[0] as $intermediate | .[1] as $final
202                     | $intermediate | map(select(.roothash != null) | { "uuid":.uuid,"roothash":.roothash }) as $uuids
203                     | $final + $uuids
204                     | group_by(.uuid)
205                     | map(add)
206                     | sort_by(.offset)' \
207                       ${config.system.build.intermediateImage}/repart-output.json \
208                       repart-output_orig.json \
209                   > repart-output.json
211                 rm -v repart-output_orig.json
212               '';
214               # the image will be self-contained so we can drop references
215               # to the closure that was used to build it
216               unsafeDiscardReferences.out = true;
217             }
218           );
219     };
220   };
222   meta.maintainers = with lib.maintainers; [
223     nikstur
224     willibutz
225   ];