Merge pull request #16 from blitz/lanzatool-bootspec-from-generation
Lanzatool read bootspec for each generation
This commit is contained in:
		
						commit
						f080c010e9
					
				
							
								
								
									
										18
									
								
								flake.nix
								
								
								
								
							
							
						
						
									
										18
									
								
								flake.nix
								
								
								
								
							|  | @ -140,8 +140,7 @@ | |||
|             import json | ||||
|             import os.path | ||||
|             bootspec = None | ||||
|             def extract_bspec_attr(attr): | ||||
|                 return bootspec.get(attr) | ||||
| 
 | ||||
|             def convert_to_esp(store_file_path): | ||||
|                 store_dir = os.path.basename(os.path.dirname(store_file_path)) | ||||
|                 filename = os.path.basename(store_file_path) | ||||
|  | @ -192,8 +191,8 @@ | |||
|               }; | ||||
| 
 | ||||
|               boot.initrd.preDeviceCommands = '' | ||||
|               grep "this is a very secure secret" /etc/iamasecret | ||||
|             ''; | ||||
|                 grep "this is a very secure secret" /etc/iamasecret | ||||
|               ''; | ||||
|             }; | ||||
|             testScript = '' | ||||
|             machine.start() | ||||
|  | @ -203,18 +202,17 @@ | |||
|           is-initrd-secured = mkUnsignedTest { | ||||
|             name = "unsigned-initrd-do-not-boot-under-secureboot"; | ||||
|             path = { | ||||
|               src = "extract_bspec_attr('initrd')"; | ||||
|               dst = "convert_to_esp(extract_bspec_attr('initrd'))"; | ||||
|               src = "bootspec.get('initrd')"; | ||||
|               dst = "convert_to_esp(bootspec.get('initrd'))"; | ||||
|             }; | ||||
|           }; | ||||
|           is-kernel-secured = mkUnsignedTest { | ||||
|             name = "unsigned-kernel-do-not-boot-under-secureboot"; | ||||
|             path = { | ||||
|               src = "extract_bspec_attr('kernel')"; | ||||
|               dst = "convert_to_esp(extract_bspec_attr('kernel'))"; | ||||
|               src = "bootspec.get('kernel')"; | ||||
|               dst = "convert_to_esp(bootspec.get('kernel'))"; | ||||
|             }; | ||||
|           }; | ||||
| 
 | ||||
|         }; | ||||
|       }; | ||||
|     }; | ||||
| } | ||||
|  |  | |||
|  | @ -38,16 +38,20 @@ in | |||
|     boot.loader.supportsInitrdSecrets = mkForce true; | ||||
|     boot.loader.external = { | ||||
|       enable = true; | ||||
|       passBootspec = true; | ||||
|       installHook = "${pkgs.writeShellScriptBin "bootinstall" '' | ||||
|           ${optionalString cfg.enrollKeys '' | ||||
|             mkdir -p /tmp/pki | ||||
|             cp -r ${cfg.pkiBundle}/* /tmp/pki | ||||
|             ${sbctlWithPki}/bin/sbctl enroll-keys --yes-this-might-brick-my-machine | ||||
|           ''} | ||||
|         ${cfg.package}/bin/lanzatool install --pki-bundle ${cfg.pkiBundle} --public-key ${cfg.publicKeyFile} --private-key ${cfg.privateKeyFile} "$@" /nix/var/nix/profiles/system-*-link | ||||
|       ''}/bin/bootinstall"; | ||||
|       # ${cfg.package}/bin/lanzatool install ${optionalString cfg.enrollKeys "--auto-enroll"} --pki-bundle ${cfg.pkiBundle} | ||||
|       installHook = pkgs.writeShellScript "bootinstall" '' | ||||
|         ${optionalString cfg.enrollKeys '' | ||||
|           mkdir -p /tmp/pki | ||||
|           cp -r ${cfg.pkiBundle}/* /tmp/pki | ||||
|           ${sbctlWithPki}/bin/sbctl enroll-keys --yes-this-might-brick-my-machine | ||||
|         ''} | ||||
|    | ||||
|         ${cfg.package}/bin/lanzatool install \ | ||||
|           --pki-bundle ${cfg.pkiBundle} \ | ||||
|           --public-key ${cfg.publicKeyFile} \ | ||||
|           --private-key ${cfg.privateKeyFile} \ | ||||
|           ${config.boot.loader.efi.efiSysMountPoint} \ | ||||
|           /nix/var/nix/profiles/system-*-link | ||||
|       ''; | ||||
|     }; | ||||
|   }; | ||||
| } | ||||
|  |  | |||
|  | @ -15,9 +15,6 @@ | |||
|   "label": "LanzaOS", | ||||
|   "toplevel": "/run/current-system", | ||||
|   "extension": { | ||||
|     "esp": "esp", | ||||
|     "systemd": "/run/current-system/systemd", | ||||
|     "bootctl": "/run/current-system/sw/bin/bootctl", | ||||
|     "osRelease": "/etc/os-release" | ||||
|   } | ||||
| } | ||||
|  | @ -25,8 +25,5 @@ pub struct Bootspec { | |||
| #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct Extension { | ||||
|     pub esp: String, | ||||
|     pub bootctl: PathBuf, | ||||
|     pub os_release: PathBuf, | ||||
|     pub systemd: PathBuf, | ||||
| } | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ struct InstallCommand { | |||
|     #[arg(long, default_value = "false")] | ||||
|     auto_enroll: bool, | ||||
| 
 | ||||
|     bootspec: PathBuf, | ||||
|     esp: PathBuf, | ||||
| 
 | ||||
|     generations: Vec<PathBuf>, | ||||
| } | ||||
|  | @ -67,7 +67,7 @@ fn install(args: InstallCommand) -> Result<()> { | |||
|         args.private_key, | ||||
|         args.pki_bundle, | ||||
|         args.auto_enroll, | ||||
|         args.bootspec, | ||||
|         args.esp, | ||||
|         args.generations, | ||||
|     ) | ||||
|     .install() | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| use anyhow::{Context, Result}; | ||||
| use std::path::{Path, PathBuf}; | ||||
| 
 | ||||
| use crate::bootspec::Bootspec; | ||||
| use crate::generation::Generation; | ||||
| 
 | ||||
| pub struct EspPaths { | ||||
|  | @ -18,20 +17,22 @@ pub struct EspPaths { | |||
| } | ||||
| 
 | ||||
| impl EspPaths { | ||||
|     pub fn new(esp: &str, generation: Generation, bootspec: &Bootspec) -> Result<Self> { | ||||
|         let esp = Path::new(esp); | ||||
|     pub fn new(esp: impl AsRef<Path>, generation: &Generation) -> Result<Self> { | ||||
|         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; | ||||
| 
 | ||||
|         Ok(Self { | ||||
|             esp: esp.to_owned(), | ||||
|             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")?), | ||||
|             linux: esp_linux.clone(), | ||||
|             lanzaboote_image: esp_linux.join(generation_path(generation)), | ||||
|             lanzaboote_image: esp_linux.join(generation_path(&generation)), | ||||
|             efi_fallback_dir: esp_efi_fallback_dir.clone(), | ||||
|             efi_fallback: esp_efi_fallback_dir.join("BOOTX64.EFI"), | ||||
|             systemd: esp_systemd.clone(), | ||||
|  | @ -60,6 +61,6 @@ fn nixos_path(path: impl AsRef<Path>, name: &str) -> Result<PathBuf> { | |||
|     Ok(PathBuf::from(nixos_filename)) | ||||
| } | ||||
| 
 | ||||
| fn generation_path(generation: Generation) -> PathBuf { | ||||
| fn generation_path(generation: &Generation) -> PathBuf { | ||||
|     PathBuf::from(format!("nixos-generation-{}.efi", generation)) | ||||
| } | ||||
|  |  | |||
|  | @ -1,36 +1,55 @@ | |||
| use std::fmt; | ||||
| use std::fs; | ||||
| use std::path::Path; | ||||
| 
 | ||||
| use anyhow::{Context, Result}; | ||||
| 
 | ||||
| use crate::bootspec::Bootspec; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct Generation(u64); | ||||
| pub struct Generation { | ||||
|     version: u64, | ||||
|     pub bootspec: Bootspec, | ||||
| } | ||||
| 
 | ||||
| impl Generation { | ||||
|     pub fn from_toplevel(toplevel: impl AsRef<Path>) -> Result<Self> { | ||||
|         let file_name = toplevel.as_ref().file_name().ok_or(anyhow::anyhow!( | ||||
|             "Failed to extract file name from generation" | ||||
|         ))?; | ||||
|         let bootspec_path = toplevel.as_ref().join("bootspec/boot.v1.json"); | ||||
|         let bootspec: Bootspec = serde_json::from_slice( | ||||
|             &fs::read(&bootspec_path).context("Failed to read bootspec file")?, | ||||
|         ) | ||||
|         .context("Failed to parse bootspec json")?; | ||||
| 
 | ||||
|         let file_name_str = file_name | ||||
|             .to_str() | ||||
|             .with_context(|| "Failed to convert file name of generation to string")?; | ||||
| 
 | ||||
|         let generation_version = file_name_str | ||||
|             .split("-") | ||||
|             .nth(1) | ||||
|             .ok_or(anyhow::anyhow!("Failed to extract version from generation"))?; | ||||
| 
 | ||||
|         let parsed_generation_version = generation_version.parse().with_context(|| { | ||||
|             format!("Failed to parse generation version: {}", generation_version) | ||||
|         })?; | ||||
| 
 | ||||
|         Ok(Self(parsed_generation_version)) | ||||
|         Ok(Self { | ||||
|             version: parse_version(toplevel)?, | ||||
|             bootspec, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for Generation { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         write!(f, "{}", self.0) | ||||
|         write!(f, "{}", self.version) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn parse_version(toplevel: impl AsRef<Path>) -> Result<u64> { | ||||
|     let file_name = toplevel.as_ref().file_name().ok_or(anyhow::anyhow!( | ||||
|         "Failed to extract file name from generation" | ||||
|     ))?; | ||||
| 
 | ||||
|     let file_name_str = file_name | ||||
|         .to_str() | ||||
|         .with_context(|| "Failed to convert file name of generation to string")?; | ||||
| 
 | ||||
|     let generation_version = file_name_str | ||||
|         .split("-") | ||||
|         .nth(1) | ||||
|         .ok_or(anyhow::anyhow!("Failed to extract version from generation"))?; | ||||
| 
 | ||||
|     let parsed_generation_version = generation_version | ||||
|         .parse() | ||||
|         .with_context(|| format!("Failed to parse generation version: {}", generation_version))?; | ||||
| 
 | ||||
|     Ok(parsed_generation_version) | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ use anyhow::{Context, Result}; | |||
| use nix::unistd::sync; | ||||
| use tempfile::tempdir; | ||||
| 
 | ||||
| use crate::bootspec::Bootspec; | ||||
| use crate::esp::EspPaths; | ||||
| use crate::generation::Generation; | ||||
| use crate::pe; | ||||
|  | @ -20,7 +19,7 @@ pub struct Installer { | |||
|     private_key: PathBuf, | ||||
|     _pki_bundle: Option<PathBuf>, | ||||
|     _auto_enroll: bool, | ||||
|     bootspec: PathBuf, | ||||
|     esp: PathBuf, | ||||
|     generations: Vec<PathBuf>, | ||||
| } | ||||
| 
 | ||||
|  | @ -32,7 +31,7 @@ impl Installer { | |||
|         private_key: PathBuf, | ||||
|         _pki_bundle: Option<PathBuf>, | ||||
|         _auto_enroll: bool, | ||||
|         bootspec: PathBuf, | ||||
|         esp: PathBuf, | ||||
|         generations: Vec<PathBuf>, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|  | @ -42,7 +41,7 @@ impl Installer { | |||
|             private_key, | ||||
|             _pki_bundle, | ||||
|             _auto_enroll, | ||||
|             bootspec, | ||||
|             esp, | ||||
|             generations, | ||||
|         } | ||||
|     } | ||||
|  | @ -50,7 +49,7 @@ impl Installer { | |||
|     pub fn install(&self) -> Result<()> { | ||||
|         for toplevel in &self.generations { | ||||
|             let generation = Generation::from_toplevel(toplevel).with_context(|| { | ||||
|                 format!("Failed to extract generation version from: {toplevel:?}") | ||||
|                 format!("Failed to build generation from toplevel: {toplevel:?}") | ||||
|             })?; | ||||
| 
 | ||||
|             println!("Installing generation {generation}"); | ||||
|  | @ -64,16 +63,14 @@ impl Installer { | |||
|     pub fn install_generation(&self, generation: Generation) -> Result<()> { | ||||
|         println!("Reading bootspec..."); | ||||
| 
 | ||||
|         let bootspec_doc: Bootspec = serde_json::from_slice( | ||||
|             &fs::read(&self.bootspec).context("Failed to read bootspec file")?, | ||||
|         ) | ||||
|         .context("Failed to parse bootspec json")?; | ||||
|         let bootspec = &generation.bootspec; | ||||
| 
 | ||||
|         let esp_paths = EspPaths::new(&bootspec_doc.extension.esp, generation, &bootspec_doc)?; | ||||
|         let esp_paths = EspPaths::new(&self.esp, &generation)?; | ||||
| 
 | ||||
|         println!("Assembling lanzaboote image..."); | ||||
| 
 | ||||
|         let kernel_cmdline = assemble_kernel_cmdline(bootspec_doc.init, bootspec_doc.kernel_params); | ||||
|         let kernel_cmdline = | ||||
|             assemble_kernel_cmdline(&bootspec.init, bootspec.kernel_params.clone()); | ||||
| 
 | ||||
|         // prepare a secure temporary directory
 | ||||
|         // permission bits are not set, because when files below
 | ||||
|  | @ -86,7 +83,7 @@ impl Installer { | |||
|         let lanzaboote_image = pe::lanzaboote_image( | ||||
|             &secure_temp_dir, | ||||
|             &self.lanzaboote_stub, | ||||
|             &bootspec_doc.extension.os_release, | ||||
|             &bootspec.extension.os_release, | ||||
|             &kernel_cmdline, | ||||
|             &esp_paths.kernel, | ||||
|             &esp_paths.initrd, | ||||
|  | @ -97,8 +94,8 @@ impl Installer { | |||
|         println!("Wrapping initrd into a PE binary..."); | ||||
| 
 | ||||
|         let initrd_location = secure_temp_dir.path().join("initrd"); | ||||
|         copy(&bootspec_doc.initrd, &initrd_location)?; | ||||
|         if let Some(initrd_secrets_script) = bootspec_doc.initrd_secrets { | ||||
|         copy(&bootspec.initrd, &initrd_location)?; | ||||
|         if let Some(initrd_secrets_script) = &bootspec.initrd_secrets { | ||||
|             append_initrd_secrets(&initrd_secrets_script, &initrd_location)?; | ||||
|         } | ||||
|         let wrapped_initrd = pe::wrap_initrd(&secure_temp_dir, &self.initrd_stub, &initrd_location) | ||||
|  | @ -108,16 +105,15 @@ impl Installer { | |||
| 
 | ||||
|         let signer = Signer::new(&self.public_key, &self.private_key); | ||||
| 
 | ||||
|         let systemd_boot = bootspec_doc | ||||
|             .extension | ||||
|             .systemd | ||||
|             .join("lib/systemd/boot/efi/systemd-bootx64.efi"); | ||||
|         let systemd_boot = bootspec | ||||
|             .toplevel | ||||
|             .join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi"); | ||||
| 
 | ||||
|         let files_to_copy_and_sign = [ | ||||
|             (&systemd_boot, &esp_paths.efi_fallback), | ||||
|             (&systemd_boot, &esp_paths.systemd_boot), | ||||
|             (&lanzaboote_image, &esp_paths.lanzaboote_image), | ||||
|             (&bootspec_doc.kernel, &esp_paths.kernel), | ||||
|             (&bootspec.kernel, &esp_paths.kernel), | ||||
|             (&wrapped_initrd, &esp_paths.initrd), | ||||
|         ]; | ||||
| 
 | ||||
|  | @ -160,11 +156,11 @@ pub fn append_initrd_secrets( | |||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn assemble_kernel_cmdline(init: PathBuf, kernel_params: Vec<String>) -> Vec<String> { | ||||
|     let init_string = init | ||||
|         .into_os_string() | ||||
|         .into_string() | ||||
|         .expect("Failed to convert init path to string"); | ||||
| fn assemble_kernel_cmdline(init: &Path, kernel_params: Vec<String>) -> Vec<String> { | ||||
|     let init_string = String::from( | ||||
|         init.to_str() | ||||
|             .expect("Failed to convert init path to string"), | ||||
|     ); | ||||
|     let mut kernel_cmdline: Vec<String> = vec![format!("init={}", init_string)]; | ||||
|     kernel_cmdline.extend(kernel_params); | ||||
|     kernel_cmdline | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 nikstur
						nikstur