Image building take 2

We want verity protected partitions as well as encrypted state/data along with verified boot.
This PR integrates Peter Marshall's awesome little Nixlet project as a starting point, especially the nice testing scaffolding will be super helpful! 

https://github.com/petm5/nixlet/
This commit is contained in:
Daniel Lundin 2024-11-11 23:02:38 +01:00
parent da5bdb3d47
commit c59ea29957
Signed by: dln
SSH key fingerprint: SHA256:dQy1Xj3UiqJYpKR5ggQ2bxgz4jCH8IF+k3AB8o0kmdI
39 changed files with 1311 additions and 3272 deletions

128
modules/image/disk/:w Normal file
View file

@ -0,0 +1,128 @@
{
config,
lib,
pkgs,
...
}:
{
imports = [
./updater.nix
./ssh.nix
./builder.nix
./veritysetup.nix
];
system.build.updatePackage = pkgs.runCommand "update-package" { } ''
mkdir "$out"
cd "$out"
cp "${config.system.build.image}"/* .
${pkgs.coreutils}/bin/sha256sum * > SHA256SUMS
'';
boot.initrd.systemd.enable = true;
boot.initrd.systemd.repart.enable = true;
systemd.repart.partitions = {
"10-esp" = {
Type = "esp";
Format = "vfat";
SizeMinBytes = "96M";
SizeMaxBytes = "96M";
};
"20-root-verity-a" = {
Type = "root-verity";
SizeMinBytes = "64M";
SizeMaxBytes = "64M";
};
"22-root-a" = {
Type = "root";
SizeMinBytes = "512M";
SizeMaxBytes = "512M";
};
"30-root-verity-b" = {
Type = "root-verity";
SizeMinBytes = "64M";
SizeMaxBytes = "64M";
Label = "_empty";
ReadOnly = 1;
};
"32-root-b" = {
Type = "root";
SizeMinBytes = "512M";
SizeMaxBytes = "512M";
Label = "_empty";
ReadOnly = 1;
};
"40-home" = {
Type = "home";
Format = "btrfs";
SizeMinBytes = "512M";
Encrypt = "tpm2";
};
};
boot.initrd.compressor = "zstd";
boot.initrd.compressorArgs = [ "-8" ];
boot.loader.grub.enable = false;
boot.initrd.luks.forceLuksSupportInInitrd = true;
boot.initrd.kernelModules = [
"dm_mod"
"dm_crypt"
] ++ config.boot.initrd.luks.cryptoModules;
boot.initrd.supportedFilesystems = {
btrfs = true;
erofs = true;
};
system.etc.overlay.mutable = false;
users.mutableUsers = false;
boot.initrd.systemd.services.systemd-repart.after = lib.mkForce [ "sysroot.mount" ];
boot.initrd.systemd.services.systemd-repart.requires = [ "sysroot.mount" ];
boot.kernelParams = [
"rootfstype=erofs"
"rootflags=ro"
"roothash=${config.system.build.verityRootHash}"
];
fileSystems."/var" = {
fsType = "tmpfs";
options = [ "mode=0755" ];
};
# Required to mount the efi partition
boot.kernelModules = [
"vfat"
"nls_cp437"
"nls_iso8859-1"
];
# Store SSH host keys on /home since /etc is read-only
services.openssh.hostKeys = [
{
path = "/home/.ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
environment.etc."machine-id" = {
text = "";
mode = "0755";
};
boot.initrd.systemd.services.systemd-repart.serviceConfig.Environment = [
"SYSTEMD_REPART_MKFS_OPTIONS_BTRFS=--nodiscard"
];
# Refuse to boot on mount failure
systemd.targets."sysinit".requires = [ "local-fs.target" ];
# Make sure home gets mounted
systemd.targets."local-fs".requires = [ "home.mount" ];
}

View file

@ -0,0 +1,167 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (pkgs.stdenv.hostPlatform) efiArch;
initialPartitions = {
"10-root" = {
storePaths = [ config.system.build.toplevel ];
repartConfig = {
Type = "root";
Minimize = "best";
Format = "erofs";
MakeDirectories = "/home /root /etc /dev /sys /bin /var /proc /run /usr /srv /tmp /mnt /lib /efi";
Verity = "data";
VerityMatchKey = "root";
SplitName = "root";
};
};
"20-root-verity" = {
repartConfig = {
Type = "root-verity";
Minimize = "best";
Verity = "hash";
VerityMatchKey = "root";
SplitName = "verity";
};
};
};
# TODO: We don't need a combined image here - add dry-run flag to repart invocation
verityRepart = import (pkgs.path + "/nixos/lib/eval-config.nix") {
inherit lib pkgs;
system = null;
modules = [
(
{ modulesPath, ... }:
{
imports = [ (modulesPath + "/image/repart.nix") ];
image.repart = {
name = "verity";
split = true;
mkfsOptions = lib.mkIf config.image.compress {
erofs = [
"-zlz4hc,level=12"
"-Efragments,dedupe,ztailpacking"
];
};
partitions = initialPartitions;
};
}
)
];
};
rootPart = "${verityRepart.config.system.build.image}/${verityRepart.config.image.repart.imageFileBasename}.root.raw";
verityPart = "${verityRepart.config.system.build.image}/${verityRepart.config.image.repart.imageFileBasename}.verity.raw";
verityImgAttrs = builtins.fromJSON (
builtins.readFile "${verityRepart.config.system.build.image}/repart-output.json"
);
rootAttrs = builtins.elemAt verityImgAttrs 0;
verityAttrs = builtins.elemAt verityImgAttrs 1;
rootUuid = rootAttrs.uuid;
verityUuid = verityAttrs.uuid;
verityRootHash = rootAttrs.roothash;
finalPartitions = {
"10-esp" = {
contents = {
"/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source = "${pkgs.systemdUkify}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi";
"/EFI/Linux/${config.system.boot.loader.ukiFile}".source = "${config.system.build.uki}/${config.system.boot.loader.ukiFile}";
"/default-ssh-authorized-keys.txt" = lib.mkIf config.system.image.sshKeys.enable {
source = pkgs.writeText "ssh-keys" (lib.concatStringsSep "\n" config.system.image.sshKeys.keys);
};
};
repartConfig = {
Type = "esp";
Format = "vfat";
SizeMinBytes = "96M";
SizeMaxBytes = "96M";
SplitName = "-";
};
};
"20-root-verity-a" = {
repartConfig = {
Type = "root-verity";
Label = "verity-${config.system.image.version}";
CopyBlocks = "${verityPart}";
SplitName = "-";
SizeMinBytes = "64M";
SizeMaxBytes = "64M";
UUID = "${verityUuid}";
ReadOnly = 1;
};
};
# TODO: Add signature partition for systemd-nspawn
"22-root-a" = {
repartConfig = {
Type = "root";
Label = "root-${config.system.image.version}";
CopyBlocks = "${rootPart}";
SplitName = "-";
UUID = "${rootUuid}";
ReadOnly = 1;
};
};
};
finalRepart = import (pkgs.path + "/nixos/lib/eval-config.nix") {
inherit lib pkgs;
system = null;
modules = [
(
{ modulesPath, ... }:
{
imports = [ (modulesPath + "/image/repart.nix") ];
image.repart = {
name = "${config.system.image.id}";
partitions = finalPartitions;
};
}
)
];
};
in
{
options.image.compress = lib.mkEnableOption "image compression" // {
default = true;
};
config.system.build = {
inherit verityRootHash;
image =
(pkgs.linkFarm "image-release" [
{
name = "${config.system.image.id}_${config.system.image.version}.efi";
path = "${config.system.build.uki}/${config.system.boot.loader.ukiFile}";
}
{
name = "${config.system.image.id}_${config.system.image.version}_${verityUuid}.verity";
path = "${verityRepart.config.system.build.image}/${verityRepart.config.image.repart.imageFileBasename}.verity.raw";
}
{
name = "${config.system.image.id}_${config.system.image.version}_${rootUuid}.root";
path = "${verityRepart.config.system.build.image}/${verityRepart.config.image.repart.imageFileBasename}.root.raw";
}
{
name = "${config.system.image.id}_${config.system.image.version}.img";
path = "${finalRepart.config.system.build.image}/${finalRepart.config.image.repart.imageFileBasename}.raw";
}
])
// {
imageFile = "${config.system.image.id}_${config.system.image.version}.img";
};
};
}

View file

@ -0,0 +1,128 @@
{
config,
lib,
pkgs,
...
}:
{
imports = [
./updater.nix
./ssh.nix
./builder.nix
./veritysetup.nix
];
system.build.updatePackage = pkgs.runCommand "update-package" { } ''
mkdir "$out"
cd "$out"
cp "${config.system.build.image}"/* .
${pkgs.coreutils}/bin/sha256sum * > SHA256SUMS
'';
boot.initrd.systemd.enable = true;
boot.initrd.systemd.repart.enable = true;
systemd.repart.partitions = {
"10-esp" = {
Type = "esp";
Format = "vfat";
SizeMinBytes = "96M";
SizeMaxBytes = "96M";
};
"20-root-verity-a" = {
Type = "root-verity";
SizeMinBytes = "64M";
SizeMaxBytes = "64M";
};
"22-root-a" = {
Type = "root";
SizeMinBytes = "512M";
SizeMaxBytes = "512M";
};
"30-root-verity-b" = {
Type = "root-verity";
SizeMinBytes = "64M";
SizeMaxBytes = "64M";
Label = "_empty";
ReadOnly = 1;
};
"32-root-b" = {
Type = "root";
SizeMinBytes = "512M";
SizeMaxBytes = "512M";
Label = "_empty";
ReadOnly = 1;
};
"40-home" = {
Type = "home";
Format = "btrfs";
SizeMinBytes = "512M";
Encrypt = "tpm2";
};
};
boot.initrd.compressor = "zstd";
boot.initrd.compressorArgs = [ "-8" ];
boot.loader.grub.enable = false;
boot.initrd.luks.forceLuksSupportInInitrd = true;
boot.initrd.kernelModules = [
"dm_mod"
"dm_crypt"
] ++ config.boot.initrd.luks.cryptoModules;
boot.initrd.supportedFilesystems = {
btrfs = true;
erofs = true;
};
system.etc.overlay.mutable = false;
users.mutableUsers = false;
boot.initrd.systemd.services.systemd-repart.after = lib.mkForce [ "sysroot.mount" ];
boot.initrd.systemd.services.systemd-repart.requires = [ "sysroot.mount" ];
boot.kernelParams = [
"rootfstype=erofs"
"rootflags=ro"
"roothash=${config.system.build.verityRootHash}"
];
fileSystems."/var" = {
fsType = "tmpfs";
options = [ "mode=0755" ];
};
# Required to mount the efi partition
boot.kernelModules = [
"vfat"
"nls_cp437"
"nls_iso8859-1"
];
# Store SSH host keys on /home since /etc is read-only
services.openssh.hostKeys = [
{
path = "/home/.ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
environment.etc."machine-id" = {
text = "";
mode = "0755";
};
boot.initrd.systemd.services.systemd-repart.serviceConfig.Environment = [
"SYSTEMD_REPART_MKFS_OPTIONS_BTRFS=--nodiscard"
];
# Refuse to boot on mount failure
systemd.targets."sysinit".requires = [ "local-fs.target" ];
# Make sure home gets mounted
systemd.targets."local-fs".requires = [ "home.mount" ];
}

View file

@ -0,0 +1,40 @@
{ config, lib, ... }:
{
options.system.image.sshKeys = {
enable = lib.mkEnableOption "provisioning of default SSH keys from ESP";
keys = lib.mkOption {
type = lib.types.listOf lib.types.singleLineStr;
default = [ ];
};
};
config = lib.mkIf config.system.image.sshKeys.enable {
assertions = [
{
assertion = config.services.openssh.enable;
message = "OpenSSH must be enabled to preseed authorized keys";
}
];
systemd.services."default-ssh-keys" = {
script = ''
mkdir -p /home/admin/.ssh/
cat /efi/default-ssh-authorized-keys.txt >> /home/admin/.ssh/authorized_keys
'';
wantedBy = [
"sshd.service"
"sshd.socket"
];
unitConfig = {
ConditionPathExists = [
"/home/admin"
"!/home/admin/.ssh/authorized_keys"
"/efi/default-ssh-authorized-keys.txt"
];
};
};
};
}

View file

@ -0,0 +1,86 @@
{ config, lib, ... }: {
options.system.image.updates = {
enable = lib.mkEnableOption "system updates via systemd-sysupdate" // {
default = config.system.image.updates.url != null;
};
url = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
};
config = lib.mkIf config.system.image.updates.enable {
assertions = [
{ assertion = config.system.image.updates.url != null; }
];
systemd.sysupdate.enable = true;
systemd.sysupdate.reboot.enable = lib.mkDefault true;
systemd.sysupdate.transfers = {
"10-uki" = {
Transfer = {
Verify = "no";
};
Source = {
Type = "url-file";
Path = "${config.system.image.updates.url}";
MatchPattern = "${config.boot.uki.name}_@v.efi";
};
Target = {
Type = "regular-file";
Path = "/EFI/Linux";
PathRelativeTo = "esp";
MatchPattern = "${config.boot.uki.name}_@v+@l-@d.efi ${config.boot.uki.name}_@v+@l.efi ${config.boot.uki.name}_@v.efi";
Mode = "0444";
TriesLeft = 3;
TriesDone = 0;
InstancesMax = 2;
};
};
"20-root-verity" = {
Transfer = {
Verify = "no";
};
Source = {
Type = "url-file";
Path = "${config.system.image.updates.url}";
MatchPattern = "${config.system.image.id}_@v_@u.verity";
};
Target = {
Type = "partition";
Path = "auto";
MatchPattern = "verity-@v";
MatchPartitionType = "root-verity";
ReadOnly = 1;
};
};
"22-root" = {
Transfer = {
Verify = "no";
};
Source = {
Type = "url-file";
Path = "${config.system.image.updates.url}";
MatchPattern = "${config.system.image.id}_@v_@u.root";
};
Target = {
Type = "partition";
Path = "auto";
MatchPattern = "root-@v";
MatchPartitionType = "root";
ReadOnly = 1;
};
};
};
systemd.additionalUpstreamSystemUnits = [
"systemd-bless-boot.service"
"boot-complete.target"
];
};
}

View file

@ -0,0 +1,39 @@
{ config, lib, ... }:
{
options.boot.initrd.systemd.root = lib.mkOption {
type = lib.types.enum [
"fstab"
"gpt-auto"
""
];
};
config.boot.initrd = {
kernelModules = [
"dm_mod"
"dm_verity"
];
systemd = {
# Required to activate systemd-fstab-generator
root = "";
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"
];
};
};
}