diff --git a/README.md b/README.md index 90ffc8f..ce0cf63 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ to take something up: - Experimenting with `fwupd` / Green Checkmark in GNOME Device Security - https://github.com/fwupd/fwupd/issues/5284 - Experimenting with TPM2 measurements +- Support bootspec with no initrd - Studying the initrd secrets feature in NixOS wrt SecureBoot & TPM2 - ... diff --git a/flake.lock b/flake.lock index c270a15..ad147dc 100644 --- a/flake.lock +++ b/flake.lock @@ -14,11 +14,11 @@ ] }, "locked": { - "lastModified": 1669605882, - "narHash": "sha256-TiQtL5sUI5rp28S63v+VX25qNjcrc8Xeu+shf3g7Tj4=", + "lastModified": 1670900067, + "narHash": "sha256-VXVa+KBfukhmWizaiGiHRVX/fuk66P8dgSFfkVN4/MY=", "owner": "ipetkov", "repo": "crane", - "rev": "24591d5f8cc979f7b243b88a2d39da09976970ad", + "rev": "59b31b41a589c0a65e4a1f86b0e5eac68081468b", "type": "github" }, "original": { @@ -30,11 +30,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1650374568, - "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "lastModified": 1668681692, + "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", "owner": "edolstra", "repo": "flake-compat", - "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "rev": "009399224d5e398d03b22badca40a37ac85412a1", "type": "github" }, "original": { @@ -60,32 +60,32 @@ }, "nixpkgs": { "locked": { - "lastModified": 1669535121, - "narHash": "sha256-koZLM7oWVGrjyHnYDo7/w5qlmUn9UZUKSFNfmIjueE8=", + "lastModified": 1671755972, + "narHash": "sha256-X977apvpqBqqRf2XBNorfunZmQNn3cQYGEnQE4L90Fo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b45ec953794bb07922f0468152ad1ebaf8a084b3", + "rev": "e8ee153b1717dca9c6aa38d5cf198329480d5b41", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixpkgs-unstable", + "ref": "nixos-unstable-small", "repo": "nixpkgs", "type": "github" } }, "nixpkgs-test": { "locked": { - "lastModified": 1669495095, - "narHash": "sha256-wasu8T7ac+LVm4aXuAYkH76Rr98VW0Cp9oZvNPuKiiU=", + "lastModified": 1671812130, + "narHash": "sha256-GALBK+qB9rhnB+lVnxdgtMoXCySXughZZ3+qGO1Ke/k=", "owner": "RaitoBezarius", "repo": "nixpkgs", - "rev": "8bbe1bb1f7352dd9c2e448e8d68846a66d0c2aca", + "rev": "e51bf8cc8e2c75192e930ad83ed272938729e7be", "type": "github" }, "original": { "owner": "RaitoBezarius", - "ref": "experimental-secureboot", + "ref": "simplified-qemu-boot-disks", "repo": "nixpkgs", "type": "github" } @@ -109,11 +109,11 @@ ] }, "locked": { - "lastModified": 1669602829, - "narHash": "sha256-I3LBvBiVui4Rf0iQvTqUIgBovaLDzpOzsoNEzCsDowg=", + "lastModified": 1671243596, + "narHash": "sha256-vQ1q6uwx2gKsHbQVhkq17nT8HwUmRbIG8cJVFafNb5s=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "b9da8e68a08707115be750c0cf7ade33f49d8ec4", + "rev": "905db21103d646ddc1eb81920e05180e6e2b6734", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e6be2f7..9d74262 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,8 @@ description = "Lanzaboot Secure Boot Madness"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small"; + nixpkgs-test.url = "github:RaitoBezarius/nixpkgs/simplified-qemu-boot-disks"; crane = { url = "github:ipetkov/crane"; @@ -11,7 +12,6 @@ inputs.flake-utils.follows = "flake-utils"; }; - nixpkgs-test.url = "github:RaitoBezarius/nixpkgs/experimental-secureboot"; rust-overlay = { url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; @@ -21,7 +21,7 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, crane, nixpkgs-test, rust-overlay, ... }: + outputs = { self, nixpkgs, nixpkgs-test, crane, rust-overlay, ... }: let pkgs = import nixpkgs { system = "x86_64-linux"; @@ -140,7 +140,6 @@ }; boot.loader.efi = { - enable = true; canTouchEfiVariables = true; }; boot.lanzaboote = { @@ -166,7 +165,8 @@ return f'/boot/EFI/nixos/{store_dir}-{filename}.efi' machine.start() - bootspec = json.loads(machine.succeed("cat /run/current-system/bootspec/boot.v1.json")) + bootspec = json.loads(machine.succeed("cat /run/current-system/boot.json")).get('v1') + assert bootspec is not None, "Unsupported bootspec version!" src_path = ${path.src} dst_path = ${path.dst} machine.succeed(f"cp -rf {src_path} {dst_path}") @@ -263,7 +263,7 @@ testScript = '' machine.start() print(machine.succeed("ls -lah /boot/EFI/Linux")) - print(machine.succeed("cat /run/current-system/bootspec/boot.v1.json")) + print(machine.succeed("cat /run/current-system/boot.json")) # TODO: make it more reliable to find this filename, i.e. read it from somewhere? machine.succeed("bootctl set-default nixos-generation-1-specialisation-variant.efi") machine.succeed("sync") diff --git a/nix/lanzaboote.nix b/nix/lanzaboote.nix index 57605e6..d223b19 100644 --- a/nix/lanzaboote.nix +++ b/nix/lanzaboote.nix @@ -32,9 +32,11 @@ in }; config = mkIf cfg.enable { - # bootspec is putting at false - # until we fix this upstream, we will mkForce it. - boot.loader.supportsInitrdSecrets = mkForce true; + boot.bootspec = { + enable = true; + extensions."lanzaboote"."osRelease" = config.environment.etc."os-release".source; + }; + boot.loader.supportsInitrdSecrets = true; boot.loader.external = { enable = true; installHook = pkgs.writeShellScript "bootinstall" '' diff --git a/rust/lanzatool/Cargo.lock b/rust/lanzatool/Cargo.lock index 3ef33e6..cb5fc3a 100644 --- a/rust/lanzatool/Cargo.lock +++ b/rust/lanzatool/Cargo.lock @@ -69,7 +69,7 @@ dependencies = [ [[package]] name = "bootspec" version = "0.1.0" -source = "git+https://github.com/RaitoBezarius/bootspec-1?branch=secureboot-needs#a5d327ceb25da6ae4147fbf0b15088f44214a91a" +source = "git+https://github.com/RaitoBezarius/bootspec-1?branch=secureboot-needs#3d5acac24d353c5e9998067abb1b458b996cba81" dependencies = [ "serde", "serde_json", diff --git a/rust/lanzatool/bootspec.json b/rust/lanzatool/bootspec.json index 5e92499..010f446 100644 --- a/rust/lanzatool/bootspec.json +++ b/rust/lanzatool/bootspec.json @@ -17,7 +17,7 @@ "toplevel": "/run/current-system", "specialisation": {}, "extensions": { - "org.lanzaboote.osRelease": "/etc/os-release" + "lanzaboote": { "osRelease": "/etc/os-release" } } } } diff --git a/rust/lanzatool/src/esp.rs b/rust/lanzatool/src/esp.rs index 5bd340e..6affdf9 100644 --- a/rust/lanzatool/src/esp.rs +++ b/rust/lanzatool/src/esp.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use std::path::{Path, PathBuf}; -use crate::generation::OSGeneration; +use crate::generation::Generation; pub struct EspPaths { pub esp: PathBuf, @@ -17,20 +17,26 @@ pub struct EspPaths { } impl EspPaths { - pub fn new(esp: impl AsRef, generation: &OSGeneration) -> Result { + pub fn new(esp: impl AsRef, generation: &Generation) -> Result { let esp = esp.as_ref(); let esp_nixos = esp.join("EFI/nixos"); let esp_linux = esp.join("EFI/Linux"); let esp_systemd = esp.join("EFI/systemd"); let esp_efi_fallback_dir = esp.join("EFI/BOOT"); - let bootspec = &generation.bootspec; + let bootspec = &generation.spec.bootspec; Ok(Self { esp: esp.to_path_buf(), nixos: esp_nixos.clone(), kernel: esp_nixos.join(nixos_path(&bootspec.kernel, "bzImage")?), - initrd: esp_nixos.join(nixos_path(&bootspec.initrd, "initrd")?), + initrd: esp_nixos.join(nixos_path( + bootspec + .initrd + .as_ref() + .context("Lanzaboote does not support missing initrd yet")?, + "initrd", + )?), linux: esp_linux.clone(), lanzaboote_image: esp_linux.join(generation_path(generation)), efi_fallback_dir: esp_efi_fallback_dir.clone(), @@ -66,7 +72,7 @@ fn nixos_path(path: impl AsRef, name: &str) -> Result { Ok(PathBuf::from(nixos_filename)) } -fn generation_path(generation: &OSGeneration) -> PathBuf { +fn generation_path(generation: &Generation) -> PathBuf { if let Some(specialisation_name) = generation.is_specialized() { PathBuf::from(format!( "nixos-generation-{}-specialisation-{}.efi", diff --git a/rust/lanzatool/src/generation.rs b/rust/lanzatool/src/generation.rs index 10583f9..cb10a2d 100644 --- a/rust/lanzatool/src/generation.rs +++ b/rust/lanzatool/src/generation.rs @@ -1,63 +1,79 @@ +use serde::de::IntoDeserializer; use serde::{Deserialize, Serialize}; use std::fmt; use std::fs; use std::path::{Path, PathBuf}; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; +use bootspec::generation::Generation as BootspecGeneration; use bootspec::BootJson; use bootspec::SpecialisationName; -use bootspec::generation::Generation; -// TODO: actually, I'm not sure it's a good thing to have Default -// we should maybe have TryDefault? -// discuss this with upstream. -#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct SecureBootExtension { - #[serde(rename="org.lanzaboote.osRelease")] - pub os_release: PathBuf + #[serde(rename = "osRelease")] + pub os_release: PathBuf, } -pub type ExtendedBootJson = BootJson; +#[derive(Debug, Clone)] +pub struct ExtendedBootJson { + pub bootspec: BootJson, + pub extensions: SecureBootExtension, +} #[derive(Debug)] -pub struct OSGeneration { - /// Top-level nixpkgs version +pub struct Generation { + /// Profile symlink index version: u64, /// Top-level specialisation name specialisation_name: Option, - /// Top-level bootspec document - pub bootspec: ExtendedBootJson, + /// Top-level extended boot specification + pub spec: ExtendedBootJson, } -fn into_boot_json(generation: Generation) -> Result { - Ok(match generation { - Generation::V1(json) => json, - _ => panic!("Failed") - }) -} +impl Generation { + fn extract_extensions(bootspec: &BootJson) -> Result { + Ok(Deserialize::deserialize( + bootspec.extensions.get("lanzaboote") + .context("Failed to extract Lanzaboote-specific extension from Bootspec, missing lanzaboote field in `extensions`")? + .clone() + .into_deserializer() + )?) + } -impl OSGeneration { pub fn from_toplevel(toplevel: impl AsRef) -> Result { - let bootspec_path = toplevel.as_ref().join("bootspec/boot.json"); - let generation: Generation = serde_json::from_slice( + let bootspec_path = toplevel.as_ref().join("boot.json"); + let generation: BootspecGeneration = serde_json::from_slice( &fs::read(bootspec_path).context("Failed to read bootspec file")?, ) .context("Failed to parse bootspec json")?; + let bootspec: BootJson = generation + .try_into() + .map_err(|err: &'static str| anyhow!(err))?; + + let extensions = Self::extract_extensions(&bootspec)?; + Ok(Self { version: parse_version(toplevel)?, specialisation_name: None, - bootspec: into_boot_json(generation)?, + spec: ExtendedBootJson { + bootspec, + extensions, + }, }) } - pub fn specialise(&self, name: &SpecialisationName, bootspec: &ExtendedBootJson) -> Self { - Self { + pub fn specialise(&self, name: &SpecialisationName, bootspec: &BootJson) -> Result { + Ok(Self { version: self.version, specialisation_name: Some(name.clone()), - bootspec: bootspec.clone() - } + spec: ExtendedBootJson { + bootspec: bootspec.clone(), + extensions: Self::extract_extensions(&bootspec)?, + }, + }) } pub fn is_specialized(&self) -> Option { @@ -65,7 +81,7 @@ impl OSGeneration { } } -impl fmt::Display for OSGeneration { +impl fmt::Display for Generation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.version) } diff --git a/rust/lanzatool/src/install.rs b/rust/lanzatool/src/install.rs index 6a5b204..fcd99ba 100644 --- a/rust/lanzatool/src/install.rs +++ b/rust/lanzatool/src/install.rs @@ -8,7 +8,7 @@ use nix::unistd::sync; use tempfile::tempdir; use crate::esp::EspPaths; -use crate::generation::OSGeneration; +use crate::generation::Generation; use crate::pe; use crate::signature::KeyPair; @@ -36,7 +36,7 @@ impl Installer { pub fn install(&self) -> Result<()> { for toplevel in &self.generations { - let generation = OSGeneration::from_toplevel(toplevel).with_context(|| { + let generation = Generation::from_toplevel(toplevel).with_context(|| { format!("Failed to build generation from toplevel: {toplevel:?}") })?; @@ -45,8 +45,8 @@ impl Installer { self.install_generation(&generation) .context("Failed to install generation")?; - for (name, bootspec) in &generation.bootspec.specialisation { - let specialised_generation = generation.specialise(name, bootspec); + for (name, bootspec) in &generation.spec.bootspec.specialisation { + let specialised_generation = generation.specialise(name, bootspec)?; println!("Installing specialisation: {name} of generation: {generation}"); @@ -58,8 +58,9 @@ impl Installer { Ok(()) } - fn install_generation(&self, generation: &OSGeneration) -> Result<()> { - let bootspec = &generation.bootspec; + fn install_generation(&self, generation: &Generation) -> Result<()> { + let bootspec = &generation.spec.bootspec; + let secureboot_extensions = &generation.spec.extensions; let esp_paths = EspPaths::new(&self.esp, generation)?; @@ -77,13 +78,20 @@ impl Installer { println!("Appending secrets to initrd..."); let initrd_location = secure_temp_dir.path().join("initrd"); - copy(&bootspec.initrd, &initrd_location)?; + copy( + bootspec + .initrd + .as_ref() + .context("Lanzaboote does not support missing initrd yet")?, + &initrd_location, + )?; if let Some(initrd_secrets_script) = &bootspec.initrd_secrets { append_initrd_secrets(initrd_secrets_script, &initrd_location)?; } let systemd_boot = bootspec - .toplevel.0 + .toplevel + .0 .join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi"); [ @@ -102,7 +110,7 @@ impl Installer { let lanzaboote_image = pe::lanzaboote_image( &secure_temp_dir, &self.lanzaboote_stub, - &bootspec.extensions.os_release, + &secureboot_extensions.os_release, &kernel_cmdline, &esp_paths.kernel, &esp_paths.initrd,