summaryrefslogtreecommitdiff
path: root/sys/boot
diff options
context:
space:
mode:
Diffstat (limited to 'sys/boot')
-rw-r--r--sys/boot/chain.nix47
-rw-r--r--sys/boot/default.nix15
-rw-r--r--sys/boot/detached-luks.nix117
-rw-r--r--sys/boot/efi.nix49
-rw-r--r--sys/boot/firmware.nix33
-rw-r--r--sys/boot/fscrypt.nix30
-rw-r--r--sys/boot/impermanence.nix56
-rw-r--r--sys/boot/namespaced.nix33
-rw-r--r--sys/boot/pxe.nix187
-rw-r--r--sys/boot/secure-boot.nix51
-rw-r--r--sys/boot/stack/btrfs-toplevel-multidrive.nix99
-rw-r--r--sys/boot/stack/default.nix6
-rw-r--r--sys/boot/stack/luks-ext4-fscrypt-impermanence.nix98
-rw-r--r--sys/boot/tpm.nix128
14 files changed, 949 insertions, 0 deletions
diff --git a/sys/boot/chain.nix b/sys/boot/chain.nix
new file mode 100644
index 0000000..b6a9ab2
--- /dev/null
+++ b/sys/boot/chain.nix
@@ -0,0 +1,47 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot;
+in {
+ options.local.boot = {
+ loader = mkOption {
+ type = types.enum ["none" "out-of-band" "grub" "systemd-boot"];
+ };
+
+ kernel = mkOption {
+ type = types.raw;
+ };
+ };
+
+ config = mkIf (cfg.loader != "none") {
+ boot = {
+ kernelPackages = cfg.kernel;
+
+ loader =
+ if cfg.loader == "none" || cfg.loader == "out-of-band"
+ then {
+ grub.enable = false;
+ }
+ else if cfg.loader == "grub"
+ then {
+ grub = {
+ enable = true;
+ device = "nodev";
+ efiSupport = true;
+ };
+ }
+ else if cfg.loader == "systemd-boot"
+ then {
+ systemd-boot = {
+ enable = true;
+ editor = true;
+ };
+ }
+ else throw "unexpected config.local.boot.loader setting: ${cfg.loader}";
+ };
+ };
+}
diff --git a/sys/boot/default.nix b/sys/boot/default.nix
new file mode 100644
index 0000000..3cbbb6f
--- /dev/null
+++ b/sys/boot/default.nix
@@ -0,0 +1,15 @@
+{
+ imports = [
+ ./chain.nix
+ ./detached-luks.nix
+ ./efi.nix
+ ./firmware.nix
+ ./fscrypt.nix
+ ./impermanence.nix
+ ./namespaced.nix
+ ./pxe.nix
+ ./secure-boot.nix
+ ./stack
+ ./tpm.nix
+ ];
+}
diff --git a/sys/boot/detached-luks.nix b/sys/boot/detached-luks.nix
new file mode 100644
index 0000000..78ae35c
--- /dev/null
+++ b/sys/boot/detached-luks.nix
@@ -0,0 +1,117 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.detachedLuks;
+
+ bootFs = config.fileSystems."/boot";
+ tpmInitrd = config.local.boot.tpm.initrd.enable;
+
+ pcrList = concatStringsSep "," (map toString config.local.boot.tpm.initrd.pcrs);
+in {
+ options.local.boot.detachedLuks = {
+ enable = mkEnableOption "detached LUKS header in initrd";
+
+ headerFromBoot = mkOption {
+ type = types.str;
+ };
+
+ tpmStorageFromBoot = mkOption {
+ type = types.str;
+ default = "tpm-boot";
+ };
+
+ crypt = mkOption {
+ type = types.str;
+ };
+
+ target = mkOption {
+ type = types.str;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.initrd = let
+ headerPath = "/initrd-boot/${cfg.headerFromBoot}";
+ headerPathEscaped = escapeShellArg headerPath;
+
+ tpmPath = escapeShellArg "/initrd-boot/${cfg.tpmStorageFromBoot}";
+ hardwareKeyPath = "/tpm/unsealed.luks-key";
+ in {
+ preDeviceCommands = ''
+ mkdir -p `dirname ${headerPathEscaped}`
+ touch ${headerPathEscaped}
+ '';
+
+ postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+ # Set the system time from the hardware clock to work around a
+ # bug in qemu-kvm > 1.5.2 (where the VM clock is initialised
+ # to the *boot time* of the host).
+ hwclock -s
+ '';
+
+ #FIXME: Demasiado vulgar
+ preLVMCommands = optionalString (config.local.boot.efi.enable && config.local.boot.efi.removable) ''
+ sleep 2
+ '';
+
+ luks.devices.${cfg.target} = {
+ device = cfg.crypt;
+ header = headerPath;
+ preLVM = false;
+
+ keyFile = mkIf tpmInitrd hardwareKeyPath;
+ fallbackToPassword = tpmInitrd;
+
+ preOpenCommands =
+ ''
+ mount -o ro -t ${bootFs.fsType} ${bootFs.device} /initrd-boot
+ ''
+ + optionalString tpmInitrd ''
+ mkdir /tpm
+ touch ${escapeShellArg hardwareKeyPath}
+
+ unseal_tpm_key() {
+ tpm2 createprimary -Q -C owner -g sha256 -G ecc -c /tpm/prim.ctx || return
+
+ tpm2 loadexternal -Q -C owner -G rsa -u ${tpmPath}/signing-key.pub -c /tpm/signing-key.ctx -n /tpm/signing-key.name || return
+ tpm2 verifysignature -Q -c /tpm/signing-key.ctx -g sha256 -m ${tpmPath}/auth.policy -s ${tpmPath}/auth.sig -t /tpm/verified.ticket -f rsassa || return
+
+ tpm2 startauthsession -Q -S /tpm/session.ctx --policy-session || return
+
+ tpm_resets=`tpm2 readclock | grep reset_count | sed 's/.*: //g'`
+ tpm2 policycountertimer -Q -S /tpm/session.ctx resets="$tpm_resets" || return
+ tpm2 policypcr -Q -S /tpm/session.ctx -l sha256:${pcrList} || return
+ tpm2 policyauthorize -Q -S /tpm/session.ctx -i ${tpmPath}/auth.policy -n /tpm/signing-key.name -t /tpm/verified.ticket || return
+
+ tpm2 load -Q -C /tpm/prim.ctx -u ${tpmPath}/key.pub -r ${tpmPath}/key.priv -c /tpm/key.ctx || return
+ tpm2 unseal -Q -c /tpm/key.ctx -p session:/tpm/session.ctx -o ${escapeShellArg hardwareKeyPath} || return
+
+ tpm2 flushcontext /tpm/session.ctx
+ }
+
+ unseal_tpm_key
+ '';
+
+ postOpenCommands = mkBefore (''
+ umount /initrd-boot
+ ''
+ + optionalString tpmInitrd ''
+ rm -r /tpm
+ '');
+ };
+ };
+
+ local.boot = {
+ stack = {
+ btrfsToplevelMultidrive.toplevel.device = "/dev/mapper/${cfg.target}";
+ luksExt4FscryptImpermanence = {inherit (cfg) target;};
+ };
+
+ tpm.initrd.enable = mkDefault config.local.boot.tpm.enable;
+ };
+ };
+}
diff --git a/sys/boot/efi.nix b/sys/boot/efi.nix
new file mode 100644
index 0000000..71c42c8
--- /dev/null
+++ b/sys/boot/efi.nix
@@ -0,0 +1,49 @@
+{
+ config,
+ lib,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.efi;
+in {
+ options.local.boot.efi = {
+ enable = mkEnableOption "EFI with FAT32 system partition";
+
+ esp = {
+ mountpoint = mkOption {
+ type = types.enum ["/boot" "/boot/efi"];
+ default = "/boot";
+ };
+
+ uuid = mkOption {
+ type = types.strMatching "[0-9A-F]{4}-[0-9A-F]{4}";
+ };
+ };
+
+ removable = mkOption {
+ type = types.bool;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot = {
+ initrd.supportedFilesystems = ["vfat"];
+
+ loader = {
+ efi = {
+ efiSysMountPoint = cfg.esp.mountpoint;
+ canTouchEfiVariables = !cfg.removable;
+ };
+
+ grub.efiInstallAsRemovable = cfg.removable;
+ };
+ };
+
+ fileSystems.${cfg.esp.mountpoint} = {
+ device = "/dev/disk/by-uuid/${cfg.esp.uuid}";
+ fsType = "vfat";
+ options = ["noatime" "umask=027" "sync"];
+ neededForBoot = true;
+ };
+ };
+}
diff --git a/sys/boot/firmware.nix b/sys/boot/firmware.nix
new file mode 100644
index 0000000..b3598a7
--- /dev/null
+++ b/sys/boot/firmware.nix
@@ -0,0 +1,33 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.firmware;
+in {
+ options.local.boot.firmware = {
+ mode = mkOption {
+ type = types.enum ["none" "redistributable" "all"];
+ };
+
+ cpuVendor = mkOption {
+ type = types.enum ["amd" "intel"];
+ };
+ };
+
+ config = mkIf (cfg.mode != "none") {
+ hardware = {
+ cpu = {
+ amd.updateMicrocode = cfg.cpuVendor == "amd";
+ intel.updateMicrocode = cfg.cpuVendor == "intel";
+ };
+
+ enableAllFirmware = cfg.mode == "all";
+ enableRedistributableFirmware = true;
+ };
+
+ services.fwupd.enable = true;
+ };
+}
diff --git a/sys/boot/fscrypt.nix b/sys/boot/fscrypt.nix
new file mode 100644
index 0000000..459e02b
--- /dev/null
+++ b/sys/boot/fscrypt.nix
@@ -0,0 +1,30 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.fscrypt;
+in {
+ options.local.boot.fscrypt = {
+ enable = mkEnableOption "fscrypt support";
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [pkgs.fscrypt-experimental];
+
+ local.boot.impermanence = {
+ directories = [
+ {
+ directory = "/.fscrypt";
+ mode = "u=rwx,g=rx,o=rx";
+ }
+ ];
+
+ files = [
+ "/etc/fscrypt.conf"
+ ];
+ };
+ };
+}
diff --git a/sys/boot/impermanence.nix b/sys/boot/impermanence.nix
new file mode 100644
index 0000000..632094b
--- /dev/null
+++ b/sys/boot/impermanence.nix
@@ -0,0 +1,56 @@
+{
+ config,
+ lib,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.impermanence;
+in {
+ options.local.boot.impermanence = {
+ enable = mkEnableOption "root fs impermanence";
+
+ #TODO: type correcto de files, directories?
+
+ directories = mkOption {
+ type = with lib.types; listOf (either str attrs);
+ default = [];
+ };
+
+ files = mkOption {
+ type = with lib.types; listOf (either str attrs);
+ default = [];
+ };
+ };
+
+ config = mkMerge [
+ {
+ local.boot.impermanence = {
+ directories = [
+ "/etc/lvm"
+ "/var/lib/nixos"
+ "/var/log"
+ ];
+
+ files = [
+ "/etc/machine-id"
+ "/var/lib/logrotate.status"
+ ];
+ };
+ }
+ (mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = (config.fileSystems ? "/persist") && config.fileSystems."/persist".neededForBoot;
+ message = "Impermanence requires /persist to be a neededForBoot mountpoint";
+ }
+ ];
+
+ environment.persistence."/persist" = {
+ hideMounts = true;
+
+ files = cfg.files;
+ directories = cfg.directories;
+ };
+ })
+ ];
+}
diff --git a/sys/boot/namespaced.nix b/sys/boot/namespaced.nix
new file mode 100644
index 0000000..3f95960
--- /dev/null
+++ b/sys/boot/namespaced.nix
@@ -0,0 +1,33 @@
+{
+ config,
+ lib,
+ options,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.namespaced;
+in {
+ options.local.boot.namespaced = {
+ enable = mkEnableOption "system containerization";
+ };
+
+ config = mkIf cfg.enable {
+ boot.isContainer = true;
+
+ local.boot = mkMerge ([
+ {
+ loader = mkForce "none";
+
+ efi.enable = mkForce false;
+ firmware.mode = mkForce "none";
+ secureBoot.enable = mkForce false;
+ impermanence.enable = mkForce false;
+ }
+ ]
+ ++ map
+ (name: {
+ stack.${name}.enable = mkForce false;
+ })
+ (attrNames options.local.boot.stack));
+ };
+}
diff --git a/sys/boot/pxe.nix b/sys/boot/pxe.nix
new file mode 100644
index 0000000..e25ba9d
--- /dev/null
+++ b/sys/boot/pxe.nix
@@ -0,0 +1,187 @@
+{
+ config,
+ lib,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.pxe;
+in {
+ options.local.boot.pxe = {
+ enable = mkEnableOption "PXE netboot";
+
+ setupMode = mkOption {
+ type = types.bool;
+ default = false;
+ };
+
+ initrdInterface = mkOption {
+ type = types.str;
+ };
+
+ linkLocal6 = mkOption {
+ type = types.str;
+ };
+
+ networkDriver = mkOption {
+ type = types.str;
+ };
+
+ mac = mkOption {
+ type = types.str;
+ };
+
+ panicTimeout = mkOption {
+ type = types.nullOr types.ints.u16;
+ default = 30;
+ };
+ };
+
+ # Based on nixpkgs/nixos/modules/installer/netboot/netboot.nix
+ config = mkIf cfg.enable {
+ boot = {
+ initrd = {
+ kernelModules = [cfg.networkDriver];
+
+ network = {
+ enable = true;
+ flushBeforeStage2 = true;
+ ssh = {
+ enable = true;
+ ignoreEmptyHostKeys = true;
+ };
+ };
+
+ preFailCommands = mkIf (!cfg.setupMode) ''
+ echo "init stage1 failed in unattended mode, rebooting"
+ reboot -f
+ '';
+
+ postResumeCommands = ''
+ store_img_dir="/nix/.ro-store-img"
+ store_img="$store_img_dir/nix-store.squashfs"
+
+ mkdir -p "$store_img_dir"
+ mount -t tmpfs -o mode=0700,nr_inodes=2,huge=within_size,nodev,nosuid tmpfs "$store_img_dir"
+
+ ip link set up ${cfg.initrdInterface}
+ ip addr add ${cfg.linkLocal6}/64 dev ${cfg.initrdInterface}
+
+ recv_init_timeout=120
+ echo -n "Waiting up to ''${recv_init_timeout}s for $store_img"
+
+ t="$recv_init_timeout"
+ while true; do
+ echo -n . | nc -u -w1 -s ${cfg.linkLocal6}%${cfg.initrdInterface} fe80::b007:b007%${cfg.initrdInterface} 1792 >/dev/null 2>&1 &
+
+ if [ -e "$store_img.tmp" ]; then
+ kill -x nc
+ break
+ elif [ "$t" -le 0 ]; then
+ echo "timed out"
+ fail
+ fi
+
+ # sleep builtin de ash retorna cuando nc termina (lo cual puede pasar muchas veces debido a IPv6 DAD)
+ /bin/sleep 1
+ echo -n .
+ t=$((t - 1))
+ done
+
+ recv_poll=60
+ recv_timeout=1800
+
+ t=0
+ last_mtime=""
+ poll_time="$recv_poll"
+ while true; do
+ if [ -e "$store_img" ]; then
+ break
+ elif [ "$t" -ge "$recv_timeout" ]; then
+ echo "Store image upload timed out"
+ fail
+ fi
+
+ sleep 1
+ t=$((t + 1))
+ poll_time=$((poll_time - 1))
+
+ if [ "$poll_time" -le 0 ]; then
+ current_size=$(stat -c '%s' "$store_img.tmp" || echo -)
+ current_mtime=$(stat -c '%y' "$store_img.tmp" || echo final)
+ echo "Store upload: $current_mtime: $current_size bytes"
+
+ if [ "$current_mtime" = "$last_mtime" ]; then
+ echo "Store image upload stalled, image not modified in ''${recv_poll}s"
+ fail
+ fi
+
+ last_mtime="$current_mtime"
+ poll_time="$recv_poll"
+ fi
+ done
+
+ sleep 1
+ kill -x sshd
+
+ chmod 0500 "$store_img_dir"
+ chmod 0400 "$store_img"
+ chattr +i "$store_img"
+ mount -o remount,ro "$store_img_dir"
+
+ store_size=$(stat -c '%s' "$store_img")
+ echo "Store image upload complete, final size is $store_size bytes"
+
+ ln -sf "$store_img" /dev/nix-store
+ '';
+
+ postMountCommands = ''
+ mkdir -p "/mnt-root$store_img_dir"
+ mount --move "$store_img_dir" "/mnt-root$store_img_dir"
+ '';
+ };
+
+ kernelParams =
+ optional (cfg.panicTimeout != null) "panic=${toString cfg.panicTimeout}"
+ ++ optional cfg.setupMode "boot.shell_on_fail";
+ };
+
+ fileSystems = {
+ "/" = {
+ fsType = "tmpfs";
+ neededForBoot = true;
+
+ options = ["mode=0755"];
+ };
+
+ "/nix/store" = {
+ fsType = "overlay";
+ neededForBoot = true;
+
+ overlay = {
+ lowerdir = ["/nix/.ro-store"];
+ upperdir = "/nix/.rw-store/store";
+ workdir = "/nix/.rw-store/work";
+ };
+ };
+
+ "/nix/.ro-store" = {
+ fsType = "squashfs";
+ neededForBoot = true;
+
+ device = "/dev/nix-store";
+ options = ["loop" "threads=multi"];
+ };
+
+ "/nix/.rw-store" = {
+ fsType = "tmpfs";
+ neededForBoot = true;
+
+ options = ["mode=0755"];
+ };
+ };
+
+ local = {
+ boot.loader = "out-of-band";
+ };
+ };
+}
diff --git a/sys/boot/secure-boot.nix b/sys/boot/secure-boot.nix
new file mode 100644
index 0000000..b13ab7c
--- /dev/null
+++ b/sys/boot/secure-boot.nix
@@ -0,0 +1,51 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.secureBoot;
+
+ pkiBundle =
+ if cfg.legacyPath
+ then "/etc/secureboot"
+ else "/var/lib/sbctl";
+in {
+ options.local.boot.secureBoot = {
+ enable = mkEnableOption "secure boot";
+
+ legacyPath = mkOption {
+ type = types.bool;
+ default = false;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = config.local.boot.efi.enable;
+ message = "secure boot requires EFI";
+ }
+ {
+ assertion = config.local.boot.loader == "systemd-boot";
+ message = "lanzaboote requires systemd-boot";
+ }
+ ];
+
+ boot = {
+ loader.systemd-boot.enable = mkForce false;
+
+ lanzaboote = {
+ enable = true;
+ inherit pkiBundle;
+ };
+ };
+
+ environment.systemPackages = [
+ pkgs.sbctl
+ ];
+
+ local.boot.impermanence.directories = [pkiBundle];
+ };
+}
diff --git a/sys/boot/stack/btrfs-toplevel-multidrive.nix b/sys/boot/stack/btrfs-toplevel-multidrive.nix
new file mode 100644
index 0000000..52db865
--- /dev/null
+++ b/sys/boot/stack/btrfs-toplevel-multidrive.nix
@@ -0,0 +1,99 @@
+{
+ config,
+ lib,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.stack.btrfsToplevelMultidrive;
+in {
+ options.local.boot.stack.btrfsToplevelMultidrive = {
+ enable = mkEnableOption "filesystem stack: persistent btrfs toplevel with optional hdd drive";
+
+ toplevel = {
+ device = mkOption {
+ type = types.str;
+ };
+
+ ssd = mkOption {
+ type = types.bool;
+ };
+
+ snapshot = mkOption {
+ type = types.bool;
+ default = false;
+ };
+
+ root = mkOption {
+ type = types.str;
+ };
+
+ pivot = mkOption {
+ type = types.str;
+ default = "/";
+ };
+ };
+
+ secondary = {
+ device = mkOption {
+ type = types.str;
+ };
+
+ ssd = mkOption {
+ type = types.bool;
+ };
+
+ snapshot = mkOption {
+ type = types.bool;
+ default = false;
+ };
+
+ home = mkOption {
+ type = types.str;
+ };
+
+ pivot = mkOption {
+ type = types.str;
+ default = "/";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ local.btrfs = {
+ mounts = {
+ "/" = {
+ inherit (cfg.toplevel) device ssd;
+ subvol = cfg.toplevel.root;
+ };
+
+ "/toplevel" = {
+ inherit (cfg.toplevel) device ssd;
+ subvol = cfg.toplevel.pivot;
+ };
+
+ #FIXME: Este nombre es legacy
+ "/hdd" = {
+ inherit (cfg.secondary) device ssd;
+ subvol = cfg.secondary.pivot;
+ };
+
+ "/home" = {
+ inherit (cfg.secondary) device ssd;
+ subvol = cfg.secondary.home;
+ };
+ };
+
+ snapper =
+ optionalAttrs cfg.toplevel.snapshot
+ {
+ root = "/";
+ }
+ // optionalAttrs cfg.secondary.snapshot {
+ home = "/home";
+ };
+ };
+
+ # Asegura que /hdd sea descifrado antes de intentar montar /home
+ fileSystems."/home".depends = ["/hdd"];
+ };
+}
diff --git a/sys/boot/stack/default.nix b/sys/boot/stack/default.nix
new file mode 100644
index 0000000..ff211e6
--- /dev/null
+++ b/sys/boot/stack/default.nix
@@ -0,0 +1,6 @@
+{
+ imports = [
+ ./btrfs-toplevel-multidrive.nix
+ ./luks-ext4-fscrypt-impermanence.nix
+ ];
+}
diff --git a/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix
new file mode 100644
index 0000000..81feb60
--- /dev/null
+++ b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix
@@ -0,0 +1,98 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.stack.luksExt4FscryptImpermanence;
+in {
+ options.local.boot.stack.luksExt4FscryptImpermanence = {
+ enable = mkEnableOption "filesystem stack: whatever LUKS approach+ext4+impermanence with per-boot keys";
+
+ target = mkOption {
+ type = types.str;
+ };
+ };
+
+ # - boot device
+ # - some unknown fs, probably vfat
+ # - detached luks header file
+ #
+ # - toplevel device
+ # - headerless luks
+ # - /toplevel (ext4)
+ # - /toplevel/nix
+ # - /toplevel/persist
+ # - /toplevel/boot-archive.pub
+ # - /toplevel/boot-keys
+ # - /toplevel/boot-keys/2000-01-01T00:00:00-06:00.key.crypt (encrypted for /toplevel/boot-archive.pub)
+ # - /toplevel/boot-keys/...
+ # - /toplevel/boot-keys/last.key.crypt -> 2000-01-01T00:00:00-06:00.key.crypt
+ # - /toplevel/boots
+ # - /toplevel/boots/2000-01-01T00:00:00-06:00 (raw protector in last.key.crypt)
+ # - /toplevel/boots/...
+ # - /toplevel/boots/last -> 2000-01-01T00:00:00-06:00 (mounted as /)
+ config = mkIf cfg.enable {
+ boot.initrd.luks.devices.${cfg.target}.postOpenCommands = let
+ fscryptctl = "${pkgs.fscryptctl}/bin/fscryptctl";
+ in ''
+ # FIXME: posiblemente algunos --make-* son innecesarios a partir de aquĆ­
+ mkdir -p /mnt-root /mnt-toplevel
+ mount -o noatime /dev/mapper/${cfg.target} /mnt-toplevel
+ mount --make-private /mnt-toplevel
+
+ boot_stamp="$(date -Is)"
+ root_from_toplevel="/mnt-toplevel/boots/$boot_stamp"
+
+ mkdir -p "$root_from_toplevel" /mnt-toplevel/boot-keys
+ chmod 700 /mnt-toplevel/boot-keys
+
+ head -c64 /dev/urandom >/boot-key
+ key_id=$(${fscryptctl} add_key /mnt-toplevel </boot-key)
+ ${fscryptctl} set_policy "$key_id" "$root_from_toplevel"
+ (umask 077; test -f /mnt-toplevel/boot-archive.pub && \
+ ${pkgs.openssl}/bin/openssl pkeyutl -encrypt \
+ -in /boot-key -pubin -inkey /mnt-toplevel/boot-archive.pub \
+ -out "/mnt-toplevel/boot-keys/$boot_stamp.key.crypt")
+ rm -f /boot-key
+
+ ln -Tsf "$boot_stamp" /mnt-toplevel/boots/last
+ ln -Tsf "$boot_stamp.key.crypt" /mnt-toplevel/boot-keys/last.key.crypt
+
+ mount --bind "$root_from_toplevel" /mnt-root
+ mount --make-shared /mnt-root
+
+ # mount --move es mala idea, ya que "moving a mount residing under a
+ # shared mount is unsupported"
+ mkdir -p /mnt-root/toplevel
+ mount --bind /mnt-toplevel /mnt-root/toplevel
+ mount --make-private /mnt-root/toplevel
+ umount /mnt-toplevel
+ '';
+
+ fileSystems = {
+ "/" = {
+ device = "none";
+ fsType = "ext4";
+ options = ["remount"];
+ };
+
+ "/nix" = {
+ device = "/persist/nix";
+ options = ["bind"];
+ };
+
+ "/persist" = {
+ device = "/toplevel/persist";
+ options = ["bind"];
+ neededForBoot = true;
+ };
+ };
+
+ local.boot = {
+ fscrypt.enable = true;
+ impermanence.enable = true;
+ };
+ };
+}
diff --git a/sys/boot/tpm.nix b/sys/boot/tpm.nix
new file mode 100644
index 0000000..da6f73a
--- /dev/null
+++ b/sys/boot/tpm.nix
@@ -0,0 +1,128 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.tpm;
+
+ pcrList = concatStringsSep "," (map toString cfg.initrd.pcrs);
+
+ # Crear signing-key con:
+ # $ openssl genrsa -out ~/vtmp/signing-key.priv 2048
+ # $ openssl rsa -in ~/vtmp/signing-key.priv -out ~/vtmp/signing-key.pub -pubout
+ # Y copiar signing-key.pub a /boot/tpm-boot. Guardar signing-key.priv en lugar seguro.
+ #
+ # Crear llave con:
+ # $ tpm2_loadexternal -G rsa -C owner -u signing-key.pub -c signing-key.ctx -n signing-key.name
+ # $ tpm2_startauthsession -S session.ctx
+ # $ tpm2_policyauthorize -S session.ctx -L require-signed.policy -n signing-key.name
+ # $ tpm2_flushcontext session.ctx
+ # $ tpm2_createprimary -C owner -g sha256 -G ecc -c prim.ctx
+ # $ head -c128 /dev/urandom | tpm2_create -C prim.ctx -u key.pub -r key.priv -c key.ctx -L require-signed.policy -i-
+ # Y mover key.priv/key.pub a /boot/tpm-boot
+ #
+ # Usage: tpm2-grant-next-boot < signing-key.priv
+ # Genera auth.policy y auth.sig
+ tpm2-grant-next-boot = pkgs.writeShellApplication {
+ name = "tpm2-grant-next-boot";
+
+ runtimeInputs = [
+ pkgs.jq
+ pkgs.openssl
+ pkgs.sbctl
+ pkgs.tpm2-tools
+ ];
+
+ text = ''
+ if [ -z "''${YES_I_DO_WANT_TO_SIGN_WITH_SECURE_BOOT_DISABLED:=}" ] && [ "$(sbctl status --json | jq .secure_boot)" != "truee" ]; then
+ echo "$0: bad Secure Boot state, check the output of \`sbctl status\`" >&2
+ echo "$0: signing a TPM PCR policy with Secure Boot disabled is dangerous" >&2
+ echo "$0: set 'YES_I_DO_WANT_TO_SIGN_WITH_SECURE_BOOT_DISABLED' to skip this check" >&2
+ exit 1
+ fi
+
+ ctx_dir="$(mktemp -d)"
+ trap 'rm -rf -- "$ctx_dir"' EXIT
+
+ tpm2_createprimary -Q -C owner -g sha256 -G ecc -c "$ctx_dir/prim.ctx"
+
+ tpm2_startauthsession -Q -S "$ctx_dir/session.ctx"
+ tpm_resets=$(tpm2_readclock | grep reset_count | sed 's/.*: //')
+ tpm2_policycountertimer -Q -S "$ctx_dir/session.ctx" resets="$((tpm_resets+1))"
+ tpm2_policypcr -Q -S "$ctx_dir/session.ctx" -L auth.policy -l sha256:${pcrList}
+ tpm2_flushcontext -Q "$ctx_dir/session.ctx"
+
+ openssl dgst -sha256 -sign /dev/stdin -out auth.sig auth.policy
+ '';
+ };
+in {
+ options.local.boot.tpm = {
+ enable = mkEnableOption "Trusted Platform Module 2.0";
+
+ driver = mkOption {
+ type = types.enum ["tis" "crb"];
+ };
+
+ initrd = {
+ enable = mkEnableOption "TPM2 in initrd";
+
+ pcrs = mkOption {
+ type = with types; listOf (ints.between 0 23);
+
+ # From 'systemd-analyze pcrs'
+ default = [
+ 0 # platform-code
+ 1 # platform-config
+ 2 # external-code
+ 3 # external-config
+ 4 # boot-loader-code
+ 5 # boot-loader-config
+ 7 # secure-boot-policy
+ 9 # kernel-initrd
+ 11 # kernel-boot
+ 12 # kernel-config
+ 13 # sysexts
+ 14 # shim-policy
+ ];
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = cfg.initrd.enable -> config.local.boot.efi.enable;
+ message = "TPM2 in initrd requires EFI";
+ }
+ {
+ assertion = cfg.initrd.enable -> cfg.enable;
+ message = "TPM2 in initrd requires TPM2";
+ }
+ ];
+
+ boot.initrd = mkIf cfg.initrd.enable {
+ extraUtilsCommands = ''
+ copy_bin_and_libs ${pkgs.tpm2-tools}/bin/.tpm2-wrapped
+ mv $out/bin/{.tpm2-wrapped,tpm2}
+ cp {${pkgs.tpm2-tss},$out}/lib/libtss2-tcti-device.so.0
+ '';
+
+ kernelModules = [
+ "tpm_${cfg.driver}"
+ ];
+ };
+
+ environment.systemPackages = optionals cfg.initrd.enable [
+ tpm2-grant-next-boot
+ ];
+
+ security.tpm2 = {
+ enable = true;
+
+ pkcs11.enable = true;
+ tctiEnvironment.enable = true;
+ };
+ };
+}