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