forked from NixOS/nixpkgs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for building fully dm-verity protected images with system…
…d-repart (NixOS#343252)
- Loading branch information
Showing
8 changed files
with
487 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import json | ||
import sys | ||
|
||
store_verity_type = "@NIX_STORE_VERITY@" # replaced at import by Nix | ||
|
||
|
||
def extract_uki_cmdline_params(ukify_json: dict) -> dict[str, str]: | ||
""" | ||
Return a dict of the parameters in the .cmdline section of the UKI | ||
Exits early if "usrhash" is not included. | ||
""" | ||
cmdline = ukify_json.get(".cmdline", {}).get("text") | ||
if cmdline is None: | ||
print("Failed to get cmdline from ukify output") | ||
|
||
params = {} | ||
for param in cmdline.split(): | ||
key, val = param.partition("=")[::2] | ||
params[key] = val | ||
|
||
if "usrhash" not in params: | ||
print( | ||
f"UKI cmdline does not contain a usrhash:\n{cmdline}" | ||
) | ||
exit(1) | ||
|
||
return params | ||
|
||
|
||
def hashes_match(partition: dict[str, str], expected: str) -> bool: | ||
""" | ||
Checks if the value of the "roothash" key in the passed partition object matches `expected`. | ||
""" | ||
if partition.get("roothash") != expected: | ||
pretty_part = json.dumps(partition, indent=2) | ||
print( | ||
f"hash mismatch, expected to find roothash {expected} in:\n{pretty_part}" | ||
) | ||
return False | ||
else: | ||
return True | ||
|
||
|
||
def check_partitions( | ||
partitions: list[dict], uki_params: dict[str, str] | ||
) -> bool: | ||
""" | ||
Checks if the usrhash from `uki_params` has a matching roothash | ||
for the corresponding partition in `partitions`. | ||
""" | ||
for part in partitions: | ||
if part.get("type") == store_verity_type: | ||
expected = uki_params["usrhash"] | ||
return hashes_match(part, expected) | ||
|
||
return False | ||
|
||
|
||
def main() -> None: | ||
ukify_json = json.load(sys.stdin) | ||
repart_json_output = sys.argv[1] | ||
|
||
with open(repart_json_output, "r") as r: | ||
repart_json = json.load(r) | ||
|
||
uki_params = extract_uki_cmdline_params(ukify_json) | ||
|
||
if check_partitions(repart_json, uki_params): | ||
print("UKI and repart verity hashes match") | ||
else: | ||
print("Compatibility check for UKI and image failed!") | ||
print(f"UKI cmdline parameters:\n{uki_params}") | ||
print(f"repart config: {repart_json_output}") | ||
exit(1) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
# opinionated module that can be used to build nixos images with | ||
# a dm-verity protected nix store | ||
{ | ||
config, | ||
pkgs, | ||
lib, | ||
... | ||
}: | ||
let | ||
cfg = config.image.repart.verityStore; | ||
|
||
verityMatchKey = "store"; | ||
|
||
# TODO: make these and other arch mappings available from systemd-lib for example | ||
partitionTypes = { | ||
usr = | ||
{ | ||
"x86_64" = "usr-x86-64"; | ||
"arm64" = "usr-arm64"; | ||
} | ||
."${pkgs.stdenv.hostPlatform.linuxArch}"; | ||
|
||
usr-verity = | ||
{ | ||
"x86_64" = "usr-x86-64-verity"; | ||
"arm64" = "usr-arm64-verity"; | ||
} | ||
."${pkgs.stdenv.hostPlatform.linuxArch}"; | ||
}; | ||
|
||
verityHashCheck = | ||
pkgs.buildPackages.writers.writePython3Bin "assert_uki_repart_match.py" | ||
{ | ||
flakeIgnore = [ "E501" ]; # ignores PEP8's line length limit of 79 (black defaults to 88 characters) | ||
} | ||
( | ||
builtins.replaceStrings [ "@NIX_STORE_VERITY@" ] [ | ||
partitionTypes.usr-verity | ||
] (builtins.readFile ./assert_uki_repart_match.py) | ||
); | ||
in | ||
{ | ||
options.image.repart.verityStore = { | ||
enable = lib.mkEnableOption "building images with a dm-verity protected nix store"; | ||
|
||
ukiPath = lib.mkOption { | ||
type = lib.types.str; | ||
default = "/EFI/Linux/${config.system.boot.loader.ukiFile}"; | ||
defaultText = "/EFI/Linux/\${config.system.boot.loader.ukiFile}"; | ||
description = '' | ||
Specify the location on the ESP where the UKI is placed. | ||
''; | ||
}; | ||
|
||
partitionIds = { | ||
esp = lib.mkOption { | ||
type = lib.types.str; | ||
default = "00-esp"; | ||
description = '' | ||
Specify the attribute name of the ESP. | ||
''; | ||
}; | ||
store-verity = lib.mkOption { | ||
type = lib.types.str; | ||
default = "10-store-verity"; | ||
description = '' | ||
Specify the attribute name of the store's dm-verity hash partition. | ||
''; | ||
}; | ||
store = lib.mkOption { | ||
type = lib.types.str; | ||
default = "20-store"; | ||
description = '' | ||
Specify the attribute name of the store partition. | ||
''; | ||
}; | ||
}; | ||
}; | ||
|
||
config = lib.mkIf cfg.enable { | ||
boot.initrd.systemd.dmVerity.enable = true; | ||
|
||
image.repart.partitions = { | ||
# dm-verity hash partition | ||
${cfg.partitionIds.store-verity}.repartConfig = { | ||
Type = partitionTypes.usr-verity; | ||
Verity = "hash"; | ||
VerityMatchKey = lib.mkDefault verityMatchKey; | ||
Label = lib.mkDefault "store-verity"; | ||
}; | ||
# dm-verity data partition that contains the nix store | ||
${cfg.partitionIds.store} = { | ||
storePaths = [ config.system.build.toplevel ]; | ||
repartConfig = { | ||
Type = partitionTypes.usr; | ||
Verity = "data"; | ||
Format = lib.mkDefault "erofs"; | ||
VerityMatchKey = lib.mkDefault verityMatchKey; | ||
Label = lib.mkDefault "store"; | ||
}; | ||
}; | ||
|
||
}; | ||
|
||
system.build = { | ||
|
||
# intermediate system image without ESP | ||
intermediateImage = | ||
(config.system.build.image.override { | ||
# always disable compression for the intermediate image | ||
compression.enable = false; | ||
}).overrideAttrs | ||
( | ||
_: previousAttrs: { | ||
# make it easier to identify the intermediate image in build logs | ||
pname = "${previousAttrs.pname}-intermediate"; | ||
|
||
# do not prepare the ESP, this is done in the final image | ||
systemdRepartFlags = previousAttrs.systemdRepartFlags ++ [ "--defer-partitions=esp" ]; | ||
|
||
# the image will be self-contained so we can drop references | ||
# to the closure that was used to build it | ||
unsafeDiscardReferences.out = true; | ||
} | ||
); | ||
|
||
# UKI with embedded usrhash from intermediateImage | ||
uki = | ||
let | ||
inherit (config.system.boot.loader) ukiFile; | ||
cmdline = "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}"; | ||
in | ||
# override the default UKI | ||
lib.mkOverride 99 ( | ||
pkgs.runCommand ukiFile | ||
{ | ||
nativeBuildInputs = [ | ||
pkgs.jq | ||
pkgs.systemdUkify | ||
]; | ||
} | ||
'' | ||
mkdir -p $out | ||
# Extract the usrhash from the output of the systemd-repart invocation for the intermediate image. | ||
usrhash=$(jq -r \ | ||
'.[] | select(.type=="${partitionTypes.usr-verity}") | .roothash' \ | ||
${config.system.build.intermediateImage}/repart-output.json | ||
) | ||
# Build UKI with the embedded usrhash. | ||
ukify build \ | ||
--config=${config.boot.uki.configFile} \ | ||
--cmdline="${cmdline} usrhash=$usrhash" \ | ||
--output="$out/${ukiFile}" | ||
'' | ||
); | ||
|
||
# final system image that is created from the intermediate image by injecting the UKI from above | ||
finalImage = | ||
(config.system.build.image.override { | ||
# continue building with existing intermediate image | ||
createEmpty = false; | ||
}).overrideAttrs | ||
( | ||
finalAttrs: previousAttrs: | ||
let | ||
copyUki = "CopyFiles=${config.system.build.uki}/${config.system.boot.loader.ukiFile}:${cfg.ukiPath}"; | ||
in | ||
{ | ||
nativeBuildInputs = previousAttrs.nativeBuildInputs ++ [ | ||
pkgs.systemdUkify | ||
verityHashCheck | ||
]; | ||
|
||
postPatch = '' | ||
# add entry to inject UKI into ESP | ||
echo '${copyUki}' >> $finalRepartDefinitions/${cfg.partitionIds.esp}.conf | ||
''; | ||
|
||
preBuild = '' | ||
# check that we build the final image with the same intermediate image for | ||
# which the injected UKI was built by comparing the UKI cmdline with the repart output | ||
# of the intermediate image | ||
# | ||
# This is necessary to notice incompatible substitutions of | ||
# non-reproducible store paths, for example when working with distributed | ||
# builds, or when offline-signing the UKI. | ||
ukify --json=short inspect ${config.system.build.uki}/${config.system.boot.loader.ukiFile} \ | ||
| assert_uki_repart_match.py "${config.system.build.intermediateImage}/repart-output.json" | ||
# copy the uncompressed intermediate image, so that systemd-repart picks it up | ||
cp -v ${config.system.build.intermediateImage}/${config.image.repart.imageFileBasename}.raw . | ||
chmod +w ${config.image.repart.imageFileBasename}.raw | ||
''; | ||
|
||
# the image will be self-contained so we can drop references | ||
# to the closure that was used to build it | ||
unsafeDiscardReferences.out = true; | ||
} | ||
); | ||
}; | ||
}; | ||
|
||
meta.maintainers = with lib.maintainers; [ | ||
nikstur | ||
willibutz | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
{ config, lib, ... }: | ||
|
||
let | ||
cfg = config.boot.initrd.systemd.dmVerity; | ||
in | ||
{ | ||
options = { | ||
boot.initrd.systemd.dmVerity = { | ||
enable = lib.mkEnableOption "dm-verity" // { | ||
description = '' | ||
Mount verity-protected block devices in the initrd. | ||
Enabling this option allows to use `systemd-veritysetup` and | ||
`systemd-veritysetup-generator` in the initrd. | ||
''; | ||
}; | ||
}; | ||
}; | ||
|
||
config = lib.mkIf cfg.enable { | ||
assertions = [ | ||
{ | ||
assertion = config.boot.initrd.systemd.enable; | ||
message = '' | ||
'boot.initrd.systemd.dmVerity.enable' requires 'boot.initrd.systemd.enable' to be enabled. | ||
''; | ||
} | ||
]; | ||
|
||
boot.initrd = { | ||
availableKernelModules = [ | ||
"dm_mod" | ||
"dm_verity" | ||
]; | ||
|
||
# dm-verity needs additional udev rules from LVM to work. | ||
services.lvm.enable = true; | ||
|
||
# The additional targets and store paths allow users to integrate verity-protected devices | ||
# through the systemd tooling. | ||
systemd = { | ||
additionalUpstreamUnits = [ | ||
"veritysetup-pre.target" | ||
"veritysetup.target" | ||
"remote-veritysetup.target" | ||
]; | ||
|
||
storePaths = [ | ||
"${config.boot.initrd.systemd.package}/lib/systemd/systemd-veritysetup" | ||
"${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-veritysetup-generator" | ||
]; | ||
}; | ||
}; | ||
}; | ||
|
||
meta.maintainers = with lib.maintainers; [ | ||
msanft | ||
nikstur | ||
willibutz | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.