From 7b1335e93561a50eafa5e3299642b6ae1b3ab2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= <joerg@thalheim.io> Date: Fri, 29 Nov 2024 14:11:05 +0100 Subject: [PATCH] add wip verity --- lib/types/verity.nix | 155 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 lib/types/verity.nix diff --git a/lib/types/verity.nix b/lib/types/verity.nix new file mode 100644 index 00000000..75c431c2 --- /dev/null +++ b/lib/types/verity.nix @@ -0,0 +1,155 @@ +{ config, options, lib, diskoLib, parent, ... }: +# EXPERIMENTAL: This module provides verification of install images through dm-verity +# We do not provide updating nixos configuration through nixos-rebuild switch, when using this. +# This is mainly useful in the context of generated images, that are booted with secure boot. +let + diskoConfig = config; +in +{ + options = { + type = lib.mkOption { + type = lib.types.enum [ "verity" ]; + internal = true; + description = "Type"; + }; + name = lib.mkOption { + type = lib.types.str; + default = config._module.args.name; + description = "Name of the veritysetup device"; + }; + # can we get those from device directly? + dataDevice = lib.mkOption { + type = lib.types.str; + description = "Device to store the data"; + }; + hashDevice = lib.mkOption { + type = lib.types.str; + description = "Device to store the merkle tree"; + }; + extraFormatArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Extra arguments to pass to `veritysetup format`"; + example = [ "--debug" ]; + }; + extraOpenArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Extra arguments to pass to `cryptsetup luksOpen` when opening"; + example = [ "--timeout 10" ]; + }; + content = diskoLib.deviceType { parent = config; device = "/dev/mapper/${config.name}"; }; + _parent = lib.mkOption { + internal = true; + default = parent; + }; + _meta = lib.mkOption { + internal = true; + readOnly = true; + type = lib.types.functionTo diskoLib.jsonType; + default = dev: + lib.optionalAttrs (config.content != null) (config.content._meta dev); + description = "Metadata"; + }; + _create = diskoLib.mkCreateOption { + inherit config options; + default = {}; + }; + _mount = diskoLib.mkMountOption { + inherit config options; + default = {}; + }; + _unmount = diskoLib.mkMountOption { + inherit config options; + default = {}; + }; + _config = lib.mkOption { + internal = true; + readOnly = true; + default = [ + ({config, pkgs, ...}: { + # Maybe secureboot should be left to the user? + boot.uki.settings = { + # TODO + # SecureBootPrivateKey = "./out"; + }; + # TODO: upstream this. + assertions = [ + { + assertion = config.boot.inird.systemd.enable; + message = '' + veritysetup with disko requires systemd in the initrd to be enabled. + ''; + } + ]; + # TODO: we want actually + boot.kernelParams = [ + "systemd.verity_root_data=${diskoConfig.dataDevice}" + "systemd.verity_root_hash=${diskoConfig.hashDevice}" + ]; + boot.initrd = { + availableKernelModules = [ "dm_mod" "dm_verity" ]; + # We need LVM for dm-verity to work. + services.lvm.enable = true; + + 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" + ]; + }; + }; + + boot.bootspec.enable = true; + boot.loader.external = { + enable = true; + installHook = + let + bootspecNamespace = ''"org.nixos.bootspec.v1"''; + installer = pkgs.writeShellApplication { + name = "install-uki"; + runtimeInputs = with pkgs; [ jq systemd binutils ]; + text = '' + boot_json=/nix/var/nix/profiles/system-1-link/boot.json + tempdir=$(mktemp -d) + trap 'rm -rf "$tempdir"' EXIT + kernel=$(jq -r '.${bootspecNamespace}.kernel' "$boot_json") + initrd=$(jq -r '.${bootspecNamespace}.initrd' "$boot_json") + init=$(jq -r '.${bootspecNamespace}.init' "$boot_json") + + # TODO: get access to the unmount script for config.dataDevice by injecting toplevel-config into disko + veritysetup format ${config.dataDevice} ${config.hashDevice} \ + --root-hash-file "$tempdir/verity_roothash_${config.name}" + + ${pkgs.systemdUkify}/lib/systemd/ukify \ + "$kernel" \ + "$initrd" \ + --stub="${pkgs.systemd}/lib/systemd/boot/efi/linux${pkgs.hostPlatform.efiArch}.efi.stub" \ + --cmdline="init=$init ${builtins.toString config.boot.kernelParams} roothash=$(<$tempdir/verity_roothash_${config.name})" \ + --os-release="@${config.system.build.etc}/etc/os-release" \ + --output=uki.efi + + esp=${config.boot.loader.efi.efiSysMountPoint} + + bootctl install --esp-path="$esp" + install uki.efi "$esp"/EFI/Linux/ + ''; + }; + in + "${lib.getExe installer}"; + }; + + }) + ] ++ (lib.optional (config.content != null) config.content._config); + description = "NixOS configuration"; + }; + _pkgs = lib.mkOption { + internal = true; + readOnly = true; + type = lib.types.functionTo (lib.types.listOf lib.types.package); + default = pkgs: lib.optionals (config.content != null) (config.content._pkgs pkgs); + description = "Packages"; + }; + }; +}