lanzatool: bootspec from generation

The bootspec is now read from each generation so that more than one
entry can be generated when calling install
This commit is contained in:
nikstur 2022-11-26 22:23:00 +01:00
parent 3c094ee5ff
commit 0a96623461
7 changed files with 81 additions and 67 deletions

View File

@ -38,16 +38,20 @@ in
boot.loader.supportsInitrdSecrets = mkForce true;
boot.loader.external = {
enable = true;
passBootspec = true;
installHook = "${pkgs.writeShellScriptBin "bootinstall" ''
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} "$@" /nix/var/nix/profiles/system-*-link
''}/bin/bootinstall";
# ${cfg.package}/bin/lanzatool install ${optionalString cfg.enrollKeys "--auto-enroll"} --pki-bundle ${cfg.pkiBundle}
${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
'';
};
};
}

View File

@ -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"
}
}

View File

@ -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,
}

View File

@ -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()

View File

@ -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))
}

View File

@ -1,13 +1,39 @@
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 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")?;
Ok(Self {
version: parse_version(toplevel)?,
bootspec,
})
}
}
impl fmt::Display for Generation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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"
))?;
@ -21,16 +47,9 @@ impl Generation {
.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)
})?;
let parsed_generation_version = generation_version
.parse()
.with_context(|| format!("Failed to parse generation version: {}", generation_version))?;
Ok(Self(parsed_generation_version))
}
}
impl fmt::Display for Generation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
Ok(parsed_generation_version)
}

View File

@ -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