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:
parent
da5bdb3d47
commit
c59ea29957
39 changed files with 1311 additions and 3272 deletions
modules/image/disk
128
modules/image/disk/:w
Normal file
128
modules/image/disk/:w
Normal 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" ];
|
||||
|
||||
}
|
167
modules/image/disk/builder.nix
Normal file
167
modules/image/disk/builder.nix
Normal 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";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
128
modules/image/disk/default.nix
Normal file
128
modules/image/disk/default.nix
Normal 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" ];
|
||||
|
||||
}
|
40
modules/image/disk/ssh.nix
Normal file
40
modules/image/disk/ssh.nix
Normal 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"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
86
modules/image/disk/updater.nix
Normal file
86
modules/image/disk/updater.nix
Normal 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"
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
}
|
39
modules/image/disk/veritysetup.nix
Normal file
39
modules/image/disk/veritysetup.nix
Normal 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"
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue