lanzatool: make it more typedriven

This commit is contained in:
nikstur 2022-11-26 14:55:15 +01:00
parent 8a430b6578
commit 240c80368f
5 changed files with 222 additions and 210 deletions

View File

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
@ -8,33 +8,36 @@ use crate::install;
#[derive(Parser)] #[derive(Parser)]
pub struct Cli { pub struct Cli {
#[clap(subcommand)] #[clap(subcommand)]
pub commands: Commands, commands: Commands,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum Commands { enum Commands {
Install { Install(InstallCommand),
}
#[derive(Parser)]
struct InstallCommand {
// Secure Boot Public Key // Secure Boot Public Key
#[clap(long)] #[arg(long)]
public_key: PathBuf, public_key: PathBuf,
// Secure Boot Private Key // Secure Boot Private Key
#[clap(long)] #[arg(long)]
private_key: PathBuf, private_key: PathBuf,
// Secure Boot PKI Bundle for auto enrolling key // Secure Boot PKI Bundle for auto enrolling key
#[clap(long)] #[arg(long)]
pki_bundle: Option<PathBuf>, pki_bundle: Option<PathBuf>,
// Enable auto enrolling your keys in UEFI // Enable auto enrolling your keys in UEFI
// Be aware that this might irrevocably brick your device // Be aware that this might irrevocably brick your device
#[clap(long, default_value = "false")] #[arg(long, default_value = "false")]
auto_enroll: bool, auto_enroll: bool,
bootspec: PathBuf, bootspec: PathBuf,
generations: Vec<PathBuf>, generations: Vec<PathBuf>,
},
} }
impl Cli { impl Cli {
@ -46,46 +49,26 @@ impl Cli {
impl Commands { impl Commands {
pub fn call(self) -> Result<()> { pub fn call(self) -> Result<()> {
match self { match self {
Commands::Install { Commands::Install(args) => install(args),
public_key,
private_key,
pki_bundle,
auto_enroll,
bootspec,
generations,
} => install(
&public_key,
&private_key,
&pki_bundle,
auto_enroll,
&bootspec,
generations,
),
} }
} }
} }
fn install( fn install(args: InstallCommand) -> Result<()> {
public_key: &Path,
private_key: &Path,
pki_bundle: &Option<PathBuf>,
auto_enroll: bool,
bootspec: &Path,
generations: Vec<PathBuf>,
) -> Result<()> {
let lanzaboote_stub = let lanzaboote_stub =
std::env::var("LANZABOOTE_STUB").context("Failed to read LANZABOOTE_STUB env variable")?; std::env::var("LANZABOOTE_STUB").context("Failed to read LANZABOOTE_STUB env variable")?;
let initrd_stub = std::env::var("LANZABOOTE_INITRD_STUB") let initrd_stub = std::env::var("LANZABOOTE_INITRD_STUB")
.context("Failed to read LANZABOOTE_INITRD_STUB env variable")?; .context("Failed to read LANZABOOTE_INITRD_STUB env variable")?;
install::install( install::Installer::new(
public_key, PathBuf::from(lanzaboote_stub),
private_key, PathBuf::from(initrd_stub),
pki_bundle, args.public_key,
auto_enroll, args.private_key,
bootspec, args.pki_bundle,
generations, args.auto_enroll,
Path::new(&lanzaboote_stub), args.bootspec,
Path::new(&initrd_stub), args.generations,
) )
.install()
} }

View File

@ -2,6 +2,7 @@ use anyhow::{Context, Result};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::bootspec::Bootspec; use crate::bootspec::Bootspec;
use crate::generation::Generation;
pub struct EspPaths { pub struct EspPaths {
pub esp: PathBuf, pub esp: PathBuf,
@ -17,7 +18,7 @@ pub struct EspPaths {
} }
impl EspPaths { impl EspPaths {
pub fn new(esp: &str, generation: u64, bootspec: &Bootspec) -> Result<Self> { pub fn new(esp: &str, generation: Generation, bootspec: &Bootspec) -> Result<Self> {
let esp = Path::new(esp); let esp = Path::new(esp);
let esp_nixos = esp.join("EFI/nixos"); let esp_nixos = esp.join("EFI/nixos");
let esp_linux = esp.join("EFI/Linux"); let esp_linux = esp.join("EFI/Linux");
@ -59,6 +60,6 @@ fn nixos_path(path: impl AsRef<Path>, name: &str) -> Result<PathBuf> {
Ok(PathBuf::from(nixos_filename)) Ok(PathBuf::from(nixos_filename))
} }
fn generation_path(generation: u64) -> PathBuf { fn generation_path(generation: Generation) -> PathBuf {
PathBuf::from(format!("nixos-generation-{}.efi", generation)) PathBuf::from(format!("nixos-generation-{}.efi", generation))
} }

View File

@ -0,0 +1,36 @@
use std::fmt;
use std::path::Path;
use anyhow::{Context, Result};
#[derive(Debug)]
pub struct Generation(u64);
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 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))
}
}
impl fmt::Display for Generation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View File

@ -1,86 +1,72 @@
use std::fs; use std::fs;
use std::os::unix::prelude::PermissionsExt; use std::os::unix::prelude::PermissionsExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use nix::unistd::sync; use std::process::Command;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use nix::unistd::sync;
use tempfile::tempdir;
use crate::bootspec::Bootspec; use crate::bootspec::Bootspec;
use crate::esp::EspPaths; use crate::esp::EspPaths;
use crate::generation::Generation;
use crate::pe; use crate::pe;
use crate::signer::Signer; use crate::signer::Signer;
use tempfile::tempdir; pub struct Installer {
lanzaboote_stub: PathBuf,
use std::process::Command; initrd_stub: PathBuf,
public_key: PathBuf,
pub fn install( private_key: PathBuf,
public_key: &Path, _pki_bundle: Option<PathBuf>,
private_key: &Path, _auto_enroll: bool,
pki_bundle: &Option<PathBuf>, bootspec: PathBuf,
auto_enroll: bool,
bootspec: &Path,
generations: Vec<PathBuf>, generations: Vec<PathBuf>,
lanzaboote_stub: &Path, }
initrd_stub: &Path,
) -> Result<()> {
for generation in generations {
let generation_version = extract_generation_version(&generation).with_context(|| {
format!(
"Failed to extract generation version from generation: {}",
generation.display()
)
})?;
println!("Installing generation {generation_version}"); impl Installer {
pub fn new(
install_generation( lanzaboote_stub: PathBuf,
generation_version, initrd_stub: PathBuf,
public_key, public_key: PathBuf,
private_key, private_key: PathBuf,
pki_bundle, _pki_bundle: Option<PathBuf>,
auto_enroll, _auto_enroll: bool,
bootspec, bootspec: PathBuf,
generations: Vec<PathBuf>,
) -> Self {
Self {
lanzaboote_stub, lanzaboote_stub,
initrd_stub, initrd_stub,
)?; public_key,
private_key,
_pki_bundle,
_auto_enroll,
bootspec,
generations,
}
}
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:?}")
})?;
println!("Installing generation {generation}");
self.install_generation(generation)?
} }
Ok(()) Ok(())
} }
fn extract_generation_version(path: impl AsRef<Path>) -> Result<u64> { pub fn install_generation(&self, generation: Generation) -> Result<()> {
let file_name = path.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"))?;
Ok(generation_version
.parse()
.with_context(|| format!("Failed to parse generation version: {}", generation_version))?)
}
fn install_generation(
generation: u64,
public_key: &Path,
private_key: &Path,
_pki_bundle: &Option<PathBuf>,
_auto_enroll: bool,
bootspec: &Path,
lanzaboote_stub: &Path,
initrd_stub: &Path,
) -> Result<()> {
println!("Reading bootspec..."); println!("Reading bootspec...");
let bootspec_doc: Bootspec = let bootspec_doc: Bootspec = serde_json::from_slice(
serde_json::from_slice(&fs::read(bootspec).context("Failed to read bootspec file")?) &fs::read(&self.bootspec).context("Failed to read bootspec file")?,
)
.context("Failed to parse bootspec json")?; .context("Failed to parse bootspec json")?;
let esp_paths = EspPaths::new(&bootspec_doc.extension.esp, generation, &bootspec_doc)?; let esp_paths = EspPaths::new(&bootspec_doc.extension.esp, generation, &bootspec_doc)?;
@ -99,7 +85,7 @@ fn install_generation(
let lanzaboote_image = pe::lanzaboote_image( let lanzaboote_image = pe::lanzaboote_image(
&secure_temp_dir, &secure_temp_dir,
lanzaboote_stub, &self.lanzaboote_stub,
&bootspec_doc.extension.os_release, &bootspec_doc.extension.os_release,
&kernel_cmdline, &kernel_cmdline,
&esp_paths.kernel, &esp_paths.kernel,
@ -113,11 +99,10 @@ fn install_generation(
let initrd_location = secure_temp_dir.path().join("initrd"); let initrd_location = secure_temp_dir.path().join("initrd");
copy(&bootspec_doc.initrd, &initrd_location)?; copy(&bootspec_doc.initrd, &initrd_location)?;
if let Some(initrd_secrets_script) = bootspec_doc.initrd_secrets { if let Some(initrd_secrets_script) = bootspec_doc.initrd_secrets {
append_initrd_secrets(&initrd_secrets_script, append_initrd_secrets(&initrd_secrets_script, &initrd_location)?;
&initrd_location)?;
} }
let wrapped_initrd = let wrapped_initrd = pe::wrap_initrd(&secure_temp_dir, &self.initrd_stub, &initrd_location)
pe::wrap_initrd(&secure_temp_dir, initrd_stub, &initrd_location).context("Failed to assemble stub")?; .context("Failed to assemble stub")?;
println!("Copy files to EFI system partition..."); println!("Copy files to EFI system partition...");
@ -143,7 +128,7 @@ fn install_generation(
println!("Signing files..."); println!("Signing files...");
let signer = Signer::new(&public_key, &private_key); let signer = Signer::new(&self.public_key, &self.private_key);
let files_to_sign = [ let files_to_sign = [
&esp_paths.efi_fallback, &esp_paths.efi_fallback,
@ -167,17 +152,23 @@ fn install_generation(
); );
Ok(()) Ok(())
}
} }
pub fn append_initrd_secrets(append_initrd_secrets_path: &Path, initrd_path: &PathBuf) -> Result<()> { pub fn append_initrd_secrets(
append_initrd_secrets_path: &Path,
initrd_path: &PathBuf,
) -> Result<()> {
let status = Command::new(append_initrd_secrets_path) let status = Command::new(append_initrd_secrets_path)
.args(vec![ .args(vec![initrd_path])
initrd_path
])
.status() .status()
.context("Failed to append initrd secrets")?; .context("Failed to append initrd secrets")?;
if !status.success() { if !status.success() {
return Err(anyhow::anyhow!("Failed to append initrd secrets with args `{:?}`", vec![append_initrd_secrets_path, initrd_path]).into()); return Err(anyhow::anyhow!(
"Failed to append initrd secrets with args `{:?}`",
vec![append_initrd_secrets_path, initrd_path]
)
.into());
} }
Ok(()) Ok(())

View File

@ -1,6 +1,7 @@
mod bootspec; mod bootspec;
mod cli; mod cli;
mod esp; mod esp;
mod generation;
mod install; mod install;
mod pe; mod pe;
mod signer; mod signer;